luxlabs 1.0.1 → 1.0.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 (60) hide show
  1. package/package.json +1 -1
  2. package/templates/interface-boilerplate/app/api/auth/[...all]/route.ts +52 -0
  3. package/templates/interface-boilerplate/app/auth/callback/page.tsx +22 -31
  4. package/templates/interface-boilerplate/app/dashboard/page.tsx +41 -214
  5. package/templates/interface-boilerplate/app/favicon.ico +0 -0
  6. package/templates/interface-boilerplate/app/globals.css +18 -113
  7. package/templates/interface-boilerplate/app/layout.tsx +21 -33
  8. package/templates/interface-boilerplate/app/page.tsx +37 -116
  9. package/templates/interface-boilerplate/app/settings/page.tsx +71 -0
  10. package/templates/interface-boilerplate/app/sign-in/page.tsx +19 -0
  11. package/templates/interface-boilerplate/app/sign-up/page.tsx +19 -0
  12. package/templates/interface-boilerplate/components/auth/sign-in-form.tsx +137 -0
  13. package/templates/interface-boilerplate/components/auth/sign-up-form.tsx +225 -0
  14. package/templates/interface-boilerplate/components/ui/badge.tsx +18 -14
  15. package/templates/interface-boilerplate/components/ui/button.tsx +29 -24
  16. package/templates/interface-boilerplate/components/ui/card.tsx +76 -57
  17. package/templates/interface-boilerplate/components/ui/input.tsx +8 -9
  18. package/templates/interface-boilerplate/eslint.config.mjs +18 -0
  19. package/templates/interface-boilerplate/lib/auth-client.ts +18 -0
  20. package/templates/interface-boilerplate/lib/auth.config.ts +111 -0
  21. package/templates/interface-boilerplate/lib/utils.ts +3 -3
  22. package/templates/interface-boilerplate/middleware.ts +60 -0
  23. package/templates/interface-boilerplate/next.config.ts +7 -0
  24. package/templates/interface-boilerplate/package-lock.json +6855 -0
  25. package/templates/interface-boilerplate/package.json +18 -37
  26. package/templates/interface-boilerplate/postcss.config.mjs +7 -0
  27. package/templates/interface-boilerplate/tsconfig.json +18 -4
  28. package/templates/interface-boilerplate/.env.example +0 -2
  29. package/templates/interface-boilerplate/.eslintrc.json +0 -4
  30. package/templates/interface-boilerplate/app/login/page.tsx +0 -178
  31. package/templates/interface-boilerplate/app/signup/page.tsx +0 -199
  32. package/templates/interface-boilerplate/components/AnalyticsProvider.tsx +0 -142
  33. package/templates/interface-boilerplate/components/AuthGuard.tsx +0 -76
  34. package/templates/interface-boilerplate/components/ErrorBoundary.tsx +0 -106
  35. package/templates/interface-boilerplate/components/theme-provider.tsx +0 -9
  36. package/templates/interface-boilerplate/components/theme-toggle.tsx +0 -39
  37. package/templates/interface-boilerplate/components/ui/avatar.tsx +0 -46
  38. package/templates/interface-boilerplate/components/ui/checkbox.tsx +0 -27
  39. package/templates/interface-boilerplate/components/ui/dialog.tsx +0 -100
  40. package/templates/interface-boilerplate/components/ui/dropdown-menu.tsx +0 -173
  41. package/templates/interface-boilerplate/components/ui/index.ts +0 -53
  42. package/templates/interface-boilerplate/components/ui/label.tsx +0 -20
  43. package/templates/interface-boilerplate/components/ui/progress.tsx +0 -24
  44. package/templates/interface-boilerplate/components/ui/select.tsx +0 -149
  45. package/templates/interface-boilerplate/components/ui/separator.tsx +0 -25
  46. package/templates/interface-boilerplate/components/ui/skeleton.tsx +0 -12
  47. package/templates/interface-boilerplate/components/ui/switch.tsx +0 -28
  48. package/templates/interface-boilerplate/components/ui/tabs.tsx +0 -54
  49. package/templates/interface-boilerplate/components/ui/textarea.tsx +0 -22
  50. package/templates/interface-boilerplate/components/ui/tooltip.tsx +0 -29
  51. package/templates/interface-boilerplate/lib/analytics.ts +0 -182
  52. package/templates/interface-boilerplate/lib/auth-context.tsx +0 -83
  53. package/templates/interface-boilerplate/lib/auth.ts +0 -199
  54. package/templates/interface-boilerplate/lib/callFlow.ts +0 -234
  55. package/templates/interface-boilerplate/lib/flowTracer.ts +0 -195
  56. package/templates/interface-boilerplate/lib/hooks/.gitkeep +0 -0
  57. package/templates/interface-boilerplate/lib/stores/.gitkeep +0 -0
  58. package/templates/interface-boilerplate/next.config.js +0 -6
  59. package/templates/interface-boilerplate/postcss.config.js +0 -6
  60. package/templates/interface-boilerplate/tailwind.config.js +0 -103
