From 113ca17e1f1cc177088d0719ed822736079408a0 Mon Sep 17 00:00:00 2001 From: Kneesal Date: Thu, 19 Feb 2026 10:29:46 +1300 Subject: [PATCH 01/14] feat: use turbo for root dev commands (dev:web, dev:backend, codegen) Co-authored-by: Cursor --- package.json | 4 +++- turbo.json | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 937ce737..9c549a21 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "prepare": "husky", "build": "turbo run build", "dev": "turbo run dev --parallel", + "dev:web": "turbo run dev --filter=@forge/web", + "dev:backend": "turbo run dev --parallel --filter=@forge/cms --filter=@forge/ai-orchestrator", "lint": "turbo run lint", "test": "turbo run test", "codegen": "pnpm --filter @forge/graphql run generate", @@ -29,4 +31,4 @@ "turbo": "^2.8.9", "typescript-eslint": "^8.56.0" } -} +} \ No newline at end of file diff --git a/turbo.json b/turbo.json index 3d943c0e..8c3e4c97 100644 --- a/turbo.json +++ b/turbo.json @@ -25,6 +25,9 @@ }, "validate": { "dependsOn": ["^validate"] + }, + "generate": { + "outputs": ["src/graphql-env.d.ts"] } } } From 86df7b14c57ff50fc4163f6aea136ac06c24e2ca Mon Sep 17 00:00:00 2001 From: Kneesal Date: Thu, 19 Feb 2026 10:50:07 +1300 Subject: [PATCH 02/14] feat(web): add header, footer, layout with shadcn and theming - Add shadcn/ui (new-york, neutral), Button, Separator, NavigationMenu - Add Header and Footer components; Layout composes both - Theme CSS variables (:root + .dark) and breakpoints in globals.css - Component dirs: Header/, Footer/, Layout/ with ComponentName.tsx + index.ts Resolves #61 Co-authored-by: Cursor --- apps/web/components.json | 19 + apps/web/package.json | 10 +- apps/web/src/app/globals.css | 104 ++ apps/web/src/app/layout.tsx | 7 +- apps/web/src/components/Footer/Footer.tsx | 16 + apps/web/src/components/Footer/index.ts | 1 + apps/web/src/components/Header/Header.tsx | 37 + apps/web/src/components/Header/index.ts | 1 + apps/web/src/components/Layout/Layout.tsx | 15 + apps/web/src/components/Layout/index.ts | 1 + apps/web/src/components/ui/button.tsx | 64 + .../web/src/components/ui/navigation-menu.tsx | 168 ++ apps/web/src/components/ui/separator.tsx | 28 + apps/web/src/lib/utils.ts | 6 + pnpm-lock.yaml | 1503 ++++++++++++++++- 15 files changed, 1962 insertions(+), 18 deletions(-) create mode 100644 apps/web/components.json create mode 100644 apps/web/src/components/Footer/Footer.tsx create mode 100644 apps/web/src/components/Footer/index.ts create mode 100644 apps/web/src/components/Header/Header.tsx create mode 100644 apps/web/src/components/Header/index.ts create mode 100644 apps/web/src/components/Layout/Layout.tsx create mode 100644 apps/web/src/components/Layout/index.ts create mode 100644 apps/web/src/components/ui/button.tsx create mode 100644 apps/web/src/components/ui/navigation-menu.tsx create mode 100644 apps/web/src/components/ui/separator.tsx create mode 100644 apps/web/src/lib/utils.ts diff --git a/apps/web/components.json b/apps/web/components.json new file mode 100644 index 00000000..ea90fbea --- /dev/null +++ b/apps/web/components.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} diff --git a/apps/web/package.json b/apps/web/package.json index 09e3592c..2035713c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -12,10 +12,15 @@ "dependencies": { "@apollo/client": "^4.1.4", "@forge/graphql": "workspace:*", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "graphql": "^16.12.0", + "lucide-react": "^0.574.0", "next": "^16.1.6", + "radix-ui": "^1.4.3", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "tailwind-merge": "^3.4.1" }, "devDependencies": { "@tailwindcss/postcss": "^4.1.18", @@ -24,6 +29,7 @@ "eslint": "^9.0.0", "eslint-config-next": "^16.1.6", "postcss": "^8.5.6", - "tailwindcss": "^4.1.18" + "tailwindcss": "^4.1.18", + "tw-animate-css": "^1.4.0" } } diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index f1d8c73c..7ed22460 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -1 +1,105 @@ @import "tailwindcss"; +@import "tw-animate-css"; + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --destructive-foreground: oklch(0.985 0 0); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.269 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.371 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --destructive-foreground: oklch(0.985 0 0); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@theme inline { + /* Breakpoints (min-width, mobile-first). Use rem for consistent ordering. */ + --breakpoint-sm: 40rem; /* 640px */ + --breakpoint-md: 48rem; /* 768px */ + --breakpoint-lg: 64rem; /* 1024px */ + --breakpoint-xl: 80rem; /* 1280px */ + --breakpoint-2xl: 96rem; /* 1536px */ + + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: calc(var(--radius) + 2px); +} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 7dee0020..29ad14b3 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,10 +1,13 @@ import type { ReactNode } from "react" import "./globals.css" +import { Layout } from "../components/Layout" export default function RootLayout(props: { children: ReactNode }) { return ( - - {props.children} + + + {props.children} + ) } diff --git a/apps/web/src/components/Footer/Footer.tsx b/apps/web/src/components/Footer/Footer.tsx new file mode 100644 index 00000000..588ab6c7 --- /dev/null +++ b/apps/web/src/components/Footer/Footer.tsx @@ -0,0 +1,16 @@ +import { Separator } from "@/components/ui/separator" + +export function Footer() { + return ( +
+
+ +
+

+ © {new Date().getFullYear()} Forge. All rights reserved. +

+
+
+
+ ) +} diff --git a/apps/web/src/components/Footer/index.ts b/apps/web/src/components/Footer/index.ts new file mode 100644 index 00000000..246e88f5 --- /dev/null +++ b/apps/web/src/components/Footer/index.ts @@ -0,0 +1 @@ +export { Footer } from "./Footer" diff --git a/apps/web/src/components/Header/Header.tsx b/apps/web/src/components/Header/Header.tsx new file mode 100644 index 00000000..0804580b --- /dev/null +++ b/apps/web/src/components/Header/Header.tsx @@ -0,0 +1,37 @@ +import Link from "next/link" +import { Button } from "@/components/ui/button" +import { + NavigationMenu, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuList, + navigationMenuTriggerStyle, +} from "@/components/ui/navigation-menu" + +export function Header() { + return ( +
+ +
+ ) +} diff --git a/apps/web/src/components/Header/index.ts b/apps/web/src/components/Header/index.ts new file mode 100644 index 00000000..1593fdce --- /dev/null +++ b/apps/web/src/components/Header/index.ts @@ -0,0 +1 @@ +export { Header } from "./Header" diff --git a/apps/web/src/components/Layout/Layout.tsx b/apps/web/src/components/Layout/Layout.tsx new file mode 100644 index 00000000..17877b94 --- /dev/null +++ b/apps/web/src/components/Layout/Layout.tsx @@ -0,0 +1,15 @@ +import type { ReactNode } from "react" +import { Header } from "../Header" +import { Footer } from "../Footer" + +export function Layout({ children }: { children: ReactNode }) { + return ( + <> +
+
+ {children} +
+