@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,61 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { createServerClient } from '@/shared/db/supabase-client';
3
+ import { registerInputSchema } from './schemas';
4
+ import type { RegisterOutput } from './schemas';
5
+
6
+ // --- ASA GENERATED START ---
7
+ // Route handler for auth/register slice.
8
+ // Uses Supabase Auth signUp — profile created automatically via handle_new_user() trigger.
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<RegisterOutput>> {
15
+ try {
16
+ const body = await request.json();
17
+ const parsed = registerInputSchema.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, display_name } = parsed.data;
27
+ const supabase = createServerClient();
28
+
29
+ const { data, error } = await supabase.auth.signUp({
30
+ email,
31
+ password,
32
+ options: {
33
+ data: { display_name },
34
+ },
35
+ });
36
+
37
+ if (error) {
38
+ return NextResponse.json(
39
+ { success: false, error: error.message },
40
+ { status: 400 },
41
+ );
42
+ }
43
+
44
+ // Check if user already exists (Supabase returns user with identities: [] for duplicates)
45
+ if (data.user && data.user.identities?.length === 0) {
46
+ return NextResponse.json(
47
+ { success: false, error: 'An account with this email already exists' },
48
+ { status: 409 },
49
+ );
50
+ }
51
+
52
+ return NextResponse.json({ success: true, error: null }, { status: 201 });
53
+ } catch {
54
+ return NextResponse.json(
55
+ { success: false, error: 'Registration failed. Please try again.' },
56
+ { status: 500 },
57
+ );
58
+ }
59
+ }
60
+
61
+ // --- USER CODE END ---
@@ -0,0 +1,25 @@
1
+ import { createServerClient } from '@/shared/db/supabase-client';
2
+
3
+ // --- ASA GENERATED START ---
4
+ // Repository for auth/register slice.
5
+ // Profile creation is handled by handle_new_user() database trigger.
6
+ // This repository provides profile query helpers.
7
+ // --- ASA GENERATED END ---
8
+
9
+ // --- USER CODE START ---
10
+
11
+ export async function getProfileByUserId(userId: string) {
12
+ const supabase = createServerClient();
13
+ const { data, error } = await supabase
14
+ .from('profiles')
15
+ .select('*')
16
+ .eq('id', userId)
17
+ .single();
18
+
19
+ if (error) {
20
+ return null;
21
+ }
22
+ return data;
23
+ }
24
+
25
+ // --- USER CODE END ---
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+
3
+ // --- ASA GENERATED START ---
4
+ // Validation schemas for auth/register slice.
5
+ // --- ASA GENERATED END ---
6
+
7
+ // --- USER CODE START ---
8
+
9
+ export const registerInputSchema = z.object({
10
+ email: z.string().email('Please enter a valid email address'),
11
+ password: z
12
+ .string()
13
+ .min(8, 'Password must be at least 8 characters')
14
+ .max(72, 'Password must be at most 72 characters'),
15
+ display_name: z
16
+ .string()
17
+ .min(1, 'Display name is required')
18
+ .max(100, 'Display name must be at most 100 characters'),
19
+ });
20
+
21
+ export const registerOutputSchema = z.object({
22
+ success: z.boolean(),
23
+ error: z.string().nullable(),
24
+ });
25
+
26
+ export type RegisterInput = z.infer<typeof registerInputSchema>;
27
+ export type RegisterOutput = z.infer<typeof registerOutputSchema>;
28
+
29
+ // --- USER CODE END ---
@@ -0,0 +1,21 @@
1
+ {
2
+ "domain": "auth",
3
+ "slice": "register",
4
+ "type": "route",
5
+ "has_ui": true,
6
+ "has_repository": true,
7
+ "description": "User registration with email, password, and display name.",
8
+ "input": {
9
+ "email": "string",
10
+ "password": "string",
11
+ "display_name": "string"
12
+ },
13
+ "output": {
14
+ "success": "boolean",
15
+ "error": "string | null"
16
+ },
17
+ "side_effects": [
18
+ "Creates auth.users entry via Supabase Auth",
19
+ "Triggers handle_new_user() → creates profiles entry"
20
+ ]
21
+ }
@@ -0,0 +1,118 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import Link from 'next/link';
5
+ import { useRouter } from 'next/navigation';
6
+ import { useRegister } from './hook';
7
+
8
+ // --- ASA GENERATED START ---
9
+ // UI component for auth/register slice.
10
+ // Uses shadcn/ui-compatible styling with Tailwind CSS.
11
+ // --- ASA GENERATED END ---
12
+
13
+ // --- USER CODE START ---
14
+
15
+ export default function AuthRegister() {
16
+ const router = useRouter();
17
+ const { register, loading, error, success } = useRegister();
18
+ const [form, setForm] = useState({
19
+ email: '',
20
+ password: '',
21
+ display_name: '',
22
+ });
23
+
24
+ async function handleSubmit(e: React.FormEvent) {
25
+ e.preventDefault();
26
+ const result = await register(form);
27
+ if (result.success) {
28
+ router.push('/auth/login?registered=true');
29
+ }
30
+ }
31
+
32
+ return (
33
+ <div className="flex min-h-screen items-center justify-center bg-neutral-50 px-4">
34
+ <div className="w-full max-w-md rounded-xl border border-neutral-200 bg-white p-8 shadow-sm">
35
+ <h1 className="text-2xl font-bold text-neutral-900">Create your account</h1>
36
+ <p className="mt-2 text-sm text-neutral-500">
37
+ Already have an account?{' '}
38
+ <Link href="/auth/login" className="font-medium text-neutral-900 underline underline-offset-4">
39
+ Sign in
40
+ </Link>
41
+ </p>
42
+
43
+ <form onSubmit={handleSubmit} className="mt-8 space-y-4">
44
+ <div>
45
+ <label htmlFor="display_name" className="block text-sm font-medium text-neutral-700">
46
+ Full name
47
+ </label>
48
+ <input
49
+ id="display_name"
50
+ type="text"
51
+ required
52
+ autoComplete="name"
53
+ value={form.display_name}
54
+ onChange={(e) => setForm((f) => ({ ...f, display_name: e.target.value }))}
55
+ 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"
56
+ placeholder="Jane Doe"
57
+ />
58
+ </div>
59
+
60
+ <div>
61
+ <label htmlFor="email" className="block text-sm font-medium text-neutral-700">
62
+ Email
63
+ </label>
64
+ <input
65
+ id="email"
66
+ type="email"
67
+ required
68
+ autoComplete="email"
69
+ value={form.email}
70
+ onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
71
+ 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"
72
+ placeholder="jane@example.com"
73
+ />
74
+ </div>
75
+
76
+ <div>
77
+ <label htmlFor="password" className="block text-sm font-medium text-neutral-700">
78
+ Password
79
+ </label>
80
+ <input
81
+ id="password"
82
+ type="password"
83
+ required
84
+ autoComplete="new-password"
85
+ minLength={8}
86
+ value={form.password}
87
+ onChange={(e) => setForm((f) => ({ ...f, password: e.target.value }))}
88
+ 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"
89
+ placeholder="Minimum 8 characters"
90
+ />
91
+ </div>
92
+
93
+ {error && (
94
+ <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
95
+ {error}
96
+ </div>
97
+ )}
98
+
99
+ {success && (
100
+ <div className="rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-700">
101
+ Check your email for a confirmation link.
102
+ </div>
103
+ )}
104
+
105
+ <button
106
+ type="submit"
107
+ disabled={loading}
108
+ 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"
109
+ >
110
+ {loading ? 'Creating account...' : 'Create account'}
111
+ </button>
112
+ </form>
113
+ </div>
114
+ </div>
115
+ );
116
+ }
117
+
118
+ // --- USER CODE END ---
@@ -0,0 +1,48 @@
1
+ import { useState } from 'react';
2
+ import type { RegisterInput, RegisterOutput } from '../schemas';
3
+
4
+ // --- ASA GENERATED START ---
5
+ // React hook for auth/register slice.
6
+ // --- ASA GENERATED END ---
7
+
8
+ // --- USER CODE START ---
9
+
10
+ export function useRegister() {
11
+ const [loading, setLoading] = useState(false);
12
+ const [error, setError] = useState<string | null>(null);
13
+ const [success, setSuccess] = useState(false);
14
+
15
+ async function register(input: RegisterInput): Promise<RegisterOutput> {
16
+ setLoading(true);
17
+ setError(null);
18
+ setSuccess(false);
19
+
20
+ try {
21
+ const res = await fetch('/api/auth/register', {
22
+ method: 'POST',
23
+ headers: { 'Content-Type': 'application/json' },
24
+ body: JSON.stringify(input),
25
+ });
26
+
27
+ const data: RegisterOutput = await res.json();
28
+
29
+ if (!data.success) {
30
+ setError(data.error);
31
+ } else {
32
+ setSuccess(true);
33
+ }
34
+
35
+ return data;
36
+ } catch {
37
+ const output: RegisterOutput = { success: false, error: 'Network error. Please try again.' };
38
+ setError(output.error);
39
+ return output;
40
+ } finally {
41
+ setLoading(false);
42
+ }
43
+ }
44
+
45
+ return { register, loading, error, success };
46
+ }
47
+
48
+ // --- USER CODE END ---
@@ -0,0 +1,47 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { createServerClient } from '@/shared/db/supabase-client';
3
+ import { resetPasswordInputSchema } from './schemas';
4
+ import type { ResetPasswordOutput } from './schemas';
5
+
6
+ // --- ASA GENERATED START ---
7
+ // Route handler for auth/reset-password slice.
8
+ // Sends password reset email via Supabase Auth.
9
+ // Security: always returns success to prevent email enumeration.
10
+ // --- ASA GENERATED END ---
11
+
12
+ // --- USER CODE START ---
13
+
14
+ export async function POST(request: NextRequest): Promise<NextResponse<ResetPasswordOutput>> {
15
+ try {
16
+ const body = await request.json();
17
+ const parsed = resetPasswordInputSchema.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 } = parsed.data;
27
+ const supabase = createServerClient();
28
+
29
+ const { error } = await supabase.auth.resetPasswordForEmail(email, {
30
+ redirectTo: `${process.env.NEXT_PUBLIC_APP_URL}/auth/update-password`,
31
+ });
32
+
33
+ if (error) {
34
+ console.error('[auth/reset-password] Supabase error:', error.message);
35
+ }
36
+
37
+ // Always return success to prevent email enumeration attacks
38
+ return NextResponse.json({ success: true, error: null });
39
+ } catch {
40
+ return NextResponse.json(
41
+ { success: false, error: 'Something went wrong. Please try again.' },
42
+ { status: 500 },
43
+ );
44
+ }
45
+ }
46
+
47
+ // --- USER CODE END ---
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod';
2
+
3
+ // --- ASA GENERATED START ---
4
+ // Validation schemas for auth/reset-password slice.
5
+ // --- ASA GENERATED END ---
6
+
7
+ // --- USER CODE START ---
8
+
9
+ export const resetPasswordInputSchema = z.object({
10
+ email: z.string().email('Please enter a valid email address'),
11
+ });
12
+
13
+ export const resetPasswordOutputSchema = z.object({
14
+ success: z.boolean(),
15
+ error: z.string().nullable(),
16
+ });
17
+
18
+ export type ResetPasswordInput = z.infer<typeof resetPasswordInputSchema>;
19
+ export type ResetPasswordOutput = z.infer<typeof resetPasswordOutputSchema>;
20
+
21
+ // --- USER CODE END ---
@@ -0,0 +1,18 @@
1
+ {
2
+ "domain": "auth",
3
+ "slice": "reset-password",
4
+ "type": "route",
5
+ "has_ui": true,
6
+ "has_repository": true,
7
+ "description": "Password reset flow. Sends reset email via Supabase Auth.",
8
+ "input": {
9
+ "email": "string"
10
+ },
11
+ "output": {
12
+ "success": "boolean",
13
+ "error": "string | null"
14
+ },
15
+ "side_effects": [
16
+ "Sends password reset email via Supabase Auth"
17
+ ]
18
+ }
@@ -0,0 +1,79 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import Link from 'next/link';
5
+ import { useResetPassword } from './hook';
6
+
7
+ // --- ASA GENERATED START ---
8
+ // UI component for auth/reset-password slice.
9
+ // Uses shadcn/ui-compatible styling with Tailwind CSS.
10
+ // --- ASA GENERATED END ---
11
+
12
+ // --- USER CODE START ---
13
+
14
+ export default function AuthResetPassword() {
15
+ const { resetPassword, loading, error, success } = useResetPassword();
16
+ const [email, setEmail] = useState('');
17
+
18
+ async function handleSubmit(e: React.FormEvent) {
19
+ e.preventDefault();
20
+ await resetPassword({ email });
21
+ }
22
+
23
+ return (
24
+ <div className="flex min-h-screen items-center justify-center bg-neutral-50 px-4">
25
+ <div className="w-full max-w-md rounded-xl border border-neutral-200 bg-white p-8 shadow-sm">
26
+ <h1 className="text-2xl font-bold text-neutral-900">Reset your password</h1>
27
+ <p className="mt-2 text-sm text-neutral-500">
28
+ Enter your email and we&apos;ll send you a reset link.
29
+ </p>
30
+
31
+ <form onSubmit={handleSubmit} className="mt-8 space-y-4">
32
+ <div>
33
+ <label htmlFor="email" className="block text-sm font-medium text-neutral-700">
34
+ Email
35
+ </label>
36
+ <input
37
+ id="email"
38
+ type="email"
39
+ required
40
+ autoComplete="email"
41
+ value={email}
42
+ onChange={(e) => setEmail(e.target.value)}
43
+ 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"
44
+ placeholder="jane@example.com"
45
+ />
46
+ </div>
47
+
48
+ {error && (
49
+ <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
50
+ {error}
51
+ </div>
52
+ )}
53
+
54
+ {success && (
55
+ <div className="rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-700">
56
+ If an account with that email exists, you&apos;ll receive a reset link shortly.
57
+ </div>
58
+ )}
59
+
60
+ <button
61
+ type="submit"
62
+ disabled={loading || success}
63
+ 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"
64
+ >
65
+ {loading ? 'Sending...' : 'Send reset link'}
66
+ </button>
67
+ </form>
68
+
69
+ <p className="mt-6 text-center text-sm text-neutral-500">
70
+ <Link href="/auth/login" className="font-medium text-neutral-900 underline underline-offset-4">
71
+ Back to sign in
72
+ </Link>
73
+ </p>
74
+ </div>
75
+ </div>
76
+ );
77
+ }
78
+
79
+ // --- USER CODE END ---
@@ -0,0 +1,48 @@
1
+ import { useState } from 'react';
2
+ import type { ResetPasswordInput, ResetPasswordOutput } from '../schemas';
3
+
4
+ // --- ASA GENERATED START ---
5
+ // React hook for auth/reset-password slice.
6
+ // --- ASA GENERATED END ---
7
+
8
+ // --- USER CODE START ---
9
+
10
+ export function useResetPassword() {
11
+ const [loading, setLoading] = useState(false);
12
+ const [error, setError] = useState<string | null>(null);
13
+ const [success, setSuccess] = useState(false);
14
+
15
+ async function resetPassword(input: ResetPasswordInput): Promise<ResetPasswordOutput> {
16
+ setLoading(true);
17
+ setError(null);
18
+ setSuccess(false);
19
+
20
+ try {
21
+ const res = await fetch('/api/auth/reset-password', {
22
+ method: 'POST',
23
+ headers: { 'Content-Type': 'application/json' },
24
+ body: JSON.stringify(input),
25
+ });
26
+
27
+ const data: ResetPasswordOutput = await res.json();
28
+
29
+ if (!data.success) {
30
+ setError(data.error);
31
+ } else {
32
+ setSuccess(true);
33
+ }
34
+
35
+ return data;
36
+ } catch {
37
+ const output: ResetPasswordOutput = { success: false, error: 'Network error. Please try again.' };
38
+ setError(output.error);
39
+ return output;
40
+ } finally {
41
+ setLoading(false);
42
+ }
43
+ }
44
+
45
+ return { resetPassword, loading, error, success };
46
+ }
47
+
48
+ // --- USER CODE END ---
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "db-basic",
3
+ "version": "1.0.0",
4
+ "description": "Basic database layer for SaaS apps — Supabase client, types, utils, migrations",
5
+ "min_cli_version": "2.0.0",
6
+ "stack": "asa-native-v1",
7
+ "internal": true,
8
+ "dependencies": [],
9
+ "package_dependencies": {
10
+ "@supabase/supabase-js": "^2.45.0",
11
+ "@supabase/ssr": "^0.5.0"
12
+ },
13
+ "package_dev_dependencies": {},
14
+ "slices": [],
15
+ "shared": [
16
+ { "src": "shared/supabase-client.ts", "dest": "shared/db/supabase-client.ts", "overwrite": false },
17
+ { "src": "shared/types.ts", "dest": "shared/db/types.ts", "overwrite": false },
18
+ { "src": "shared/utils.ts", "dest": "shared/db/utils.ts", "overwrite": false },
19
+ { "src": "shared/seed.ts", "dest": "shared/db/seed.ts", "overwrite": false }
20
+ ],
21
+ "migrations": [],
22
+ "env_vars": [
23
+ "NEXT_PUBLIC_SUPABASE_URL",
24
+ "NEXT_PUBLIC_SUPABASE_ANON_KEY",
25
+ "SUPABASE_SERVICE_ROLE_KEY",
26
+ "DATABASE_URL"
27
+ ],
28
+ "post_install_notes": [
29
+ "Create a Supabase project at https://supabase.com",
30
+ "Copy your project URL and keys to .env.local",
31
+ "Run: npm install"
32
+ ]
33
+ }
@@ -0,0 +1,27 @@
1
+ // --- ASA GENERATED START ---
2
+ // Seed data template — customize for your project.
3
+ // Run: npx tsx shared/db/seed.ts
4
+ // --- ASA GENERATED END ---
5
+
6
+ // --- USER CODE START ---
7
+
8
+ import { createAdminClient } from './supabase-client';
9
+
10
+ async function seed() {
11
+ const supabase = createAdminClient();
12
+
13
+ console.log('🌱 Seeding database...');
14
+
15
+ // TODO: Add your seed data here
16
+ // Example:
17
+ // await supabase.from('profiles').insert([
18
+ // { display_name: 'Test User', role: 'user' },
19
+ // { display_name: 'Admin', role: 'admin' },
20
+ // ]);
21
+
22
+ console.log('✅ Seed complete');
23
+ }
24
+
25
+ seed().catch(console.error);
26
+
27
+ // --- USER CODE END ---
@@ -0,0 +1,70 @@
1
+ import { createBrowserClient as createBrowserSupabase } from '@supabase/ssr';
2
+ import { createServerClient as createServerSupabase } from '@supabase/ssr';
3
+ import { createClient } from '@supabase/supabase-js';
4
+ import type { Database } from './types';
5
+
6
+ // --- ASA GENERATED START ---
7
+ // Canonical Supabase client for entire project.
8
+ // All modules (auth-basic, payments-basic, etc.) use this single client.
9
+ // Do NOT create additional Supabase clients elsewhere.
10
+ // --- ASA GENERATED END ---
11
+
12
+ // --- USER CODE START ---
13
+
14
+ /**
15
+ * Browser client — for client components and hooks.
16
+ * Uses ANON key only. Safe for browser.
17
+ */
18
+ export function createBrowserClient() {
19
+ return createBrowserSupabase<Database>(
20
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
21
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
22
+ );
23
+ }
24
+
25
+ /**
26
+ * Server client — for route handlers, server actions, middleware.
27
+ * Uses ANON key + cookies for authenticated requests.
28
+ */
29
+ export function createServerClient(cookieStore: any) {
30
+ return createServerSupabase<Database>(
31
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
32
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
33
+ {
34
+ cookies: {
35
+ getAll() {
36
+ return cookieStore.getAll();
37
+ },
38
+ setAll(cookiesToSet: any[]) {
39
+ try {
40
+ cookiesToSet.forEach(({ name, value, options }) =>
41
+ cookieStore.set(name, value, options),
42
+ );
43
+ } catch {
44
+ // Server Component — ignore
45
+ }
46
+ },
47
+ },
48
+ },
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Admin client — for privileged operations that bypass RLS.
54
+ * Uses SERVICE_ROLE key. NEVER import in browser-capable files.
55
+ * asa lint will flag this as a security violation if used in app/ or ui/.
56
+ */
57
+ export function createAdminClient() {
58
+ return createClient<Database>(
59
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
60
+ process.env.SUPABASE_SERVICE_ROLE_KEY!,
61
+ {
62
+ auth: {
63
+ autoRefreshToken: false,
64
+ persistSession: false,
65
+ },
66
+ },
67
+ );
68
+ }
69
+
70
+ // --- USER CODE END ---
@@ -0,0 +1,20 @@
1
+ // --- ASA GENERATED START ---
2
+ // Database types — regenerate with: supabase gen types typescript --local > shared/db/types.ts
3
+ // --- ASA GENERATED END ---
4
+
5
+ // --- USER CODE START ---
6
+
7
+ export type Database = {
8
+ public: {
9
+ Tables: {
10
+ // Tables will be added by modules (auth-basic → profiles, payments-basic → subscriptions)
11
+ // After adding tables, regenerate types with Supabase CLI:
12
+ // npx supabase gen types typescript --local > shared/db/types.ts
13
+ };
14
+ Views: {};
15
+ Functions: {};
16
+ Enums: {};
17
+ };
18
+ };
19
+
20
+ // --- USER CODE END ---