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 e5b7c3a..0000000 Binary files a/frontend/public/assets/images/favicon.png and /dev/null differ diff --git a/frontend/public/assets/images/fluksio-dark.svg b/frontend/public/assets/images/fluksio-dark.svg new file mode 100644 index 0000000..cccdc8a --- /dev/null +++ b/frontend/public/assets/images/fluksio-dark.svg @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 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 (