Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .markdownlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ MD033: false # Allow inline HTML (<details>, <tags>, etc.)
MD036: false # Allow emphasis (bold/italic) without treating it as heading
MD040: false # Fenced code blocks don't need language specified
MD041: false # First line doesn't need to be h1
MD029: false # Allow any ordered list style (1/2/3 or continuous numbering)
13 changes: 11 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ The project uses British English - strictly.
- **React Compiler** enabled for automatic optimisations
- **TypeScript 5** with strict mode
- **Tailwind CSS v4** with PostCSS
- **CI/CD** Vitest, PlayWright, GitHub Actions, Vercel
- **CI/CD** Vitest, Playwright, GitHub Actions, Vercel
- **Biome** for linting/formatting (replaces ESLint + Prettier)
- **Lefthook** for Git hooks (pre-commit: lint, typecheck, unit tests; pre-push: E2E tests)
- **Clerk** for authentication (installed via shadcn/ui CLI with `@clerk/themes`)

## Key Commands

Expand All @@ -38,7 +39,15 @@ vercel whoami # Verify CLI is authenticated

- Tailwind v4 uses `@import "tailwindcss"` syntax (not `@tailwind` directives)
- Next.js 16 Dynamic route `params` is a Promise - must await: `{ params }: { params: Promise<{ id: string }> }`
- Next.js 16 Middleware renamed to Proxy - `middleware.ts` → `proxy.ts`, export `proxy()` not `middleware()`
- Next.js 16 Middleware renamed to Proxy - `middleware.ts` → `proxy.ts` (but still uses `clerkMiddleware()` function)

## Authentication (Clerk)

- ClerkProvider: `components/clerk-provider.tsx` (applies shadcn theme + Inter font)
- Auth routes: `app/(auth)/sign-in/[[...sign-in]]`, `app/(auth)/sign-up/[[...sign-up]]`
- Sign In: `components/auth/clerk-signin.tsx` — client component with theme-aware logo
- Sign Up: `components/auth/clerk-signup.tsx` — static logo
- Proxy: `proxy.ts` with `clerkMiddleware()` (not middleware.ts)

## Common Additions for New Projects

Expand Down
4 changes: 2 additions & 2 deletions app/(auth)/sign-in/[[...sign-in]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { SignIn } from "@clerk/nextjs";
import { connection } from "next/server";
import { ClerkSignIn } from "@/components/auth/clerk-signin";

export default async function SignInPage() {
await connection();

return (
<div className="flex min-h-screen items-center justify-center p-6">
<SignIn />
<ClerkSignIn />
</div>
);
}
53 changes: 4 additions & 49 deletions app/(auth)/sign-up/[[...sign-up]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,12 @@
import { SignUp } from "@clerk/nextjs";
import { ChartLine, Clock, ShieldCheck, Sparkles } from "lucide-react";
import { connection } from "next/server";
import { ClerkSignUp } from "@/components/auth/clerk-signup";

export default async function SignUpPage() {
await connection();

return (
<div className="grid min-h-screen lg:grid-cols-2">
<div className="hidden flex-1 items-center justify-end p-6 md:p-10 lg:flex">
<ul className="max-w-sm space-y-8">
<li>
<div className="flex items-center gap-2">
<Clock className="size-4" aria-hidden="true" />
<p className="font-semibold">Save on development time</p>
</div>
<p className="text-muted-foreground mt-2 text-sm">
Add authentication and user management to your app with just a few
lines of code.
</p>
</li>
<li>
<div className="flex items-center gap-2">
<ChartLine className="size-4" aria-hidden="true" />
<p className="font-semibold">Increase engagement</p>
</div>
<p className="text-muted-foreground mt-2 text-sm">
Add intuitive UIs designed to decrease friction for your users.
</p>
</li>
<li>
<div className="flex items-center gap-2">
<ShieldCheck className="size-4" aria-hidden="true" />
<p className="font-semibold">Protect your users</p>
</div>
<p className="text-muted-foreground mt-2 text-sm">
Enable features like two-step verification and enjoy automatic
security updates.
</p>
</li>
<li>
<div className="flex items-center gap-2">
<Sparkles className="size-4" aria-hidden="true" />
<p className="font-semibold">Match your brand</p>
</div>
<p className="text-muted-foreground mt-2 text-sm">
Theme our pre-built components, or integrate with our easy-to-use
APIs.
</p>
</li>
</ul>
</div>
<div className="flex flex-1 items-center justify-center p-6 md:p-10 lg:justify-start">
<SignUp />
</div>
<div className="flex min-h-screen items-center justify-center p-6">
<ClerkSignUp />
</div>
);
}
17 changes: 15 additions & 2 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@layer theme, base, clerk, components, utilities; /* Must be at top */
@import "tailwindcss";
@import "tw-animate-css";
@import "@clerk/themes/shadcn.css";
Expand Down Expand Up @@ -57,7 +58,7 @@

--background: oklch(0.994 0 0); /* #FDFDFD */
--foreground: oklch(0.129 0.042 264.695);
--card: oklch(1 0 0);
--card: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--card-foreground: oklch(0.129 0.042 264.695);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.129 0.042 264.695);
Expand Down Expand Up @@ -86,13 +87,22 @@
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
--sidebar-border: oklch(0.929 0.013 255.508);
--sidebar-ring: oklch(0.704 0.04 256.788);
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* #E2995F */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
);

