@react-spa-scaffold/mcp 2.2.0 → 2.3.0

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 (112) hide show
  1. package/dist/constants.d.ts +3 -0
  2. package/dist/constants.d.ts.map +1 -1
  3. package/dist/constants.js +3 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/features/definitions/database.d.ts +3 -0
  6. package/dist/features/definitions/database.d.ts.map +1 -0
  7. package/dist/features/definitions/database.js +45 -0
  8. package/dist/features/definitions/database.js.map +1 -0
  9. package/dist/features/definitions/deployment.d.ts +3 -0
  10. package/dist/features/definitions/deployment.d.ts.map +1 -0
  11. package/dist/features/definitions/deployment.js +14 -0
  12. package/dist/features/definitions/deployment.js.map +1 -0
  13. package/dist/features/definitions/index.d.ts +2 -0
  14. package/dist/features/definitions/index.d.ts.map +1 -1
  15. package/dist/features/definitions/index.js +2 -0
  16. package/dist/features/definitions/index.js.map +1 -1
  17. package/dist/features/registry.d.ts.map +1 -1
  18. package/dist/features/registry.js +3 -1
  19. package/dist/features/registry.js.map +1 -1
  20. package/dist/features/types.test.js +4 -2
  21. package/dist/features/types.test.js.map +1 -1
  22. package/dist/resources/docs.d.ts.map +1 -1
  23. package/dist/resources/docs.js +5 -0
  24. package/dist/resources/docs.js.map +1 -1
  25. package/dist/tools/add-features.js +1 -1
  26. package/dist/tools/add-features.js.map +1 -1
  27. package/dist/utils/docs.d.ts.map +1 -1
  28. package/dist/utils/docs.js +2 -0
  29. package/dist/utils/docs.js.map +1 -1
  30. package/dist/utils/scaffold/claude-md/index.d.ts.map +1 -1
  31. package/dist/utils/scaffold/claude-md/index.js +3 -1
  32. package/dist/utils/scaffold/claude-md/index.js.map +1 -1
  33. package/dist/utils/scaffold/claude-md/sections.d.ts +2 -0
  34. package/dist/utils/scaffold/claude-md/sections.d.ts.map +1 -1
  35. package/dist/utils/scaffold/claude-md/sections.js +132 -2
  36. package/dist/utils/scaffold/claude-md/sections.js.map +1 -1
  37. package/dist/utils/scaffold/compute.js +1 -1
  38. package/dist/utils/scaffold/compute.js.map +1 -1
  39. package/dist/utils/scaffold/generators.d.ts +2 -2
  40. package/dist/utils/scaffold/generators.d.ts.map +1 -1
  41. package/dist/utils/scaffold/generators.js +57 -22
  42. package/dist/utils/scaffold/generators.js.map +1 -1
  43. package/package.json +1 -1
  44. package/templates/.env.example +40 -12
  45. package/templates/.github/workflows/ci.yml +4 -1
  46. package/templates/.github/workflows/deploy.yml +59 -0
  47. package/templates/CLAUDE.md +177 -1
  48. package/templates/docs/AUTHENTICATION.md +325 -0
  49. package/templates/docs/DEPLOYMENT.md +268 -0
  50. package/templates/docs/E2E_TESTING.md +81 -4
  51. package/templates/docs/SUPABASE_INTEGRATION.md +310 -0
  52. package/templates/docs/TESTING.md +195 -77
  53. package/templates/e2e/auth/auth.setup.ts +60 -0
  54. package/templates/e2e/fixtures/index.ts +11 -0
  55. package/templates/e2e/tests/profile.auth.spec.ts +103 -0
  56. package/templates/e2e/tests/profile.spec.ts +64 -0
  57. package/templates/e2e/tests/register-form.spec.ts +38 -0
  58. package/templates/gitignore +5 -0
  59. package/templates/package.json +8 -0
  60. package/templates/playwright.config.ts +33 -3
  61. package/templates/src/App.tsx +32 -19
  62. package/templates/src/components/layout/Header.test.tsx +17 -1
  63. package/templates/src/components/layout/Header.tsx +11 -0
  64. package/templates/src/components/shared/AccountButton/AccountButton.test.tsx +3 -3
  65. package/templates/src/components/shared/ProfileSync/ProfileSync.test.tsx +44 -0
  66. package/templates/src/components/shared/ProfileSync/ProfileSync.tsx +104 -0
  67. package/templates/src/components/shared/ProfileSync/index.ts +1 -0
  68. package/templates/src/components/shared/ProtectedRoute/ProtectedRoute.test.tsx +3 -3
  69. package/templates/src/components/shared/index.ts +1 -0
  70. package/templates/src/contexts/performanceContext.tsx +3 -3
  71. package/templates/src/contexts/supabaseContext.test.tsx +59 -0
  72. package/templates/src/contexts/supabaseContext.tsx +87 -0
  73. package/templates/src/hooks/index.ts +17 -0
  74. package/templates/src/hooks/supabase/index.ts +12 -0
  75. package/templates/src/hooks/supabase/useProfiles.test.tsx +207 -0
  76. package/templates/src/hooks/supabase/useProfiles.ts +213 -0
  77. package/templates/src/hooks/supabase/useSupabaseQuery.test.tsx +150 -0
  78. package/templates/src/hooks/supabase/useSupabaseQuery.ts +91 -0
  79. package/templates/src/lib/api.test.ts +30 -38
  80. package/templates/src/lib/api.ts +1 -7
  81. package/templates/src/lib/config.ts +54 -4
  82. package/templates/src/lib/env.ts +36 -14
  83. package/templates/src/lib/index.ts +4 -2
  84. package/templates/src/lib/routes.ts +1 -0
  85. package/templates/src/lib/sentry.ts +13 -10
  86. package/templates/src/lib/supabase/client.ts +58 -0
  87. package/templates/src/lib/supabase/index.ts +5 -0
  88. package/templates/src/main.tsx +17 -39
  89. package/templates/src/mocks/constants.ts +31 -0
  90. package/templates/src/mocks/fixtures/index.ts +3 -1
  91. package/templates/src/mocks/fixtures/profiles.ts +55 -0
  92. package/templates/src/mocks/fixtures/users.ts +91 -0
  93. package/templates/src/mocks/handlers/index.ts +2 -1
  94. package/templates/src/mocks/handlers/supabase.ts +64 -0
  95. package/templates/src/mocks/handlers/todos.ts +1 -1
  96. package/templates/src/mocks/index.ts +6 -0
  97. package/templates/src/pages/Profile.test.tsx +263 -0
  98. package/templates/src/pages/Profile.tsx +171 -0
  99. package/templates/src/pages/index.ts +1 -0
  100. package/templates/src/stores/preferencesStore.ts +2 -1
  101. package/templates/src/test/clerkMock.tsx +49 -9
  102. package/templates/src/test/fetchMock.ts +58 -0
  103. package/templates/src/test/index.ts +49 -3
  104. package/templates/src/test/mocks.ts +128 -1
  105. package/templates/src/test/providers.tsx +7 -4
  106. package/templates/src/test/supabaseMock.ts +112 -0
  107. package/templates/src/test-setup.ts +26 -0
  108. package/templates/src/types/database.ts +46 -0
  109. package/templates/src/types/index.ts +1 -0
  110. package/templates/src/types/supabase.ts +167 -0
  111. package/templates/src/vite-env.d.ts +6 -0
  112. package/templates/supabase/migrations/20260104000000_create_profiles_table.sql +67 -0
