@chaaskit/client 0.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/dist/favicon.svg +11 -0
- package/dist/index.html +17 -0
- package/dist/lib/LoadingSkeletons-IcIC2JPq.js +132 -0
- package/dist/lib/LoadingSkeletons-IcIC2JPq.js.map +1 -0
- package/dist/lib/ServerThemeProvider-DNF0LAyk.js +42 -0
- package/dist/lib/ServerThemeProvider-DNF0LAyk.js.map +1 -0
- package/dist/lib/extensions.js +10 -0
- package/dist/lib/extensions.js.map +1 -0
- package/dist/lib/favicon.svg +11 -0
- package/dist/lib/index.js +74126 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/logo.svg +12 -0
- package/dist/lib/routes/AcceptInviteRoute.js +19 -0
- package/dist/lib/routes/AcceptInviteRoute.js.map +1 -0
- package/dist/lib/routes/AdminDashboardRoute.js +19 -0
- package/dist/lib/routes/AdminDashboardRoute.js.map +1 -0
- package/dist/lib/routes/AdminTeamRoute.js +19 -0
- package/dist/lib/routes/AdminTeamRoute.js.map +1 -0
- package/dist/lib/routes/AdminTeamsRoute.js +19 -0
- package/dist/lib/routes/AdminTeamsRoute.js.map +1 -0
- package/dist/lib/routes/AdminUsersRoute.js +19 -0
- package/dist/lib/routes/AdminUsersRoute.js.map +1 -0
- package/dist/lib/routes/ApiKeysRoute.js +19 -0
- package/dist/lib/routes/ApiKeysRoute.js.map +1 -0
- package/dist/lib/routes/AutomationsRoute.js +19 -0
- package/dist/lib/routes/AutomationsRoute.js.map +1 -0
- package/dist/lib/routes/ChatRoute.js +19 -0
- package/dist/lib/routes/ChatRoute.js.map +1 -0
- package/dist/lib/routes/DocumentsRoute.js +19 -0
- package/dist/lib/routes/DocumentsRoute.js.map +1 -0
- package/dist/lib/routes/OAuthConsentRoute.js +19 -0
- package/dist/lib/routes/OAuthConsentRoute.js.map +1 -0
- package/dist/lib/routes/PricingRoute.js +19 -0
- package/dist/lib/routes/PricingRoute.js.map +1 -0
- package/dist/lib/routes/PrivacyRoute.js +19 -0
- package/dist/lib/routes/PrivacyRoute.js.map +1 -0
- package/dist/lib/routes/TeamSettingsRoute.js +19 -0
- package/dist/lib/routes/TeamSettingsRoute.js.map +1 -0
- package/dist/lib/routes/TermsRoute.js +19 -0
- package/dist/lib/routes/TermsRoute.js.map +1 -0
- package/dist/lib/routes/VerifyEmailRoute.js +19 -0
- package/dist/lib/routes/VerifyEmailRoute.js.map +1 -0
- package/dist/lib/routes.js +79 -0
- package/dist/lib/routes.js.map +1 -0
- package/dist/lib/ssr-utils.js +29 -0
- package/dist/lib/ssr-utils.js.map +1 -0
- package/dist/lib/ssr.js +60 -0
- package/dist/lib/ssr.js.map +1 -0
- package/dist/lib/styles.css +2410 -0
- package/dist/lib/useExtensions-B5nX_8XD.js +155 -0
- package/dist/lib/useExtensions-B5nX_8XD.js.map +1 -0
- package/dist/logo.svg +12 -0
- package/package.json +84 -0
- package/src/components/AgentSelector.tsx +90 -0
- package/src/components/BranchModal.tsx +129 -0
- package/src/components/ClientOnly.tsx +27 -0
- package/src/components/ExportMenu.tsx +122 -0
- package/src/components/LoadingSkeletons.tsx +110 -0
- package/src/components/MCPCredentialsSection.tsx +309 -0
- package/src/components/MentionChip.tsx +149 -0
- package/src/components/MentionDropdown.tsx +175 -0
- package/src/components/MentionInput.tsx +293 -0
- package/src/components/MessageItem.tsx +300 -0
- package/src/components/MessageList.tsx +159 -0
- package/src/components/OAuthAppsSection.tsx +124 -0
- package/src/components/ProjectFolder.tsx +141 -0
- package/src/components/ProjectModal.tsx +296 -0
- package/src/components/SSRMessageList.tsx +153 -0
- package/src/components/SearchModal.tsx +173 -0
- package/src/components/SettingsModal.tsx +412 -0
- package/src/components/ShareModal.tsx +280 -0
- package/src/components/Sidebar.tsx +491 -0
- package/src/components/TeamSwitcher.tsx +273 -0
- package/src/components/ToolCallDisplay.tsx +473 -0
- package/src/components/ToolConfirmationModal.tsx +130 -0
- package/src/components/UsageChart.tsx +177 -0
- package/src/components/content/CodeBlock.tsx +69 -0
- package/src/components/content/MarkdownRenderer.tsx +64 -0
- package/src/components/content/SSRMarkdownRenderer.tsx +158 -0
- package/src/contexts/AuthContext.tsx +119 -0
- package/src/contexts/ConfigContext.tsx +214 -0
- package/src/contexts/ProjectContext.tsx +167 -0
- package/src/contexts/ServerConfigProvider.tsx +41 -0
- package/src/contexts/ServerThemeProvider.tsx +47 -0
- package/src/contexts/TeamContext.tsx +255 -0
- package/src/contexts/ThemeContext.tsx +113 -0
- package/src/extensions/index.ts +15 -0
- package/src/extensions/registry.ts +187 -0
- package/src/extensions/useExtensions.ts +52 -0
- package/src/hooks/useAppPath.ts +34 -0
- package/src/hooks/useBasePath.ts +13 -0
- package/src/hooks/useKeyboardShortcuts.ts +50 -0
- package/src/hooks/useMentionSearch.ts +106 -0
- package/src/index.tsx +116 -0
- package/src/layouts/MainLayout.tsx +98 -0
- package/src/pages/AcceptInvitePage.tsx +175 -0
- package/src/pages/AdminDashboardPage.tsx +362 -0
- package/src/pages/AdminTeamPage.tsx +304 -0
- package/src/pages/AdminTeamsPage.tsx +242 -0
- package/src/pages/AdminUsersPage.tsx +385 -0
- package/src/pages/ApiKeysPage.tsx +449 -0
- package/src/pages/ChatPage.tsx +310 -0
- package/src/pages/DocumentsPage.tsx +577 -0
- package/src/pages/LoginPage.tsx +232 -0
- package/src/pages/OAuthConsentPage.tsx +234 -0
- package/src/pages/PricingPage.tsx +314 -0
- package/src/pages/PrivacyPage.tsx +65 -0
- package/src/pages/RegisterPage.tsx +153 -0
- package/src/pages/ScheduledPromptsPage.tsx +702 -0
- package/src/pages/SharedThreadPage.tsx +116 -0
- package/src/pages/TeamSettingsPage.tsx +1085 -0
- package/src/pages/TermsPage.tsx +82 -0
- package/src/pages/VerifyEmailPage.tsx +202 -0
- package/src/routes/AcceptInviteRoute.tsx +24 -0
- package/src/routes/AdminDashboardRoute.tsx +24 -0
- package/src/routes/AdminTeamRoute.tsx +24 -0
- package/src/routes/AdminTeamsRoute.tsx +24 -0
- package/src/routes/AdminUsersRoute.tsx +24 -0
- package/src/routes/ApiKeysRoute.tsx +24 -0
- package/src/routes/AutomationsRoute.tsx +24 -0
- package/src/routes/ChatRoute.tsx +28 -0
- package/src/routes/DocumentsRoute.tsx +24 -0
- package/src/routes/OAuthConsentRoute.tsx +24 -0
- package/src/routes/PricingRoute.tsx +24 -0
- package/src/routes/PrivacyRoute.tsx +24 -0
- package/src/routes/TeamSettingsRoute.tsx +24 -0
- package/src/routes/TermsRoute.tsx +24 -0
- package/src/routes/VerifyEmailRoute.tsx +24 -0
- package/src/routes/index.ts +57 -0
- package/src/ssr-utils.tsx +84 -0
- package/src/ssr.ts +123 -0
- package/src/stores/chatStore.ts +670 -0
- package/src/styles/index.css +254 -0
- package/src/utils/api.ts +78 -0
- package/src/vite-env.d.ts +13 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { Link, useNavigate, useSearchParams } from 'react-router';
|
|
3
|
+
import { Check, Loader2, Users, User } from 'lucide-react';
|
|
4
|
+
import { useAuth } from '../contexts/AuthContext';
|
|
5
|
+
import { useConfig } from '../contexts/ConfigContext';
|
|
6
|
+
import { useTeam } from '../contexts/TeamContext';
|
|
7
|
+
|
|
8
|
+
interface Plan {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
type: 'free' | 'monthly' | 'credits';
|
|
13
|
+
scope: 'personal' | 'team' | 'both';
|
|
14
|
+
priceUSD: number;
|
|
15
|
+
monthlyMessageLimit?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function PricingPage() {
|
|
19
|
+
const { user } = useAuth();
|
|
20
|
+
const config = useConfig();
|
|
21
|
+
const navigate = useNavigate();
|
|
22
|
+
const [searchParams] = useSearchParams();
|
|
23
|
+
const { teams } = useTeam();
|
|
24
|
+
|
|
25
|
+
const [plans, setPlans] = useState<Plan[]>([]);
|
|
26
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
27
|
+
const [checkoutLoading, setCheckoutLoading] = useState<string | null>(null);
|
|
28
|
+
const [selectedTeamId, setSelectedTeamId] = useState<string | null>(null);
|
|
29
|
+
const [error, setError] = useState('');
|
|
30
|
+
|
|
31
|
+
// Get teamId from query params if present
|
|
32
|
+
const teamIdFromParams = searchParams.get('teamId');
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
loadPlans();
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (teamIdFromParams && teams.length > 0) {
|
|
40
|
+
const team = teams.find((t) => t.id === teamIdFromParams);
|
|
41
|
+
if (team && (team.role === 'owner' || team.role === 'admin')) {
|
|
42
|
+
setSelectedTeamId(teamIdFromParams);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}, [teamIdFromParams, teams]);
|
|
46
|
+
|
|
47
|
+
async function loadPlans() {
|
|
48
|
+
try {
|
|
49
|
+
const response = await fetch('/api/payments/plans');
|
|
50
|
+
if (response.ok) {
|
|
51
|
+
const data = await response.json();
|
|
52
|
+
setPlans(data.plans);
|
|
53
|
+
}
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error('Failed to load plans:', err);
|
|
56
|
+
} finally {
|
|
57
|
+
setIsLoading(false);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function handleCheckout(planId: string) {
|
|
62
|
+
if (!user) {
|
|
63
|
+
navigate('/login?redirect=/pricing');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setError('');
|
|
68
|
+
setCheckoutLoading(planId);
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const response = await fetch('/api/payments/checkout', {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: { 'Content-Type': 'application/json' },
|
|
74
|
+
credentials: 'include',
|
|
75
|
+
body: JSON.stringify({
|
|
76
|
+
planId,
|
|
77
|
+
teamId: selectedTeamId || undefined,
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
const data = await response.json();
|
|
83
|
+
throw new Error(data.error || 'Failed to start checkout');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const { url } = await response.json();
|
|
87
|
+
window.location.href = url;
|
|
88
|
+
} catch (err) {
|
|
89
|
+
setError(err instanceof Error ? err.message : 'Checkout failed');
|
|
90
|
+
} finally {
|
|
91
|
+
setCheckoutLoading(null);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Get available teams for team billing (where user is admin/owner)
|
|
96
|
+
const adminTeams = teams.filter((t) => t.role === 'owner' || t.role === 'admin');
|
|
97
|
+
|
|
98
|
+
// Filter plans based on selected context (personal vs team)
|
|
99
|
+
const filteredPlans = plans.filter((plan) => {
|
|
100
|
+
if (selectedTeamId) {
|
|
101
|
+
return plan.scope === 'team' || plan.scope === 'both';
|
|
102
|
+
}
|
|
103
|
+
return plan.scope === 'personal' || plan.scope === 'both';
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (isLoading) {
|
|
107
|
+
return (
|
|
108
|
+
<div className="min-h-screen bg-background flex items-center justify-center">
|
|
109
|
+
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div className="min-h-screen bg-background py-12 px-4">
|
|
116
|
+
<div className="mx-auto max-w-5xl">
|
|
117
|
+
{/* Header */}
|
|
118
|
+
<div className="text-center mb-12">
|
|
119
|
+
<h1 className="text-3xl font-bold text-text-primary mb-4">
|
|
120
|
+
Choose Your Plan
|
|
121
|
+
</h1>
|
|
122
|
+
<p className="text-text-secondary max-w-2xl mx-auto">
|
|
123
|
+
Select the plan that best fits your needs. Upgrade or downgrade anytime.
|
|
124
|
+
</p>
|
|
125
|
+
|
|
126
|
+
{/* Team/Personal Toggle */}
|
|
127
|
+
{user && adminTeams.length > 0 && (
|
|
128
|
+
<div className="mt-8 inline-flex items-center gap-4 p-1 bg-background-secondary rounded-lg">
|
|
129
|
+
<button
|
|
130
|
+
onClick={() => setSelectedTeamId(null)}
|
|
131
|
+
className={`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
|
132
|
+
!selectedTeamId
|
|
133
|
+
? 'bg-primary text-white'
|
|
134
|
+
: 'text-text-secondary hover:text-text-primary'
|
|
135
|
+
}`}
|
|
136
|
+
>
|
|
137
|
+
<User size={16} />
|
|
138
|
+
Personal
|
|
139
|
+
</button>
|
|
140
|
+
<div className="relative">
|
|
141
|
+
<select
|
|
142
|
+
value={selectedTeamId || ''}
|
|
143
|
+
onChange={(e) => setSelectedTeamId(e.target.value || null)}
|
|
144
|
+
className={`appearance-none flex items-center gap-2 px-4 py-2 pr-8 rounded-md text-sm font-medium transition-colors cursor-pointer ${
|
|
145
|
+
selectedTeamId
|
|
146
|
+
? 'bg-primary text-white'
|
|
147
|
+
: 'bg-transparent text-text-secondary hover:text-text-primary'
|
|
148
|
+
}`}
|
|
149
|
+
>
|
|
150
|
+
<option value="">Team billing</option>
|
|
151
|
+
{adminTeams.map((team) => (
|
|
152
|
+
<option key={team.id} value={team.id}>
|
|
153
|
+
{team.name}
|
|
154
|
+
</option>
|
|
155
|
+
))}
|
|
156
|
+
</select>
|
|
157
|
+
<Users
|
|
158
|
+
size={16}
|
|
159
|
+
className={`absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none ${
|
|
160
|
+
selectedTeamId ? 'text-white' : 'text-text-secondary'
|
|
161
|
+
}`}
|
|
162
|
+
/>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
{error && (
|
|
169
|
+
<div className="mb-8 rounded-lg bg-error/10 p-4 text-center text-sm text-error">
|
|
170
|
+
{error}
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
{/* Plans Grid */}
|
|
175
|
+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
176
|
+
{filteredPlans
|
|
177
|
+
.filter((plan) => plan.type !== 'credits')
|
|
178
|
+
.map((plan) => (
|
|
179
|
+
<div
|
|
180
|
+
key={plan.id}
|
|
181
|
+
className={`rounded-xl border p-6 ${
|
|
182
|
+
plan.id === 'pro'
|
|
183
|
+
? 'border-primary bg-primary/5'
|
|
184
|
+
: 'border-border bg-background-secondary'
|
|
185
|
+
}`}
|
|
186
|
+
>
|
|
187
|
+
{/* Plan Badge */}
|
|
188
|
+
<div className="flex items-center justify-between mb-4">
|
|
189
|
+
<span
|
|
190
|
+
className={`inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium ${
|
|
191
|
+
plan.scope === 'team'
|
|
192
|
+
? 'bg-secondary/10 text-secondary'
|
|
193
|
+
: plan.scope === 'both'
|
|
194
|
+
? 'bg-primary/10 text-primary'
|
|
195
|
+
: 'bg-background text-text-muted'
|
|
196
|
+
}`}
|
|
197
|
+
>
|
|
198
|
+
{plan.scope === 'team' && <Users size={12} />}
|
|
199
|
+
{plan.scope === 'personal' && <User size={12} />}
|
|
200
|
+
{plan.scope === 'both' && (
|
|
201
|
+
<>
|
|
202
|
+
<User size={12} />
|
|
203
|
+
<span>/</span>
|
|
204
|
+
<Users size={12} />
|
|
205
|
+
</>
|
|
206
|
+
)}
|
|
207
|
+
{plan.scope === 'team'
|
|
208
|
+
? 'Team'
|
|
209
|
+
: plan.scope === 'both'
|
|
210
|
+
? 'Personal or Team'
|
|
211
|
+
: 'Personal'}
|
|
212
|
+
</span>
|
|
213
|
+
{plan.id === 'pro' && (
|
|
214
|
+
<span className="rounded-full bg-primary px-2.5 py-0.5 text-xs font-medium text-white">
|
|
215
|
+
Popular
|
|
216
|
+
</span>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
{/* Plan Name & Price */}
|
|
221
|
+
<h3 className="text-xl font-bold text-text-primary">{plan.name}</h3>
|
|
222
|
+
<div className="mt-2 flex items-baseline gap-1">
|
|
223
|
+
<span className="text-3xl font-bold text-text-primary">
|
|
224
|
+
${plan.priceUSD}
|
|
225
|
+
</span>
|
|
226
|
+
{plan.type === 'monthly' && (
|
|
227
|
+
<span className="text-text-muted">/month</span>
|
|
228
|
+
)}
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
{plan.description && (
|
|
232
|
+
<p className="mt-2 text-sm text-text-secondary">{plan.description}</p>
|
|
233
|
+
)}
|
|
234
|
+
|
|
235
|
+
{/* Features */}
|
|
236
|
+
<ul className="mt-6 space-y-3">
|
|
237
|
+
<li className="flex items-start gap-2 text-sm text-text-secondary">
|
|
238
|
+
<Check size={16} className="mt-0.5 text-success shrink-0" />
|
|
239
|
+
<span>
|
|
240
|
+
{plan.monthlyMessageLimit === -1
|
|
241
|
+
? 'Unlimited messages'
|
|
242
|
+
: `${plan.monthlyMessageLimit?.toLocaleString()} messages/month`}
|
|
243
|
+
</span>
|
|
244
|
+
</li>
|
|
245
|
+
{plan.type === 'monthly' && (
|
|
246
|
+
<li className="flex items-start gap-2 text-sm text-text-secondary">
|
|
247
|
+
<Check size={16} className="mt-0.5 text-success shrink-0" />
|
|
248
|
+
<span>Priority support</span>
|
|
249
|
+
</li>
|
|
250
|
+
)}
|
|
251
|
+
{plan.scope === 'team' && (
|
|
252
|
+
<li className="flex items-start gap-2 text-sm text-text-secondary">
|
|
253
|
+
<Check size={16} className="mt-0.5 text-success shrink-0" />
|
|
254
|
+
<span>Shared message pool for all team members</span>
|
|
255
|
+
</li>
|
|
256
|
+
)}
|
|
257
|
+
</ul>
|
|
258
|
+
|
|
259
|
+
{/* CTA Button */}
|
|
260
|
+
<button
|
|
261
|
+
onClick={() => handleCheckout(plan.id)}
|
|
262
|
+
disabled={plan.type === 'free' || checkoutLoading === plan.id}
|
|
263
|
+
className={`mt-6 w-full rounded-lg py-2.5 text-sm font-medium transition-colors ${
|
|
264
|
+
plan.type === 'free'
|
|
265
|
+
? 'bg-background-secondary text-text-muted cursor-default'
|
|
266
|
+
: plan.id === 'pro'
|
|
267
|
+
? 'bg-primary text-white hover:bg-primary-hover'
|
|
268
|
+
: 'bg-background text-text-primary border border-border hover:bg-background-secondary'
|
|
269
|
+
} disabled:opacity-50`}
|
|
270
|
+
>
|
|
271
|
+
{checkoutLoading === plan.id ? (
|
|
272
|
+
<Loader2 className="h-4 w-4 animate-spin mx-auto" />
|
|
273
|
+
) : plan.type === 'free' ? (
|
|
274
|
+
'Current Plan'
|
|
275
|
+
) : (
|
|
276
|
+
'Get Started'
|
|
277
|
+
)}
|
|
278
|
+
</button>
|
|
279
|
+
</div>
|
|
280
|
+
))}
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
{/* Credits Section */}
|
|
284
|
+
{filteredPlans.some((p) => p.type === 'credits') && (
|
|
285
|
+
<div className="mt-12 rounded-xl border border-border bg-background-secondary p-6">
|
|
286
|
+
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
|
287
|
+
<div>
|
|
288
|
+
<h3 className="text-lg font-semibold text-text-primary">
|
|
289
|
+
Need more messages?
|
|
290
|
+
</h3>
|
|
291
|
+
<p className="text-sm text-text-secondary mt-1">
|
|
292
|
+
Buy credits to use when you exceed your monthly limit.
|
|
293
|
+
</p>
|
|
294
|
+
</div>
|
|
295
|
+
<Link
|
|
296
|
+
to={user ? '/settings' : '/login'}
|
|
297
|
+
className="inline-flex items-center justify-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white hover:bg-primary-hover"
|
|
298
|
+
>
|
|
299
|
+
Buy Credits
|
|
300
|
+
</Link>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
)}
|
|
304
|
+
|
|
305
|
+
{/* Back Link */}
|
|
306
|
+
<div className="mt-8 text-center">
|
|
307
|
+
<Link to="/" className="text-sm text-text-muted hover:text-text-primary">
|
|
308
|
+
Back to {config.app.name}
|
|
309
|
+
</Link>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useConfig } from '../contexts/ConfigContext';
|
|
2
|
+
|
|
3
|
+
export default function PrivacyPage() {
|
|
4
|
+
const config = useConfig();
|
|
5
|
+
|
|
6
|
+
return (
|
|
7
|
+
<div className="min-h-screen bg-background py-12">
|
|
8
|
+
<div className="mx-auto max-w-3xl px-4">
|
|
9
|
+
<h1 className="mb-8 text-3xl font-bold text-text-primary">
|
|
10
|
+
Privacy Policy
|
|
11
|
+
</h1>
|
|
12
|
+
|
|
13
|
+
<div className="prose prose-gray dark:prose-invert">
|
|
14
|
+
<p className="text-text-secondary">
|
|
15
|
+
Last updated: {new Date().toLocaleDateString()}
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<h2 className="mt-8 text-xl font-semibold text-text-primary">
|
|
19
|
+
1. Information We Collect
|
|
20
|
+
</h2>
|
|
21
|
+
<p className="text-text-secondary">
|
|
22
|
+
{config.app.name} collects information you provide directly to us,
|
|
23
|
+
such as when you create an account, send messages, or contact us for
|
|
24
|
+
support.
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
<h2 className="mt-8 text-xl font-semibold text-text-primary">
|
|
28
|
+
2. How We Use Your Information
|
|
29
|
+
</h2>
|
|
30
|
+
<p className="text-text-secondary">
|
|
31
|
+
We use the information we collect to provide, maintain, and improve
|
|
32
|
+
our services, and to communicate with you about your account and our
|
|
33
|
+
services.
|
|
34
|
+
</p>
|
|
35
|
+
|
|
36
|
+
<h2 className="mt-8 text-xl font-semibold text-text-primary">
|
|
37
|
+
3. Data Retention
|
|
38
|
+
</h2>
|
|
39
|
+
<p className="text-text-secondary">
|
|
40
|
+
We retain your information for as long as your account is active or
|
|
41
|
+
as needed to provide you services. You can request deletion of your
|
|
42
|
+
data at any time.
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
<h2 className="mt-8 text-xl font-semibold text-text-primary">
|
|
46
|
+
4. Data Security
|
|
47
|
+
</h2>
|
|
48
|
+
<p className="text-text-secondary">
|
|
49
|
+
We implement appropriate technical and organizational measures to
|
|
50
|
+
protect your personal information against unauthorized access,
|
|
51
|
+
alteration, disclosure, or destruction.
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
<h2 className="mt-8 text-xl font-semibold text-text-primary">
|
|
55
|
+
5. Contact Us
|
|
56
|
+
</h2>
|
|
57
|
+
<p className="text-text-secondary">
|
|
58
|
+
If you have any questions about this Privacy Policy, please contact
|
|
59
|
+
us.
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Link, useNavigate } from 'react-router';
|
|
3
|
+
import { useAuth } from '../contexts/AuthContext';
|
|
4
|
+
import { useConfig } from '../contexts/ConfigContext';
|
|
5
|
+
import { useTheme } from '../contexts/ThemeContext';
|
|
6
|
+
import { useAppPath } from '../hooks/useAppPath';
|
|
7
|
+
|
|
8
|
+
export default function RegisterPage() {
|
|
9
|
+
const navigate = useNavigate();
|
|
10
|
+
const appPath = useAppPath();
|
|
11
|
+
const { register } = useAuth();
|
|
12
|
+
const config = useConfig();
|
|
13
|
+
const { theme } = useTheme();
|
|
14
|
+
|
|
15
|
+
const [name, setName] = useState('');
|
|
16
|
+
const [email, setEmail] = useState('');
|
|
17
|
+
const [password, setPassword] = useState('');
|
|
18
|
+
const [error, setError] = useState('');
|
|
19
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
20
|
+
|
|
21
|
+
async function handleSubmit(e: React.FormEvent) {
|
|
22
|
+
e.preventDefault();
|
|
23
|
+
setError('');
|
|
24
|
+
|
|
25
|
+
if (password.length < 8) {
|
|
26
|
+
setError('Password must be at least 8 characters');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setIsLoading(true);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const { requiresVerification } = await register(email, password, name || undefined);
|
|
34
|
+
if (requiresVerification) {
|
|
35
|
+
navigate('/verify-email');
|
|
36
|
+
} else {
|
|
37
|
+
navigate(appPath('/'));
|
|
38
|
+
}
|
|
39
|
+
} catch (err) {
|
|
40
|
+
setError(err instanceof Error ? err.message : 'Registration failed');
|
|
41
|
+
} finally {
|
|
42
|
+
setIsLoading(false);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className="flex min-h-screen items-center justify-center bg-background p-4">
|
|
48
|
+
<div className="w-full max-w-md">
|
|
49
|
+
<div className="mb-8 text-center">
|
|
50
|
+
{config.ui.logo && (
|
|
51
|
+
<img
|
|
52
|
+
src={typeof config.ui.logo === 'string' ? config.ui.logo : (theme === 'dark' ? config.ui.logo.dark : config.ui.logo.light)}
|
|
53
|
+
alt={config.app.name}
|
|
54
|
+
className="mx-auto mb-4 h-16 w-16 rounded-lg object-contain"
|
|
55
|
+
/>
|
|
56
|
+
)}
|
|
57
|
+
<h1 className="text-3xl font-bold text-text-primary">Create account</h1>
|
|
58
|
+
<p className="mt-2 text-text-secondary">
|
|
59
|
+
Get started with {config.app.name}
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
{error && (
|
|
64
|
+
<div className="mb-4 rounded-lg bg-error/10 p-3 text-sm text-error">
|
|
65
|
+
{error}
|
|
66
|
+
</div>
|
|
67
|
+
)}
|
|
68
|
+
|
|
69
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
70
|
+
<div>
|
|
71
|
+
<label
|
|
72
|
+
htmlFor="name"
|
|
73
|
+
className="block text-sm font-medium text-text-primary"
|
|
74
|
+
>
|
|
75
|
+
Name (optional)
|
|
76
|
+
</label>
|
|
77
|
+
<input
|
|
78
|
+
type="text"
|
|
79
|
+
id="name"
|
|
80
|
+
value={name}
|
|
81
|
+
onChange={(e) => setName(e.target.value)}
|
|
82
|
+
className="mt-1 w-full rounded-lg border border-input-border bg-input-background px-4 py-2 text-text-primary focus:border-primary focus:outline-none"
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div>
|
|
87
|
+
<label
|
|
88
|
+
htmlFor="email"
|
|
89
|
+
className="block text-sm font-medium text-text-primary"
|
|
90
|
+
>
|
|
91
|
+
Email
|
|
92
|
+
</label>
|
|
93
|
+
<input
|
|
94
|
+
type="email"
|
|
95
|
+
id="email"
|
|
96
|
+
value={email}
|
|
97
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
98
|
+
required
|
|
99
|
+
className="mt-1 w-full rounded-lg border border-input-border bg-input-background px-4 py-2 text-text-primary focus:border-primary focus:outline-none"
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div>
|
|
104
|
+
<label
|
|
105
|
+
htmlFor="password"
|
|
106
|
+
className="block text-sm font-medium text-text-primary"
|
|
107
|
+
>
|
|
108
|
+
Password
|
|
109
|
+
</label>
|
|
110
|
+
<input
|
|
111
|
+
type="password"
|
|
112
|
+
id="password"
|
|
113
|
+
value={password}
|
|
114
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
115
|
+
required
|
|
116
|
+
minLength={8}
|
|
117
|
+
className="mt-1 w-full rounded-lg border border-input-border bg-input-background px-4 py-2 text-text-primary focus:border-primary focus:outline-none"
|
|
118
|
+
/>
|
|
119
|
+
<p className="mt-1 text-xs text-text-muted">
|
|
120
|
+
Must be at least 8 characters
|
|
121
|
+
</p>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<button
|
|
125
|
+
type="submit"
|
|
126
|
+
disabled={isLoading}
|
|
127
|
+
className="w-full rounded-lg bg-primary px-4 py-2 font-medium text-white hover:bg-primary-hover disabled:opacity-50"
|
|
128
|
+
>
|
|
129
|
+
{isLoading ? 'Creating account...' : 'Create account'}
|
|
130
|
+
</button>
|
|
131
|
+
</form>
|
|
132
|
+
|
|
133
|
+
<p className="mt-6 text-center text-sm text-text-secondary">
|
|
134
|
+
Already have an account?{' '}
|
|
135
|
+
<Link to="/login" className="text-primary hover:underline">
|
|
136
|
+
Sign in
|
|
137
|
+
</Link>
|
|
138
|
+
</p>
|
|
139
|
+
|
|
140
|
+
<p className="mt-4 text-center text-xs text-text-muted">
|
|
141
|
+
By creating an account, you agree to our{' '}
|
|
142
|
+
<Link to={config.legal.termsOfServiceUrl} className="text-primary hover:underline">
|
|
143
|
+
Terms of Service
|
|
144
|
+
</Link>{' '}
|
|
145
|
+
and{' '}
|
|
146
|
+
<Link to={config.legal.privacyPolicyUrl} className="text-primary hover:underline">
|
|
147
|
+
Privacy Policy
|
|
148
|
+
</Link>
|
|
149
|
+
</p>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|