@vibecodiq/cli 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/foundation/admin_basic/manifest.json +37 -0
  2. package/dist/foundation/admin_basic/migrations/004_user_roles.sql +117 -0
  3. package/dist/foundation/admin_basic/migrations/005_audit_log.sql +34 -0
  4. package/dist/foundation/admin_basic/migrations/006_impersonation_sessions.sql +22 -0
  5. package/dist/foundation/admin_basic/shared/audit.ts +97 -0
  6. package/dist/foundation/admin_basic/shared/guards.ts +58 -0
  7. package/dist/foundation/admin_basic/shared/impersonation.ts +165 -0
  8. package/dist/foundation/admin_basic/shared/permissions.ts +27 -0
  9. package/dist/foundation/admin_basic/shared/roles.ts +151 -0
  10. package/dist/foundation/admin_basic/slices/audit_log/handler.ts +34 -0
  11. package/dist/foundation/admin_basic/slices/audit_log/slice.contract.json +19 -0
  12. package/dist/foundation/admin_basic/slices/dashboard/handler.ts +51 -0
  13. package/dist/foundation/admin_basic/slices/dashboard/slice.contract.json +13 -0
  14. package/dist/foundation/admin_basic/slices/impersonation/handler.ts +61 -0
  15. package/dist/foundation/admin_basic/slices/impersonation/slice.contract.json +21 -0
  16. package/dist/foundation/admin_basic/slices/roles/handler.ts +90 -0
  17. package/dist/foundation/admin_basic/slices/roles/slice.contract.json +21 -0
  18. package/dist/foundation/admin_basic/slices/users/handler.ts +48 -0
  19. package/dist/foundation/admin_basic/slices/users/slice.contract.json +15 -0
  20. package/dist/foundation/auth_basic/manifest.json +32 -0
  21. package/dist/foundation/auth_basic/migrations/001_create_profiles.sql +36 -0
  22. package/dist/foundation/auth_basic/shared/guards.ts +89 -0
  23. package/dist/foundation/auth_basic/shared/hooks.ts +63 -0
  24. package/dist/foundation/auth_basic/shared/middleware.ts +46 -0
  25. package/dist/foundation/auth_basic/shared/server-user.ts +61 -0
  26. package/dist/foundation/auth_basic/shared/session.ts +38 -0
  27. package/dist/foundation/auth_basic/shared/types.ts +29 -0
  28. package/dist/foundation/auth_basic/slices/login/handler.ts +50 -0
  29. package/dist/foundation/auth_basic/slices/login/repository.ts +23 -0
  30. package/dist/foundation/auth_basic/slices/login/schemas.ts +22 -0
  31. package/dist/foundation/auth_basic/slices/login/slice.contract.json +19 -0
  32. package/dist/foundation/auth_basic/slices/login/ui/AuthLogin.tsx +107 -0
  33. package/dist/foundation/auth_basic/slices/login/ui/hook.ts +44 -0
  34. package/dist/foundation/auth_basic/slices/logout/handler.ts +19 -0
  35. package/dist/foundation/auth_basic/slices/logout/slice.contract.json +16 -0
  36. package/dist/foundation/auth_basic/slices/register/handler.ts +61 -0
  37. package/dist/foundation/auth_basic/slices/register/repository.ts +25 -0
  38. package/dist/foundation/auth_basic/slices/register/schemas.ts +29 -0
  39. package/dist/foundation/auth_basic/slices/register/slice.contract.json +21 -0
  40. package/dist/foundation/auth_basic/slices/register/ui/AuthRegister.tsx +118 -0
  41. package/dist/foundation/auth_basic/slices/register/ui/hook.ts +48 -0
  42. package/dist/foundation/auth_basic/slices/reset_password/handler.ts +47 -0
  43. package/dist/foundation/auth_basic/slices/reset_password/schemas.ts +21 -0
  44. package/dist/foundation/auth_basic/slices/reset_password/slice.contract.json +18 -0
  45. package/dist/foundation/auth_basic/slices/reset_password/ui/AuthResetPassword.tsx +79 -0
  46. package/dist/foundation/auth_basic/slices/reset_password/ui/hook.ts +48 -0
  47. package/dist/foundation/db_basic/manifest.json +33 -0
  48. package/dist/foundation/db_basic/shared/seed.ts +27 -0
  49. package/dist/foundation/db_basic/shared/supabase-client.ts +70 -0
  50. package/dist/foundation/db_basic/shared/types.ts +20 -0
  51. package/dist/foundation/db_basic/shared/utils.ts +43 -0
  52. package/dist/foundation/payments_basic/manifest.json +54 -0
  53. package/dist/foundation/payments_basic/migrations/002_create_subscriptions.sql +44 -0
  54. package/dist/foundation/payments_basic/migrations/003_create_entitlements.sql +54 -0
  55. package/dist/foundation/payments_basic/migrations/003b_create_webhook_events.sql +28 -0
  56. package/dist/foundation/payments_basic/shared/entitlement-hooks.ts +50 -0
  57. package/dist/foundation/payments_basic/shared/entitlement-types.ts +29 -0
  58. package/dist/foundation/payments_basic/shared/entitlements.ts +78 -0
  59. package/dist/foundation/payments_basic/shared/guards.ts +110 -0
  60. package/dist/foundation/payments_basic/shared/hooks.ts +45 -0
  61. package/dist/foundation/payments_basic/shared/plans.ts +54 -0
  62. package/dist/foundation/payments_basic/shared/reconciliation.ts +85 -0
  63. package/dist/foundation/payments_basic/shared/resolver.ts +61 -0
  64. package/dist/foundation/payments_basic/shared/stripe-client.ts +15 -0
  65. package/dist/foundation/payments_basic/shared/types.ts +84 -0
  66. package/dist/foundation/payments_basic/shared/webhook-handler.ts +198 -0
  67. package/dist/foundation/payments_basic/shared/webhook-processor.ts +174 -0
  68. package/dist/foundation/payments_basic/slices/cancel/handler.ts +55 -0
  69. package/dist/foundation/payments_basic/slices/cancel/slice.contract.json +17 -0
  70. package/dist/foundation/payments_basic/slices/cancel/ui/hook.ts +45 -0
  71. package/dist/foundation/payments_basic/slices/check_limits/handler.ts +33 -0
  72. package/dist/foundation/payments_basic/slices/check_limits/slice.contract.json +17 -0
  73. package/dist/foundation/payments_basic/slices/subscribe/handler.ts +79 -0
  74. package/dist/foundation/payments_basic/slices/subscribe/repository.ts +32 -0
  75. package/dist/foundation/payments_basic/slices/subscribe/schemas.ts +21 -0
  76. package/dist/foundation/payments_basic/slices/subscribe/slice.contract.json +20 -0
  77. package/dist/foundation/payments_basic/slices/subscribe/ui/BillingSubscribe.tsx +93 -0
  78. package/dist/foundation/payments_basic/slices/subscribe/ui/hook.ts +44 -0
  79. package/dist/foundation/payments_basic/slices/webhook/handler.ts +67 -0
  80. package/dist/foundation/payments_basic/slices/webhook/slice.contract.json +19 -0
  81. package/dist/index.js +19 -18
  82. package/package.json +11 -2