@@ -6,46 +6,27 @@
6
6
  "dev": "next dev",
7
7
  "build": "next build",
8
8
  "start": "next start",
9
- "lint": "next lint"
9
+ "lint": "eslint"
10
10
  },
11
11
  "dependencies": {
12
- "next": "^14.2.0",
13
- "react": "^18.2.0",
14
- "react-dom": "^18.2.0",
15
- "zustand": "^4.5.0",
16
- "chokidar": "^3.6.0",
17
- "tailwindcss": "^3.4.0",
18
- "autoprefixer": "^10.4.0",
19
- "postcss": "^8.4.0",
20
- "@radix-ui/react-slot": "^1.0.0",
21
- "@radix-ui/react-dialog": "^1.0.0",
22
- "@radix-ui/react-dropdown-menu": "^2.0.0",
23
- "@radix-ui/react-label": "^2.0.0",
24
- "@radix-ui/react-select": "^2.0.0",
25
- "@radix-ui/react-tabs": "^1.0.0",
26
- "@radix-ui/react-tooltip": "^1.0.0",
27
- "@radix-ui/react-checkbox": "^1.0.0",
28
- "@radix-ui/react-switch": "^1.0.0",
29
- "@radix-ui/react-avatar": "^1.0.0",
30
- "@radix-ui/react-progress": "^1.0.0",
31
- "@radix-ui/react-separator": "^1.0.0",
32
- "class-variance-authority": "^0.7.0",
33
- "clsx": "^2.1.0",
34
- "tailwind-merge": "^2.2.0",
35
- "tailwindcss-animate": "^1.0.7",
36
- "lucide-react": "^0.400.0",
37
- "framer-motion": "^11.0.0",
38
- "next-themes": "^0.3.0",
39
- "posthog-js": "^1.200.0",
40
- "@posthog/react": "^1.0.0"
12
+ "@radix-ui/react-slot": "^1.2.4",
13
+ "better-auth": "^1.4.5",
14
+ "class-variance-authority": "^0.7.1",
15
+ "clsx": "^2.1.1",
16
+ "date-fns": "^4.1.0",
17
+ "next": "16.0.8",
18
+ "react": "19.2.1",
19
+ "react-dom": "19.2.1",
20
+ "tailwind-merge": "^3.4.0"
41
21
  },
42
22
  "devDependencies": {
43
- "@types/node": "^20.0.0",
44
- "@types/react": "^18.2.0",
45
- "@types/react-dom": "^18.2.0",
46
- "eslint": "^8.0.0",
47
- "eslint-config-next": "^14.2.0",
48
- "eslint-plugin-jsx-a11y": "^6.8.0",
49
- "typescript": "^5.0.0"
23
+ "@tailwindcss/postcss": "^4",
24
+ "@types/node": "^20",
25
+ "@types/react": "^19",
26
+ "@types/react-dom": "^19",
27
+ "eslint": "^9",
28
+ "eslint-config-next": "16.0.8",
29
+ "tailwindcss": "^4",
30
+ "typescript": "^5"
50
31
  }
51
32
  }
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "compilerOptions": {
3
+ "target": "ES2017",
3
4
  "lib": ["dom", "dom.iterable", "esnext"],
4
5
  "allowJs": true,
5
6
  "skipLibCheck": true,
@@ -10,11 +11,24 @@
10
11
  "moduleResolution": "bundler",
11
12
  "resolveJsonModule": true,
12
13
  "isolatedModules": true,
13
- "jsx": "preserve",
14
+ "jsx": "react-jsx",
14
15
  "incremental": true,
15
- "plugins": [{ "name": "next" }],
16
- "paths": { "@/*": ["./*"] }
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
17
24
  },
