From e098ff51f1da05ca6332523eec334393cfe56870 Mon Sep 17 00:00:00 2001 From: me Date: Thu, 14 May 2026 05:59:40 +0530 Subject: [PATCH] style: update glass card layout padding and form copy --- frontend/src/index.css | 102 +++++------ frontend/src/pages/Login.tsx | 238 ++++++++++++------------ frontend/src/pages/Register.tsx | 316 ++++++++++++++++---------------- 3 files changed, 327 insertions(+), 329 deletions(-) diff --git a/frontend/src/index.css b/frontend/src/index.css index a35eb7f..c6f21c6 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -2,77 +2,75 @@ @plugin "daisyui"; @plugin "daisyui/theme" { - name: "piku"; - default: true; - prefersdark: true; - color-scheme: dark; + name: "piku"; + default: true; + prefersdark: true; + color-scheme: dark; - --color-base-100: oklch(14% 0.012 35); - --color-base-200: oklch(18% 0.014 33); - --color-base-300: oklch(22% 0.016 32); - --color-base-content: oklch(82% 0.02 70); + --color-base-100: oklch(14% 0.012 35); + --color-base-200: oklch(18% 0.014 33); + --color-base-300: oklch(22% 0.016 32); + --color-base-content: oklch(82% 0.02 70); - --color-primary: oklch(67% 0.11 78); - --color-primary-content: oklch(15% 0.03 70); + --color-primary: oklch(67% 0.11 78); + --color-primary-content: oklch(15% 0.03 70); - --color-secondary: oklch(48% 0.08 305); - --color-secondary-content: oklch(92% 0.01 305); + --color-secondary: oklch(48% 0.08 305); + --color-secondary-content: oklch(92% 0.01 305); - --color-accent: oklch(55% 0.06 325); - --color-accent-content: oklch(18% 0.03 295); + --color-accent: oklch(55% 0.06 325); + --color-accent-content: oklch(18% 0.03 295); - --color-neutral: oklch(38% 0.02 45); - --color-neutral-content: oklch(80% 0.015 60); + --color-neutral: oklch(38% 0.02 45); + --color-neutral-content: oklch(80% 0.015 60); - --color-info: oklch(60% 0.06 250); - --color-info-content: oklch(95% 0.01 240); - --color-success: oklch(65% 0.05 140); - --color-success-content: oklch(16% 0.03 150); - --color-warning: oklch(68% 0.08 72); - --color-warning-content: oklch(18% 0.03 60); - --color-error: oklch(55% 0.1 22); - --color-error-content: oklch(92% 0.01 22); + --color-info: oklch(60% 0.06 250); + --color-info-content: oklch(95% 0.01 240); + --color-success: oklch(65% 0.05 140); + --color-success-content: oklch(16% 0.03 150); + --color-warning: oklch(68% 0.08 72); + --color-warning-content: oklch(18% 0.03 60); + --color-error: oklch(55% 0.1 22); + --color-error-content: oklch(92% 0.01 22); - --radius-selector: 0.5rem; - --radius-field: 0.375rem; - --radius-box: 0.5rem; + --radius-selector: 0.5rem; + --radius-field: 0.375rem; + --radius-box: 0.5rem; - --depth: 1; - --noise: 0.03; + --depth: 1; + --noise: 0.03; - --border: 1px; + --border: 1px; } @theme { - --font-display: "Playwrite HR Lijeva Variable", cursive; - --font-sans: "Jost Variable", sans-serif; - --font-serif: "Fraunces Variable", serif; - --font-mono: "Space Mono", monospace; - --font-ink: "Kavivanar", sans-serif; - --font-redact: "Redacted Script", cursive; - --font-slab: "Josefin Slab Variable", serif; - --font-hand: "Architects Daughter", cursive; - --color-glass-bg: rgba(28, 22, 16, 0.45); - --shadow-warm: 0 20px 50px -12px rgba(30, 20, 12, 0.6); - --radius-xl: 1.5rem; - --color-paper: oklch(97% 0.008 80); - --text-xxs: 10px; - --tracking-widester: 0.5em; - --background-image-vig: radial-gradient( - circle at center, - transparent 0%, - rgba(0, 0, 0, 0.4) 100% - ); + --font-display: "Playwrite HR Lijeva Variable", cursive; + --font-sans: "Jost Variable", sans-serif; + --font-serif: "Fraunces Variable", serif; + --font-mono: "Space Mono", monospace; + --font-ink: "Kavivanar", sans-serif; + --font-redact: "Redacted Script", cursive; + --font-slab: "Josefin Slab Variable", serif; + --font-hand: "Architects Daughter", cursive; + --color-glass-bg: rgba(28, 22, 16, 0.45); + --shadow-warm: 0 20px 50px -12px rgba(30, 20, 12, 0.6); + --radius-xl: 1.5rem; + --color-paper: oklch(97% 0.008 80); + --text-xxs: 10px; + --tracking-widester: 0.5em; + --background-image-vig: radial-gradient(circle at center, + transparent 0%, + rgba(0, 0, 0, 0.4) 100%); } .glass-card { - @apply bg-glass-bg max-w-xs md:max-w-sm backdrop-blur-xl border border-neutral-content/10 shadow-warm rounded-xl m-4; + @apply bg-glass-bg max-w-xs md:max-w-sm backdrop-blur-xl border border-neutral-content/10 shadow-warm rounded-xl m-2 md:m-4; } .ul-wavy { - @apply decoration-primary/40 underline decoration-wavy underline-offset-4; + @apply decoration-primary/40 underline decoration-wavy underline-offset-4; } a { - @apply text-primary underline decoration-base-content/20 underline-offset-4 hover:decoration-primary/60 transition-colors; + @apply text-primary underline decoration-base-content/20 underline-offset-4 hover:decoration-primary/60 transition-colors; } diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 98b319f..e9f0b94 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -16,136 +16,136 @@ 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, if you'd ever 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); - } - }; - - return ( -
- {!showWelcome && } - {showWelcome && } -
-
-

-   Enter Archive -

- - {apiError && ( -
- {apiError} -
- )} - - setSaajanMessage("I remember you.")} - /> - - - setSaajanMessage("The one thing I cannot know for you.") + 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); + } + }; -
- -
-
or
-
- New to - ?  - - . -
- -
-
- ); + return ( +
+ {!showWelcome && } + {showWelcome && } +
+
+

+  Unlock Archive +

+ + {apiError && ( +
+ {apiError} +
+ )} + + setSaajanMessage("I remember you.")} + /> + + + setSaajanMessage("The one thing I cannot know for you.") + } + /> + +
+ +
+
or
+
+ New to + ?  + + . +
+ +
+
+ ); } diff --git a/frontend/src/pages/Register.tsx b/frontend/src/pages/Register.tsx index f7b82a7..db1c8e8 100644 --- a/frontend/src/pages/Register.tsx +++ b/frontend/src/pages/Register.tsx @@ -14,172 +14,172 @@ 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} + {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. +

+
+ +
+ +
+
or
+
+ Been here before?  + + . +
+
- )} - - - 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. -

-
- -
- -
-
or
-
- Been here before?  - - . -
- -
-
- ); +
+ ); }