Redesign the login page around what the app can do
- Hero ("Read it. Study it. Teach it.") with small previews of the outline
and the translation comparison
- Four groups of features (Read, Study, Teach, Everywhere), all things the
app really does
- Sign-in / create-account card stays in view beside the tour on wide
screens and comes first on a phone; theme switcher before signing in
- Sign-in, account creation, 2FA and error handling unchanged
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
49a0eb9d0b
commit
46a6e3bf7a
2 files changed
+234
-77
No files matched your search
@@ -391,3 +391,53 @@ describe('Window title', () => {
|
||||
await waitFor(() => expect(document.title).toMatch(/^[A-Za-z0-9 ]+ \d+ · Bible Study$/));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Login page', () => {
|
||||
async function openLogin() {
|
||||
stubServer({ user: null });
|
||||
render(<App />);
|
||||
return screen.findByRole('button', { name: /^sign in$/i });
|
||||
}
|
||||
|
||||
test('tells you what the app can do, in groups', async () => {
|
||||
await openLogin();
|
||||
expect(screen.getByRole('heading', { level: 1, name: /read it\. study it\./i })).toBeInTheDocument();
|
||||
for (const group of ['Read', 'Study', 'Teach', 'Everywhere']) expect(screen.getByRole('region', { name: group })).toBeInTheDocument();
|
||||
expect(screen.getByText('A real outline')).toBeInTheDocument();
|
||||
expect(screen.getByText('Compare them')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('switches between signing in and creating an account', async () => {
|
||||
const user = userEvent.setup();
|
||||
await openLogin();
|
||||
expect(screen.getByRole('heading', { name: 'Sign in' })).toBeInTheDocument();
|
||||
await user.click(screen.getByRole('button', { name: /need an account/i }));
|
||||
expect(screen.getByRole('heading', { name: 'Create an account' })).toBeInTheDocument();
|
||||
expect(screen.getByText(/at least 8 characters/i)).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /^create account$/i })).toBeInTheDocument();
|
||||
await user.click(screen.getByRole('button', { name: /already have an account/i }));
|
||||
expect(screen.getByRole('heading', { name: 'Sign in' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('a theme can be picked before signing in', async () => {
|
||||
const user = userEvent.setup();
|
||||
await openLogin();
|
||||
await user.click(screen.getByRole('button', { name: 'Dark' }));
|
||||
expect(screen.getByRole('button', { name: 'Dark' })).toHaveAttribute('aria-pressed', 'true');
|
||||
expect(localStorage.getItem('reader-theme')).toBe('dark');
|
||||
});
|
||||
|
||||
test('a wrong password shows the error', async () => {
|
||||
const user = userEvent.setup();
|
||||
vi.stubGlobal('fetch', vi.fn((url, opts = {}) => {
|
||||
if (url === '/api/auth/me') return json(401, {});
|
||||
if (url === '/api/auth/login') return json(401, { error: 'Incorrect email or password.' });
|
||||
return Promise.resolve({ ok: false });
|
||||
}));
|
||||
render(<App />);
|
||||
await user.type(await screen.findByLabelText(/email/i), 'a@b.co');
|
||||
await user.type(screen.getByLabelText(/^password/i), 'wrong-password');
|
||||
await user.click(screen.getByRole('button', { name: /^sign in$/i }));
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent(/incorrect email or password/i);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user