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.
- package/package.json +1 -1
- package/templates/interface-boilerplate/app/api/auth/[...all]/route.ts +52 -0
- package/templates/interface-boilerplate/app/auth/callback/page.tsx +22 -31
- package/templates/interface-boilerplate/app/dashboard/page.tsx +41 -214
- package/templates/interface-boilerplate/app/favicon.ico +0 -0
- package/templates/interface-boilerplate/app/globals.css +18 -113
- package/templates/interface-boilerplate/app/layout.tsx +21 -33
- package/templates/interface-boilerplate/app/page.tsx +37 -116
- package/templates/interface-boilerplate/app/settings/page.tsx +71 -0
- package/templates/interface-boilerplate/app/sign-in/page.tsx +19 -0
- package/templates/interface-boilerplate/app/sign-up/page.tsx +19 -0
- package/templates/interface-boilerplate/components/auth/sign-in-form.tsx +137 -0
- package/templates/interface-boilerplate/components/auth/sign-up-form.tsx +225 -0
- package/templates/interface-boilerplate/components/ui/badge.tsx +18 -14
- package/templates/interface-boilerplate/components/ui/button.tsx +29 -24
- package/templates/interface-boilerplate/components/ui/card.tsx +76 -57
- package/templates/interface-boilerplate/components/ui/input.tsx +8 -9
- package/templates/interface-boilerplate/eslint.config.mjs +18 -0
- package/templates/interface-boilerplate/lib/auth-client.ts +18 -0
- package/templates/interface-boilerplate/lib/auth.config.ts +111 -0
- package/templates/interface-boilerplate/lib/utils.ts +3 -3
- package/templates/interface-boilerplate/middleware.ts +60 -0
- package/templates/interface-boilerplate/next.config.ts +7 -0
- package/templates/interface-boilerplate/package-lock.json +6855 -0
- package/templates/interface-boilerplate/package.json +18 -37
- package/templates/interface-boilerplate/postcss.config.mjs +7 -0
- package/templates/interface-boilerplate/tsconfig.json +18 -4
- package/templates/interface-boilerplate/.env.example +0 -2
- package/templates/interface-boilerplate/.eslintrc.json +0 -4
- package/templates/interface-boilerplate/app/login/page.tsx +0 -178
- package/templates/interface-boilerplate/app/signup/page.tsx +0 -199
- package/templates/interface-boilerplate/components/AnalyticsProvider.tsx +0 -142
- package/templates/interface-boilerplate/components/AuthGuard.tsx +0 -76
- package/templates/interface-boilerplate/components/ErrorBoundary.tsx +0 -106
- package/templates/interface-boilerplate/components/theme-provider.tsx +0 -9
- package/templates/interface-boilerplate/components/theme-toggle.tsx +0 -39
- package/templates/interface-boilerplate/components/ui/avatar.tsx +0 -46
- package/templates/interface-boilerplate/components/ui/checkbox.tsx +0 -27
- package/templates/interface-boilerplate/components/ui/dialog.tsx +0 -100
- package/templates/interface-boilerplate/components/ui/dropdown-menu.tsx +0 -173
- package/templates/interface-boilerplate/components/ui/index.ts +0 -53
- package/templates/interface-boilerplate/components/ui/label.tsx +0 -20
- package/templates/interface-boilerplate/components/ui/progress.tsx +0 -24
- package/templates/interface-boilerplate/components/ui/select.tsx +0 -149
- package/templates/interface-boilerplate/components/ui/separator.tsx +0 -25
- package/templates/interface-boilerplate/components/ui/skeleton.tsx +0 -12
- package/templates/interface-boilerplate/components/ui/switch.tsx +0 -28
- package/templates/interface-boilerplate/components/ui/tabs.tsx +0 -54
- package/templates/interface-boilerplate/components/ui/textarea.tsx +0 -22
- package/templates/interface-boilerplate/components/ui/tooltip.tsx +0 -29
- package/templates/interface-boilerplate/lib/analytics.ts +0 -182
- package/templates/interface-boilerplate/lib/auth-context.tsx +0 -83
- package/templates/interface-boilerplate/lib/auth.ts +0 -199
- package/templates/interface-boilerplate/lib/callFlow.ts +0 -234
- package/templates/interface-boilerplate/lib/flowTracer.ts +0 -195
- package/templates/interface-boilerplate/lib/hooks/.gitkeep +0 -0
- package/templates/interface-boilerplate/lib/stores/.gitkeep +0 -0
- package/templates/interface-boilerplate/next.config.js +0 -6
- package/templates/interface-boilerplate/postcss.config.js +0 -6
- 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": "
|
|
9
|
+
"lint": "eslint"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
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
|
-
"@
|
|
44
|
-
"@types/
|
|
45
|
-
"@types/react
|
|
46
|
-
"
|
|
47
|
-
"eslint
|
|
48
|
-
"eslint-
|
|
49
|
-
"
|
|
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
|
}
|
|
@@ -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": "
|
|
14
|
+
"jsx": "react-jsx",
|
|
14
15
|
"incremental": true,
|
|
15
|
-
"plugins": [
|
|
16
|
-
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./*"]
|
|
23
|
+
}
|
|
17
24
|
},
|
|
18
|
-
"include": [
|
|
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,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'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
|
-
}
|