From 566ba369ecaa6f4899093cacc79df9e400fbfe32 Mon Sep 17 00:00:00 2001 From: me Date: Fri, 8 May 2026 06:07:22 +0530 Subject: [PATCH] refactor: lint --- frontend/src/components/Logo.tsx | 106 ++--- frontend/src/components/RouteGuards.test.tsx | 10 +- frontend/src/components/SplashScreen.tsx | 5 +- .../src/components/drawer/DrawerSection.tsx | 172 +++++---- .../src/components/drawer/PasskeyModal.tsx | 5 +- .../src/components/reader/EnvelopeReveal.tsx | 7 +- frontend/src/index.css | 102 ++--- frontend/src/pages/Drawer.test.tsx | 192 +++++----- frontend/src/pages/Drawer.tsx | 361 +++++++++--------- frontend/src/pages/Editor.test.tsx | 15 +- frontend/src/pages/Login.test.tsx | 17 +- frontend/src/pages/Login.tsx | 234 ++++++------ frontend/src/pages/Reader.test.tsx | 10 +- frontend/src/pages/Register.tsx | 288 +++++++------- 14 files changed, 788 insertions(+), 736 deletions(-) diff --git a/frontend/src/components/Logo.tsx b/frontend/src/components/Logo.tsx index 17bdf03..aa9bf44 100644 --- a/frontend/src/components/Logo.tsx +++ b/frontend/src/components/Logo.tsx @@ -2,63 +2,63 @@ import { DotIcon } from "@phosphor-icons/react"; import "@fontsource/knewave/400.css"; interface LogoProps { - scale?: number; - type?: "inline" | "mono" | "logo" | null; - ul?: boolean; + scale?: number; + type?: "inline" | "mono" | "logo" | null; + ul?: boolean; } export default function Logo({ - scale = 1, - type = null, - ul = false, + scale = 1, + type = null, + ul = false, }: LogoProps) { - if (type === "inline") { - return ( - - pi. ku - .  - - ); - } - - if (type === "mono") { - return ( - - pi. ku. - - ); - } - - if (type === "logo") { - return ( - Pi. Ku. logo - ); - } - + if (type === "inline") { return ( -
- Pi - -  Ku - -
+ + pi. ku + .  + ); + } + + if (type === "mono") { + return ( + + pi. ku. + + ); + } + + if (type === "logo") { + return ( + Pi. Ku. logo + ); + } + + return ( +
+ Pi + +  Ku + +
+ ); } diff --git a/frontend/src/components/RouteGuards.test.tsx b/frontend/src/components/RouteGuards.test.tsx index 7eea6d4..79606f9 100644 --- a/frontend/src/components/RouteGuards.test.tsx +++ b/frontend/src/components/RouteGuards.test.tsx @@ -9,8 +9,14 @@ function renderGuard(ui: React.ReactNode, mountPath: "/protected" | "/public") { return render( - Login Page} /> - Drawer Page} /> + Login Page} + /> + Drawer Page} + /> diff --git a/frontend/src/components/SplashScreen.tsx b/frontend/src/components/SplashScreen.tsx index 7c4297e..0874d18 100644 --- a/frontend/src/components/SplashScreen.tsx +++ b/frontend/src/components/SplashScreen.tsx @@ -3,7 +3,10 @@ import Logo from "./Logo"; export default function SplashScreen() { return ( -
+
diff --git a/frontend/src/components/drawer/DrawerSection.tsx b/frontend/src/components/drawer/DrawerSection.tsx index 7fe6ff0..14874e4 100644 --- a/frontend/src/components/drawer/DrawerSection.tsx +++ b/frontend/src/components/drawer/DrawerSection.tsx @@ -1,95 +1,101 @@ import { GearFineIcon } from "@phosphor-icons/react"; interface DrawerSectionProps { - id: string; - title: string; - count: number; - subtext: string; - isOpen: boolean; - onClick: () => void; - children: React.ReactNode; - icon: React.ReactNode; + id: string; + title: string; + count: number; + subtext: string; + isOpen: boolean; + onClick: () => void; + children: React.ReactNode; + icon: React.ReactNode; } export function DrawerSection({ - id, - title, - count, - subtext, - isOpen, - onClick, - children, - icon, + id, + title, + count, + subtext, + isOpen, + onClick, + children, + icon, }: DrawerSectionProps) { - return ( + return ( +
+
-
-
- {children} - {count === 0 && ( -

- This drawer remains silent -

- )} -
-
- - + This drawer remains silent +

+ )}
- ); +
+ + +
+ ); } diff --git a/frontend/src/components/drawer/PasskeyModal.tsx b/frontend/src/components/drawer/PasskeyModal.tsx index 04ba0ed..e33a38b 100644 --- a/frontend/src/components/drawer/PasskeyModal.tsx +++ b/frontend/src/components/drawer/PasskeyModal.tsx @@ -12,7 +12,10 @@ export function PasskeyModal() { className="text-primary mx-auto mb-8 animate-pulse" weight="duotone" /> -

+

You've been away a while.

diff --git a/frontend/src/components/reader/EnvelopeReveal.tsx b/frontend/src/components/reader/EnvelopeReveal.tsx index a830307..9ac9f14 100644 --- a/frontend/src/components/reader/EnvelopeReveal.tsx +++ b/frontend/src/components/reader/EnvelopeReveal.tsx @@ -123,7 +123,12 @@ export function EnvelopeReveal({ to -

{recipient}

+

+ {recipient} +

{date}

({ - WelcomeLetterOverlay: ({ onComplete }: WelcomeLetterOverlayProps) => ( -
- -
- ), + WelcomeLetterOverlay: ({ onComplete }: WelcomeLetterOverlayProps) => ( +
+ +
+ ), })); describe("Drawer Page", () => { - beforeEach(() => { - // Setup authenticated state for the test - useAuthStore.setState({ - user: mockUser, - accessToken: "fake-token", - isInitializing: false, - }); - - vi.mocked(useLetters).mockReturnValue({ - drafts: [], - kept: [], - sent: [], - vault: [], - loading: false, - isAuthRequired: false, - }); + beforeEach(() => { + // Setup authenticated state for the test + useAuthStore.setState({ + user: mockUser, + accessToken: "fake-token", + isInitializing: false, }); - it("renders the drawer sections and empty state message", () => { - render( - - - , - ); + vi.mocked(useLetters).mockReturnValue({ + drafts: [], + kept: [], + sent: [], + vault: [], + loading: false, + isAuthRequired: false, + }); + }); - expect(screen.getByTestId("drawer-section-drafts")).toBeInTheDocument(); - expect(screen.getAllByTestId("drawer-section-title").length).toBeGreaterThanOrEqual(1); - expect(screen.getByTestId("drawer-section-vault")).toBeInTheDocument(); - expect(screen.getByTestId("empty-drawer-message-drafts")).toBeInTheDocument(); + it("renders the drawer sections and empty state message", () => { + render( + + + , + ); + + expect(screen.getByTestId("drawer-section-drafts")).toBeInTheDocument(); + expect( + screen.getAllByTestId("drawer-section-title").length, + ).toBeGreaterThanOrEqual(1); + expect(screen.getByTestId("drawer-section-vault")).toBeInTheDocument(); + expect( + screen.getByTestId("empty-drawer-message-drafts"), + ).toBeInTheDocument(); + }); + + it("renders the loading state", () => { + vi.mocked(useLetters).mockReturnValue({ + drafts: [], + kept: [], + sent: [], + vault: [], + loading: true, + isAuthRequired: false, }); - it("renders the loading state", () => { - vi.mocked(useLetters).mockReturnValue({ - drafts: [], - kept: [], - sent: [], - vault: [], - loading: true, - isAuthRequired: false, - }); + render( + + + , + ); - render( - - - , - ); + expect(screen.getByTestId("drawer-loading-state")).toBeInTheDocument(); + }); - expect(screen.getByTestId("drawer-loading-state")).toBeInTheDocument(); + it("renders the authentication required modal when api requires auth", () => { + vi.mocked(useLetters).mockReturnValue({ + drafts: [], + kept: [], + sent: [], + vault: [], + loading: false, + isAuthRequired: true, }); - it("renders the authentication required modal when api requires auth", () => { - vi.mocked(useLetters).mockReturnValue({ - drafts: [], - kept: [], - sent: [], - vault: [], - loading: false, - isAuthRequired: true, - }); + render( + + + , + ); - render( - - - , - ); + expect(screen.getByTestId("passkey-modal-title")).toBeInTheDocument(); + expect(screen.getByPlaceholderText(/password/i)).toBeInTheDocument(); + }); - expect(screen.getByTestId("passkey-modal-title")).toBeInTheDocument(); - expect(screen.getByPlaceholderText(/password/i)).toBeInTheDocument(); - }); + it("renders the welcome letter when firstTime state is present", () => { + render( + + + , + ); - it("renders the welcome letter when firstTime state is present", () => { - render( - - - , - ); + expect(screen.getByTestId("welcome-letter-overlay")).toBeInTheDocument(); + }); - expect(screen.getByTestId("welcome-letter-overlay")).toBeInTheDocument(); - }); + it("renders the drawer content when the letter is closed", () => { + render( + + + , + ); - it("renders the drawer content when the letter is closed", () => { - render( - - - , - ); + const completeButton = screen.getByTestId("overlay-exit-button"); + fireEvent.click(completeButton); - const completeButton = screen.getByTestId("overlay-exit-button"); - fireEvent.click(completeButton); - - expect( - screen.queryByTestId("welcome-letter-overlay"), - ).not.toBeInTheDocument(); - }); + expect( + screen.queryByTestId("welcome-letter-overlay"), + ).not.toBeInTheDocument(); + }); }); diff --git a/frontend/src/pages/Drawer.tsx b/frontend/src/pages/Drawer.tsx index 425956a..0a61541 100644 --- a/frontend/src/pages/Drawer.tsx +++ b/frontend/src/pages/Drawer.tsx @@ -1,9 +1,9 @@ import { - ArchiveIcon, - FeatherIcon, - FileDashedIcon, - PaperPlaneTiltIcon, - VaultIcon, + ArchiveIcon, + FeatherIcon, + FileDashedIcon, + PaperPlaneTiltIcon, + VaultIcon, } from "@phosphor-icons/react"; import { useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; @@ -17,188 +17,191 @@ import { PATHS } from "../config/routes"; import { useAuth } from "../hooks/useAuth"; import { useLetters } from "../hooks/useLetters"; import { - formatRelativeDate, - formatRelativeDateWithoutTime, + formatRelativeDate, + formatRelativeDateWithoutTime, } from "../utils/dateFormat.ts"; export default function Drawer() { - const { user, logout } = useAuth(); + const { user, logout } = useAuth(); - const [openSection, setOpenSection] = useState(null); - const navigate = useNavigate(); - const location = useLocation(); - const [showWelcomeLetter, setShowWelcomeLetter] = useState( - !!location.state?.firstTime, - ); - const { drafts, kept, sent, vault, loading, isAuthRequired } = useLetters(); + const [openSection, setOpenSection] = useState(null); + const navigate = useNavigate(); + const location = useLocation(); + const [showWelcomeLetter, setShowWelcomeLetter] = useState( + !!location.state?.firstTime, + ); + const { drafts, kept, sent, vault, loading, isAuthRequired } = useLetters(); - if (!user) return null; + if (!user) return null; - const toggleSection = (id: string) => - setOpenSection(openSection === id ? null : id); + const toggleSection = (id: string) => + setOpenSection(openSection === id ? null : id); - return ( -
-
+ return ( +
+
- {showWelcomeLetter && ( - { - setShowWelcomeLetter(false); - navigate(location.pathname, { replace: true, state: {} }); - }} - /> - )} + {showWelcomeLetter && ( + { + setShowWelcomeLetter(false); + navigate(location.pathname, { replace: true, state: {} }); + }} + /> + )} - {isAuthRequired && } -
- -
- Personal Archive -
-
- Welcome Back{" "} - {user.full_name} - -
-
- -
- {loading ? ( -
- - - Opening your cabinet... - -
- ) : ( - <> - toggleSection("drafts")} - icon={} - > - {drafts.map((draft) => ( - - ))} - - - toggleSection("kept")} - icon={} - > - {kept.map((letter) => ( - - ))} - - toggleSection("sent")} - icon={} - > - {sent.map((letter) => ( - - ))} - - toggleSection("vault")} - icon={} - > - {vault.map((letter) => ( - new Date().toISOString()} - /> - ))} - - - )} -
- - - -
- For your unsaid. -
- {!showWelcomeLetter && ( -
- -
- )} + {isAuthRequired && } +
+ +
+ Personal Archive
- ); +
+ Welcome Back{" "} + {user.full_name} + +
+
+ +
+ {loading ? ( +
+ + + Opening your cabinet... + +
+ ) : ( + <> + toggleSection("drafts")} + icon={} + > + {drafts.map((draft) => ( + + ))} + + + toggleSection("kept")} + icon={} + > + {kept.map((letter) => ( + + ))} + + toggleSection("sent")} + icon={} + > + {sent.map((letter) => ( + + ))} + + toggleSection("vault")} + icon={} + > + {vault.map((letter) => ( + new Date().toISOString()} + /> + ))} + + + )} +
+ + + +
+ For your unsaid. +
+ {!showWelcomeLetter && ( +
+ +
+ )} +
+ ); } diff --git a/frontend/src/pages/Editor.test.tsx b/frontend/src/pages/Editor.test.tsx index 6b32945..7b8ad34 100644 --- a/frontend/src/pages/Editor.test.tsx +++ b/frontend/src/pages/Editor.test.tsx @@ -1,4 +1,9 @@ -import { fireEvent, render, screen, waitForElementToBeRemoved } from "@testing-library/react"; +import { + fireEvent, + render, + screen, + waitForElementToBeRemoved, +} from "@testing-library/react"; import { HttpResponse, http } from "msw"; import { MemoryRouter, Route, Routes } from "react-router-dom"; import { beforeEach, describe, expect, it, vi } from "vitest"; @@ -79,7 +84,9 @@ describe("Editor Page", () => { ); // Wait for initial load to complete - await waitForElementToBeRemoved(() => screen.queryByTestId("opening-draft-overlay")); + await waitForElementToBeRemoved(() => + screen.queryByTestId("opening-draft-overlay"), + ); const canvas = screen.getByTestId("canvas"); expect(canvas.getAttribute("data-readonly")).toBe("false"); @@ -136,7 +143,9 @@ describe("Editor Page", () => { , ); - await waitForElementToBeRemoved(() => screen.queryByTestId("opening-draft-overlay")); + await waitForElementToBeRemoved(() => + screen.queryByTestId("opening-draft-overlay"), + ); const canvas = screen.getByTestId("canvas"); diff --git a/frontend/src/pages/Login.test.tsx b/frontend/src/pages/Login.test.tsx index 7f58b75..3b77068 100644 --- a/frontend/src/pages/Login.test.tsx +++ b/frontend/src/pages/Login.test.tsx @@ -31,7 +31,9 @@ describe("Login Page", () => { await userEvent.type(screen.getByLabelText(/password/i), "password123"); await userEvent.click(screen.getByRole("button", { name: /sign in/i })); - expect(await screen.findByTestId("login-error-message")).toHaveTextContent(/technical issues/i); + expect(await screen.findByTestId("login-error-message")).toHaveTextContent( + /technical issues/i, + ); }); it.each([ @@ -73,8 +75,14 @@ describe("Login Page", () => { > } /> - Drawer
} /> - Reader
} /> + Drawer
} + /> + Reader
} + /> , ); @@ -83,7 +91,8 @@ describe("Login Page", () => { await userEvent.type(screen.getByLabelText(/password/i), "password123"); await userEvent.click(screen.getByRole("button", { name: /sign in/i })); - const expectedTestId = nextRoute.toLowerCase() === "drawer" ? "drawer-page" : "reader-page"; + const expectedTestId = + nextRoute.toLowerCase() === "drawer" ? "drawer-page" : "reader-page"; expect(await screen.findByTestId(expectedTestId)).toBeInTheDocument(); }); }); diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 5183c37..2f4b32e 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -16,135 +16,135 @@ import { useAuth } from "../hooks/useAuth"; import { CryptoUtils } from "../utils/crypto"; const loginSchema = z.object({ - email: z.email("Please enter a valid email"), - password: z.string().min(1, "Password is required"), + email: z.email("Please enter a valid email"), + password: z.string().min(1, "Password is required"), }); type LoginInputs = z.infer; export default function Login() { - const navigate = useNavigate(); - const location = useLocation(); - const [isLoading, setIsLoading] = useState(false); - const [apiError, setApiError] = useState(null); - const { setAuthStore } = useAuth(); - const [showWelcome, setShowWelcome] = useState(!!location.state?.firstTime); - const [saajanMessage, setSaajanMessage] = useState( - "I was wondering when you'd return.", - ); - const nextRoute = location.state?.redirectUrl || ROUTES.DRAWER; + const navigate = useNavigate(); + const location = useLocation(); + const [isLoading, setIsLoading] = useState(false); + const [apiError, setApiError] = useState(null); + const { setAuthStore } = useAuth(); + const [showWelcome, setShowWelcome] = useState(!!location.state?.firstTime); + const [saajanMessage, setSaajanMessage] = useState( + "I was wondering when you'd return.", + ); + const nextRoute = location.state?.redirectUrl || ROUTES.DRAWER; - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - resolver: zodResolver(loginSchema), - }); + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(loginSchema), + }); - const onSubmit = async (data: LoginInputs) => { - setIsLoading(true); - setApiError(null); - try { - // client side key derivation for e2e encryption - const { masterKey, authHash } = await CryptoUtils.deriveKeyBundle( - data.password, - data.email, - ); + const onSubmit = async (data: LoginInputs) => { + setIsLoading(true); + setApiError(null); + try { + // client side key derivation for e2e encryption + const { masterKey, authHash } = await CryptoUtils.deriveKeyBundle( + data.password, + data.email, + ); - // send just the authHash as the password to the server - const { data: authData } = await publicApi.post(endpoints.LOGIN, { - email: data.email, - password: authHash, - }); + // send just the authHash as the password to the server + const { data: authData } = await publicApi.post(endpoints.LOGIN, { + email: data.email, + password: authHash, + }); - const { data: userData } = await api.get(endpoints.ME, { - headers: { Authorization: `Bearer ${authData.access}` }, - }); + const { data: userData } = await api.get(endpoints.ME, { + headers: { Authorization: `Bearer ${authData.access}` }, + }); - await setAuthStore(authData.access, userData, masterKey); + await setAuthStore(authData.access, userData, masterKey); - navigate(nextRoute, { replace: true, state: location.state }); - } catch (err) { - let message = - "Sorry, we're experiencing technical issues.\nPlease try again later."; - if (axios.isAxiosError(err) && err.response?.status !== 500) { - message = err.response?.data?.detail || err.response?.data?.message; - } - setApiError(message); - } finally { - setIsLoading(false); - } - }; + navigate(nextRoute, { replace: true, state: location.state }); + } catch (err) { + let message = + "Sorry, we're experiencing technical issues.\nPlease try again later."; + if (axios.isAxiosError(err) && err.response?.status !== 500) { + message = err.response?.data?.detail || err.response?.data?.message; + } + setApiError(message); + } finally { + setIsLoading(false); + } + }; - return ( -
- {!showWelcome && } - {showWelcome && } -
-
-

-   Enter Archive -

+ return ( +
+ {!showWelcome && } + {showWelcome && } +
+ +

+   Enter Archive +

- {apiError && ( -
- {apiError} -
- )} - - setSaajanMessage("I remember you.")} - /> - - - setSaajanMessage("The one thing I cannot know for you.") - } - /> - -
- -
- -
- Don't have an account?{" "} - -
- + {apiError && ( +
+ {apiError}
-
- ); + )} + + setSaajanMessage("I remember you.")} + /> + + + setSaajanMessage("The one thing I cannot know for you.") + } + /> + +
+ +
+ +
+ Don't have an account?{" "} + +
+ +
+
+ ); } diff --git a/frontend/src/pages/Reader.test.tsx b/frontend/src/pages/Reader.test.tsx index 50164b1..ec19415 100644 --- a/frontend/src/pages/Reader.test.tsx +++ b/frontend/src/pages/Reader.test.tsx @@ -76,7 +76,9 @@ describe("Reader Page", () => { , ); - expect(await screen.findByTestId("envelope-recipient")).toHaveTextContent(/Guest/i); + expect(await screen.findByTestId("envelope-recipient")).toHaveTextContent( + /Guest/i, + ); }); it("should display an error message if the server request fails", async () => { @@ -97,9 +99,9 @@ describe("Reader Page", () => { , ); - expect( - await screen.findByTestId("log-modal-message"), - ).toHaveTextContent(/Failed to load letter/i); + expect(await screen.findByTestId("log-modal-message")).toHaveTextContent( + /Failed to load letter/i, + ); }); it("should navigate to the login page with redirect url when the letter has no sharing key and the user is not logged in", async () => { diff --git a/frontend/src/pages/Register.tsx b/frontend/src/pages/Register.tsx index 174da9e..b28403a 100644 --- a/frontend/src/pages/Register.tsx +++ b/frontend/src/pages/Register.tsx @@ -14,158 +14,158 @@ import { ROUTES } from "../config/routes"; import { CryptoUtils } from "../utils/crypto"; const registerSchema = z - .object({ - full_name: z.string().min(2, "Name must be at least 2 characters"), - email: z.email("Please enter a valid email"), - password: z.string().min(8, "Password must be at least 8 characters"), - confirm_password: z.string(), - }) - .refine((data) => data.password === data.confirm_password, { - message: "Passwords don't match", - path: ["confirm_password"], - }); + .object({ + full_name: z.string().min(2, "Name must be at least 2 characters"), + email: z.email("Please enter a valid email"), + password: z.string().min(8, "Password must be at least 8 characters"), + confirm_password: z.string(), + }) + .refine((data) => data.password === data.confirm_password, { + message: "Passwords don't match", + path: ["confirm_password"], + }); type RegisterInputs = z.infer; export default function Register() { - const navigate = useNavigate(); - const [isLoading, setIsLoading] = useState(false); - const [apiError, setApiError] = useState(null); - const [saajanMessage, setSaajanMessage] = useState( - "I didn't think I'd be here either.\nAnd yet, here we are.", - ); + const navigate = useNavigate(); + const [isLoading, setIsLoading] = useState(false); + const [apiError, setApiError] = useState(null); + const [saajanMessage, setSaajanMessage] = useState( + "I didn't think I'd be here either.\nAnd yet, here we are.", + ); - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - resolver: zodResolver(registerSchema), - }); + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(registerSchema), + }); - const onSubmit = async (data: RegisterInputs) => { - setSaajanMessage("Good. I'll remember that."); - setIsLoading(true); - setApiError(null); - try { - // we generate the key bundle here to get the authHash (password) to be haSHed and stored in the db. - const { authHash } = await CryptoUtils.deriveKeyBundle( - data.password, - data.email, - ); + const onSubmit = async (data: RegisterInputs) => { + setSaajanMessage("Good. I'll remember that."); + setIsLoading(true); + setApiError(null); + try { + // we generate the key bundle here to get the authHash (password) to be haSHed and stored in the db. + const { authHash } = await CryptoUtils.deriveKeyBundle( + data.password, + data.email, + ); - await publicApi.post(endpoints.REGISTER, { - full_name: data.full_name, - email: data.email, - password: authHash, - }); - navigate(ROUTES.VERIFY_EMAIL, { replace: true }); - } catch (err) { - let message = "Registration failed. Please try again."; - if (axios.isAxiosError(err)) { - message = err.response?.data?.message || message; - } - setApiError(message); - } finally { - setIsLoading(false); - } - }; + await publicApi.post(endpoints.REGISTER, { + full_name: data.full_name, + email: data.email, + password: authHash, + }); + navigate(ROUTES.VERIFY_EMAIL, { replace: true }); + } catch (err) { + let message = "Registration failed. Please try again."; + if (axios.isAxiosError(err)) { + message = err.response?.data?.message || message; + } + setApiError(message); + } finally { + setIsLoading(false); + } + }; - return ( -
- -
-
-
- Create a Account -
+ return ( +
+ +
+ +
+ Create a Account +
- {apiError && ( -
- {apiError} -
- )} - - - setSaajanMessage("Hello friend. What should I call you?") - } - /> - - - setSaajanMessage( - "Where should I send your letters?\nNo empty lunchboxes, please.", - ) - } - /> - - - setSaajanMessage( - "Something only you know.\nI have one of those too.", - ) - } - /> - - - setSaajanMessage( - "Just once? Trust me, \nsome things are worth repeating twice.", - ) - } - /> - -
- -

- Choose a password you won't forget.
- Just like life,{" "} - there is no reset{" "} - here. If you lose it, your letters cannot be recovered. -

-
- -
- -
- + {apiError && ( +
+ {apiError}
-
- ); + )} + + + setSaajanMessage("Hello friend. What should I call you?") + } + /> + + + setSaajanMessage( + "Where should I send your letters?\nNo empty lunchboxes, please.", + ) + } + /> + + + setSaajanMessage( + "Something only you know.\nI have one of those too.", + ) + } + /> + + + setSaajanMessage( + "Just once? Trust me, \nsome things are worth repeating twice.", + ) + } + /> + +
+ +

+ Choose a password you won't forget.
+ Just like life,{" "} + there is no reset{" "} + here. If you lose it, your letters cannot be recovered. +

+
+ +
+ +
+ +
+
+ ); }