@promakeai/cli 0.0.5 → 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/index.js +214 -135
- package/dist/registry/about-page.json +5 -3
- package/dist/registry/about-section.json +2 -2
- package/dist/registry/announcement-bar.json +43 -0
- package/dist/registry/api.json +55 -0
- package/dist/registry/auth-core.json +43 -0
- package/dist/registry/auth.json +70 -0
- package/dist/registry/bento-grid-section.json +1 -1
- package/dist/registry/blog-list-page.json +3 -2
- package/dist/registry/blog-section.json +2 -2
- package/dist/registry/cart-drawer.json +1 -1
- package/dist/registry/cart-page.json +5 -4
- package/dist/registry/case-study-page.json +48 -0
- package/dist/registry/category-section.json +1 -1
- package/dist/registry/checkout-page.json +7 -5
- package/dist/registry/coming-soon-page-minimal.json +45 -0
- package/dist/registry/coming-soon-page.json +47 -0
- package/dist/registry/contact-info-grid.json +2 -2
- package/dist/registry/contact-page-centered.json +2 -2
- package/dist/registry/contact-page-map-overlay.json +4 -3
- package/dist/registry/contact-page-map-split.json +4 -3
- package/dist/registry/contact-page-split.json +3 -3
- package/dist/registry/contact-page.json +5 -3
- package/dist/registry/cookie-consent.json +43 -0
- package/dist/registry/cookies-page.json +4 -2
- package/dist/registry/cta-section.json +2 -2
- package/dist/registry/db.json +129 -0
- package/dist/registry/docs/about-page.md +5 -0
- package/dist/registry/docs/announcement-bar.md +38 -0
- package/dist/registry/docs/auth-core.md +64 -0
- package/dist/registry/docs/blog-list-page.md +1 -0
- package/dist/registry/docs/cart-page.md +1 -0
- package/dist/registry/docs/case-study-page.md +39 -0
- package/dist/registry/docs/checkout-page.md +3 -1
- package/dist/registry/docs/coming-soon-page-minimal.md +32 -0
- package/dist/registry/docs/coming-soon-page.md +37 -0
- package/dist/registry/docs/contact-page-map-overlay.md +2 -2
- package/dist/registry/docs/contact-page-map-split.md +2 -2
- package/dist/registry/docs/contact-page.md +5 -0
- package/dist/registry/docs/cookie-consent.md +37 -0
- package/dist/registry/docs/cookies-page.md +5 -0
- package/dist/registry/docs/ecommerce-core.md +4 -3
- package/dist/registry/docs/forgot-password-page-split.md +45 -0
- package/dist/registry/docs/forgot-password-page.md +46 -0
- package/dist/registry/docs/header-ecommerce.md +2 -0
- package/dist/registry/docs/hero-carousel.md +37 -0
- package/dist/registry/docs/landing-page-app.md +43 -0
- package/dist/registry/docs/landing-page-saas.md +41 -0
- package/dist/registry/docs/login-page-split.md +13 -4
- package/dist/registry/docs/login-page.md +17 -4
- package/dist/registry/docs/logo-cloud.md +33 -0
- package/dist/registry/docs/masonry-grid.md +37 -0
- package/dist/registry/docs/my-orders-page.md +44 -0
- package/dist/registry/docs/order-confirmation-page.md +41 -0
- package/dist/registry/docs/portfolio-page.md +38 -0
- package/dist/registry/docs/pricing-page.md +38 -0
- package/dist/registry/docs/privacy-page.md +5 -0
- package/dist/registry/docs/product-quick-view.md +37 -0
- package/dist/registry/docs/products-page.md +1 -0
- package/dist/registry/docs/reading-progress.md +31 -0
- package/dist/registry/docs/register-page-split.md +45 -0
- package/dist/registry/docs/register-page.md +46 -0
- package/dist/registry/docs/reset-password-page-split.md +45 -0
- package/dist/registry/docs/reset-password-page.md +36 -0
- package/dist/registry/docs/share-buttons.md +37 -0
- package/dist/registry/docs/team-page.md +38 -0
- package/dist/registry/docs/terms-page.md +5 -0
- package/dist/registry/docs/timeline-section.md +37 -0
- package/dist/registry/docs/video-hero.md +41 -0
- package/dist/registry/ecommerce-core.json +18 -2
- package/dist/registry/empty-page.json +1 -1
- package/dist/registry/faq-categorized.json +2 -2
- package/dist/registry/faq-simple.json +2 -2
- package/dist/registry/favorites-blog-block.json +1 -1
- package/dist/registry/favorites-ecommerce-block.json +1 -1
- package/dist/registry/feature-section.json +2 -2
- package/dist/registry/featured-products.json +1 -1
- package/dist/registry/footer-detailed.json +1 -1
- package/dist/registry/footer-minimal.json +3 -3
- package/dist/registry/footer.json +2 -2
- package/dist/registry/forgot-password-page-split.json +50 -0
- package/dist/registry/forgot-password-page.json +51 -0
- package/dist/registry/header-ecommerce.json +4 -2
- package/dist/registry/header-mega.json +2 -2
- package/dist/registry/header-minimal.json +1 -1
- package/dist/registry/header-simple.json +1 -1
- package/dist/registry/hero-carousel.json +45 -0
- package/dist/registry/hero-cta.json +2 -2
- package/dist/registry/hero-gradient.json +2 -2
- package/dist/registry/hero-profile.json +1 -1
- package/dist/registry/hero.json +2 -2
- package/dist/registry/index.json +24 -1
- package/dist/registry/landing-page-app.json +47 -0
- package/dist/registry/landing-page-saas.json +47 -0
- package/dist/registry/login-page-split.json +11 -7
- package/dist/registry/login-page.json +4 -4
- package/dist/registry/logo-cloud.json +41 -0
- package/dist/registry/masonry-grid.json +43 -0
- package/dist/registry/my-orders-page.json +52 -0
- package/dist/registry/order-confirmation-page.json +49 -0
- package/dist/registry/orders-list-block.json +1 -1
- package/dist/registry/payment-success-block.json +1 -1
- package/dist/registry/portfolio-page.json +45 -0
- package/dist/registry/post-detail-block.json +1 -1
- package/dist/registry/pricing-page.json +47 -0
- package/dist/registry/pricing-section.json +2 -2
- package/dist/registry/privacy-page.json +4 -2
- package/dist/registry/product-detail-block.json +1 -1
- package/dist/registry/product-quick-view.json +46 -0
- package/dist/registry/products-page.json +5 -4
- package/dist/registry/reading-progress.json +43 -0
- package/dist/registry/register-page-split.json +50 -0
- package/dist/registry/register-page.json +51 -0
- package/dist/registry/related-posts-block.json +1 -1
- package/dist/registry/reset-password-page-split.json +50 -0
- package/dist/registry/reset-password-page.json +39 -0
- package/dist/registry/share-buttons.json +46 -0
- package/dist/registry/team-page.json +47 -0
- package/dist/registry/terms-page.json +4 -2
- package/dist/registry/testimonials-carousel.json +2 -2
- package/dist/registry/testimonials-grid.json +2 -2
- package/dist/registry/timeline-section.json +43 -0
- package/dist/registry/video-hero.json +42 -0
- package/package.json +1 -1
- package/template/index.html +5 -5
- package/template/src/App.tsx +7 -24
- package/template/src/components/GoogleAnalytics.tsx +34 -0
- package/template/src/components/Layout.tsx +1 -5
- package/template/src/components/ScriptInjector.tsx +62 -0
- package/template/src/constants/constants.json +8 -2
- package/template/src/index.css +1 -0
- package/template/src/lang/en/index.json +1 -28
- package/template/src/lang/tr/index.json +1 -28
- package/template/src/pages/Index.tsx +1 -98
- package/template/src/components/Footer.tsx +0 -100
- package/template/src/components/Header.tsx +0 -79
- package/template/src/components/Hero.tsx +0 -69
- package/template/src/modules/api/USAGE.md +0 -515
- package/template/src/modules/api/customer-client.ts +0 -20
- package/template/src/modules/api/get-error-message.ts +0 -18
- package/template/src/modules/api/validation/en.json +0 -29
- package/template/src/modules/api/validation/tr.json +0 -29
- package/template/src/modules/auth/USAGE.md +0 -248
- package/template/src/modules/auth/auth-header-menu.tsx +0 -123
- package/template/src/modules/auth/auth-store.ts +0 -57
- package/template/src/modules/auth/forgot-password-page.tsx +0 -371
- package/template/src/modules/auth/login-page.tsx +0 -183
- package/template/src/modules/auth/register-page.tsx +0 -252
- package/template/src/modules/auth/use-auth.ts +0 -273
- package/template/src/modules/db/adapters/IDataAdapter.ts +0 -26
- package/template/src/modules/db/adapters/SqliteAdapter.ts +0 -364
- package/template/src/modules/db/adapters/index.ts +0 -2
- package/template/src/modules/db/config.ts +0 -59
- package/template/src/modules/db/core/DataManager.ts +0 -125
- package/template/src/modules/db/core/types.ts +0 -101
- package/template/src/modules/db/index.ts +0 -42
- package/template/src/modules/db/react/QueryProvider.tsx +0 -16
- package/template/src/modules/db/react/index.ts +0 -23
- package/template/src/modules/db/react/queryClient.ts +0 -64
- package/template/src/modules/db/react/useRepository.ts +0 -400
- package/template/src/modules/db/utils/parsers.ts +0 -96
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from "react";
|
|
2
|
-
import { Link, useNavigate } from "react-router";
|
|
3
|
-
import { toast } from "sonner";
|
|
4
|
-
import { Layout } from "@/components/Layout";
|
|
5
|
-
import { usePageTitle } from "@/hooks/use-page-title";
|
|
6
|
-
import { useTranslation } from "react-i18next";
|
|
7
|
-
import { useAuth } from "@/modules/auth/use-auth";
|
|
8
|
-
import { getErrorMessage } from "@/modules/api/get-error-message";
|
|
9
|
-
import { Button } from "@/components/ui/button";
|
|
10
|
-
import { Input } from "@/components/ui/input";
|
|
11
|
-
import { Label } from "@/components/ui/label";
|
|
12
|
-
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
13
|
-
import { UserPlus, Eye, EyeOff, CheckCircle } from "lucide-react";
|
|
14
|
-
|
|
15
|
-
export default function RegisterPage() {
|
|
16
|
-
const { t } = useTranslation("register");
|
|
17
|
-
usePageTitle({ title: t("title") });
|
|
18
|
-
|
|
19
|
-
const navigate = useNavigate();
|
|
20
|
-
const { register, isAuthenticated } = useAuth();
|
|
21
|
-
|
|
22
|
-
const [formData, setFormData] = useState({
|
|
23
|
-
username: "",
|
|
24
|
-
email: "",
|
|
25
|
-
password: "",
|
|
26
|
-
confirmPassword: "",
|
|
27
|
-
});
|
|
28
|
-
const [showPassword, setShowPassword] = useState(false);
|
|
29
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
30
|
-
const [error, setError] = useState<string | null>(null);
|
|
31
|
-
const [success, setSuccess] = useState(false);
|
|
32
|
-
|
|
33
|
-
// Redirect if already authenticated
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
if (isAuthenticated) {
|
|
36
|
-
navigate("/", { replace: true });
|
|
37
|
-
}
|
|
38
|
-
}, [isAuthenticated, navigate]);
|
|
39
|
-
|
|
40
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
41
|
-
e.preventDefault();
|
|
42
|
-
setIsSubmitting(true);
|
|
43
|
-
setError(null);
|
|
44
|
-
|
|
45
|
-
// Validate passwords match
|
|
46
|
-
if (formData.password !== formData.confirmPassword) {
|
|
47
|
-
setError(t("passwordMismatch"));
|
|
48
|
-
toast.error(t("toastErrorTitle", "Registration failed"), {
|
|
49
|
-
description: t("passwordMismatch"),
|
|
50
|
-
});
|
|
51
|
-
setIsSubmitting(false);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
await register(formData.username, formData.email, formData.password);
|
|
57
|
-
setSuccess(true);
|
|
58
|
-
toast.success(t("toastSuccessTitle", "Account created!"), {
|
|
59
|
-
description: t(
|
|
60
|
-
"toastSuccessDesc",
|
|
61
|
-
"Please check your email to verify your account.",
|
|
62
|
-
),
|
|
63
|
-
});
|
|
64
|
-
} catch (err) {
|
|
65
|
-
const errorMessage = getErrorMessage(err, t("errorGeneric"));
|
|
66
|
-
setError(errorMessage);
|
|
67
|
-
toast.error(t("toastErrorTitle", "Registration failed"), {
|
|
68
|
-
description: errorMessage,
|
|
69
|
-
});
|
|
70
|
-
} finally {
|
|
71
|
-
setIsSubmitting(false);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
76
|
-
setFormData((prev) => ({
|
|
77
|
-
...prev,
|
|
78
|
-
[e.target.name]: e.target.value,
|
|
79
|
-
}));
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
if (success) {
|
|
83
|
-
return (
|
|
84
|
-
<Layout>
|
|
85
|
-
<div className="min-h-screen bg-muted/30 py-12">
|
|
86
|
-
<div className="container mx-auto px-4">
|
|
87
|
-
<div className="max-w-md mx-auto">
|
|
88
|
-
<Card>
|
|
89
|
-
<CardContent className="pt-6">
|
|
90
|
-
<div className="text-center space-y-4">
|
|
91
|
-
<CheckCircle className="w-16 h-16 text-green-500 mx-auto" />
|
|
92
|
-
<h2 className="text-2xl font-bold text-foreground">
|
|
93
|
-
{t("successTitle")}
|
|
94
|
-
</h2>
|
|
95
|
-
<p className="text-muted-foreground">
|
|
96
|
-
{t("successMessage")}
|
|
97
|
-
</p>
|
|
98
|
-
<Button asChild className="mt-4">
|
|
99
|
-
<Link to="/login">{t("goToLogin")}</Link>
|
|
100
|
-
</Button>
|
|
101
|
-
</div>
|
|
102
|
-
</CardContent>
|
|
103
|
-
</Card>
|
|
104
|
-
</div>
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
</Layout>
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return (
|
|
112
|
-
<Layout>
|
|
113
|
-
<div className="min-h-screen bg-muted/30 py-12">
|
|
114
|
-
<div className="container mx-auto px-4">
|
|
115
|
-
{/* Hero Section */}
|
|
116
|
-
<div className="text-center mb-12">
|
|
117
|
-
<h1 className="text-4xl font-bold text-foreground mb-4">
|
|
118
|
-
{t("title")}
|
|
119
|
-
</h1>
|
|
120
|
-
<div className="w-16 h-1 bg-primary mx-auto mb-6"></div>
|
|
121
|
-
<p className="text-lg text-muted-foreground max-w-xl mx-auto">
|
|
122
|
-
{t("description")}
|
|
123
|
-
</p>
|
|
124
|
-
</div>
|
|
125
|
-
|
|
126
|
-
<div className="max-w-md mx-auto">
|
|
127
|
-
<Card>
|
|
128
|
-
<CardHeader>
|
|
129
|
-
<CardTitle className="flex items-center gap-2">
|
|
130
|
-
<UserPlus className="w-5 h-5 text-primary" />
|
|
131
|
-
{t("cardTitle")}
|
|
132
|
-
</CardTitle>
|
|
133
|
-
</CardHeader>
|
|
134
|
-
<CardContent>
|
|
135
|
-
<form onSubmit={handleSubmit} className="space-y-6">
|
|
136
|
-
<div>
|
|
137
|
-
<Label htmlFor="username">{t("username")} *</Label>
|
|
138
|
-
<Input
|
|
139
|
-
id="username"
|
|
140
|
-
name="username"
|
|
141
|
-
type="text"
|
|
142
|
-
value={formData.username}
|
|
143
|
-
onChange={handleChange}
|
|
144
|
-
placeholder={t("usernamePlaceholder")}
|
|
145
|
-
required
|
|
146
|
-
className="mt-1"
|
|
147
|
-
autoComplete="username"
|
|
148
|
-
/>
|
|
149
|
-
</div>
|
|
150
|
-
|
|
151
|
-
<div>
|
|
152
|
-
<Label htmlFor="email">{t("email")} *</Label>
|
|
153
|
-
<Input
|
|
154
|
-
id="email"
|
|
155
|
-
name="email"
|
|
156
|
-
type="email"
|
|
157
|
-
value={formData.email}
|
|
158
|
-
onChange={handleChange}
|
|
159
|
-
placeholder={t("emailPlaceholder")}
|
|
160
|
-
required
|
|
161
|
-
className="mt-1"
|
|
162
|
-
autoComplete="email"
|
|
163
|
-
/>
|
|
164
|
-
</div>
|
|
165
|
-
|
|
166
|
-
<div>
|
|
167
|
-
<Label htmlFor="password">{t("password")} *</Label>
|
|
168
|
-
<div className="relative">
|
|
169
|
-
<Input
|
|
170
|
-
id="password"
|
|
171
|
-
name="password"
|
|
172
|
-
type={showPassword ? "text" : "password"}
|
|
173
|
-
value={formData.password}
|
|
174
|
-
onChange={handleChange}
|
|
175
|
-
placeholder={t("passwordPlaceholder")}
|
|
176
|
-
required
|
|
177
|
-
className="mt-1 pr-10"
|
|
178
|
-
autoComplete="new-password"
|
|
179
|
-
/>
|
|
180
|
-
<button
|
|
181
|
-
type="button"
|
|
182
|
-
onClick={() => setShowPassword(!showPassword)}
|
|
183
|
-
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
|
184
|
-
>
|
|
185
|
-
{showPassword ? (
|
|
186
|
-
<EyeOff className="w-4 h-4" />
|
|
187
|
-
) : (
|
|
188
|
-
<Eye className="w-4 h-4" />
|
|
189
|
-
)}
|
|
190
|
-
</button>
|
|
191
|
-
</div>
|
|
192
|
-
</div>
|
|
193
|
-
|
|
194
|
-
<div>
|
|
195
|
-
<Label htmlFor="confirmPassword">
|
|
196
|
-
{t("confirmPassword")} *
|
|
197
|
-
</Label>
|
|
198
|
-
<Input
|
|
199
|
-
id="confirmPassword"
|
|
200
|
-
name="confirmPassword"
|
|
201
|
-
type={showPassword ? "text" : "password"}
|
|
202
|
-
value={formData.confirmPassword}
|
|
203
|
-
onChange={handleChange}
|
|
204
|
-
placeholder={t("confirmPasswordPlaceholder")}
|
|
205
|
-
required
|
|
206
|
-
className="mt-1"
|
|
207
|
-
autoComplete="new-password"
|
|
208
|
-
/>
|
|
209
|
-
</div>
|
|
210
|
-
|
|
211
|
-
{error && (
|
|
212
|
-
<div className="p-4 bg-red-50 border border-red-200 rounded-lg">
|
|
213
|
-
<p className="text-red-800 text-sm font-medium">
|
|
214
|
-
{error}
|
|
215
|
-
</p>
|
|
216
|
-
</div>
|
|
217
|
-
)}
|
|
218
|
-
|
|
219
|
-
<Button
|
|
220
|
-
type="submit"
|
|
221
|
-
size="lg"
|
|
222
|
-
className="w-full"
|
|
223
|
-
disabled={isSubmitting}
|
|
224
|
-
>
|
|
225
|
-
{isSubmitting ? (
|
|
226
|
-
<>
|
|
227
|
-
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2" />
|
|
228
|
-
{t("submitting")}
|
|
229
|
-
</>
|
|
230
|
-
) : (
|
|
231
|
-
t("submit")
|
|
232
|
-
)}
|
|
233
|
-
</Button>
|
|
234
|
-
|
|
235
|
-
<div className="text-center text-sm text-muted-foreground">
|
|
236
|
-
{t("hasAccount")}{" "}
|
|
237
|
-
<Link
|
|
238
|
-
to="/login"
|
|
239
|
-
className="text-primary hover:underline font-medium"
|
|
240
|
-
>
|
|
241
|
-
{t("loginLink")}
|
|
242
|
-
</Link>
|
|
243
|
-
</div>
|
|
244
|
-
</form>
|
|
245
|
-
</CardContent>
|
|
246
|
-
</Card>
|
|
247
|
-
</div>
|
|
248
|
-
</div>
|
|
249
|
-
</div>
|
|
250
|
-
</Layout>
|
|
251
|
-
);
|
|
252
|
-
}
|
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
-
import {
|
|
3
|
-
useAuthStore,
|
|
4
|
-
type User,
|
|
5
|
-
type AuthTokens,
|
|
6
|
-
} from "@/modules/auth/auth-store";
|
|
7
|
-
import { customerClient } from "@/modules/api/customer-client";
|
|
8
|
-
|
|
9
|
-
// Refresh token 1 minute before expiry
|
|
10
|
-
const REFRESH_BUFFER_MS = 60 * 1000;
|
|
11
|
-
|
|
12
|
-
export function useAuth() {
|
|
13
|
-
const {
|
|
14
|
-
user,
|
|
15
|
-
tokens,
|
|
16
|
-
isAuthenticated,
|
|
17
|
-
setAuth,
|
|
18
|
-
updateTokens,
|
|
19
|
-
clearAuth,
|
|
20
|
-
isTokenExpired,
|
|
21
|
-
getTimeUntilExpiry,
|
|
22
|
-
} = useAuthStore();
|
|
23
|
-
|
|
24
|
-
const refreshTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
25
|
-
const isRefreshingRef = useRef(false);
|
|
26
|
-
|
|
27
|
-
// Refresh token using the refresh token
|
|
28
|
-
const refreshAccessToken = useCallback(async (): Promise<boolean> => {
|
|
29
|
-
const currentTokens = useAuthStore.getState().tokens;
|
|
30
|
-
|
|
31
|
-
// Don't refresh if no refresh token exists
|
|
32
|
-
if (!currentTokens?.refreshToken || isRefreshingRef.current) {
|
|
33
|
-
console.log("⚠️ No refresh token available, skipping refresh");
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
isRefreshingRef.current = true;
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
// Make a refresh request using the axios instance directly
|
|
41
|
-
const response = await customerClient.axios.post<{
|
|
42
|
-
accessToken: string;
|
|
43
|
-
refreshToken?: string;
|
|
44
|
-
expiresIn?: number;
|
|
45
|
-
}>("/auth/refresh", {
|
|
46
|
-
refreshToken: currentTokens.refreshToken,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const { accessToken, refreshToken, expiresIn } = response.data;
|
|
50
|
-
|
|
51
|
-
// Validate response has required data
|
|
52
|
-
if (!accessToken) {
|
|
53
|
-
console.error("❌ Refresh response missing accessToken");
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const newTokens: AuthTokens = {
|
|
58
|
-
accessToken,
|
|
59
|
-
refreshToken: refreshToken || currentTokens.refreshToken,
|
|
60
|
-
idToken: currentTokens.idToken, // Preserve existing idToken
|
|
61
|
-
encryptionKey: currentTokens.encryptionKey, // Preserve existing encryptionKey
|
|
62
|
-
expiresAt: expiresIn ? Date.now() + expiresIn * 1000 : undefined,
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
customerClient.setToken(accessToken);
|
|
66
|
-
updateTokens(newTokens);
|
|
67
|
-
|
|
68
|
-
console.log("✅ Token refreshed successfully");
|
|
69
|
-
return true;
|
|
70
|
-
} catch (error) {
|
|
71
|
-
console.error("❌ Token refresh failed:", error);
|
|
72
|
-
// DON'T clear auth on refresh failure - just return false
|
|
73
|
-
// User can still use their existing token until it expires
|
|
74
|
-
return false;
|
|
75
|
-
} finally {
|
|
76
|
-
isRefreshingRef.current = false;
|
|
77
|
-
}
|
|
78
|
-
}, [updateTokens]);
|
|
79
|
-
|
|
80
|
-
// Schedule automatic token refresh
|
|
81
|
-
const scheduleTokenRefresh = useCallback(() => {
|
|
82
|
-
// Clear any existing timeout
|
|
83
|
-
if (refreshTimeoutRef.current) {
|
|
84
|
-
clearTimeout(refreshTimeoutRef.current);
|
|
85
|
-
refreshTimeoutRef.current = null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const timeUntilExpiry = getTimeUntilExpiry();
|
|
89
|
-
|
|
90
|
-
// Only schedule if we have an expiry time and a refresh token
|
|
91
|
-
if (timeUntilExpiry === null || !tokens?.refreshToken) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Calculate when to refresh (REFRESH_BUFFER_MS before expiry)
|
|
96
|
-
const refreshIn = Math.max(timeUntilExpiry - REFRESH_BUFFER_MS, 0);
|
|
97
|
-
|
|
98
|
-
// Don't schedule if expiry is too far in the future (> 24 hours)
|
|
99
|
-
if (refreshIn > 24 * 60 * 60 * 1000) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
refreshTimeoutRef.current = setTimeout(async () => {
|
|
104
|
-
const success = await refreshAccessToken();
|
|
105
|
-
if (success) {
|
|
106
|
-
// Reschedule for the new token
|
|
107
|
-
scheduleTokenRefresh();
|
|
108
|
-
}
|
|
109
|
-
}, refreshIn);
|
|
110
|
-
}, [getTimeUntilExpiry, tokens?.refreshToken, refreshAccessToken]);
|
|
111
|
-
|
|
112
|
-
// Sync token with API client and set up refresh on mount and token changes
|
|
113
|
-
useEffect(() => {
|
|
114
|
-
if (tokens?.accessToken) {
|
|
115
|
-
console.log("🔑 Setting token in API client");
|
|
116
|
-
customerClient.setToken(tokens.accessToken);
|
|
117
|
-
|
|
118
|
-
// Only try to refresh if we have a refresh token AND token is expired
|
|
119
|
-
if (isTokenExpired() && tokens.refreshToken) {
|
|
120
|
-
console.log("⏰ Token expired, attempting refresh...");
|
|
121
|
-
refreshAccessToken().then((success) => {
|
|
122
|
-
if (success) {
|
|
123
|
-
scheduleTokenRefresh();
|
|
124
|
-
} else {
|
|
125
|
-
console.log("⚠️ Refresh failed, but keeping existing token");
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
} else if (tokens.refreshToken) {
|
|
129
|
-
// Only schedule refresh if we have a refresh token
|
|
130
|
-
scheduleTokenRefresh();
|
|
131
|
-
}
|
|
132
|
-
} else if (tokens && Object.keys(tokens).length === 0) {
|
|
133
|
-
// tokens is empty object {} - this shouldn't happen, log it
|
|
134
|
-
console.warn("⚠️ Tokens object is empty, this may indicate a bug");
|
|
135
|
-
} else {
|
|
136
|
-
customerClient.setToken(null);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Cleanup timeout on unmount
|
|
140
|
-
return () => {
|
|
141
|
-
if (refreshTimeoutRef.current) {
|
|
142
|
-
clearTimeout(refreshTimeoutRef.current);
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
}, [
|
|
146
|
-
tokens?.accessToken,
|
|
147
|
-
tokens?.refreshToken,
|
|
148
|
-
isTokenExpired,
|
|
149
|
-
refreshAccessToken,
|
|
150
|
-
scheduleTokenRefresh,
|
|
151
|
-
]);
|
|
152
|
-
|
|
153
|
-
// Set up axios interceptor for 401 responses (token expired during request)
|
|
154
|
-
useEffect(() => {
|
|
155
|
-
const interceptorId = customerClient.axios.interceptors.response.use(
|
|
156
|
-
(response) => response,
|
|
157
|
-
async (error) => {
|
|
158
|
-
const originalRequest = error.config;
|
|
159
|
-
|
|
160
|
-
// Skip refresh for auth endpoints to prevent infinite loops
|
|
161
|
-
const isAuthEndpoint = originalRequest?.url?.includes("/auth/");
|
|
162
|
-
|
|
163
|
-
// If we get a 401 and haven't retried yet, try to refresh
|
|
164
|
-
if (
|
|
165
|
-
error.response?.status === 401 &&
|
|
166
|
-
!originalRequest._retry &&
|
|
167
|
-
tokens?.refreshToken &&
|
|
168
|
-
!isAuthEndpoint
|
|
169
|
-
) {
|
|
170
|
-
originalRequest._retry = true;
|
|
171
|
-
|
|
172
|
-
const success = await refreshAccessToken();
|
|
173
|
-
if (success) {
|
|
174
|
-
// Retry the original request with new token
|
|
175
|
-
const newTokens = useAuthStore.getState().tokens;
|
|
176
|
-
if (newTokens?.accessToken) {
|
|
177
|
-
originalRequest.headers.Authorization = `Bearer ${newTokens.accessToken}`;
|
|
178
|
-
return customerClient.axios(originalRequest);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return Promise.reject(error);
|
|
184
|
-
},
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
return () => {
|
|
188
|
-
customerClient.axios.interceptors.response.eject(interceptorId);
|
|
189
|
-
};
|
|
190
|
-
}, [tokens?.refreshToken, refreshAccessToken]);
|
|
191
|
-
|
|
192
|
-
const login = useCallback(async (username: string, password: string) => {
|
|
193
|
-
const response = await customerClient.auth.login({ username, password });
|
|
194
|
-
|
|
195
|
-
console.log("🔐 Login response:", response);
|
|
196
|
-
console.log("🔐 accessToken:", response.accessToken);
|
|
197
|
-
console.log("🔐 refreshToken:", response.refreshToken);
|
|
198
|
-
console.log("🔐 encryptionKey:", response.encryptionKey);
|
|
199
|
-
|
|
200
|
-
const newTokens: AuthTokens = {
|
|
201
|
-
accessToken: response.accessToken,
|
|
202
|
-
refreshToken: response.refreshToken,
|
|
203
|
-
idToken: response.idToken,
|
|
204
|
-
encryptionKey: response.encryptionKey,
|
|
205
|
-
expiresAt: response.expiresIn
|
|
206
|
-
? Date.now() + response.expiresIn * 1000
|
|
207
|
-
: undefined,
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
console.log("🔐 newTokens object:", newTokens);
|
|
211
|
-
|
|
212
|
-
const newUser: User = {
|
|
213
|
-
username,
|
|
214
|
-
email: (response as any).email || (response as any).user?.email,
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
customerClient.setToken(newTokens.accessToken);
|
|
218
|
-
setAuth(newUser, newTokens);
|
|
219
|
-
|
|
220
|
-
console.log(
|
|
221
|
-
"🔐 Auth set complete, checking store:",
|
|
222
|
-
useAuthStore.getState().tokens,
|
|
223
|
-
);
|
|
224
|
-
}, []);
|
|
225
|
-
|
|
226
|
-
const register = useCallback(
|
|
227
|
-
async (username: string, email: string, password: string) => {
|
|
228
|
-
await customerClient.auth.register({ username, email, password });
|
|
229
|
-
},
|
|
230
|
-
[],
|
|
231
|
-
);
|
|
232
|
-
|
|
233
|
-
const confirmEmail = useCallback(async (username: string, code: string) => {
|
|
234
|
-
await customerClient.auth.confirm({ username, code });
|
|
235
|
-
}, []);
|
|
236
|
-
|
|
237
|
-
const forgotPassword = useCallback(async (username: string) => {
|
|
238
|
-
await customerClient.auth.forgotPassword({ username });
|
|
239
|
-
}, []);
|
|
240
|
-
|
|
241
|
-
const resetPassword = useCallback(
|
|
242
|
-
async (username: string, code: string, newPassword: string) => {
|
|
243
|
-
await customerClient.auth.resetPassword({ username, code, newPassword });
|
|
244
|
-
},
|
|
245
|
-
[],
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
const logout = useCallback(() => {
|
|
249
|
-
// Clear any scheduled refresh
|
|
250
|
-
if (refreshTimeoutRef.current) {
|
|
251
|
-
clearTimeout(refreshTimeoutRef.current);
|
|
252
|
-
refreshTimeoutRef.current = null;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
customerClient.setToken(null);
|
|
256
|
-
clearAuth();
|
|
257
|
-
}, [clearAuth]);
|
|
258
|
-
|
|
259
|
-
return {
|
|
260
|
-
user,
|
|
261
|
-
token: tokens?.accessToken ?? null,
|
|
262
|
-
tokens,
|
|
263
|
-
isAuthenticated,
|
|
264
|
-
api: customerClient,
|
|
265
|
-
login,
|
|
266
|
-
register,
|
|
267
|
-
confirmEmail,
|
|
268
|
-
forgotPassword,
|
|
269
|
-
resetPassword,
|
|
270
|
-
logout,
|
|
271
|
-
refreshAccessToken,
|
|
272
|
-
};
|
|
273
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { QueryOptions } from "../core/types";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Data Adapter Interface
|
|
5
|
-
* Implement this interface to create custom data sources
|
|
6
|
-
*/
|
|
7
|
-
export interface IDataAdapter {
|
|
8
|
-
// Connection
|
|
9
|
-
connect(): Promise<void>;
|
|
10
|
-
disconnect(): Promise<void>;
|
|
11
|
-
|
|
12
|
-
// CRUD operations
|
|
13
|
-
findMany<T>(table: string, options?: QueryOptions): Promise<T[]>;
|
|
14
|
-
findOne<T>(table: string, options: QueryOptions): Promise<T | null>;
|
|
15
|
-
findById<T>(table: string, id: number | string): Promise<T | null>;
|
|
16
|
-
create<T>(table: string, data: Partial<T>): Promise<T>;
|
|
17
|
-
update<T>(table: string, id: number | string, data: Partial<T>): Promise<T>;
|
|
18
|
-
delete(table: string, id: number | string): Promise<boolean>;
|
|
19
|
-
|
|
20
|
-
// Utilities
|
|
21
|
-
count(table: string, options?: QueryOptions): Promise<number>;
|
|
22
|
-
|
|
23
|
-
// Raw SQL queries - for complex queries that can't be expressed with QueryOptions
|
|
24
|
-
raw<T>(sql: string, params?: any[]): Promise<T[]>;
|
|
25
|
-
rawOne<T>(sql: string, params?: any[]): Promise<T | null>;
|
|
26
|
-
}
|