@dailyautomations/ui 1.0.1 → 1.1.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.
- package/PAGE_STRUCTURES.md +718 -0
- package/README.md +86 -55
- package/examples/README.md +150 -0
- package/examples/home-page.tsx +321 -0
- package/examples/index.html +12 -0
- package/examples/main.tsx +90 -0
- package/examples/pages/AboutPage.tsx +343 -0
- package/examples/pages/BlogPage.tsx +294 -0
- package/examples/pages/ContactPage.tsx +328 -0
- package/examples/pages/DashboardPage.tsx +355 -0
- package/examples/pages/ListPage.tsx +310 -0
- package/examples/pages/LoginPage.tsx +166 -0
- package/examples/pages/OnboardingPage.tsx +385 -0
- package/examples/pages/PricingPage.tsx +402 -0
- package/examples/pages/SettingsPage.tsx +417 -0
- package/examples/pages/SignupPage.tsx +194 -0
- package/examples/pages/index.ts +13 -0
- package/examples/styles.css +12 -0
- package/package.json +3 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Button } from "../../src/components/common/button"
|
|
5
|
+
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "../../src/components/common/card"
|
|
6
|
+
import { Input } from "../../src/components/common/input"
|
|
7
|
+
import { Label } from "../../src/components/common/label"
|
|
8
|
+
import { Checkbox } from "../../src/components/common/checkbox"
|
|
9
|
+
|
|
10
|
+
// Logo Component
|
|
11
|
+
function Logo({ className }: { className?: string }) {
|
|
12
|
+
return (
|
|
13
|
+
<span className={className}>
|
|
14
|
+
<span className="text-purple-500">daily</span>
|
|
15
|
+
<span className="text-orange-500">X</span>
|
|
16
|
+
</span>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Social Login Button
|
|
21
|
+
interface SocialButtonProps {
|
|
22
|
+
icon: React.ReactNode
|
|
23
|
+
children: React.ReactNode
|
|
24
|
+
onClick?: () => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function SocialButton({ icon, children, onClick }: SocialButtonProps) {
|
|
28
|
+
return (
|
|
29
|
+
<button
|
|
30
|
+
onClick={onClick}
|
|
31
|
+
className="flex-1 h-10 px-4 rounded-md border border-zinc-800 bg-transparent text-sm font-medium hover:bg-zinc-800 transition-colors flex items-center justify-center gap-2"
|
|
32
|
+
>
|
|
33
|
+
{icon}
|
|
34
|
+
{children}
|
|
35
|
+
</button>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Icons
|
|
40
|
+
const Icons = {
|
|
41
|
+
google: (props: React.SVGProps<SVGSVGElement>) => (
|
|
42
|
+
<svg viewBox="0 0 24 24" {...props}>
|
|
43
|
+
<path fill="#4285F4" 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"/>
|
|
44
|
+
<path fill="#34A853" 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"/>
|
|
45
|
+
<path fill="#FBBC05" 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"/>
|
|
46
|
+
<path fill="#EA4335" 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"/>
|
|
47
|
+
</svg>
|
|
48
|
+
),
|
|
49
|
+
github: (props: React.SVGProps<SVGSVGElement>) => (
|
|
50
|
+
<svg fill="currentColor" viewBox="0 0 24 24" {...props}>
|
|
51
|
+
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
|
52
|
+
</svg>
|
|
53
|
+
),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Main Login Page Component
|
|
57
|
+
export function LoginPage() {
|
|
58
|
+
const [email, setEmail] = React.useState("")
|
|
59
|
+
const [password, setPassword] = React.useState("")
|
|
60
|
+
const [rememberMe, setRememberMe] = React.useState(false)
|
|
61
|
+
const [isLoading, setIsLoading] = React.useState(false)
|
|
62
|
+
|
|
63
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
64
|
+
e.preventDefault()
|
|
65
|
+
setIsLoading(true)
|
|
66
|
+
// Simulate API call
|
|
67
|
+
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
68
|
+
setIsLoading(false)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className="min-h-screen bg-[#0F0F14] text-white flex items-center justify-center p-4">
|
|
73
|
+
<div className="w-full max-w-[400px]">
|
|
74
|
+
{/* Logo */}
|
|
75
|
+
<div className="text-center mb-8">
|
|
76
|
+
<Logo className="text-3xl font-bold" />
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{/* Login Card */}
|
|
80
|
+
<Card>
|
|
81
|
+
<CardHeader className="text-center">
|
|
82
|
+
<CardTitle className="text-xl">Welcome back</CardTitle>
|
|
83
|
+
<CardDescription>Sign in to your account to continue</CardDescription>
|
|
84
|
+
</CardHeader>
|
|
85
|
+
<CardContent>
|
|
86
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
87
|
+
<div className="space-y-2">
|
|
88
|
+
<Label htmlFor="email">Email</Label>
|
|
89
|
+
<Input
|
|
90
|
+
id="email"
|
|
91
|
+
type="email"
|
|
92
|
+
placeholder="you@example.com"
|
|
93
|
+
value={email}
|
|
94
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
95
|
+
required
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div className="space-y-2">
|
|
100
|
+
<Label htmlFor="password">Password</Label>
|
|
101
|
+
<Input
|
|
102
|
+
id="password"
|
|
103
|
+
type="password"
|
|
104
|
+
placeholder="Enter your password"
|
|
105
|
+
value={password}
|
|
106
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
107
|
+
required
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div className="flex items-center justify-between">
|
|
112
|
+
<div className="flex items-center gap-2">
|
|
113
|
+
<Checkbox
|
|
114
|
+
id="remember"
|
|
115
|
+
checked={rememberMe}
|
|
116
|
+
onCheckedChange={(checked) => setRememberMe(checked as boolean)}
|
|
117
|
+
/>
|
|
118
|
+
<Label htmlFor="remember" className="text-sm font-normal cursor-pointer">
|
|
119
|
+
Remember me
|
|
120
|
+
</Label>
|
|
121
|
+
</div>
|
|
122
|
+
<a href="#" className="text-sm text-purple-400 hover:text-purple-300 transition-colors">
|
|
123
|
+
Forgot password?
|
|
124
|
+
</a>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<Button type="submit" className="w-full" loading={isLoading}>
|
|
128
|
+
Sign in
|
|
129
|
+
</Button>
|
|
130
|
+
</form>
|
|
131
|
+
|
|
132
|
+
{/* Divider */}
|
|
133
|
+
<div className="relative my-6">
|
|
134
|
+
<div className="absolute inset-0 flex items-center">
|
|
135
|
+
<div className="w-full border-t border-zinc-800" />
|
|
136
|
+
</div>
|
|
137
|
+
<div className="relative flex justify-center text-xs">
|
|
138
|
+
<span className="bg-zinc-900 px-2 text-zinc-500">or continue with</span>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
{/* Social Login */}
|
|
143
|
+
<div className="flex gap-3">
|
|
144
|
+
<SocialButton icon={<Icons.google className="w-5 h-5" />}>
|
|
145
|
+
Google
|
|
146
|
+
</SocialButton>
|
|
147
|
+
<SocialButton icon={<Icons.github className="w-5 h-5" />}>
|
|
148
|
+
GitHub
|
|
149
|
+
</SocialButton>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
{/* Sign Up Link */}
|
|
153
|
+
<p className="text-center text-sm text-zinc-400 mt-6">
|
|
154
|
+
Don't have an account?{" "}
|
|
155
|
+
<a href="#" className="text-purple-400 hover:text-purple-300 transition-colors font-medium">
|
|
156
|
+
Sign up
|
|
157
|
+
</a>
|
|
158
|
+
</p>
|
|
159
|
+
</CardContent>
|
|
160
|
+
</Card>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export default LoginPage
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Button } from "../../src/components/common/button"
|
|
5
|
+
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "../../src/components/common/card"
|
|
6
|
+
import { Input } from "../../src/components/common/input"
|
|
7
|
+
import { Label } from "../../src/components/common/label"
|
|
8
|
+
import { cn } from "../../src/utils/cn"
|
|
9
|
+
|
|
10
|
+
// Logo Component
|
|
11
|
+
function Logo({ className }: { className?: string }) {
|
|
12
|
+
return (
|
|
13
|
+
<span className={className}>
|
|
14
|
+
<span className="text-purple-500">daily</span>
|
|
15
|
+
<span className="text-orange-500">X</span>
|
|
16
|
+
</span>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Progress Steps Component
|
|
21
|
+
interface StepIndicatorProps {
|
|
22
|
+
currentStep: number
|
|
23
|
+
totalSteps: number
|
|
24
|
+
labels: string[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function StepIndicator({ currentStep, totalSteps, labels }: StepIndicatorProps) {
|
|
28
|
+
return (
|
|
29
|
+
<div className="flex items-center justify-center gap-2">
|
|
30
|
+
{Array.from({ length: totalSteps }).map((_, index) => (
|
|
31
|
+
<React.Fragment key={index}>
|
|
32
|
+
<div className="flex flex-col items-center gap-1">
|
|
33
|
+
<div
|
|
34
|
+
className={cn(
|
|
35
|
+
"w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium transition-colors",
|
|
36
|
+
index < currentStep
|
|
37
|
+
? "bg-purple-500 text-white"
|
|
38
|
+
: index === currentStep
|
|
39
|
+
? "bg-purple-500 text-white ring-4 ring-purple-500/20"
|
|
40
|
+
: "bg-zinc-800 text-zinc-500"
|
|
41
|
+
)}
|
|
42
|
+
>
|
|
43
|
+
{index < currentStep ? (
|
|
44
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
45
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7"/>
|
|
46
|
+
</svg>
|
|
47
|
+
) : (
|
|
48
|
+
index + 1
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
<span className={cn(
|
|
52
|
+
"text-xs",
|
|
53
|
+
index <= currentStep ? "text-zinc-300" : "text-zinc-500"
|
|
54
|
+
)}>
|
|
55
|
+
{labels[index]}
|
|
56
|
+
</span>
|
|
57
|
+
</div>
|
|
58
|
+
{index < totalSteps - 1 && (
|
|
59
|
+
<div
|
|
60
|
+
className={cn(
|
|
61
|
+
"w-16 h-0.5 mb-5",
|
|
62
|
+
index < currentStep ? "bg-purple-500" : "bg-zinc-800"
|
|
63
|
+
)}
|
|
64
|
+
/>
|
|
65
|
+
)}
|
|
66
|
+
</React.Fragment>
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Option Card Component
|
|
73
|
+
interface OptionCardProps {
|
|
74
|
+
icon: React.ReactNode
|
|
75
|
+
title: string
|
|
76
|
+
description: string
|
|
77
|
+
selected?: boolean
|
|
78
|
+
onClick?: () => void
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function OptionCard({ icon, title, description, selected, onClick }: OptionCardProps) {
|
|
82
|
+
return (
|
|
83
|
+
<button
|
|
84
|
+
onClick={onClick}
|
|
85
|
+
className={cn(
|
|
86
|
+
"w-full p-4 rounded-lg border text-left transition-all",
|
|
87
|
+
selected
|
|
88
|
+
? "border-purple-500 bg-purple-500/10"
|
|
89
|
+
: "border-zinc-800 hover:border-zinc-700 hover:bg-zinc-800/50"
|
|
90
|
+
)}
|
|
91
|
+
>
|
|
92
|
+
<div className="flex items-start gap-3">
|
|
93
|
+
<div className={cn(
|
|
94
|
+
"w-10 h-10 rounded-lg flex items-center justify-center shrink-0",
|
|
95
|
+
selected ? "bg-purple-500/20 text-purple-400" : "bg-zinc-800 text-zinc-400"
|
|
96
|
+
)}>
|
|
97
|
+
{icon}
|
|
98
|
+
</div>
|
|
99
|
+
<div>
|
|
100
|
+
<p className="font-medium">{title}</p>
|
|
101
|
+
<p className="text-sm text-zinc-500 mt-0.5">{description}</p>
|
|
102
|
+
</div>
|
|
103
|
+
<div className={cn(
|
|
104
|
+
"w-5 h-5 rounded-full border-2 ml-auto shrink-0 flex items-center justify-center",
|
|
105
|
+
selected ? "border-purple-500 bg-purple-500" : "border-zinc-700"
|
|
106
|
+
)}>
|
|
107
|
+
{selected && (
|
|
108
|
+
<svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
109
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7"/>
|
|
110
|
+
</svg>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</button>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Team Member Input
|
|
119
|
+
interface TeamMemberInputProps {
|
|
120
|
+
value: string
|
|
121
|
+
onChange: (value: string) => void
|
|
122
|
+
onRemove: () => void
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function TeamMemberInput({ value, onChange, onRemove }: TeamMemberInputProps) {
|
|
126
|
+
return (
|
|
127
|
+
<div className="flex gap-2">
|
|
128
|
+
<Input
|
|
129
|
+
type="email"
|
|
130
|
+
placeholder="colleague@company.com"
|
|
131
|
+
value={value}
|
|
132
|
+
onChange={(e) => onChange(e.target.value)}
|
|
133
|
+
/>
|
|
134
|
+
<Button variant="ghost" size="icon" onClick={onRemove}>
|
|
135
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
136
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12"/>
|
|
137
|
+
</svg>
|
|
138
|
+
</Button>
|
|
139
|
+
</div>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Icons
|
|
144
|
+
const Icons = {
|
|
145
|
+
user: (props: React.SVGProps<SVGSVGElement>) => (
|
|
146
|
+
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" {...props}>
|
|
147
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
|
148
|
+
</svg>
|
|
149
|
+
),
|
|
150
|
+
users: (props: React.SVGProps<SVGSVGElement>) => (
|
|
151
|
+
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" {...props}>
|
|
152
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
|
|
153
|
+
</svg>
|
|
154
|
+
),
|
|
155
|
+
briefcase: (props: React.SVGProps<SVGSVGElement>) => (
|
|
156
|
+
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" {...props}>
|
|
157
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
|
|
158
|
+
</svg>
|
|
159
|
+
),
|
|
160
|
+
rocket: (props: React.SVGProps<SVGSVGElement>) => (
|
|
161
|
+
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" {...props}>
|
|
162
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/>
|
|
163
|
+
</svg>
|
|
164
|
+
),
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
type UsePurpose = "personal" | "team" | "client"
|
|
168
|
+
|
|
169
|
+
// Main Onboarding Page Component
|
|
170
|
+
export function OnboardingPage() {
|
|
171
|
+
const [currentStep, setCurrentStep] = React.useState(0)
|
|
172
|
+
const [isLoading, setIsLoading] = React.useState(false)
|
|
173
|
+
|
|
174
|
+
// Step 1: Workspace
|
|
175
|
+
const [workspaceName, setWorkspaceName] = React.useState("")
|
|
176
|
+
const [usePurpose, setUsePurpose] = React.useState<UsePurpose | null>(null)
|
|
177
|
+
|
|
178
|
+
// Step 2: Team
|
|
179
|
+
const [teamMembers, setTeamMembers] = React.useState([""])
|
|
180
|
+
|
|
181
|
+
const stepLabels = ["Workspace", "Team", "Ready"]
|
|
182
|
+
|
|
183
|
+
const handleNext = async () => {
|
|
184
|
+
if (currentStep === 2) {
|
|
185
|
+
setIsLoading(true)
|
|
186
|
+
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
187
|
+
setIsLoading(false)
|
|
188
|
+
// Redirect to dashboard
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
setCurrentStep((prev) => Math.min(prev + 1, 2))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const handleBack = () => {
|
|
195
|
+
setCurrentStep((prev) => Math.max(prev - 1, 0))
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const handleSkip = () => {
|
|
199
|
+
setCurrentStep((prev) => Math.min(prev + 1, 2))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const addTeamMember = () => {
|
|
203
|
+
setTeamMembers([...teamMembers, ""])
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const updateTeamMember = (index: number, value: string) => {
|
|
207
|
+
const updated = [...teamMembers]
|
|
208
|
+
updated[index] = value
|
|
209
|
+
setTeamMembers(updated)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const removeTeamMember = (index: number) => {
|
|
213
|
+
if (teamMembers.length > 1) {
|
|
214
|
+
setTeamMembers(teamMembers.filter((_, i) => i !== index))
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const canProceed = () => {
|
|
219
|
+
if (currentStep === 0) {
|
|
220
|
+
return workspaceName.trim().length > 0 && usePurpose !== null
|
|
221
|
+
}
|
|
222
|
+
return true
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<div className="min-h-screen bg-[#0F0F14] text-white flex items-center justify-center p-4">
|
|
227
|
+
<div className="w-full max-w-[520px]">
|
|
228
|
+
{/* Logo */}
|
|
229
|
+
<div className="text-center mb-8">
|
|
230
|
+
<Logo className="text-3xl font-bold" />
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
{/* Progress */}
|
|
234
|
+
<div className="mb-8">
|
|
235
|
+
<StepIndicator
|
|
236
|
+
currentStep={currentStep}
|
|
237
|
+
totalSteps={3}
|
|
238
|
+
labels={stepLabels}
|
|
239
|
+
/>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
{/* Step Content */}
|
|
243
|
+
<Card>
|
|
244
|
+
<CardHeader className="text-center">
|
|
245
|
+
{currentStep === 0 && (
|
|
246
|
+
<>
|
|
247
|
+
<CardTitle className="text-xl">Let's set up your workspace</CardTitle>
|
|
248
|
+
<CardDescription>
|
|
249
|
+
Create a workspace to organize your projects and collaborate with your team.
|
|
250
|
+
</CardDescription>
|
|
251
|
+
</>
|
|
252
|
+
)}
|
|
253
|
+
{currentStep === 1 && (
|
|
254
|
+
<>
|
|
255
|
+
<CardTitle className="text-xl">Invite your team</CardTitle>
|
|
256
|
+
<CardDescription>
|
|
257
|
+
Collaboration is better together. Invite teammates to join your workspace.
|
|
258
|
+
</CardDescription>
|
|
259
|
+
</>
|
|
260
|
+
)}
|
|
261
|
+
{currentStep === 2 && (
|
|
262
|
+
<>
|
|
263
|
+
<CardTitle className="text-xl">You're all set!</CardTitle>
|
|
264
|
+
<CardDescription>
|
|
265
|
+
Your workspace is ready. Start exploring Daily X.
|
|
266
|
+
</CardDescription>
|
|
267
|
+
</>
|
|
268
|
+
)}
|
|
269
|
+
</CardHeader>
|
|
270
|
+
<CardContent>
|
|
271
|
+
{/* Step 0: Workspace Setup */}
|
|
272
|
+
{currentStep === 0 && (
|
|
273
|
+
<div className="space-y-6">
|
|
274
|
+
<div className="space-y-2">
|
|
275
|
+
<Label htmlFor="workspace">Workspace name</Label>
|
|
276
|
+
<Input
|
|
277
|
+
id="workspace"
|
|
278
|
+
placeholder="My Company"
|
|
279
|
+
value={workspaceName}
|
|
280
|
+
onChange={(e) => setWorkspaceName(e.target.value)}
|
|
281
|
+
/>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
<div className="space-y-3">
|
|
285
|
+
<Label>What will you use Daily for?</Label>
|
|
286
|
+
<OptionCard
|
|
287
|
+
icon={<Icons.user className="w-5 h-5" />}
|
|
288
|
+
title="Personal projects"
|
|
289
|
+
description="Organize my own documents and workflows"
|
|
290
|
+
selected={usePurpose === "personal"}
|
|
291
|
+
onClick={() => setUsePurpose("personal")}
|
|
292
|
+
/>
|
|
293
|
+
<OptionCard
|
|
294
|
+
icon={<Icons.users className="w-5 h-5" />}
|
|
295
|
+
title="Team collaboration"
|
|
296
|
+
description="Work with my team on shared projects"
|
|
297
|
+
selected={usePurpose === "team"}
|
|
298
|
+
onClick={() => setUsePurpose("team")}
|
|
299
|
+
/>
|
|
300
|
+
<OptionCard
|
|
301
|
+
icon={<Icons.briefcase className="w-5 h-5" />}
|
|
302
|
+
title="Client work"
|
|
303
|
+
description="Manage projects for external clients"
|
|
304
|
+
selected={usePurpose === "client"}
|
|
305
|
+
onClick={() => setUsePurpose("client")}
|
|
306
|
+
/>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
)}
|
|
310
|
+
|
|
311
|
+
{/* Step 1: Team Invites */}
|
|
312
|
+
{currentStep === 1 && (
|
|
313
|
+
<div className="space-y-4">
|
|
314
|
+
<div className="space-y-3">
|
|
315
|
+
{teamMembers.map((member, index) => (
|
|
316
|
+
<TeamMemberInput
|
|
317
|
+
key={index}
|
|
318
|
+
value={member}
|
|
319
|
+
onChange={(value) => updateTeamMember(index, value)}
|
|
320
|
+
onRemove={() => removeTeamMember(index)}
|
|
321
|
+
/>
|
|
322
|
+
))}
|
|
323
|
+
</div>
|
|
324
|
+
<Button variant="outline" size="sm" onClick={addTeamMember}>
|
|
325
|
+
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
326
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4"/>
|
|
327
|
+
</svg>
|
|
328
|
+
Add another
|
|
329
|
+
</Button>
|
|
330
|
+
</div>
|
|
331
|
+
)}
|
|
332
|
+
|
|
333
|
+
{/* Step 2: Complete */}
|
|
334
|
+
{currentStep === 2 && (
|
|
335
|
+
<div className="text-center py-6">
|
|
336
|
+
<div className="w-20 h-20 rounded-full bg-green-500/20 flex items-center justify-center mx-auto mb-4">
|
|
337
|
+
<svg className="w-10 h-10 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
338
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7"/>
|
|
339
|
+
</svg>
|
|
340
|
+
</div>
|
|
341
|
+
<div className="space-y-2 mb-6">
|
|
342
|
+
<p className="text-zinc-300">
|
|
343
|
+
<span className="font-semibold text-white">{workspaceName}</span> is ready to go.
|
|
344
|
+
</p>
|
|
345
|
+
{teamMembers.filter(m => m.trim()).length > 0 && (
|
|
346
|
+
<p className="text-sm text-zinc-500">
|
|
347
|
+
{teamMembers.filter(m => m.trim()).length} team member{teamMembers.filter(m => m.trim()).length > 1 ? "s" : ""} invited.
|
|
348
|
+
</p>
|
|
349
|
+
)}
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
352
|
+
)}
|
|
353
|
+
|
|
354
|
+
{/* Navigation */}
|
|
355
|
+
<div className="flex items-center justify-between mt-8 pt-4 border-t border-zinc-800">
|
|
356
|
+
{currentStep > 0 ? (
|
|
357
|
+
<Button variant="ghost" onClick={handleBack}>
|
|
358
|
+
Back
|
|
359
|
+
</Button>
|
|
360
|
+
) : (
|
|
361
|
+
<div />
|
|
362
|
+
)}
|
|
363
|
+
<div className="flex items-center gap-3">
|
|
364
|
+
{currentStep === 1 && (
|
|
365
|
+
<Button variant="ghost" onClick={handleSkip}>
|
|
366
|
+
Skip for now
|
|
367
|
+
</Button>
|
|
368
|
+
)}
|
|
369
|
+
<Button
|
|
370
|
+
onClick={handleNext}
|
|
371
|
+
disabled={!canProceed()}
|
|
372
|
+
loading={isLoading}
|
|
373
|
+
>
|
|
374
|
+
{currentStep === 2 ? "Go to Dashboard" : "Continue"}
|
|
375
|
+
</Button>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
</CardContent>
|
|
379
|
+
</Card>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export default OnboardingPage
|