Skip to content

Commit 0f6241f

Browse files
feat: auth flow — sign up, sign in, sign out (#24) (#37)
* feat: initial commit Co-authored-by: Ona <no-reply@ona.com> * feat: auth flow — sign up, sign in, sign out (#24) - Add (auth) route group with /sign-in and /sign-up pages - Add (app) route group with auth guard redirecting to /sign-in - Sign-up passes display_name to handle_new_user trigger - Post-auth redirect to user's personal workspace (/[workspaceSlug]) - Sign-out clears session and redirects to /sign-in - GitHub and Google OAuth buttons rendered disabled with 'coming soon' tooltip - Proxy-level auth redirect for unauthenticated users on protected routes - Switch root layout to JetBrains Mono, dark-only oklch theme tokens - Add shadcn/ui Card, Input, Label, Tooltip components - Update architecture and conventions docs Co-authored-by: Ona <no-reply@ona.com> --------- Co-authored-by: Ona <no-reply@ona.com>
1 parent 2fb6f32 commit 0f6241f

21 files changed

Lines changed: 2978 additions & 41 deletions

File tree

.agents/architecture.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,30 +140,50 @@ Auto-save: debounce 500ms on editor change → write to Supabase
140140

141141
## Component Map
142142

143-
Current state (infrastructure only — no product features yet):
144-
145143
```
146144
src/
147145
├── app/ # Next.js App Router
148-
│ ├── layout.tsx # Root layout (currently Geist fonts — will switch to JetBrains Mono)
146+
│ ├── layout.tsx # Root layout (JetBrains Mono font, TooltipProvider)
149147
│ ├── page.tsx # Landing page
150148
│ ├── manifest.ts # PWA manifest (name, icons, display mode)
151149
│ ├── global-error.tsx # Sentry error boundary
150+
│ ├── globals.css # Tailwind v4 theme — dark-only oklch tokens, --radius: 0
151+
│ ├── (auth)/ # Unauthenticated route group
152+
│ │ ├── layout.tsx # Centered card layout for auth pages
153+
│ │ ├── sign-in/page.tsx # /sign-in — email/password form
154+
│ │ └── sign-up/page.tsx # /sign-up — display name + email/password form
155+
│ ├── (app)/ # Authenticated route group
156+
│ │ ├── layout.tsx # Auth guard (redirects to /sign-in if no session), sign-out button
157+
│ │ └── [workspaceSlug]/
158+
│ │ └── page.tsx # /[workspaceSlug] — workspace home (placeholder)
152159
│ └── api/
153160
│ └── health/route.ts # Health check endpoint (DB connectivity)
161+
├── components/
162+
│ ├── auth/
163+
│ │ ├── oauth-buttons.tsx # GitHub + Google buttons (disabled, "coming soon" tooltip)
164+
│ │ └── sign-out-button.tsx # Sign-out button (clears session, redirects to /sign-in)
165+
│ └── ui/ # shadcn/ui components (base-nova style, base-ui primitives)
166+
│ ├── button.tsx
167+
│ ├── card.tsx
168+
│ ├── input.tsx
169+
│ ├── label.tsx
170+
│ └── tooltip.tsx
154171
├── lib/
172+
│ ├── utils.ts # cn() utility (clsx + tailwind-merge)
173+
│ ├── types.ts # Database entity types
155174
│ └── supabase/
156175
│ ├── client.ts # Browser client (createBrowserClient)
157176
│ ├── server.ts # Server component client (createServerClient + cookies)
158-
│ └── proxy.ts # Session refresh logic (updateSession)
177+
│ └── proxy.ts # Session refresh + auth redirect logic (updateSession)
159178
├── proxy.ts # Root proxy — calls updateSession, skips static/health routes
160179
└── instrumentation.ts # Sentry server/edge init (register + onRequestError)
161180
162181
Root config files:
163182
├── instrumentation-client.ts # Sentry client init (replay, route transitions)
164183
├── sentry.server.config.ts # Sentry server SDK config
165184
├── sentry.edge.config.ts # Sentry edge SDK config
166-
└── sentry.client.config.ts # Sentry client SDK config
185+
├── sentry.client.config.ts # Sentry client SDK config
186+
└── components.json # shadcn/ui config (base-nova style, Tailwind v4)
167187
```
168188

169189
Planned structure (added as features are built):

.agents/conventions.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,71 @@ Rules:
284284
- No `as` casts unless unavoidable (add a comment explaining why)
285285
- Prefer interfaces for object shapes, types for unions/intersections
286286

287+
## shadcn/ui (base-nova style)
288+
289+
This project uses shadcn/ui v4 with the `base-nova` style, which uses `@base-ui/react`
290+
primitives instead of Radix. Key differences from older shadcn:
291+
292+
### Tooltip composition
293+
294+
base-ui's `TooltipTrigger` does NOT support `asChild`. Use the `render` prop instead:
295+
296+
```typescript
297+
// ✅ Correct — base-ui render prop
298+
<TooltipTrigger
299+
render={<Button variant="outline" disabled />}
300+
>
301+
Button label
302+
</TooltipTrigger>
303+
304+
// ❌ Wrong — asChild does not exist on base-ui primitives
305+
<TooltipTrigger asChild>
306+
<Button>...</Button>
307+
</TooltipTrigger>
308+
```
309+
310+
### Button primitives
311+
312+
Buttons use `@base-ui/react/button` internally. The `Button` component accepts
313+
`ButtonPrimitive.Props & VariantProps<typeof buttonVariants>`.
314+
315+
## Auth Flow
316+
317+
### Route protection (two layers)
318+
319+
1. **Proxy layer** (`src/lib/supabase/proxy.ts`): optimistic redirect — unauthenticated
320+
users on non-public routes get redirected to `/sign-in`. Public routes: `/`, `/sign-in`,
321+
`/sign-up`, `/invite/*`.
322+
2. **Layout layer** (`src/app/(app)/layout.tsx`): authoritative check — server component
323+
calls `supabase.auth.getUser()` and redirects if no user. This is the security boundary.
324+
325+
### Post-auth redirect
326+
327+
After sign-in or sign-up, the client fetches the user's workspace membership to get
328+
the workspace slug, then redirects to `/{workspaceSlug}`. The query joins `members`
329+
with `workspaces` to get the slug in one call:
330+
331+
```typescript
332+
const { data: membership } = await supabase
333+
.from("members")
334+
.select("workspace_id, workspaces(slug)")
335+
.eq("user_id", user.id)
336+
.limit(1)
337+
.maybeSingle();
338+
```
339+
340+
### Sign-up data
341+
342+
Pass `display_name` in `signUp` options so the `handle_new_user` trigger can use it:
343+
344+
```typescript
345+
await supabase.auth.signUp({
346+
email,
347+
password,
348+
options: { data: { display_name: displayName } },
349+
});
350+
```
351+
287352
## This file evolves
288353

289354
When you discover a new pattern that should be replicated, or an anti-pattern that

components.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema.json",
3+
"style": "base-nova",
4+
"rsc": true,
5+
"tsx": true,
6+
"tailwind": {
7+
"config": "",
8+
"css": "src/app/globals.css",
9+
"baseColor": "neutral",
10+
"cssVariables": true,
11+
"prefix": ""
12+
},
13+
"iconLibrary": "lucide",
14+
"rtl": false,
15+
"aliases": {
16+
"components": "@/components",
17+
"utils": "@/lib/utils",
18+
"ui": "@/components/ui",
19+
"lib": "@/lib",
20+
"hooks": "@/hooks"
21+
},
22+
"menuColor": "default",
23+
"menuAccent": "subtle",
24+
"registries": {}
25+
}

package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,19 @@
1313
"test:e2e": "playwright test"
1414
},
1515
"dependencies": {
16+
"@base-ui/react": "^1.4.0",
1617
"@sentry/nextjs": "^10.48.0",
1718
"@supabase/ssr": "^0.10.2",
1819
"@supabase/supabase-js": "^2.103.0",
20+
"class-variance-authority": "^0.7.1",
21+
"clsx": "^2.1.1",
22+
"lucide-react": "^1.8.0",
1923
"next": "16.2.3",
2024
"react": "19.2.4",
21-
"react-dom": "19.2.4"
25+
"react-dom": "19.2.4",
26+
"shadcn": "^4.2.0",
27+
"tailwind-merge": "^3.5.0",
28+
"tw-animate-css": "^1.4.0"
2229
},
2330
"devDependencies": {
2431
"@playwright/test": "^1.59.1",

0 commit comments

Comments
 (0)