18
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25
+ "include": [
26
+ "next-env.d.ts",
27
+ "**/*.ts",
28
+ "**/*.tsx",
29
+ ".next/types/**/*.ts",
30
+ ".next/dev/types/**/*.ts",
31
+ "**/*.mts"
32
+ ],
19
33
  "exclude": ["node_modules"]
20
34
  }
@@ -1,2 +0,0 @@
1
- # Lux API URL for Flow execution
2
- LUX_API_URL=https://app.uselux.com
@@ -1,4 +0,0 @@
1
- {
2
- "extends": ["next/core-web-vitals", "plugin:jsx-a11y/recommended"],
3
- "plugins": ["jsx-a11y"]
4
- }
@@ -1,178 +0,0 @@
1
- 'use client';
2
-
3
- import { useState } from 'react';
4
- import { useRouter } from 'next/navigation';
5
- import { motion } from 'framer-motion';
6
- import { Loader2, Mail, Lock, ArrowLeft } from 'lucide-react';
7
- import { Button } from '@/components/ui/button';
8
- import { Input } from '@/components/ui/input';
9
- import { Label } from '@/components/ui/label';
10
- import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
11
- import { ThemeToggle } from '@/components/theme-toggle';
12
- import { useAuth } from '@/lib/auth-context';
13
-
14
- export default function LoginPage() {
15
- const [email, setEmail] = useState('');
16
- const [password, setPassword] = useState('');
17
- const [error, setError] = useState('');
18
- const [loading, setLoading] = useState(false);
19
- const router = useRouter();
20
- const { signIn, signInWithGoogle } = useAuth();
21
-
22
- const handleSubmit = async (e: React.FormEvent) => {
23
- e.preventDefault();
24
- setError('');
25
- setLoading(true);
26
-
27
- try {
28
- const result = await signIn(email, password);
29
-
30
- if (result.success) {
31
- router.push('/dashboard');
32
- } else {
33
- setError(result.error || 'Invalid email or password');
34
- }
35
- } catch (err) {
36
- setError('An error occurred. Please try again.');
37
- } finally {
38
- setLoading(false);
39
- }
40
- };
41
-
42
- return (
43
- <div className="min-h-screen flex flex-col bg-background">
44
- {/* Header */}
45
- <header className="flex items-center justify-between p-4">
46
- <Button variant="ghost" size="sm" asChild>
47
- <a href="/">
48
- <ArrowLeft className="mr-2 h-4 w-4" />
49
- Back
50
- </a>
51
- </Button>
52
- <ThemeToggle />
53
- </header>
54
-
55
- {/* Login Form */}
56
- <main className="flex-1 flex items-center justify-center p-4">
57
- <motion.div
58
- initial={{ opacity: 0, y: 20 }}
59
- animate={{ opacity: 1, y: 0 }}
60
- transition={{ duration: 0.4 }}
61
- className="w-full max-w-md"
62
- >
63
- <Card>
64
- <CardHeader className="text-center">
65
- <div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-primary/10">
66
- <Lock className="h-6 w-6 text-primary" />
67
- </div>
68
- <CardTitle className="text-2xl">Welcome back</CardTitle>
69
- <CardDescription>
70
- Enter your credentials to access your account
71
- </CardDescription>
72
- </CardHeader>
73
- <form onSubmit={handleSubmit}>
74
- <CardContent className="space-y-4">
75
- {error && (
76
- <motion.div
77
- initial={{ opacity: 0, height: 0 }}
78
- animate={{ opacity: 1, height: 'auto' }}
79
- className="rounded-lg bg-destructive/10 p-3 text-sm text-destructive"
80
- >
81
- {error}
82
- </motion.div>
83
- )}
84
-
85
- <div className="space-y-2">
86
- <Label htmlFor="email">Email</Label>
87
- <div className="relative">
88
- <Mail className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
89
- <Input
90
- id="email"
91
- type="email"
92
- placeholder="name@example.com"
93
- value={email}
94
- onChange={(e) => setEmail(e.target.value)}
95
- className="pl-10"
96
- required
97
- />
98
- </div>
99
- </div>
100
-
101
- <div className="space-y-2">
102
- <Label htmlFor="password">Password</Label>
103
- <div className="relative">
104
- <Lock className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
105
- <Input
106
- id="password"
107
- type="password"
108
- placeholder="••••••••"
109
- value={password}
110
- onChange={(e) => setPassword(e.target.value)}
111
- className="pl-10"
112
- required
113
- />
114
- </div>
115
- </div>
116
- </CardContent>
117
- <CardFooter className="flex flex-col space-y-4">
118
- <Button type="submit" className="w-full" disabled={loading}>
119
- {loading ? (
120
- <>
121
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
122
- Signing in...
123
- </>
124
- ) : (
125
- 'Sign in'
126
- )}
127
- </Button>
128
-
129
- <div className="relative w-full">
130
- <div className="absolute inset-0 flex items-center">
131
- <span className="w-full border-t" />
132
- </div>
133
- <div className="relative flex justify-center text-xs uppercase">
134
- <span className="bg-background px-2 text-muted-foreground">Or continue with</span>
135
- </div>
136
- </div>
137
-
138
- <Button
139
- type="button"
140
- variant="outline"
141
- className="w-full"
142
- onClick={() => signInWithGoogle()}
143
- >
144
- <svg className="mr-2 h-4 w-4" viewBox="0 0 24 24">
145
- <path
146
- d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
147
- fill="#4285F4"
148
- />
149
- <path
150
- d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
151
- fill="#34A853"
152
- />
153
- <path
154
- d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
155
- fill="#FBBC05"
156
- />
157
- <path
158
- d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
159
- fill="#EA4335"
160
- />
161
- </svg>
162
- Continue with Google
163
- </Button>
164
-
165
- <p className="text-center text-sm text-muted-foreground">
166
- Don&apos;t have an account?{' '}
167
- <a href="/signup" className="text-primary hover:underline">
168
- Sign up
169
- </a>
170
- </p>
171
- </CardFooter>
172
- </form>
173
- </Card>
174
- </motion.div>
175
- </main>
176
- </div>
177
- );
178
- }
@@ -1,199 +0,0 @@
1
- 'use client';
2
-
3
- import { useState } from 'react';
4
- import { useRouter } from 'next/navigation';
5
- import { motion } from 'framer-motion';
6
- import { Loader2, Mail, Lock, User, ArrowLeft } from 'lucide-react';
7
- import { Button } from '@/components/ui/button';
8
- import { Input } from '@/components/ui/input';
9
- import { Label } from '@/components/ui/label';
10
- import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
11
- import { ThemeToggle } from '@/components/theme-toggle';
12
- import { useAuth } from '@/lib/auth-context';
13
-
14
- export default function SignUpPage() {
15
- const [name, setName] = useState('');
16
- const [email, setEmail] = useState('');
17
- const [password, setPassword] = useState('');
18
- const [error, setError] = useState('');
19
- const [loading, setLoading] = useState(false);
20
- const router = useRouter();
21
- const { signUp, signInWithGoogle } = useAuth();
22
-
23
- const handleSubmit = async (e: React.FormEvent) => {
24
- e.preventDefault();
25
- setError('');
26
-
27
- if (password.length < 8) {
28
- setError('Password must be at least 8 characters');
29
- return;
30
- }
31
-
32
- setLoading(true);
33
-
34
- try {
35
- const result = await signUp(email, password, name);
36
-
37
- if (result.success) {
38
- router.push('/dashboard');
39
- } else {
40
- setError(result.error || 'Sign up failed');
41
- }
42
- } catch (err) {
43
- setError('An error occurred. Please try again.');
44
- } finally {
45
- setLoading(false);
46
- }
47
- };
48
-
49
- return (
50
- <div className="min-h-screen flex flex-col bg-background">
51
- <header className="flex items-center justify-between p-4">
52
- <Button variant="ghost" size="sm" asChild>
53
- <a href="/">
54
- <ArrowLeft className="mr-2 h-4 w-4" />
55
- Back
56
- </a>
57
- </Button>
58
- <ThemeToggle />
59
- </header>
60
-
61
- <main className="flex-1 flex items-center justify-center p-4">
62
- <motion.div
63
- initial={{ opacity: 0, y: 20 }}
64
- animate={{ opacity: 1, y: 0 }}
65
- transition={{ duration: 0.4 }}
66
- className="w-full max-w-md"
67
- >
68
- <Card>
69
- <CardHeader className="text-center">
70
- <div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-primary/10">
71
- <User className="h-6 w-6 text-primary" />
72
- </div>
73
- <CardTitle className="text-2xl">Create an account</CardTitle>
74
- <CardDescription>
75
- Enter your information to get started
76
- </CardDescription>
77
- </CardHeader>
78
- <form onSubmit={handleSubmit}>
79
- <CardContent className="space-y-4">
80
- {error && (
81
- <motion.div
82
- initial={{ opacity: 0, height: 0 }}
83
- animate={{ opacity: 1, height: 'auto' }}
84
- className="rounded-lg bg-destructive/10 p-3 text-sm text-destructive"
85
- >
86
- {error}
87
- </motion.div>
88
- )}
89
-
90
- <div className="space-y-2">
91
- <Label htmlFor="name">Name</Label>
92
- <div className="relative">
93
- <User className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
94
- <Input
95
- id="name"
96
- type="text"
97
- placeholder="John Doe"
98
- value={name}
99
- onChange={(e) => setName(e.target.value)}
100
- className="pl-10"
101
- />
102
- </div>
103
- </div>
104
-
105
- <div className="space-y-2">
106
- <Label htmlFor="email">Email</Label>
107
- <div className="relative">
108
- <Mail className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
109
- <Input
110
- id="email"
111
- type="email"
112
- placeholder="name@example.com"
113
- value={email}
114
- onChange={(e) => setEmail(e.target.value)}
115
- className="pl-10"
116
- required
117
- />
118
- </div>
119
- </div>
120
-
121
- <div className="space-y-2">
122
- <Label htmlFor="password">Password</Label>
123
- <div className="relative">
124
- <Lock className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
125
- <Input
126
- id="password"
127
- type="password"
128
- placeholder="At least 8 characters"
129
- value={password}
130
- onChange={(e) => setPassword(e.target.value)}
131
- className="pl-10"
132
- required
133
- minLength={8}
134
- />
135
- </div>
136
- </div>
137
- </CardContent>
138
- <CardFooter className="flex flex-col space-y-4">
139
- <Button type="submit" className="w-full" disabled={loading}>
140
- {loading ? (
141
- <>
142
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
143
- Creating account...
144
- </>
145
- ) : (
146
- 'Sign up'
147
- )}
148
- </Button>
149
-
150
- <div className="relative w-full">
151
- <div className="absolute inset-0 flex items-center">
152
- <span className="w-full border-t" />
153
- </div>
154
- <div className="relative flex justify-center text-xs uppercase">
155
- <span className="bg-background px-2 text-muted-foreground">Or continue with</span>
156
- </div>
157
- </div>
158
-
159
- <Button
160
- type="button"
161
- variant="outline"
162
- className="w-full"
163
- onClick={() => signInWithGoogle()}
164
- >
165
- <svg className="mr-2 h-4 w-4" viewBox="0 0 24 24">
166
- <path
167
- d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
168
- fill="#4285F4"
169
- />
170
- <path
171
- d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
172
- fill="#34A853"
173
- />
174
- <path
175
- d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
176
- fill="#FBBC05"
177
- />
178
- <path
179
- d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
180
- fill="#EA4335"
181
- />
182
- </svg>
183
- Continue with Google
184
- </Button>
185
-
186
- <p className="text-center text-sm text-muted-foreground">
187
- Already have an account?{' '}
188
- <a href="/login" className="text-primary hover:underline">
189
- Sign in
190
- </a>
191
- </p>
192
- </CardFooter>
193
- </form>
194
- </Card>
195
- </motion.div>
196
- </main>
197
- </div>
198
- );
199
- }
@@ -1,142 +0,0 @@
1
- 'use client';
2
-
3
- import { useEffect, useState } from 'react';
4
- import posthog from 'posthog-js';
5
- import { PostHogProvider as PHProvider } from '@posthog/react';
6
-
7
- const POSTHOG_KEY = process.env.NEXT_PUBLIC_POSTHOG_KEY || '';
8
- const POSTHOG_HOST = process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com';
9
- const INTERFACE_ID = process.env.NEXT_PUBLIC_LUX_INTERFACE_ID || '';
10
- const IS_LOCAL_DEV = process.env.NEXT_PUBLIC_LUX_LOCAL_DEV === 'true';
11
-
12
- /**
13
- * Parse feature flag overrides from URL query params
14
- * Used for A/B test variant preview in Lux Studio
15
- *
16
- * @example
17
- * ?__lux_flag_overrides={"pricing-test":"variant-a"}
18
- */
19
- function parseFlagOverrides(): Record<string, string | boolean> | undefined {
20
- if (typeof window === 'undefined') return undefined;
21
-
22
- const params = new URLSearchParams(window.location.search);
23
- const overrides = params.get('__lux_flag_overrides');
24
-
25
- if (!overrides) return undefined;
26
-
27
- try {
28
- const parsed = JSON.parse(overrides);
29
- console.log('[analytics] Using flag overrides:', parsed);
30
- return parsed;
31
- } catch (e) {
32
- console.warn('[analytics] Failed to parse flag overrides:', e);
33
- return undefined;
34
- }
35
- }
36
-
37
- /**
38
- * Analytics Provider Component
39
- *
40
- * Initializes PostHog analytics and provides React context for feature flags and A/B testing.
41
- * Place this in your root layout to enable analytics tracking and experiments.
42
- *
43
- * Usage in components:
44
- * ```tsx
45
- * import { useFeatureFlagVariantKey } from 'posthog-js/react';
46
- *
47
- * function MyComponent() {
48
- * const variant = useFeatureFlagVariantKey('my-experiment');
49
- * // variant is 'control' | 'variant-a' | 'variant-b' | undefined
50
- * }
51
- * ```
52
- *
53
- * A/B Test Preview:
54
- * In Lux Studio, you can preview different experiment variants by passing
55
- * ?__lux_flag_overrides={"experiment-key":"variant-name"} in the URL.
56
- * The provider will bootstrap PostHog with these overrides.
57
- */
58
- export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
59
- const [isReady, setIsReady] = useState(false);
60
-
61
- useEffect(() => {
62
- // Check for flag overrides from Lux Studio preview
63
- const flagOverrides = parseFlagOverrides();
64
-
65
- // In local dev mode with flag overrides, we still want to provide mock feature flags
66
- // This allows previewing A/B test variants without a real PostHog connection
67
- if (IS_LOCAL_DEV) {
68
- if (flagOverrides) {
69
- console.log('[analytics] Local dev mode with flag overrides - mocking PostHog');
70
- // Set up a minimal mock for feature flags in local dev
71
- if (!posthog.__loaded) {
72
- // Initialize PostHog with bootstrap for local preview
73
- // Using a dummy key since we're just using it for local preview
74
- posthog.init('local-preview', {
75
- api_host: POSTHOG_HOST,
76
- autocapture: false,
77
- capture_pageview: false,
78
- persistence: 'memory',
79
- bootstrap: {
80
- featureFlags: flagOverrides,
81
- },
82
- loaded: () => {
83
- console.log('[analytics] PostHog initialized (local preview mode)');
84
- },
85
- });
86
- }
87
- } else {
88
- console.log('[analytics] Skipped - local development mode');
89
- }
90
- setIsReady(true);
91
- return;
92
- }
93
-
94
- if (!POSTHOG_KEY) {
95
- console.warn('[analytics] PostHog not configured - missing POSTHOG_KEY');
96
- setIsReady(true);
97
- return;
98
- }
99
-
100
- // Check if already initialized
101
- if (posthog.__loaded) {
102
- setIsReady(true);
103
- return;
104
- }
105
-
106
- // Build bootstrap config if we have overrides
107
- const bootstrap = flagOverrides ? { featureFlags: flagOverrides } : undefined;
108
-
109
- posthog.init(POSTHOG_KEY, {
110
- api_host: POSTHOG_HOST,
111
- autocapture: true,
112
- capture_pageview: true,
113
- capture_pageleave: true,
114
- persistence: 'localStorage',
115
- bootstrap, // <-- This forces specific flag values for preview
116
- loaded: (ph) => {
117
- // Register super properties that will be sent with every event
118
- if (INTERFACE_ID) {
119
- ph.register({
120
- $lux_interface_id: INTERFACE_ID,
121
- });
122
- }
123
- console.log('[analytics] PostHog initialized');
124
- },
125
- });
126
-
127
- setIsReady(true);
128
- }, []);
129
-
130
- // Wait for initialization before rendering
131
- if (!isReady) {
132
- return <>{children}</>;
133
- }
134
-
135
- // In local dev without overrides or without PostHog key, just render children without provider
136
- const flagOverrides = typeof window !== 'undefined' ? parseFlagOverrides() : undefined;
137
- if ((IS_LOCAL_DEV && !flagOverrides) || !POSTHOG_KEY) {
138
- return <>{children}</>;
139
- }
140
-
141
- return <PHProvider client={posthog}>{children}</PHProvider>;
142
- }