-
Notifications
You must be signed in to change notification settings - Fork 1
Marketing copy refresh + UX/UI audit fixes (23 issues) #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import type { ReactNode } from "react"; | ||
| import type { LucideIcon } from "lucide-react"; | ||
| import { Inbox } from "lucide-react"; | ||
|
|
||
| /** | ||
| * Friendly empty state. Defaults to an Inbox glyph but accepts any | ||
| * lucide icon. Optional CTA renders as a primary button or link. | ||
| */ | ||
| export function EmptyState({ | ||
| icon: Icon = Inbox, | ||
| title, | ||
| body, | ||
| cta, | ||
| }: { | ||
| icon?: LucideIcon; | ||
| title: string; | ||
| body?: ReactNode; | ||
| cta?: { label: string; href: string }; | ||
| }) { | ||
| return ( | ||
| <div className="mx-auto flex max-w-md flex-col items-center rounded-2xl border border-dashed border-border bg-surface/30 px-6 py-10 text-center"> | ||
| <div className="inline-flex h-12 w-12 items-center justify-center rounded-xl bg-primary/10 text-primary"> | ||
| <Icon className="h-5 w-5" strokeWidth={1.75} aria-hidden /> | ||
| </div> | ||
| <h3 className="mt-4 text-lg font-semibold tracking-tight">{title}</h3> | ||
| {body && <p className="mt-2 text-sm text-muted-foreground">{body}</p>} | ||
| {cta && ( | ||
| <a | ||
| href={cta.href} | ||
| className="mt-5 inline-flex h-10 items-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:opacity-95" | ||
| > | ||
| {cta.label} | ||
| </a> | ||
| )} | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import type { ReactNode } from "react"; | ||
| import { Nav } from "./Nav"; | ||
| import { Footer } from "./Footer"; | ||
|
|
||
| /** | ||
| * Standard page chrome: Nav + main content + Footer. Use for every route | ||
| * outside the marketing landing page so users never land on an "orphan" | ||
| * screen without branding or navigation. | ||
| * | ||
| * <SitePage> | ||
| * <main className="...">…</main> | ||
| * </SitePage> | ||
| */ | ||
| export function SitePage({ children }: { children: ReactNode }) { | ||
| return ( | ||
| <div className="flex min-h-screen flex-col bg-background"> | ||
| <Nav /> | ||
| <div className="flex-1">{children}</div> | ||
| <Footer /> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { useEffect, useState } from "react"; | ||
| import { Sun, Moon } from "lucide-react"; | ||
|
|
||
| type Mode = "light" | "dark"; | ||
|
|
||
| function readMode(): Mode { | ||
| if (typeof document === "undefined") return "light"; | ||
| const stored = localStorage.getItem("sas-theme"); | ||
| if (stored === "light" || stored === "dark") return stored; | ||
| return document.documentElement.classList.contains("dark") ? "dark" : "light"; | ||
| } | ||
|
|
||
| function applyMode(mode: Mode) { | ||
| document.documentElement.classList.toggle("dark", mode === "dark"); | ||
| try { localStorage.setItem("sas-theme", mode); } catch {} | ||
| } | ||
|
|
||
| export function ThemeToggle({ className = "" }: { className?: string }) { | ||
| const [mode, setMode] = useState<Mode>("light"); | ||
|
|
||
| useEffect(() => { | ||
| setMode(readMode()); | ||
| }, []); | ||
|
Comment on lines
+21
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The mount effect only updates React state ( Useful? React with 👍 / 👎. |
||
|
|
||
| const toggle = () => { | ||
| const next: Mode = mode === "dark" ? "light" : "dark"; | ||
| setMode(next); | ||
| applyMode(next); | ||
| }; | ||
|
|
||
| const Icon = mode === "dark" ? Sun : Moon; | ||
|
|
||
| return ( | ||
| <button | ||
| type="button" | ||
| onClick={toggle} | ||
| aria-label={`Switch to ${mode === "dark" ? "light" : "dark"} theme`} | ||
| title={`Switch to ${mode === "dark" ? "light" : "dark"} theme`} | ||
| className={`inline-flex h-9 w-9 items-center justify-center rounded-md border border-border bg-background/40 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${className}`} | ||
| > | ||
| <Icon className="h-4 w-4" strokeWidth={1.75} /> | ||
| </button> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
localStorage.getItem("sas-theme")is called without atry/catch. In browsers/environments where storage access is blocked (e.g., strict privacy settings), this can throw aSecurityErrorduring the effect path and break the toggle initialization inside the navigation UI.Useful? React with 👍 / 👎.