@@ -0,0 +1,89 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { createServerClient } from '@/shared/db/supabase-client';
3
+
4
+ // --- ASA GENERATED START ---
5
+ // Server-side auth guards for route handlers.
6
+ // Security: uses getUser() (verified against Supabase Auth server), NOT getSession() (unverified JWT).
7
+ // See: https://supabase.com/docs/reference/javascript/auth-getsession
8
+ // --- ASA GENERATED END ---
9
+
10
+ // --- USER CODE START ---
11
+
12
+ /**
13
+ * Require authenticated user in a route handler.
14
+ * Uses getUser() for server-side verification — NOT getSession().
15
+ *
16
+ * Usage in route handler:
17
+ * ```ts
18
+ * const { user, response } = await requireAuth(request);
19
+ * if (response) return response;
20
+ * // user is guaranteed to be authenticated
21
+ * ```
22
+ */
23
+ export async function requireAuth(request: NextRequest): Promise<{
24
+ user: { id: string; email: string } | null;
25
+ response: NextResponse | null;
26
+ }> {
27
+ const supabase = createServerClient();
28
+
29
+ // SECURITY: getUser() verifies the JWT against Supabase Auth server.
30
+ // getSession() only reads the JWT from cookies without verification — NOT safe for security checks.
31
+ const { data: { user }, error } = await supabase.auth.getUser();
32
+
33
+ if (error || !user) {
34
+ return {
35
+ user: null,
36
+ response: NextResponse.json(
37
+ { error: 'Authentication required' },
38
+ { status: 401 },
39
+ ),
40
+ };
41
+ }
42
+
43
+ return {
44
+ user: { id: user.id, email: user.email ?? '' },
45
+ response: null,
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Require specific role in a route handler.
51
+ * Checks profiles.role against required role.
52
+ *
53
+ * Usage:
54
+ * ```ts
55
+ * const { user, response } = await requireRole(request, 'admin');
56
+ * if (response) return response;
57
+ * ```
58
+ */
59
+ export async function requireRole(request: NextRequest, role: string): Promise<{
60
+ user: { id: string; email: string; role: string } | null;
61
+ response: NextResponse | null;
62
+ }> {
63
+ const { user, response } = await requireAuth(request);
64
+ if (response) return { user: null, response };
65
+
66
+ const supabase = createServerClient();
67
+ const { data: profile } = await supabase
68
+ .from('profiles')
69
+ .select('role')
70
+ .eq('id', user!.id)
71
+ .single();
72
+
73
+ if (!profile || profile.role !== role) {
74
+ return {
75
+ user: null,
76
+ response: NextResponse.json(
77
+ { error: 'Insufficient permissions' },
78
+ { status: 403 },
79
+ ),
80
+ };
81
+ }
82
+
83
+ return {
84
+ user: { ...user!, role: profile.role },
85
+ response: null,
86
+ };
87
+ }
88
+
89
+ // --- USER CODE END ---
@@ -0,0 +1,63 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { createBrowserClient } from '@/shared/db/supabase-client';
5
+ import type { User, Session } from './types';
6
+
7
+ // --- ASA GENERATED START ---
8
+ // Auth hooks for client components.
9
+ // --- ASA GENERATED END ---
10
+
11
+ // --- USER CODE START ---
12
+
13
+ export function useUser() {
14
+ const [user, setUser] = useState<User | null>(null);
15
+ const [loading, setLoading] = useState(true);
16
+
17
+ useEffect(() => {
18
+ const supabase = createBrowserClient();
19
+
20
+ supabase.auth.getUser().then(({ data: { user } }) => {
21
+ setUser(user as User | null);
22
+ setLoading(false);
23
+ });
24
+
25
+ const { data: { subscription } } = supabase.auth.onAuthStateChange(
26
+ (_event, session) => {
27
+ setUser(session?.user as User | null ?? null);
28
+ setLoading(false);
29
+ },
30
+ );
31
+
32
+ return () => subscription.unsubscribe();
33
+ }, []);
34
+
35
+ return { user, loading };
36
+ }
37
+
38
+ export function useSession() {
39
+ const [session, setSession] = useState<Session | null>(null);
40
+ const [loading, setLoading] = useState(true);
41
+
42
+ useEffect(() => {
43
+ const supabase = createBrowserClient();
44
+
45
+ supabase.auth.getSession().then(({ data: { session } }) => {
46
+ setSession(session as Session | null);
47
+ setLoading(false);
48
+ });
49
+
50
+ const { data: { subscription } } = supabase.auth.onAuthStateChange(
51
+ (_event, session) => {
52
+ setSession(session as Session | null);
53
+ setLoading(false);
54
+ },
55
+ );
56
+
57
+ return () => subscription.unsubscribe();
58
+ }, []);
59
+
60
+ return { session, loading };
61
+ }
62
+
63
+ // --- USER CODE END ---
@@ -0,0 +1,46 @@
1
+ import { NextResponse, type NextRequest } from 'next/server';
2
+ import { createServerClient } from '@/shared/db/supabase-client';
3
+
4
+ // --- ASA GENERATED START ---
5
+ // Auth middleware — session check, redirect to /login for protected routes.
6
+ // --- ASA GENERATED END ---
7
+
8
+ // --- USER CODE START ---
9
+
10
+ const PUBLIC_ROUTES = ['/auth/login', '/auth/register', '/auth/reset-password', '/'];
11
+
12
+ export async function middleware(request: NextRequest) {
13
+ const { pathname } = request.nextUrl;
14
+
15
+ // Allow public routes
16
+ if (PUBLIC_ROUTES.some((route) => pathname.startsWith(route))) {
17
+ return NextResponse.next();
18
+ }
19
+
20
+ // Check session
21
+ const response = NextResponse.next();
22
+ const supabase = createServerClient({
23
+ getAll: () => request.cookies.getAll(),
24
+ setAll: (cookies: any[]) => {
25
+ cookies.forEach(({ name, value, options }) => {
26
+ response.cookies.set(name, value, options);
27
+ });
28
+ },
29
+ });
30
+
31
+ const { data: { user } } = await supabase.auth.getUser();
32
+
33
+ if (!user) {
34
+ const loginUrl = new URL('/auth/login', request.url);
35
+ loginUrl.searchParams.set('redirect', pathname);
36
+ return NextResponse.redirect(loginUrl);
37
+ }
38
+
39
+ return response;
40
+ }
41
+
42
+ export const config = {
43
+ matcher: ['/((?!_next/static|_next/image|favicon.ico|api/auth).*)'],
44
+ };
45
+
46
+ // --- USER CODE END ---
@@ -0,0 +1,61 @@
1
+ import { createServerClient } from '@/shared/db/supabase-client';
2
+
3
+ // --- ASA GENERATED START ---
4
+ // Server-side user helpers.
5
+ // Security: ALL functions use getUser() for verified identity — NOT getSession().
6
+ // --- ASA GENERATED END ---
7
+
8
+ // --- USER CODE START ---
9
+
10
+ /**
11
+ * Get the current authenticated user (server-side, verified).
12
+ * Returns null if not authenticated.
13
+ *
14
+ * IMPORTANT: This uses getUser() which verifies the JWT against Supabase Auth server.
15
+ * Do NOT use getSession() for security-sensitive operations — it reads unverified JWT from cookies.
16
+ */
17
+ export async function getCurrentUser() {
18
+ const supabase = createServerClient();
19
+ const { data: { user }, error } = await supabase.auth.getUser();
20
+
21
+ if (error || !user) {
22
+ return null;
23
+ }
24
+
25
+ return {
26
+ id: user.id,
27
+ email: user.email ?? '',
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Get the current user or throw.
33
+ * Use in server actions / route handlers where auth is required.
34
+ */
35
+ export async function requireUser() {
36
+ const user = await getCurrentUser();
37
+ if (!user) {
38
+ throw new Error('Authentication required');
39
+ }
40
+ return user;
41
+ }
42
+
43
+ /**
44
+ * Get user profile from profiles table.
45
+ * Returns null if not found.
46
+ */
47
+ export async function getUserProfile(userId: string) {
48
+ const supabase = createServerClient();
49
+ const { data, error } = await supabase
50
+ .from('profiles')
51
+ .select('id, display_name, role, created_at, updated_at')
52
+ .eq('id', userId)
53
+ .single();
54
+
55
+ if (error) {
56
+ return null;
57
+ }
58
+ return data;
59
+ }
60
+
61
+ // --- USER CODE END ---
@@ -0,0 +1,38 @@
1
+ import { cookies } from 'next/headers';
2
+ import { createServerClient } from '@/shared/db/supabase-client';
3
+
4
+ // --- ASA GENERATED START ---
5
+ // Session helpers for server-side auth operations.
6
+ // --- ASA GENERATED END ---
7
+
8
+ // --- USER CODE START ---
9
+
10
+ export async function getCurrentUser() {
11
+ const cookieStore = await cookies();
12
+ const supabase = createServerClient(cookieStore);
13
+ const { data: { user }, error } = await supabase.auth.getUser();
14
+ if (error || !user) return null;
15
+ return user;
16
+ }
17
+
18
+ export async function requireUser() {
19
+ const user = await getCurrentUser();
20
+ if (!user) {
21
+ throw new Error('Authentication required');
22
+ }
23
+ return user;
24
+ }
25
+
26
+ export async function getUserProfile() {
27
+ const user = await requireUser();
28
+ const cookieStore = await cookies();
29
+ const supabase = createServerClient(cookieStore);
30
+ const { data: profile } = await supabase
31
+ .from('profiles')
32
+ .select('*')
33
+ .eq('id', user.id)
34
+ .single();
35
+ return { user, profile };
36
+ }
37
+
38
+ // --- USER CODE END ---
@@ -0,0 +1,29 @@
1
+ // --- ASA GENERATED START ---
2
+ // Auth types for auth-basic module.
3
+ // --- ASA GENERATED END ---
4
+
5
+ // --- USER CODE START ---
6
+
7
+ export type User = {
8
+ id: string;
9
+ email: string;
10
+ created_at: string;
11
+ updated_at: string;
12
+ };
13
+
14
+ export type Session = {
15
+ access_token: string;
16
+ refresh_token: string;
17
+ user: User;
18
+ expires_at: number;
19
+ };
20
+
21
+ export type Profile = {
22
+ id: string;
23
+ display_name: string | null;
24
+ role: 'user' | 'admin';
25
+ created_at: string;
26
+ updated_at: string;
27
+ };
28
+
29
+ // --- USER CODE END ---
@@ -0,0 +1,50 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { createServerClient } from '@/shared/db/supabase-client';
3
+ import { loginInputSchema } from './schemas';
4
+ import type { LoginOutput } from './schemas';
5
+
6
+ // --- ASA GENERATED START ---
7
+ // Route handler for auth/login slice.
8
+ // Uses Supabase Auth signInWithPassword — creates SSR cookie-based session.
9
+ // Security: server-side validation, no client trust.
10
+ // --- ASA GENERATED END ---
11
+
12
+ // --- USER CODE START ---
13
+
14
+ export async function POST(request: NextRequest): Promise<NextResponse<LoginOutput>> {
15
+ try {
16
+ const body = await request.json();
17
+ const parsed = loginInputSchema.safeParse(body);
18
+
19
+ if (!parsed.success) {
20
+ return NextResponse.json(
21
+ { success: false, error: parsed.error.errors[0]?.message ?? 'Invalid input' },
22
+ { status: 400 },
23
+ );
24
+ }
25
+
26
+ const { email, password } = parsed.data;
27
+ const supabase = createServerClient();
28
+
29
+ const { error } = await supabase.auth.signInWithPassword({
30
+ email,
31
+ password,
32
+ });
33
+
34
+ if (error) {
35
+ return NextResponse.json(
36
+ { success: false, error: 'Invalid email or password' },
37
+ { status: 401 },
38
+ );
39
+ }
40
+
41
+ return NextResponse.json({ success: true, error: null });
42
+ } catch {
43
+ return NextResponse.json(
44
+ { success: false, error: 'Login failed. Please try again.' },
45
+ { status: 500 },
46
+ );
47
+ }
48
+ }
49
+
50
+ // --- USER CODE END ---
@@ -0,0 +1,23 @@
1
+ import { createServerClient } from '@/shared/db/supabase-client';
2
+
3
+ // --- ASA GENERATED START ---
4
+ // Repository for auth/login slice.
5
+ // --- ASA GENERATED END ---
6
+
7
+ // --- USER CODE START ---
8
+
9
+ export async function getProfileByUserId(userId: string) {
10
+ const supabase = createServerClient();
11
+ const { data, error } = await supabase
12
+ .from('profiles')
13
+ .select('*')
14
+ .eq('id', userId)
15
+ .single();
16
+
17
+ if (error) {
18
+ return null;
19
+ }
20
+ return data;
21
+ }
22
+
23
+ // --- USER CODE END ---
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+
3
+ // --- ASA GENERATED START ---
4
+ // Validation schemas for auth/login slice.
5
+ // --- ASA GENERATED END ---
6
+
7
+ // --- USER CODE START ---
8
+
9
+ export const loginInputSchema = z.object({
10
+ email: z.string().email('Please enter a valid email address'),
11
+ password: z.string().min(1, 'Password is required'),
12
+ });
13
+
14
+ export const loginOutputSchema = z.object({
15
+ success: z.boolean(),
16
+ error: z.string().nullable(),
17
+ });
18
+
19
+ export type LoginInput = z.infer<typeof loginInputSchema>;
20
+ export type LoginOutput = z.infer<typeof loginOutputSchema>;
21
+
22
+ // --- USER CODE END ---
@@ -0,0 +1,19 @@
1
+ {
2
+ "domain": "auth",
3
+ "slice": "login",
4
+ "type": "route",
5
+ "has_ui": true,
6
+ "has_repository": true,
7
+ "description": "User login with email and password. Creates SSR cookie-based session.",
8
+ "input": {
9
+ "email": "string",
10
+ "password": "string"
11
+ },
12
+ "output": {
13
+ "success": "boolean",
14
+ "error": "string | null"
15
+ },
16
+ "side_effects": [
17
+ "Creates Supabase Auth session (cookie-based)"
18
+ ]
19
+ }
@@ -0,0 +1,107 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import Link from 'next/link';
5
+ import { useRouter, useSearchParams } from 'next/navigation';
6
+ import { useLogin } from './hook';
7
+
8
+ // --- ASA GENERATED START ---
9
+ // UI component for auth/login slice.
10
+ // Uses shadcn/ui-compatible styling with Tailwind CSS.
11
+ // --- ASA GENERATED END ---
12
+
13
+ // --- USER CODE START ---
14
+
15
+ export default function AuthLogin() {
16
+ const router = useRouter();
17
+ const searchParams = useSearchParams();
18
+ const justRegistered = searchParams.get('registered') === 'true';
19
+ const { login, loading, error } = useLogin();
20
+ const [form, setForm] = useState({ email: '', password: '' });
21
+
22
+ async function handleSubmit(e: React.FormEvent) {
23
+ e.preventDefault();
24
+ const result = await login(form);
25
+ if (result.success) {
26
+ router.push('/');
27
+ router.refresh();
28
+ }
29
+ }
30
+
31
+ return (
32
+ <div className="flex min-h-screen items-center justify-center bg-neutral-50 px-4">
33
+ <div className="w-full max-w-md rounded-xl border border-neutral-200 bg-white p-8 shadow-sm">
34
+ <h1 className="text-2xl font-bold text-neutral-900">Sign in</h1>
35
+ <p className="mt-2 text-sm text-neutral-500">
36
+ Don&apos;t have an account?{' '}
37
+ <Link href="/auth/register" className="font-medium text-neutral-900 underline underline-offset-4">
38
+ Create one
39
+ </Link>
40
+ </p>
41
+
42
+ {justRegistered && (
43
+ <div className="mt-4 rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-700">
44
+ Account created. Check your email for a confirmation link, then sign in.
45
+ </div>
46
+ )}
47
+
48
+ <form onSubmit={handleSubmit} className="mt-8 space-y-4">
49
+ <div>
50
+ <label htmlFor="email" className="block text-sm font-medium text-neutral-700">
51
+ Email
52
+ </label>
53
+ <input
54
+ id="email"
55
+ type="email"
56
+ required
57
+ autoComplete="email"
58
+ value={form.email}
59
+ onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
60
+ className="mt-1 block w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm shadow-sm placeholder:text-neutral-400 focus:border-neutral-500 focus:outline-none focus:ring-1 focus:ring-neutral-500"
61
+ placeholder="jane@example.com"
62
+ />
63
+ </div>
64
+
65
+ <div>
66
+ <div className="flex items-center justify-between">
67
+ <label htmlFor="password" className="block text-sm font-medium text-neutral-700">
68
+ Password
69
+ </label>
70
+ <Link
71
+ href="/auth/reset-password"
72
+ className="text-xs text-neutral-500 hover:text-neutral-700"
73
+ >
74
+ Forgot password?
75
+ </Link>
76
+ </div>
77
+ <input
78
+ id="password"
79
+ type="password"
80
+ required
81
+ autoComplete="current-password"
82
+ value={form.password}
83
+ onChange={(e) => setForm((f) => ({ ...f, password: e.target.value }))}
84
+ className="mt-1 block w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm shadow-sm placeholder:text-neutral-400 focus:border-neutral-500 focus:outline-none focus:ring-1 focus:ring-neutral-500"
85
+ />
86
+ </div>
87
+
88
+ {error && (
89
+ <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
90
+ {error}
91
+ </div>
92
+ )}
93
+
94
+ <button
95
+ type="submit"
96
+ disabled={loading}
97
+ className="w-full rounded-lg bg-neutral-900 px-4 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-neutral-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
98
+ >
99
+ {loading ? 'Signing in...' : 'Sign in'}
100
+ </button>
101
+ </form>
102
+ </div>
103
+ </div>
104
+ );
105
+ }
106
+
107
+ // --- USER CODE END ---
@@ -0,0 +1,44 @@
1
+ import { useState } from 'react';
2
+ import type { LoginInput, LoginOutput } from '../schemas';
3
+
4
+ // --- ASA GENERATED START ---
5
+ // React hook for auth/login slice.
6
+ // --- ASA GENERATED END ---
7
+
8
+ // --- USER CODE START ---
9
+
10
+ export function useLogin() {
11
+ const [loading, setLoading] = useState(false);
12
+ const [error, setError] = useState<string | null>(null);
13
+
14
+ async function login(input: LoginInput): Promise<LoginOutput> {
15
+ setLoading(true);
16
+ setError(null);
17
+
18
+ try {
19
+ const res = await fetch('/api/auth/login', {
20
+ method: 'POST',
21
+ headers: { 'Content-Type': 'application/json' },
22
+ body: JSON.stringify(input),
23
+ });
24
+
25
+ const data: LoginOutput = await res.json();
26
+
27
+ if (!data.success) {
28
+ setError(data.error);
29
+ }
30
+
31
+ return data;
32
+ } catch {
33
+ const output: LoginOutput = { success: false, error: 'Network error. Please try again.' };
34
+ setError(output.error);
35
+ return output;
36
+ } finally {
37
+ setLoading(false);
38
+ }
39
+ }
40
+
41
+ return { login, loading, error };
42
+ }
43
+
44
+ // --- USER CODE END ---
@@ -0,0 +1,19 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { createServerClient } from '@/shared/db/supabase-client';
3
+
4
+ // --- ASA GENERATED START ---
5
+ // Route handler for auth/logout slice.
6
+ // Invalidates Supabase Auth session and redirects to login page.
7
+ // No UI — logout is triggered from existing UI (e.g., header dropdown button).
8
+ // --- ASA GENERATED END ---
9
+
10
+ // --- USER CODE START ---
11
+
12
+ export async function POST(request: NextRequest): Promise<NextResponse> {
13
+ const supabase = createServerClient();
14
+ await supabase.auth.signOut();
15
+
16
+ return NextResponse.json({ success: true });
17
+ }
18
+
19
+ // --- USER CODE END ---
@@ -0,0 +1,16 @@
1
+ {
2
+ "domain": "auth",
3
+ "slice": "logout",
4
+ "type": "route",
5
+ "has_ui": false,
6
+ "has_repository": false,
7
+ "description": "User logout. Invalidates Supabase Auth session and redirects to login.",
8
+ "input": {},
9
+ "output": {
10
+ "success": "boolean"
11
+ },
12
+ "side_effects": [
13
+ "Invalidates Supabase Auth session",
14
+ "Clears session cookies"
15
+ ]
16
+ }