/* Centralised logo theming */
--logo-full-themed: url("/images/logo-light.svg");
}

.dark {
/* Overrides CSS variables when .dark class is present */
--background: oklch(0 0 0); /* #000000 */
--foreground: oklch(0.984 0.003 247.858);
--card: oklch(0.208 0.042 265.755);
--card: oklch(0.1783 0.0128 270.6); /* #0F1117 */
--card-foreground: oklch(0.984 0.003 247.858);
--popover: oklch(0.208 0.042 265.755);
--popover-foreground: oklch(0.984 0.003 247.858);
Expand Down Expand Up @@ -121,6 +131,9 @@
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.551 0.027 264.364);

/* Centralised logo theming (dark) */
--logo-full-themed: url("/images/logo-dark.svg");
}

@layer base {
Expand Down
22 changes: 7 additions & 15 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Suspense } from "react";
import { inter, jetbrainsMono, spaceGrotesk } from "@/app/fonts";
import { ClerkProvider } from "@/components/clerk-provider";
import { ThemeProvider } from "@/components/theme-provider";
import "@/app/globals.css";

const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});

const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});

export const metadata: Metadata = {
title: "DevFlow",
description:
Expand All @@ -32,10 +22,12 @@ export default function RootLayout({
return (
<Suspense fallback={null}>
<ClerkProvider>
<html lang="en" className="h-full" suppressHydrationWarning>
<body
className={`${geistSans.variable} ${geistMono.variable} flex min-h-full flex-col antialiased`}
>
<html
lang="en"
className={`${inter.variable} ${spaceGrotesk.variable} ${jetbrainsMono.variable} h-full`}
suppressHydrationWarning
>
<body className="flex min-h-full flex-col antialiased">
<ThemeProvider
attribute="class"
defaultTheme="system"
Expand Down
26 changes: 26 additions & 0 deletions components/auth/clerk-signin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { SignIn } from "@clerk/nextjs";
import { useTheme } from "next-themes";

export function ClerkSignIn() {
const { resolvedTheme } = useTheme();
const logoUrl =
resolvedTheme === "light"
? "/images/logo-light.svg"
: "/images/logo-dark.svg";

return (
<SignIn
appearance={{
layout: { logoImageUrl: logoUrl },
elements: {
header: "items-start",
logoBox: "justify-start",
headerTitle: "text-left",
headerSubtitle: "text-left",
},
}}
/>
);
}
13 changes: 13 additions & 0 deletions components/auth/clerk-signup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { SignUp } from "@clerk/nextjs";

export function ClerkSignUp() {
return (
<SignUp
appearance={{
layout: {
logoImageUrl: "/images/site-logo.svg",
},
}}
/>
);
}
26 changes: 26 additions & 0 deletions components/clerk-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,40 @@ type ClerkProviderProps = React.ComponentProps<typeof ClerkNextJSProvider>;
export function ClerkProvider({
children,
appearance,
localization,
...props
}: ClerkProviderProps) {
return (
<ClerkNextJSProvider
appearance={{
theme: shadcn,
cssLayerName: "clerk",
variables: {
fontFamily: "var(--font-sans-serif)",
},
elements: {
formButtonPrimary:
"bg-[image:var(--gradient-primary)] text-white hover:opacity-90",
footer: "bg-card",
},
...appearance,
}}
localization={{
socialButtonsBlockButton: "{{provider|titleize}}",
signIn: {
start: {
title: "Sign in",
subtitle: "to continue to DevFlow",
},
},
signUp: {
start: {
title: "Sign up",
subtitle: "to continue to DevFlow",
},
},
...localization,
}}
{...props}
>
{children}
Expand Down
15 changes: 10 additions & 5 deletions components/navigation/navbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,22 @@ export const Navbar = () => (
aria-label="Primary navigation"
className="sticky top-0 z-50 flex w-full items-center justify-between bg-sidebar px-6 py-4 shadow-sm"
>
{/* Left: Logo + DevFlow text */}
<Link href="/" className="flex items-center gap-2">
{/* Left: Logo (responsive) */}
<Link href="/" className="flex items-center">
{/* Mobile: icon only */}
<Image
src="/images/site-logo.svg"
alt="DevFlow logo"
width={25}
height={25}
className="sm:hidden"
/>
{/* Desktop: full themed logo via --logo-full-themed in globals.css */}
<span
className="hidden h-6 aspect-137/23 bg-(image:--logo-full-themed) bg-contain bg-no-repeat sm:block"
role="img"
aria-label="DevFlow logo"
/>
<h2 className="hidden font-display text-2xl font-semibold text-sidebar-foreground sm:block">
Dev<span className="text-primary">Flow</span>
</h2>
</Link>

{/* Centre: Global Search placeholder (md+ only) */}
Expand Down
2 changes: 2 additions & 0 deletions components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const buttonVariants = cva(
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
gradient:
"bg-[image:var(--gradient-primary)] text-white hover:opacity-90",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
Expand Down
Loading
Loading