From 0d9f8c81393ea88d82553aa32726bb69947e52c9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 13 Sep 2026 06:55:01 +0000 Subject: [PATCH 1/5] Redesign UI across marketing, auth, recipients, and settings - Add self-hosted Inter/Lora fonts, tighter container, global focus styles - Rework app header/footer: skip link, clear login/signup CTAs, user menu - Add real Privacy, Terms, Support, and About content on a shared prose layout - Unify auth pages on an AuthPage card; fix signup link losing redirectTo - Redesign recipients list, detail, editor (schedule presets, next-send preview, pause toggle), message thread, and new-message form - Rebuild settings: profile, subscription, password, phone, and 2FA pages - Improve shared components: checkbox, dropdown, search bar, error boundary - Remove dead /components stub route Co-authored-by: Kent C. Dodds --- app/components/auth-page.tsx | 41 ++ app/components/error-boundary.tsx | 90 +++- app/components/forms.tsx | 24 +- app/components/prose-page.tsx | 108 +++++ app/components/search-bar.tsx | 57 ++- app/components/settings-card.tsx | 34 ++ app/components/ui/checkbox.tsx | 12 +- app/components/ui/dropdown-menu.tsx | 28 +- app/components/user-profile.tsx | 71 ++-- app/root.tsx | 6 +- app/routes/$.tsx | 25 +- app/routes/_app+/_auth+/forgot-password.tsx | 125 +++--- app/routes/_app+/_auth+/login.tsx | 135 +++--- app/routes/_app+/_auth+/onboarding.tsx | 184 +++++---- app/routes/_app+/_auth+/reset-password.tsx | 89 ++-- app/routes/_app+/_auth+/signup.tsx | 106 +++-- app/routes/_app+/_auth+/verify.tsx | 169 ++++---- app/routes/_app+/_layout.tsx | 373 +++++++++++------ app/routes/_app+/_marketing+/about.tsx | 184 ++++----- app/routes/_app+/_marketing+/index.tsx | 389 ++++++++++++------ app/routes/_app+/_marketing+/privacy.tsx | 192 ++++++++- app/routes/_app+/_marketing+/support.tsx | 150 ++++++- app/routes/_app+/_marketing+/tos.tsx | 168 +++++++- .../_app+/recipients+/$recipientId.edit.tsx | 13 +- .../_app+/recipients+/$recipientId.index.tsx | 93 +++-- .../_app+/recipients+/$recipientId.new.tsx | 39 +- app/routes/_app+/recipients+/$recipientId.tsx | 159 +++---- app/routes/_app+/recipients+/__editor.tsx | 383 ++++++++++------- app/routes/_app+/recipients+/_layout.tsx | 15 +- app/routes/_app+/recipients+/index.tsx | 262 +++++++----- app/routes/_app+/recipients+/new.tsx | 17 +- .../_app+/settings.profile+/_layout.tsx | 31 +- .../_app+/settings.profile+/change-number.tsx | 76 ++-- app/routes/_app+/settings.profile+/index.tsx | 332 ++++++++------- .../_app+/settings.profile+/password.tsx | 37 +- .../_app+/settings.profile+/subscription.tsx | 265 ++++++++---- .../settings.profile+/two-factor.disable.tsx | 25 +- .../settings.profile+/two-factor.index.tsx | 78 ++-- .../settings.profile+/two-factor.verify.tsx | 166 +++++--- app/routes/_app+/users+/$username.tsx | 11 +- app/routes/_app+/users+/index.tsx | 16 +- app/routes/components.tsx | 7 - app/routes/resources+/theme-switch.tsx | 41 +- app/styles/tailwind.css | 40 +- bun.lock | 8 +- package.json | 2 + 46 files changed, 3188 insertions(+), 1688 deletions(-) create mode 100644 app/components/auth-page.tsx create mode 100644 app/components/prose-page.tsx create mode 100644 app/components/settings-card.tsx delete mode 100644 app/routes/components.tsx diff --git a/app/components/auth-page.tsx b/app/components/auth-page.tsx new file mode 100644 index 00000000..191d8724 --- /dev/null +++ b/app/components/auth-page.tsx @@ -0,0 +1,41 @@ +import { type ReactNode } from 'react' + +export function AuthPage({ + title, + description, + children, + footer, +}: { + title: string + description?: ReactNode + children: ReactNode + footer?: ReactNode +}) { + return ( +
+
+
+

+ GratiText +

+

+ {title} +

+ {description ? ( +

+ {description} +

+ ) : null} +
+
+ {children} +
+ {footer ? ( +
+ {footer} +
+ ) : null} +
+
+ ) +} diff --git a/app/components/error-boundary.tsx b/app/components/error-boundary.tsx index 66108f32..c85e819e 100644 --- a/app/components/error-boundary.tsx +++ b/app/components/error-boundary.tsx @@ -3,24 +3,90 @@ import { type ReactElement, useEffect } from 'react' import { type ErrorResponse, isRouteErrorResponse, + Link, useParams, useRouteError, } from 'react-router' import { getErrorMessage } from '#app/utils/misc.tsx' +import { Icon } from './ui/icon.tsx' type StatusHandler = (info: { error: ErrorResponse params: Record }) => ReactElement | null +function statusTitle(status: number) { + switch (status) { + case 400: + return 'That request did not look right' + case 401: + return 'Please log in to continue' + case 403: + return 'You are not allowed to do that' + case 404: + return 'We could not find that' + case 429: + return 'Slow down a little' + default: + return status >= 500 + ? 'Something went wrong on our end' + : 'Something went wrong' + } +} + +export function ErrorMessage({ + eyebrow, + title, + description, +}: { + eyebrow?: string + title: string + description?: string | null +}) { + return ( +
+ {eyebrow ? ( +

+ {eyebrow} +

+ ) : null} +

+ {title} +

+ {description ? ( +

+ {description} +

+ ) : null} + +
+ ) +} + export function GeneralErrorBoundary({ defaultStatusHandler = ({ error }) => ( -

- {error.status} {error.data} -

+ ), statusHandlers, - unexpectedErrorHandler = (error) =>

{getErrorMessage(error)}

, + unexpectedErrorHandler = (error) => ( + + ), }: { defaultStatusHandler?: StatusHandler statusHandlers?: Record @@ -39,13 +105,15 @@ export function GeneralErrorBoundary({ } return ( -
- {isRouteErrorResponse(error) - ? (statusHandlers?.[error.status] ?? defaultStatusHandler)({ - error, - params, - }) - : unexpectedErrorHandler(error)} +
+
+ {isRouteErrorResponse(error) + ? (statusHandlers?.[error.status] ?? defaultStatusHandler)({ + error, + params, + }) + : unexpectedErrorHandler(error)} +
) } diff --git a/app/components/forms.tsx b/app/components/forms.tsx index 0a6d2dfd..9663bf15 100644 --- a/app/components/forms.tsx +++ b/app/components/forms.tsx @@ -5,6 +5,7 @@ import { type OTPInputProps, } from 'input-otp' import React, { useId } from 'react' +import { cn } from '#app/utils/misc.tsx' import { Checkbox, type CheckboxProps } from './ui/checkbox.tsx' import { Icon } from './ui/icon.tsx' import { @@ -46,7 +47,7 @@ export function Field({ className, }: { labelProps: React.LabelHTMLAttributes - inputProps: React.InputHTMLAttributes + inputProps: React.ComponentProps<'input'> errors?: ListOfErrors className?: string }) { @@ -61,6 +62,7 @@ export function Field({ aria-invalid={errorId ? true : undefined} aria-describedby={errorId} {...inputProps} + className={cn('mt-2', inputProps.className)} />
{errorId ? : null} @@ -76,7 +78,7 @@ export function SelectField({ className, }: { labelProps: React.LabelHTMLAttributes - selectProps: React.SelectHTMLAttributes + selectProps: React.ComponentProps<'select'> errors?: ListOfErrors className?: string }) { @@ -86,7 +88,7 @@ export function SelectField({ return (