@@ -30,6 +30,10 @@ export function generateViteEnvDts(featureIds) {
30
30
  if (featureIds.includes(FEATURE.AUTH)) {
31
31
  envVars.push(' readonly VITE_CLERK_PUBLISHABLE_KEY: string;');
32
32
  }
33
+ if (featureIds.includes(FEATURE.DATABASE)) {
34
+ envVars.push(' readonly VITE_SUPABASE_DATABASE_URL: string;');
35
+ envVars.push(' readonly VITE_SUPABASE_ANON_KEY: string;');
36
+ }
33
37
  // Vite built-in env vars (always required for TypeScript)
34
38
  envVars.push(" readonly MODE: 'development' | 'production' | 'test';");
35
39
  envVars.push(' readonly DEV: boolean;');
@@ -48,21 +52,20 @@ interface ImportMeta {
48
52
  }
49
53
  /** Generate env.ts content based on selected features. */
50
54
  export function generateEnvTs(featureIds) {
51
- const schemaFields = [
52
- ' VITE_APP_NAME: z.string().min(1).optional(),',
53
- ' VITE_APP_URL: z.string().url().optional(),',
54
- ];
55
+ // Check if we need the booleanEnv helper
56
+ const needsBooleanEnv = featureIds.includes(FEATURE.OBSERVABILITY) || featureIds.includes(FEATURE.PERFORMANCE);
57
+ const schemaFields = [' VITE_APP_NAME: z.string().min(1),', ' VITE_APP_URL: z.string().url(),'];
55
58
  const envFields = [
56
59
  ' VITE_APP_NAME: import.meta.env.VITE_APP_NAME,',
57
60
  ' VITE_APP_URL: import.meta.env.VITE_APP_URL,',
58
61
  ];
59
62
  if (featureIds.includes(FEATURE.API)) {
60
- schemaFields.push(' VITE_API_URL: z.string().url().optional(),');
63
+ schemaFields.push(' VITE_API_URL: z.string().url(),');
61
64
  envFields.push(' VITE_API_URL: import.meta.env.VITE_API_URL,');
62
65
  }
63
66
  if (featureIds.includes(FEATURE.OBSERVABILITY)) {
64
- schemaFields.push(' VITE_SENTRY_DSN: z.string().url().optional(),');
65
- schemaFields.push(' VITE_SENTRY_ENABLED: z.string().optional(),');
67
+ schemaFields.push(' VITE_SENTRY_DSN: z.string().url(),');
68
+ schemaFields.push(' VITE_SENTRY_ENABLED: booleanEnv,');
66
69
  envFields.push(' VITE_SENTRY_DSN: import.meta.env.VITE_SENTRY_DSN,');
67
70
  envFields.push(' VITE_SENTRY_ENABLED: import.meta.env.VITE_SENTRY_ENABLED,');
68
71
  }
@@ -70,20 +73,45 @@ export function generateEnvTs(featureIds) {
70
73
  schemaFields.push(' VITE_CLERK_PUBLISHABLE_KEY: z.string().min(1),');
71
74
  envFields.push(' VITE_CLERK_PUBLISHABLE_KEY: import.meta.env.VITE_CLERK_PUBLISHABLE_KEY,');
72
75
  }
76
+ if (featureIds.includes(FEATURE.DATABASE)) {
77
+ schemaFields.push(' VITE_SUPABASE_DATABASE_URL: z.string().url(),');
78
+ schemaFields.push(' VITE_SUPABASE_ANON_KEY: z.string().min(1),');
79
+ envFields.push(' VITE_SUPABASE_DATABASE_URL: import.meta.env.VITE_SUPABASE_DATABASE_URL,');
80
+ envFields.push(' VITE_SUPABASE_ANON_KEY: import.meta.env.VITE_SUPABASE_ANON_KEY,');
81
+ }
82
+ if (featureIds.includes(FEATURE.PERFORMANCE)) {
83
+ schemaFields.push(' VITE_PERF_TEST: booleanEnv,');
84
+ envFields.push(' VITE_PERF_TEST: import.meta.env.VITE_PERF_TEST,');
85
+ }
73
86
  // Vite built-in env vars (always included)
74
- schemaFields.push(" MODE: z.enum(['development', 'production', 'test']).default('development'),");
75
- schemaFields.push(' DEV: z.boolean().default(false),');
76
- schemaFields.push(' PROD: z.boolean().default(false),');
87
+ schemaFields.push(" MODE: z.enum(['development', 'production', 'test']),");
88
+ schemaFields.push(' DEV: z.boolean(),');
89
+ schemaFields.push(' PROD: z.boolean(),');
77
90
  envFields.push(' MODE: import.meta.env.MODE,');
78
91
  envFields.push(' DEV: import.meta.env.DEV,');
79
92
  envFields.push(' PROD: import.meta.env.PROD,');
93
+ // Build the booleanEnv helper if needed
94
+ const booleanEnvHelper = needsBooleanEnv
95
+ ? `
96
+ /**
97
+ * Transforms string env var to boolean.
98
+ * - 'true', '1' → true
99
+ * - 'false', '0' → false
100
+ */
101
+ const booleanEnv = z.enum(['true', 'false', '1', '0']).transform((val) => val === 'true' || val === '1');
102
+
103
+ `
104
+ : '';
80
105
  return `/**
81
106
  * Environment variable validation using Zod.
82
107
  * Validates at runtime to catch missing/invalid env vars early.
108
+ *
109
+ * All env vars are REQUIRED. The MCP scaffold tool strips unused vars
110
+ * when scaffolding builds without certain features.
83
111
  */
84
112
 
85
113
  import { z } from 'zod';
86
-
114
+ ${booleanEnvHelper}
87
115
  const envSchema = z.object({
88
116
  ${schemaFields.join('\n')}
89
117
  });
@@ -92,7 +120,7 @@ export type Env = z.infer<typeof envSchema>;
92
120
 
93
121
  /**
94
122
  * Validate environment variables and return typed env object.
95
- * Throws if validation fails in production.
123
+ * Throws if any required env var is missing or invalid.
96
124
  */
97
125
  export function validateEnv(): Env {
98
126
  const env = {
@@ -102,15 +130,17 @@ ${envFields.join('\n')}
102
130
  const result = envSchema.safeParse(env);
103
131
 
104
132
  if (!result.success) {
105
- const errors = result.error.format();
106
- console.error('Environment validation failed:', errors);
133
+ const errors = result.error.flatten();
134
+ const fieldErrors = Object.entries(errors.fieldErrors)
135
+ .map(([key, msgs]) => \`\${key}: \${(msgs as string[]).join(', ')}\`)
136
+ .join('; ');
137
+ const formErrors = errors.formErrors.join('; ');
138
+ const allErrors = [fieldErrors, formErrors].filter(Boolean).join('; ');
107
139
 
108
- if (import.meta.env.PROD) {
109
- throw new Error('Invalid environment configuration');
110
- }
140
+ throw new Error(\`Environment validation failed: \${allErrors}\`);
111
141
  }
112
142
 
113
- return result.data
143
+ return result.data;
114
144
  }
115
145
 
116
146
  /**
@@ -120,16 +150,21 @@ ${envFields.join('\n')}
120
150
  export const env = validateEnv();
121
151
  `;
122
152
  }
123
- /** Generates routes.ts content. */
124
- export function generateRoutesTs() {
153
+ /** Generates routes.ts content based on selected features. */
154
+ export function generateRoutesTs(featureIds) {
155
+ const routes = [" HOME: '/',"];
156
+ // Add PROFILE route when database feature is selected
157
+ if (featureIds.includes(FEATURE.DATABASE)) {
158
+ routes.push(" PROFILE: '/profile',");
159
+ }
160
+ routes.push(" NOT_FOUND: '*',");
125
161
  return `/**
126
162
  * Typed route constants.
127
163
  * Use these instead of hardcoded strings for type-safe navigation.
128
164
  */
129
165
 
130
166
  export const ROUTES = {
131
- HOME: '/',
132
- NOT_FOUND: '*',
167
+ ${routes.join('\n')}
133
168
  } as const;
134
169
 
135
170
  export type AppRoute = (typeof ROUTES)[keyof typeof ROUTES];
@@ -1 +1 @@
1
- {"version":3,"file":"generators.js","sourceRoot":"","sources":["../../../src/utils/scaffold/generators.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generators.js","sourceRoot":"","sources":["../../../src/utils/scaffold/generators.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-spa-scaffold/mcp",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "MCP server for scaffolding projects based on react-spa-scaffold template",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,19 +1,17 @@
1
- # Application
1
+ # ─────────────────────────────────────────────────────────────
2
+ # Application (required)
3
+ # ─────────────────────────────────────────────────────────────
2
4
  VITE_APP_NAME="My App"
3
5
  VITE_APP_URL=http://localhost:5173
4
-
5
- # Optional: Base URL for deployment subdirectory
6
- # VITE_BASE_URL=/
6
+ VITE_API_URL=https://jsonplaceholder.typicode.com
7
7
 
8
8
  # ─────────────────────────────────────────────────────────────
9
- # Sentry Error Tracking (production only)
9
+ # Sentry Error Tracking (required)
10
10
  # ─────────────────────────────────────────────────────────────
11
- # Set to 'false' to disable Sentry entirely (opt-out)
12
- # Enabled by default when DSN is provided
13
- # VITE_SENTRY_ENABLED=true
14
-
15
11
  # Runtime DSN for error reporting (client-side, safe to expose)
16
- # VITE_SENTRY_DSN=https://xxxxx@o123456.ingest.sentry.io/789
12
+ # Get from: https://sentry.io/settings/projects/YOUR_PROJECT/keys/
13
+ VITE_SENTRY_DSN=https://xxxxx@o123456.ingest.sentry.io/789
14
+ VITE_SENTRY_ENABLED=true
17
15
 
18
16
  # CI/CD secrets for source map upload (set in GitHub Secrets):
19
17
  # - SENTRY_AUTH_TOKEN: API token for uploading source maps
@@ -21,7 +19,37 @@ VITE_APP_URL=http://localhost:5173
21
19
  # - SENTRY_PROJECT: Sentry project slug
22
20
 
23
21
  # ─────────────────────────────────────────────────────────────
24
- # Clerk Authentication
22
+ # Clerk Authentication (required)
25
23
  # ─────────────────────────────────────────────────────────────
26
24
  # Get your Publishable Key from: https://dashboard.clerk.com/~/api-keys
27
- VITE_CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY
25
+ VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx
26
+
27
+ # ─────────────────────────────────────────────────────────────
28
+ # Supabase Database (required)
29
+ # ─────────────────────────────────────────────────────────────
30
+ # Get your Project URL and API Key from:
31
+ # https://supabase.com/dashboard/project/YOUR_PROJECT/settings/api
32
+ #
33
+ # For Netlify deployments, these can be auto-configured via the
34
+ # Netlify Supabase extension: Extensions > Supabase > Connect
35
+ VITE_SUPABASE_DATABASE_URL=https://your-project.supabase.co
36
+ VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
37
+
38
+ # ─────────────────────────────────────────────────────────────
39
+ # Performance Testing (required)
40
+ # ─────────────────────────────────────────────────────────────
41
+ VITE_PERF_TEST=false
42
+
43
+ # Supabase CLI (required for npm run db:types)
44
+ # Project ID is the subdomain from your Supabase URL (e.g., abc123xyz from https://abc123xyz.supabase.co)
45
+ SUPABASE_PROJECT_ID=your-project-id
46
+
47
+ # ─────────────────────────────────────────────────────────────
48
+ # E2E Testing (optional - for authenticated Playwright tests)
49
+ # ─────────────────────────────────────────────────────────────
50
+ # Create a test user in Clerk and provide credentials here
51
+ # Required for running: npx playwright test --project=authenticated
52
+ # E2E_CLERK_USER_USERNAME=test@example.com
53
+ # E2E_CLERK_USER_PASSWORD=your-test-password
54
+ # CLERK_SECRET_KEY=sk_test_xxxxx
55
+
@@ -96,7 +96,7 @@ jobs:
96
96
  upload-on: failure
97
97
  - type: performance
98
98
  project: performance
99
- command: PERF_TEST=true npx playwright test --project=performance
99
+ command: PERF_TEST=true PERF_CI=true npx playwright test --project=performance
100
100
  report-name: performance-report
101
101
  upload-on: always
102
102
  steps:
@@ -106,6 +106,9 @@ jobs:
106
106
  with:
107
107
  name: dist
108
108
  path: dist/
109
+ - name: Rebuild with performance tracking
110
+ if: matrix.type == 'performance'
111
+ run: VITE_PERF_TEST=true npm run build
109
112
  - name: Get Playwright version
110
113
  id: playwright-version
111
114
  run: echo "version=$(npm ls @playwright/test --json | jq -r '.dependencies["@playwright/test"].version')" >> $GITHUB_OUTPUT
@@ -0,0 +1,59 @@
1
+ name: Deploy
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+ branches: [main, master]
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+ pull-requests: write
13
+ deployments: write
14
+
15
+ concurrency:
16
+ group: deploy-${{ github.ref }}
17
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
18
+
19
+ jobs:
20
+ deploy:
21
+ name: Deploy
22
+ runs-on: ubuntu-latest
23
+ timeout-minutes: 15
24
+ steps:
25
+ - uses: actions/checkout@v6
26
+
27
+ - uses: ./.github/actions/setup-node-deps
28
+
29
+ - name: Build
30
+ run: npm run build
31
+
32
+ - name: Deploy Preview
33
+ if: github.event_name == 'pull_request'
34
+ uses: nwtgck/actions-netlify@v3
35
+ with:
36
+ publish-dir: './dist'
37
+ production-deploy: false
38
+ github-token: ${{ secrets.GITHUB_TOKEN }}
39
+ deploy-message: 'Preview deploy from PR #${{ github.event.number }}'
40
+ enable-pull-request-comment: true
41
+ enable-commit-comment: false
42
+ overwrites-pull-request-comment: true
43
+ alias: pr-${{ github.event.number }}
44
+ env:
45
+ NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
46
+ NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
47
+
48
+ - name: Deploy Production
49
+ if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
50
+ uses: nwtgck/actions-netlify@v3
51
+ with:
52
+ publish-dir: './dist'
53
+ production-deploy: true
54
+ github-token: ${{ secrets.GITHUB_TOKEN }}
55
+ deploy-message: 'Production deploy from ${{ github.sha }}'
56
+ enable-commit-comment: true
57
+ env:
58
+ NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
59
+ NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
@@ -19,6 +19,9 @@ npm run e2e:mobile # Playwright E2E (mobile)
19
19
  npm run e2e:all # Playwright E2E (all viewports)
20
20
  npm run e2e:perf # Performance regression tests
21
21
  npm run i18n:extract # Extract translations to .po
22
+ npm run db:types # Generate Supabase TypeScript types
23
+ npm run db:push # Push database migrations
24
+ npm run db:studio # Open Supabase Studio
22
25
  ```
23
26
 
24
27
  ## Project Structure
@@ -213,10 +216,183 @@ import { render, mockMatchMedia, server } from '@/test';
213
216
 
214
217
  MSW handlers auto-reset after each test.
215
218
 
219
+ ## Authentication (Clerk)
220
+
221
+ When the auth feature is enabled, Clerk authentication is required.
222
+
223
+ ### Setup
224
+
225
+ 1. Create an account at [clerk.com](https://clerk.com)
226
+ 2. Get your Publishable Key from the dashboard
227
+ 3. Copy `.env.example` to `.env` and set your key:
228
+ ```
229
+ VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx
230
+ ```
231
+
232
+ ### Usage
233
+
234
+ ```tsx
235
+ // Protect routes that require authentication
236
+ import { ProtectedRoute } from '@/components/shared';
237
+
238
+ <Route
239
+ path="/dashboard"
240
+ element={
241
+ <ProtectedRoute>
242
+ <DashboardPage />
243
+ </ProtectedRoute>
244
+ }
245
+ />;
246
+ ```
247
+
248
+ ```tsx
249
+ // Conditional rendering based on auth state
250
+ import { SignedIn, SignedOut, UserButton, SignInButton } from '@clerk/react-router';
251
+
252
+ <SignedIn>
253
+ <UserButton />
254
+ </SignedIn>
255
+ <SignedOut>
256
+ <SignInButton mode="modal">
257
+ <Button>Sign In</Button>
258
+ </SignInButton>
259
+ </SignedOut>
260
+ ```
261
+
262
+ ### Testing
263
+
264
+ Clerk is automatically mocked in tests. Use test utilities to control auth state:
265
+
266
+ ```tsx
267
+ import { setMockClerkSignedIn, resetClerkMocks } from '@/test';
268
+
269
+ beforeEach(() => resetClerkMocks());
270
+
271
+ it('shows sign-in when not authenticated', () => {
272
+ setMockClerkSignedIn(false);
273
+ // ...
274
+ });
275
+ ```
276
+
277
+ ## Database (Supabase)
278
+
279
+ Supabase provides PostgreSQL database with Row Level Security (RLS), integrated with Clerk authentication.
280
+
281
+ ### Setup
282
+
283
+ 1. Create a project at [supabase.com](https://supabase.com)
284
+ 2. Configure Clerk as third-party auth provider:
285
+ - Supabase Dashboard → Authentication → Providers → Third-Party Auth → Add Clerk
286
+ 3. Enable Supabase integration in Clerk:
287
+ - Clerk Dashboard → Integrations → Supabase → Activate
288
+ 4. Set environment variables in `.env`:
289
+ ```
290
+ VITE_SUPABASE_DATABASE_URL=https://your-project.supabase.co
291
+ VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
292
+ ```
293
+
294
+ ### Database Commands
295
+
296
+ ```bash
297
+ npm run db:types # Generate TypeScript types from schema
298
+ npm run db:push # Push migrations to database
299
+ npm run db:reset # Reset database (WARNING: destructive)
300
+ npm run db:studio # Open Supabase Studio
301
+ ```
302
+
303
+ ### Usage
304
+
305
+ ```tsx
306
+ import { useSupabase, useSupabaseQuery, useProfile } from '@/hooks';
307
+
308
+ // Direct client access
309
+ const supabase = useSupabase();
310
+ const { data } = await supabase.from('profiles').select();
311
+
312
+ // TanStack Query wrapper for automatic caching
313
+ const { data, isLoading } = useSupabaseQuery({
314
+ table: 'profiles',
315
+ queryKey: ['current'],
316
+ });
317
+
318
+ // Convenience hook for current user's profile
319
+ const { profile, isLoading, exists } = useProfile();
320
+ ```
321
+
322
+ ### Profile Mutations
323
+
324
+ ```tsx
325
+ import { useUpsertProfile, useUpdateProfile, useDeleteProfile } from '@/hooks';
326
+
327
+ // Create or update profile (upsert)
328
+ const upsertProfile = useUpsertProfile();
329
+ await upsertProfile.mutateAsync({ id: userId, email: 'user@example.com' });
330
+
331
+ // Update current user's profile
332
+ const updateProfile = useUpdateProfile();
333
+ await updateProfile.mutateAsync({ full_name: 'John' });
334
+
335
+ // Delete current user's profile
336
+ const deleteProfile = useDeleteProfile();
337
+ await deleteProfile.mutateAsync();
338
+ ```
339
+
340
+ ### Auto-Sync with ProfileSync
341
+
342
+ ```tsx
343
+ import { ProfileSync } from '@/components/shared';
344
+
345
+ // Add to your app to auto-sync Clerk user data to Supabase
346
+ function App() {
347
+ return (
348
+ <>
349
+ <ProfileSync />
350
+ <Routes>...</Routes>
351
+ </>
352
+ );
353
+ }
354
+ ```
355
+
356
+ ### Row Level Security (RLS)
357
+
358
+ All tables should have RLS enabled. Policies use `auth.uid()` which equals the Clerk user_id:
359
+
360
+ ```sql
361
+ -- Users can only access their own data
362
+ CREATE POLICY "Users can view own profile"
363
+ ON profiles FOR SELECT TO authenticated
364
+ USING (id = auth.uid());
365
+ ```
366
+
367
+ ### Testing
368
+
369
+ Supabase context is mocked in tests with state controls:
370
+
371
+ ```tsx
372
+ import { render, setMockSupabaseData, setMockSupabaseError, createProfile, resetSupabaseMocks } from '@/test';
373
+
374
+ beforeEach(() => resetSupabaseMocks());
375
+
376
+ it('displays profile data', async () => {
377
+ setMockSupabaseData([createProfile({ full_name: 'Test User' })]);
378
+ render(<ProfileCard />);
379
+ // Assert profile is displayed
380
+ });
381
+
382
+ it('handles error', async () => {
383
+ setMockSupabaseError({ message: 'Failed', code: 'ERROR' });
384
+ render(<ProfileCard />);
385
+ // Assert error state
386
+ });
387
+ ```
388
+
216
389
  ## Common Gotchas
217
390
 
218
391
  1. **Node.js >= 22.0.0** required (check `.nvmrc`)
219
392
  2. **Conventional commits** enforced by commitlint
220
- 3. **Context hooks throw** outside provider (e.g., `useMobileContext()`)
393
+ 3. **Context hooks throw** outside provider (e.g., `useMobileContext()`, `useSupabase()`)
221
394
  4. **Barrel exports** in each directory via `index.ts`
222
395
  5. **UI components** import directly: `@/components/ui/button` (no barrel)
396
+ 6. **Clerk auth required** when auth feature is enabled - set `VITE_CLERK_PUBLISHABLE_KEY` in `.env`
397
+ 7. **Supabase requires Clerk** - SupabaseProvider must be inside ClerkProvider
398
+ 8. **RLS policies required** - All Supabase tables should have Row Level Security enabled