@mars-stack/cli 0.2.0 → 0.2.2

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 (173) hide show
  1. package/package.json +2 -2
  2. package/template/.cursor/rules/composition-patterns.mdc +186 -0
  3. package/template/.cursor/rules/data-access.mdc +29 -0
  4. package/template/.cursor/rules/project-structure.mdc +34 -0
  5. package/template/.cursor/rules/security.mdc +25 -0
  6. package/template/.cursor/rules/testing.mdc +24 -0
  7. package/template/.cursor/rules/ui-conventions.mdc +29 -0
  8. package/template/.cursor/skills/add-api-route/SKILL.md +122 -0
  9. package/template/.cursor/skills/add-audit-log/SKILL.md +373 -0
  10. package/template/.cursor/skills/add-blog/SKILL.md +447 -0
  11. package/template/.cursor/skills/add-command-palette/SKILL.md +438 -0
  12. package/template/.cursor/skills/add-component/SKILL.md +158 -0
  13. package/template/.cursor/skills/add-crud-routes/SKILL.md +221 -0
  14. package/template/.cursor/skills/add-e2e-test/SKILL.md +227 -0
  15. package/template/.cursor/skills/add-error-boundary/SKILL.md +472 -0
  16. package/template/.cursor/skills/add-feature/SKILL.md +174 -0
  17. package/template/.cursor/skills/add-middleware/SKILL.md +135 -0
  18. package/template/.cursor/skills/add-page/SKILL.md +151 -0
  19. package/template/.cursor/skills/add-prisma-model/SKILL.md +148 -0
  20. package/template/.cursor/skills/add-protected-resource/SKILL.md +192 -0
  21. package/template/.cursor/skills/add-role/SKILL.md +156 -0
  22. package/template/.cursor/skills/add-server-action/SKILL.md +167 -0
  23. package/template/.cursor/skills/add-webhook/SKILL.md +192 -0
  24. package/template/.cursor/skills/build-complete-feature/SKILL.md +227 -0
  25. package/template/.cursor/skills/build-dashboard/SKILL.md +211 -0
  26. package/template/.cursor/skills/build-data-table/SKILL.md +283 -0
  27. package/template/.cursor/skills/build-form/SKILL.md +231 -0
  28. package/template/.cursor/skills/build-landing-page/SKILL.md +248 -0
  29. package/template/.cursor/skills/configure-ai/SKILL.md +617 -0
  30. package/template/.cursor/skills/configure-analytics/SKILL.md +413 -0
  31. package/template/.cursor/skills/configure-dark-mode/SKILL.md +309 -0
  32. package/template/.cursor/skills/configure-email/SKILL.md +170 -0
  33. package/template/.cursor/skills/configure-email-verification/SKILL.md +333 -0
  34. package/template/.cursor/skills/configure-feature-flags/SKILL.md +361 -0
  35. package/template/.cursor/skills/configure-i18n/SKILL.md +518 -0
  36. package/template/.cursor/skills/configure-jobs/SKILL.md +500 -0
  37. package/template/.cursor/skills/configure-magic-links/SKILL.md +385 -0
  38. package/template/.cursor/skills/configure-multi-tenancy/SKILL.md +611 -0
  39. package/template/.cursor/skills/configure-notifications/SKILL.md +569 -0
  40. package/template/.cursor/skills/configure-oauth/SKILL.md +217 -0
  41. package/template/.cursor/skills/configure-onboarding/SKILL.md +483 -0
  42. package/template/.cursor/skills/configure-payments/SKILL.md +243 -0
  43. package/template/.cursor/skills/configure-realtime/SKILL.md +733 -0
  44. package/template/.cursor/skills/configure-search/SKILL.md +581 -0
  45. package/template/.cursor/skills/configure-storage/SKILL.md +273 -0
  46. package/template/.cursor/skills/configure-two-factor/SKILL.md +518 -0
  47. package/template/.cursor/skills/create-execution-plan/SKILL.md +204 -0
  48. package/template/.cursor/skills/create-seed/SKILL.md +191 -0
  49. package/template/.cursor/skills/deploy-to-vercel/SKILL.md +300 -0
  50. package/template/.cursor/skills/design-tokens/SKILL.md +138 -0
  51. package/template/.cursor/skills/mars-capture-conversation-context/SKILL.md +119 -0
  52. package/template/.cursor/skills/setup-billing/SKILL.md +322 -0
  53. package/template/.cursor/skills/setup-project/SKILL.md +104 -0
  54. package/template/.cursor/skills/setup-teams/SKILL.md +682 -0
  55. package/template/.cursor/skills/test-api-route/SKILL.md +219 -0
  56. package/template/.cursor/skills/update-architecture-docs/SKILL.md +99 -0
  57. package/template/AGENTS.md +104 -0
  58. package/template/ARCHITECTURE.md +102 -0
  59. package/template/docs/QUALITY_SCORE.md +20 -0
  60. package/template/docs/design-docs/conversation-as-system-record.md +70 -0
  61. package/template/docs/design-docs/core-beliefs.md +43 -0
  62. package/template/docs/design-docs/index.md +8 -0
  63. package/template/docs/exec-plans/active/.gitkeep +0 -0
  64. package/template/docs/exec-plans/completed/.gitkeep +0 -0
  65. package/template/docs/exec-plans/tech-debt.md +7 -0
  66. package/template/docs/generated/.gitkeep +0 -0
  67. package/template/docs/product-specs/index.md +7 -0
  68. package/template/docs/references/index.md +18 -0
  69. package/template/e2e/api.spec.ts +20 -0
  70. package/template/e2e/auth.spec.ts +24 -0
  71. package/template/e2e/public.spec.ts +25 -0
  72. package/template/eslint.config.mjs +24 -0
  73. package/template/next-env.d.ts +6 -0
  74. package/template/next.config.ts +45 -0
  75. package/template/package.json +80 -0
  76. package/template/playwright.config.ts +31 -0
  77. package/template/postcss.config.mjs +8 -0
  78. package/template/prisma/generated/prisma/browser.ts +49 -0
  79. package/template/prisma/generated/prisma/client.ts +73 -0
  80. package/template/prisma/generated/prisma/commonInputTypes.ts +406 -0
  81. package/template/prisma/generated/prisma/enums.ts +15 -0
  82. package/template/prisma/generated/prisma/internal/class.ts +254 -0
  83. package/template/prisma/generated/prisma/internal/prismaNamespace.ts +1240 -0
  84. package/template/prisma/generated/prisma/internal/prismaNamespaceBrowser.ts +190 -0
  85. package/template/prisma/generated/prisma/models/Account.ts +1543 -0
  86. package/template/prisma/generated/prisma/models/File.ts +1529 -0
  87. package/template/prisma/generated/prisma/models/Session.ts +1415 -0
  88. package/template/prisma/generated/prisma/models/Subscription.ts +1455 -0
  89. package/template/prisma/generated/prisma/models/User.ts +2235 -0
  90. package/template/prisma/generated/prisma/models/VerificationToken.ts +1099 -0
  91. package/template/prisma/generated/prisma/models.ts +17 -0
  92. package/template/prisma/schema/auth.prisma +69 -0
  93. package/template/prisma/schema/base.prisma +8 -0
  94. package/template/prisma/schema/file.prisma +15 -0
  95. package/template/prisma/schema/subscription.prisma +17 -0
  96. package/template/prisma.config.ts +13 -0
  97. package/template/scripts/check-architecture.ts +221 -0
  98. package/template/scripts/check-doc-freshness.ts +242 -0
  99. package/template/scripts/ensure-db.mjs +291 -0
  100. package/template/scripts/generate-docs.ts +143 -0
  101. package/template/scripts/generate-env-example.ts +89 -0
  102. package/template/scripts/seed.ts +56 -0
  103. package/template/scripts/update-quality-score.ts +263 -0
  104. package/template/src/__tests__/architecture.test.ts +114 -0
  105. package/template/src/app/(auth)/forgotten-password/page.tsx +92 -0
  106. package/template/src/app/(auth)/layout.tsx +11 -0
  107. package/template/src/app/(auth)/register/page.tsx +162 -0
  108. package/template/src/app/(auth)/reset-password/page.tsx +109 -0
  109. package/template/src/app/(auth)/sign-in/page.tsx +122 -0
  110. package/template/src/app/(auth)/verify/[token]/page.tsx +87 -0
  111. package/template/src/app/(auth)/verify/page.tsx +56 -0
  112. package/template/src/app/(protected)/admin/page.tsx +108 -0
  113. package/template/src/app/(protected)/dashboard/loading.tsx +20 -0
  114. package/template/src/app/(protected)/dashboard/page.tsx +22 -0
  115. package/template/src/app/(protected)/layout.tsx +262 -0
  116. package/template/src/app/(protected)/settings/page.tsx +370 -0
  117. package/template/src/app/api/auth/forgot/route.ts +63 -0
  118. package/template/src/app/api/auth/login/route.ts +121 -0
  119. package/template/src/app/api/auth/logout/route.ts +19 -0
  120. package/template/src/app/api/auth/me/route.ts +30 -0
  121. package/template/src/app/api/auth/reset/route.ts +45 -0
  122. package/template/src/app/api/auth/signup/route.ts +85 -0
  123. package/template/src/app/api/auth/verify/route.ts +46 -0
  124. package/template/src/app/api/csrf/route.ts +12 -0
  125. package/template/src/app/api/health/route.ts +10 -0
  126. package/template/src/app/api/protected/admin/users/route.ts +24 -0
  127. package/template/src/app/api/protected/billing/checkout/route.ts +83 -0
  128. package/template/src/app/api/protected/billing/portal/route.ts +39 -0
  129. package/template/src/app/api/protected/files/[fileId]/route.ts +86 -0
  130. package/template/src/app/api/protected/files/upload/route.ts +64 -0
  131. package/template/src/app/api/protected/user/password/route.ts +63 -0
  132. package/template/src/app/api/protected/user/profile/route.ts +35 -0
  133. package/template/src/app/api/protected/user/sessions/[sessionId]/route.ts +33 -0
  134. package/template/src/app/api/protected/user/sessions/route.ts +22 -0
  135. package/template/src/app/api/readiness/route.ts +15 -0
  136. package/template/src/app/api/webhooks/stripe/route.ts +166 -0
  137. package/template/src/app/error.tsx +33 -0
  138. package/template/src/app/layout.tsx +29 -0
  139. package/template/src/app/not-found.tsx +20 -0
  140. package/template/src/app/page.tsx +136 -0
  141. package/template/src/app/privacy/page.tsx +178 -0
  142. package/template/src/app/providers.tsx +8 -0
  143. package/template/src/app/terms/page.tsx +139 -0
  144. package/template/src/config/app.config.ts +70 -0
  145. package/template/src/config/routes.ts +17 -0
  146. package/template/src/features/admin/index.ts +11 -0
  147. package/template/src/features/admin/permissions.ts +64 -0
  148. package/template/src/features/auth/context/AuthContext.tsx +96 -0
  149. package/template/src/features/auth/context/index.ts +2 -0
  150. package/template/src/features/auth/index.ts +3 -0
  151. package/template/src/features/auth/server/consent.ts +66 -0
  152. package/template/src/features/auth/server/session-revocation.ts +20 -0
  153. package/template/src/features/auth/server/sessions.ts +66 -0
  154. package/template/src/features/auth/server/user.ts +166 -0
  155. package/template/src/features/auth/types.ts +19 -0
  156. package/template/src/features/auth/validators.ts +29 -0
  157. package/template/src/features/billing/server/index.ts +66 -0
  158. package/template/src/features/billing/types.ts +43 -0
  159. package/template/src/features/uploads/server/index.ts +49 -0
  160. package/template/src/features/uploads/types.ts +26 -0
  161. package/template/src/lib/core/email/templates/base-layout.ts +122 -0
  162. package/template/src/lib/core/email/templates/index.ts +4 -0
  163. package/template/src/lib/core/email/templates/password-reset-email.ts +42 -0
  164. package/template/src/lib/core/email/templates/verification-email.ts +41 -0
  165. package/template/src/lib/core/email/templates/welcome-email.ts +40 -0
  166. package/template/src/lib/mars.ts +56 -0
  167. package/template/src/lib/prisma.ts +19 -0
  168. package/template/src/proxy.ts +92 -0
  169. package/template/src/styles/brand.css +17 -0
  170. package/template/src/styles/globals.css +6 -0
  171. package/template/tsconfig.json +59 -0
  172. package/template/vitest.config.ts +41 -0
  173. package/template/vitest.setup.ts +24 -0
