From 5988822053db6818cd174b35ac5102c1dca7424a Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Sun, 9 Aug 2026 15:33:56 +0200 Subject: [PATCH] De-template the frontend and apply the design system - FastAPI branding replaced throughout: title, favicon, logo (now the org/ brand SVGs), footer links, route titles; workspace package renamed - shadcn primitives restyled to the component radius/shadow map: pill controls, rounded-lg surfaces, shadow-e1/e2/e3 - floating frosted AppSidebar over a bg-card content region - theme storage key aligned with the pre-paint script, MotionConfig added - scripts/capture-screenshots.mjs backs the root 'make verify' Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 3 + bun.lock | 9 + frontend/index.html | 17 +- frontend/package.json | 1 + .../assets/images/fastapi-icon-light.svg | 77 -------- .../public/assets/images/fastapi-icon.svg | 77 -------- .../assets/images/fastapi-logo-light.svg | 83 -------- .../public/assets/images/fastapi-logo.svg | 91 --------- frontend/public/assets/images/favicon.png | Bin 5043 -> 0 bytes .../public/assets/images/fluksio-dark.svg | 183 +++++++++++++++++ .../public/assets/images/fluksio-light.svg | 186 ++++++++++++++++++ frontend/public/favicon.svg | 186 ++++++++++++++++++ frontend/scripts/capture-screenshots.mjs | 65 ++++++ frontend/src/components/Common/Footer.tsx | 19 +- frontend/src/components/Common/Logo.tsx | 32 +-- .../src/components/Sidebar/AppSidebar.tsx | 8 +- frontend/src/components/ui/button-group.tsx | 4 +- frontend/src/components/ui/button.tsx | 11 +- frontend/src/components/ui/card.tsx | 2 +- frontend/src/components/ui/checkbox.tsx | 2 +- frontend/src/components/ui/dialog.tsx | 2 +- frontend/src/components/ui/dropdown-menu.tsx | 4 +- frontend/src/components/ui/input.tsx | 2 +- frontend/src/components/ui/loading-button.tsx | 14 +- frontend/src/components/ui/password-input.tsx | 2 +- frontend/src/components/ui/select.tsx | 4 +- frontend/src/components/ui/sheet.tsx | 2 +- frontend/src/components/ui/sidebar.tsx | 6 +- frontend/src/components/ui/tabs.tsx | 2 +- frontend/src/main.tsx | 13 +- frontend/src/routeTree.gen.ts | 6 +- frontend/src/routes/_layout.tsx | 4 +- frontend/src/routes/_layout/admin.tsx | 2 +- frontend/src/routes/_layout/index.tsx | 2 +- frontend/src/routes/_layout/items.tsx | 2 +- frontend/src/routes/_layout/settings.tsx | 2 +- frontend/src/routes/login.tsx | 2 +- frontend/src/routes/recover-password.tsx | 2 +- frontend/src/routes/reset-password.tsx | 2 +- frontend/src/routes/signup.tsx | 2 +- package.json | 2 +- 41 files changed, 727 insertions(+), 408 deletions(-) delete mode 100644 frontend/public/assets/images/fastapi-icon-light.svg delete mode 100644 frontend/public/assets/images/fastapi-icon.svg delete mode 100644 frontend/public/assets/images/fastapi-logo-light.svg delete mode 100644 frontend/public/assets/images/fastapi-logo.svg delete mode 100644 frontend/public/assets/images/favicon.png create mode 100644 frontend/public/assets/images/fluksio-dark.svg create mode 100644 frontend/public/assets/images/fluksio-light.svg create mode 100644 frontend/public/favicon.svg create mode 100644 frontend/scripts/capture-screenshots.mjs diff --git a/.gitignore b/.gitignore index 4fa5595..f385039 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ __pycache__/ # Rendered pipeline graphs (matplotlib output from backend/app/flow). pipeline.png + +# Output of `make verify` (root Makefile). +/frontend/screenshots/ diff --git a/bun.lock b/bun.lock index cc73b20..1e1939f 100644 --- a/bun.lock +++ b/bun.lock @@ -33,6 +33,7 @@ "clsx": "^2.1.1", "form-data": "4.0.5", "lucide-react": "^0.562.0", + "motion": "^13.0.0", "next-themes": "^0.4.6", "react": "^19.1.1", "react-dom": "^19.2.3", @@ -548,6 +549,8 @@ "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + "framer-motion": ["framer-motion@13.0.0", "", { "dependencies": { "motion-dom": "^13.0.0", "motion-utils": "^13.0.0", "tslib": "^2.4.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-nQGZXlsiigN48nzvE7AL1GLekql+etcphp8v+PQ0X04oxO20yVmV9rU9XQ25c136RdeIYoaWlKT9oAwvJza3mg=="], + "frontend": ["frontend@workspace:frontend"], "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="], @@ -658,6 +661,12 @@ "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], + "motion": ["motion@13.0.0", "", { "dependencies": { "framer-motion": "^13.0.0", "tslib": "^2.4.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-RYZjEpgHUCKjBNqjmUQzW93VJWLvEZGTPdKRFSqOdOl07IIMvz+WrsZmD1XXAkoFLruW/8bO1L7UF+ViQTxtLg=="], + + "motion-dom": ["motion-dom@13.0.0", "", { "dependencies": { "motion-utils": "^13.0.0" } }, "sha512-Xk+SJas70uMAUIApg+m3lZDShxI3LBFHq7mFGbBKoRXc2PVPDyAKmzN64Bbzt4CZdP/CItTiJxWtn4TA0v53Ng=="], + + "motion-utils": ["motion-utils@13.0.0", "", {}, "sha512-7DnN7TmbLcYXcG4RVadXIihWlyuM9afoUww8Y5Agg431kGKiuL2/OMyP4mJ5wLz+pvN3t5ySClLOaVXJ+wekRQ=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], diff --git a/frontend/index.html b/frontend/index.html index 57621a2..4299a86 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,10 +2,21 @@ - + - Full Stack FastAPI Project - + + Fluksio + +
diff --git a/frontend/package.json b/frontend/package.json index 8d25f49..61cfd1d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -37,6 +37,7 @@ "clsx": "^2.1.1", "form-data": "4.0.5", "lucide-react": "^0.562.0", + "motion": "^13.0.0", "next-themes": "^0.4.6", "react": "^19.1.1", "react-dom": "^19.2.3", diff --git a/frontend/public/assets/images/fastapi-icon-light.svg b/frontend/public/assets/images/fastapi-icon-light.svg deleted file mode 100644 index d069c72..0000000 --- a/frontend/public/assets/images/fastapi-icon-light.svg +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/frontend/public/assets/images/fastapi-icon.svg b/frontend/public/assets/images/fastapi-icon.svg deleted file mode 100644 index df93a70..0000000 --- a/frontend/public/assets/images/fastapi-icon.svg +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/frontend/public/assets/images/fastapi-logo-light.svg b/frontend/public/assets/images/fastapi-logo-light.svg deleted file mode 100644 index 1a84b98..0000000 --- a/frontend/public/assets/images/fastapi-logo-light.svg +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - diff --git a/frontend/public/assets/images/fastapi-logo.svg b/frontend/public/assets/images/fastapi-logo.svg deleted file mode 100644 index c90d252..0000000 --- a/frontend/public/assets/images/fastapi-logo.svg +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - diff --git a/frontend/public/assets/images/favicon.png b/frontend/public/assets/images/favicon.png deleted file mode 100644 index e5b7c3ada7d093d237711560e03f6a841a4a41b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5043 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 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/assets/images/fluksio-light.svg b/frontend/public/assets/images/fluksio-light.svg new file mode 100644 index 0000000..55fa8ac --- /dev/null +++ b/frontend/public/assets/images/fluksio-light.svg @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..55fa8ac --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/scripts/capture-screenshots.mjs b/frontend/scripts/capture-screenshots.mjs new file mode 100644 index 0000000..959f4e7 --- /dev/null +++ b/frontend/scripts/capture-screenshots.mjs @@ -0,0 +1,65 @@ +/** + * Visual verification for the integrated local stack (root `make verify`). + * + * Logs into the dashboard with the bootstrap superuser and captures the app + * shell plus the website hero in both themes. This is the standard "did the + * change actually work" gate — look at the PNGs, do not just trust the build. + * + * Env (all set by the root Makefile): + * APP_URL, WEBSITE_URL, FIRST_SUPERUSER, FIRST_SUPERUSER_PASSWORD + * SCREENSHOT_DIR (default: ./screenshots) + */ +import { mkdir } from "node:fs/promises" +import { chromium } from "@playwright/test" + +const APP_URL = process.env.APP_URL || "http://app.localhost" +const WEBSITE_URL = process.env.WEBSITE_URL || "http://localhost" +const EMAIL = process.env.FIRST_SUPERUSER +const PASSWORD = process.env.FIRST_SUPERUSER_PASSWORD +const OUT = process.env.SCREENSHOT_DIR || "screenshots" + +if (!EMAIL || !PASSWORD) { + console.error( + "FIRST_SUPERUSER / FIRST_SUPERUSER_PASSWORD are unset — run from the root `make verify`.", + ) + process.exit(1) +} + +/** Force the theme through the same storage key the pre-paint script reads. */ +async function withTheme(browser, theme) { + const context = await browser.newContext({ + viewport: { width: 1440, height: 900 }, + colorScheme: theme, + }) + await context.addInitScript((t) => { + localStorage.setItem("fluksio-ui-theme", t) + }, theme) + return context +} + +const browser = await chromium.launch() + +for (const theme of ["light", "dark"]) { + const dir = `${OUT}/${theme}` + await mkdir(dir, { recursive: true }) + const context = await withTheme(browser, theme) + const page = await context.newPage() + + await page.goto(`${WEBSITE_URL}/`, { waitUntil: "networkidle" }) + await page.screenshot({ path: `${dir}/website-hero.png` }) + + await page.goto(`${APP_URL}/login`, { waitUntil: "networkidle" }) + await page.screenshot({ path: `${dir}/app-login.png` }) + + await page.getByPlaceholder(/email/i).fill(EMAIL) + await page.getByPlaceholder(/password/i).fill(PASSWORD) + await page.getByRole("button", { name: /log in/i }).click() + await page.waitForURL(`${APP_URL}/`, { timeout: 15000 }) + await page.waitForLoadState("networkidle") + await page.screenshot({ path: `${dir}/app-dashboard.png` }) + + await context.close() + console.log(` wrote ${dir}/{website-hero,app-login,app-dashboard}.png`) +} + +await browser.close() diff --git a/frontend/src/components/Common/Footer.tsx b/frontend/src/components/Common/Footer.tsx index 279e1e7..957ed92 100644 --- a/frontend/src/components/Common/Footer.tsx +++ b/frontend/src/components/Common/Footer.tsx @@ -1,17 +1,10 @@ -import { FaGithub, FaLinkedinIn } from "react-icons/fa" -import { FaXTwitter } from "react-icons/fa6" +import { SiCodeberg } from "react-icons/si" const socialLinks = [ { - icon: FaGithub, - href: "https://github.com/fastapi/fastapi", - label: "GitHub", - }, - { icon: FaXTwitter, href: "https://x.com/fastapi", label: "X" }, - { - icon: FaLinkedinIn, - href: "https://linkedin.com/company/fastapi", - label: "LinkedIn", + icon: SiCodeberg, + href: "https://codeberg.org/fluksio", + label: "Codeberg", }, ] @@ -21,9 +14,7 @@ export function Footer() { return (