@@ -0,0 +1,413 @@
1
+ # Skill: Configure Analytics
2
+
3
+ Set up analytics tracking with Vercel Analytics, PostHog, or Google Analytics in a MARS application.
4
+
5
+ ## When to Use
6
+
7
+ Use this skill when the user asks to add analytics, tracking, page views, event tracking, Vercel Analytics, PostHog, Google Analytics, or a consent banner.
8
+
9
+ ## Prerequisites
10
+
11
+ - Next.js app set up with the MARS template
12
+
13
+ ## Architecture
14
+
15
+ MARS supports multiple analytics providers through a unified wrapper. The active provider is set in `appConfig.services.analytics`. All tracking calls go through a single `trackEvent` function, making it easy to swap providers.
16
+
17
+ Supported providers:
18
+ - `vercel` — Vercel Analytics + Web Vitals (zero-config for Vercel deployments)
19
+ - `posthog` — PostHog (self-hostable, feature flags, session replay)
20
+ - `google` — Google Analytics 4 (gtag.js)
21
+ - `none` — disabled (default)
22
+
23
+ ## Step 1: Configuration
24
+
25
+ ```typescript
26
+ // src/config/app.config.ts
27
+ services: {
28
+ analytics: {
29
+ provider: 'none' as 'vercel' | 'posthog' | 'google' | 'none',
30
+ enableInDevelopment: false,
31
+ },
32
+ },
33
+ ```
34
+
35
+ ## Step 2: Install Provider SDK
36
+
37
+ **Vercel Analytics:**
38
+ ```bash
39
+ yarn add @vercel/analytics @vercel/speed-insights
40
+ ```
41
+
42
+ **PostHog:**
43
+ ```bash
44
+ yarn add posthog-js
45
+ ```
46
+
47
+ **Google Analytics:**
48
+ No package needed — uses the gtag.js script tag.
49
+
50
+ ## Step 3: Environment Variables
51
+
52
+ **Vercel Analytics:**
53
+ No env vars needed — auto-configured on Vercel deployments.
54
+
55
+ **PostHog:**
56
+ ```bash
57
+ NEXT_PUBLIC_POSTHOG_KEY="phc_your-project-key"
58
+ NEXT_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com"
59
+ ```
60
+
61
+ **Google Analytics:**
62
+ ```bash
63
+ NEXT_PUBLIC_GA_MEASUREMENT_ID="G-XXXXXXXXXX"
64
+ ```
65
+
66
+ ## Step 4: Analytics Provider Component
67
+
68
+ ```typescript
69
+ // src/lib/shared/components/providers/AnalyticsProvider.tsx
70
+ 'use client';
71
+
72
+ import { useEffect } from 'react';
73
+ import { usePathname, useSearchParams } from 'next/navigation';
74
+ import { appConfig } from '@/config/app.config';
75
+
76
+ function usePageView() {
77
+ const pathname = usePathname();
78
+ const searchParams = useSearchParams();
79
+
80
+ useEffect(() => {
81
+ if (!pathname) return;
82
+
83
+ const url = searchParams?.toString()
84
+ ? `${pathname}?${searchParams.toString()}`
85
+ : pathname;
86
+
87
+ trackPageView(url);
88
+ }, [pathname, searchParams]);
89
+ }
90
+
91
+ export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
92
+ const provider = appConfig.services.analytics.provider;
93
+ const isDev = process.env.NODE_ENV === 'development';
94
+
95
+ if (provider === 'none' || (isDev && !appConfig.services.analytics.enableInDevelopment)) {
96
+ return <>{children}</>;
97
+ }
98
+
99
+ return (
100
+ <>
101
+ {provider === 'vercel' && <VercelAnalytics />}
102
+ {provider === 'posthog' && <PostHogProvider />}
103
+ {provider === 'google' && <GoogleAnalytics />}
104
+ <PageViewTracker />
105
+ {children}
106
+ </>
107
+ );
108
+ }
109
+
110
+ function PageViewTracker() {
111
+ usePageView();
112
+ return null;
113
+ }
114
+ ```
115
+
116
+ ## Step 5: Vercel Analytics Setup
117
+
118
+ ```typescript
119
+ // src/lib/shared/components/providers/analytics/VercelAnalytics.tsx
120
+ 'use client';
121
+
122
+ import { Analytics } from '@vercel/analytics/react';
123
+ import { SpeedInsights } from '@vercel/speed-insights/next';
124
+
125
+ export function VercelAnalytics() {
126
+ return (
127
+ <>
128
+ <Analytics />
129
+ <SpeedInsights />
130
+ </>
131
+ );
132
+ }
133
+ ```
134
+
135
+ ## Step 6: PostHog Setup
136
+
137
+ ```typescript
138
+ // src/lib/shared/components/providers/analytics/PostHogProvider.tsx
139
+ 'use client';
140
+
141
+ import posthog from 'posthog-js';
142
+ import { PostHogProvider as PHProvider } from 'posthog-js/react';
143
+ import { useEffect, useState } from 'react';
144
+
145
+ export function PostHogProvider({ children }: { children?: React.ReactNode }) {
146
+ const [initialized, setInitialized] = useState(false);
147
+
148
+ useEffect(() => {
149
+ const key = process.env.NEXT_PUBLIC_POSTHOG_KEY;
150
+ const host = process.env.NEXT_PUBLIC_POSTHOG_HOST;
151
+
152
+ if (!key) {
153
+ console.warn('PostHog: NEXT_PUBLIC_POSTHOG_KEY is not set');
154
+ return;
155
+ }
156
+
157
+ posthog.init(key, {
158
+ api_host: host || 'https://us.i.posthog.com',
159
+ person_profiles: 'identified_only',
160
+ capture_pageview: false, // We handle this manually for SPA navigation
161
+ capture_pageleave: true,
162
+ });
163
+
164
+ setInitialized(true);
165
+ }, []);
166
+
167
+ if (!initialized) return <>{children}</>;
168
+
169
+ return <PHProvider client={posthog}>{children}</PHProvider>;
170
+ }
171
+ ```
172
+
173
+ ## Step 7: Google Analytics Setup
174
+
175
+ ```typescript
176
+ // src/lib/shared/components/providers/analytics/GoogleAnalytics.tsx
177
+ 'use client';
178
+
179
+ import Script from 'next/script';
180
+
181
+ export function GoogleAnalytics() {
182
+ const measurementId = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID;
183
+
184
+ if (!measurementId) {
185
+ console.warn('Google Analytics: NEXT_PUBLIC_GA_MEASUREMENT_ID is not set');
186
+ return null;
187
+ }
188
+
189
+ return (
190
+ <>
191
+ <Script
192
+ strategy="afterInteractive"
193
+ src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}
194
+ />
195
+ <Script
196
+ id="google-analytics"
197
+ strategy="afterInteractive"
198
+ dangerouslySetInnerHTML={{
199
+ __html: `
200
+ window.dataLayer = window.dataLayer || [];
201
+ function gtag(){dataLayer.push(arguments);}
202
+ gtag('js', new Date());
203
+ gtag('config', '${measurementId}', {
204
+ page_path: window.location.pathname,
205
+ });
206
+ `,
207
+ }}
208
+ />
209
+ </>
210
+ );
211
+ }
212
+ ```
213
+
214
+ ## Step 8: Unified Tracking API
215
+
216
+ ```typescript
217
+ // src/lib/shared/utils/analytics.ts
218
+ import { appConfig } from '@/config/app.config';
219
+
220
+ export function trackPageView(url: string) {
221
+ const provider = appConfig.services.analytics.provider;
222
+
223
+ switch (provider) {
224
+ case 'posthog': {
225
+ const posthog = (window as any).posthog;
226
+ posthog?.capture('$pageview', { $current_url: url });
227
+ break;
228
+ }
229
+ case 'google': {
230
+ const gtag = (window as any).gtag;
231
+ gtag?.('config', process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID, { page_path: url });
232
+ break;
233
+ }
234
+ case 'vercel':
235
+ // Vercel Analytics handles page views automatically
236
+ break;
237
+ }
238
+ }
239
+
240
+ export function trackEvent(
241
+ eventName: string,
242
+ properties?: Record<string, string | number | boolean>,
243
+ ) {
244
+ const provider = appConfig.services.analytics.provider;
245
+
246
+ if (process.env.NODE_ENV === 'development' && !appConfig.services.analytics.enableInDevelopment) {
247
+ console.debug(`[analytics] ${eventName}`, properties);
248
+ return;
249
+ }
250
+
251
+ switch (provider) {
252
+ case 'posthog': {
253
+ const posthog = (window as any).posthog;
254
+ posthog?.capture(eventName, properties);
255
+ break;
256
+ }
257
+ case 'google': {
258
+ const gtag = (window as any).gtag;
259
+ gtag?.('event', eventName, properties);
260
+ break;
261
+ }
262
+ case 'vercel': {
263
+ // Vercel Analytics custom events via @vercel/analytics
264
+ import('@vercel/analytics').then(({ track }) => track(eventName, properties));
265
+ break;
266
+ }
267
+ }
268
+ }
269
+
270
+ export function identifyUser(userId: string, traits?: Record<string, string>) {
271
+ const provider = appConfig.services.analytics.provider;
272
+
273
+ switch (provider) {
274
+ case 'posthog': {
275
+ const posthog = (window as any).posthog;
276
+ posthog?.identify(userId, traits);
277
+ break;
278
+ }
279
+ case 'google': {
280
+ const gtag = (window as any).gtag;
281
+ gtag?.('set', { user_id: userId, ...traits });
282
+ break;
283
+ }
284
+ }
285
+ }
286
+ ```
287
+
288
+ ## Step 9: Consent Banner
289
+
290
+ ```typescript
291
+ // src/lib/shared/components/patterns/ConsentBanner.tsx
292
+ 'use client';
293
+
294
+ import { useEffect, useState } from 'react';
295
+
296
+ const CONSENT_KEY = 'mars-analytics-consent';
297
+
298
+ export function ConsentBanner() {
299
+ const [visible, setVisible] = useState(false);
300
+
301
+ useEffect(() => {
302
+ const consent = localStorage.getItem(CONSENT_KEY);
303
+ if (consent === null) setVisible(true);
304
+ }, []);
305
+
306
+ function handleAccept() {
307
+ localStorage.setItem(CONSENT_KEY, 'granted');
308
+ setVisible(false);
309
+ window.location.reload();
310
+ }
311
+
312
+ function handleDecline() {
313
+ localStorage.setItem(CONSENT_KEY, 'denied');
314
+ setVisible(false);
315
+ }
316
+
317
+ if (!visible) return null;
318
+
319
+ return (
320
+ <div className="fixed bottom-0 left-0 right-0 z-50 border-t border-border-primary bg-surface-primary p-4 shadow-lg">
321
+ <div className="mx-auto flex max-w-4xl items-center justify-between gap-4">
322
+ <p className="text-sm text-content-secondary">
323
+ We use cookies and analytics to improve your experience. You can opt out at any time.
324
+ </p>
325
+ <div className="flex shrink-0 gap-2">
326
+ <button
327
+ onClick={handleDecline}
328
+ className="rounded-md px-4 py-2 text-sm text-content-secondary hover:text-content-primary"
329
+ >
330
+ Decline
331
+ </button>
332
+ <button
333
+ onClick={handleAccept}
334
+ className="rounded-md bg-interactive-primary px-4 py-2 text-sm text-white hover:bg-interactive-primary-hover"
335
+ >
336
+ Accept
337
+ </button>
338
+ </div>
339
+ </div>
340
+ </div>
341
+ );
342
+ }
343
+ ```
344
+
345
+ Update the `AnalyticsProvider` to check consent:
346
+
347
+ ```typescript
348
+ const consent = typeof window !== 'undefined' ? localStorage.getItem('mars-analytics-consent') : null;
349
+ if (consent === 'denied') return <>{children}</>;
350
+ ```
351
+
352
+ ## Step 10: Add to Root Layout
353
+
354
+ ```typescript
355
+ // src/app/layout.tsx
356
+ import { AnalyticsProvider } from '@/lib/shared/components/providers/AnalyticsProvider';
357
+ import { ConsentBanner } from '@/lib/shared/components/patterns/ConsentBanner';
358
+
359
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
360
+ return (
361
+ <html lang="en">
362
+ <body>
363
+ <AnalyticsProvider>
364
+ {children}
365
+ <ConsentBanner />
366
+ </AnalyticsProvider>
367
+ </body>
368
+ </html>
369
+ );
370
+ }
371
+ ```
372
+
373
+ ## Usage Examples
374
+
375
+ Track a custom event anywhere in client code:
376
+
377
+ ```typescript
378
+ import { trackEvent } from '@/lib/shared/utils/analytics';
379
+
380
+ trackEvent('button_clicked', { button: 'upgrade', plan: 'pro' });
381
+ trackEvent('feature_used', { feature: 'export', format: 'csv' });
382
+ ```
383
+
384
+ Identify a user after login:
385
+
386
+ ```typescript
387
+ import { identifyUser } from '@/lib/shared/utils/analytics';
388
+
389
+ identifyUser(session.userId, { email: session.email, plan: 'free' });
390
+ ```
391
+
392
+ ## Testing
393
+
394
+ 1. Set provider to `'none'` — verify no scripts load and events log to console in dev.
395
+ 2. Set provider to `'vercel'` — deploy to Vercel, check the Analytics dashboard.
396
+ 3. Set provider to `'posthog'` — verify events appear in PostHog project.
397
+ 4. Set provider to `'google'` — check Google Analytics Realtime view.
398
+ 5. Decline consent — verify no tracking scripts load.
399
+ 6. Accept consent — verify tracking resumes after reload.
400
+ 7. Navigate between pages — verify page views are tracked.
401
+
402
+ ## Checklist
403
+
404
+ - [ ] Analytics provider set in `appConfig.services.analytics.provider`
405
+ - [ ] Provider SDK installed (Vercel, PostHog, or gtag)
406
+ - [ ] Environment variables set for the chosen provider
407
+ - [ ] `AnalyticsProvider` wraps the app in root layout
408
+ - [ ] Unified `trackEvent`, `trackPageView`, and `identifyUser` functions
409
+ - [ ] Page view tracking on SPA navigation
410
+ - [ ] Consent banner with `localStorage` persistence
411
+ - [ ] Analytics disabled when consent is denied
412
+ - [ ] Development mode logging to console
413
+ - [ ] No tracking in development unless `enableInDevelopment` is set