@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,371 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { Link } 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 {
|
|
13
|
-
Card,
|
|
14
|
-
CardContent,
|
|
15
|
-
CardHeader,
|
|
16
|
-
CardTitle,
|
|
17
|
-
CardDescription,
|
|
18
|
-
} from "@/components/ui/card";
|
|
19
|
-
import { KeyRound, ArrowLeft, Eye, EyeOff, CheckCircle2 } from "lucide-react";
|
|
20
|
-
|
|
21
|
-
type Step = "request" | "reset" | "success";
|
|
22
|
-
|
|
23
|
-
export default function ForgotPasswordPage() {
|
|
24
|
-
const { t } = useTranslation("forgotPassword");
|
|
25
|
-
usePageTitle({ title: t("title", "Forgot Password") });
|
|
26
|
-
|
|
27
|
-
const { forgotPassword, resetPassword } = useAuth();
|
|
28
|
-
|
|
29
|
-
const [step, setStep] = useState<Step>("request");
|
|
30
|
-
const [username, setUsername] = useState("");
|
|
31
|
-
const [code, setCode] = useState("");
|
|
32
|
-
const [newPassword, setNewPassword] = useState("");
|
|
33
|
-
const [confirmPassword, setConfirmPassword] = useState("");
|
|
34
|
-
const [showPassword, setShowPassword] = useState(false);
|
|
35
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
36
|
-
const [error, setError] = useState<string | null>(null);
|
|
37
|
-
|
|
38
|
-
const handleRequestCode = async (e: React.FormEvent) => {
|
|
39
|
-
e.preventDefault();
|
|
40
|
-
setIsSubmitting(true);
|
|
41
|
-
setError(null);
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
await forgotPassword(username);
|
|
45
|
-
toast.success(t("codeSentTitle", "Code Sent!"), {
|
|
46
|
-
description: t(
|
|
47
|
-
"codeSentDesc",
|
|
48
|
-
"A password reset code has been sent to your email.",
|
|
49
|
-
),
|
|
50
|
-
});
|
|
51
|
-
setStep("reset");
|
|
52
|
-
} catch (err) {
|
|
53
|
-
const errorMessage = getErrorMessage(
|
|
54
|
-
err,
|
|
55
|
-
t("errorGeneric", "Failed to send reset code. Please try again."),
|
|
56
|
-
);
|
|
57
|
-
setError(errorMessage);
|
|
58
|
-
toast.error(t("errorTitle", "Error"), {
|
|
59
|
-
description: errorMessage,
|
|
60
|
-
});
|
|
61
|
-
} finally {
|
|
62
|
-
setIsSubmitting(false);
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const handleResetPassword = async (e: React.FormEvent) => {
|
|
67
|
-
e.preventDefault();
|
|
68
|
-
setError(null);
|
|
69
|
-
|
|
70
|
-
// Validate passwords match
|
|
71
|
-
if (newPassword !== confirmPassword) {
|
|
72
|
-
setError(t("passwordMismatch", "Passwords do not match"));
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
setIsSubmitting(true);
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
await resetPassword(username, code, newPassword);
|
|
80
|
-
toast.success(t("resetSuccessTitle", "Password Reset!"), {
|
|
81
|
-
description: t(
|
|
82
|
-
"resetSuccessDesc",
|
|
83
|
-
"Your password has been successfully reset.",
|
|
84
|
-
),
|
|
85
|
-
});
|
|
86
|
-
setStep("success");
|
|
87
|
-
} catch (err) {
|
|
88
|
-
const errorMessage = getErrorMessage(
|
|
89
|
-
err,
|
|
90
|
-
t("errorResetGeneric", "Failed to reset password. Please try again."),
|
|
91
|
-
);
|
|
92
|
-
setError(errorMessage);
|
|
93
|
-
toast.error(t("errorTitle", "Error"), {
|
|
94
|
-
description: errorMessage,
|
|
95
|
-
});
|
|
96
|
-
} finally {
|
|
97
|
-
setIsSubmitting(false);
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
// Success step
|
|
102
|
-
if (step === "success") {
|
|
103
|
-
return (
|
|
104
|
-
<Layout>
|
|
105
|
-
<div className="min-h-screen bg-muted/30 py-12">
|
|
106
|
-
<div className="container mx-auto px-4">
|
|
107
|
-
<div className="max-w-md mx-auto">
|
|
108
|
-
<Card>
|
|
109
|
-
<CardContent className="pt-8 pb-8 text-center">
|
|
110
|
-
<CheckCircle2 className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
|
111
|
-
<h1 className="text-2xl font-bold mb-2">
|
|
112
|
-
{t("successTitle", "Password Reset Successfully!")}
|
|
113
|
-
</h1>
|
|
114
|
-
<p className="text-muted-foreground mb-6">
|
|
115
|
-
{t(
|
|
116
|
-
"successDescription",
|
|
117
|
-
"Your password has been changed. You can now login with your new password.",
|
|
118
|
-
)}
|
|
119
|
-
</p>
|
|
120
|
-
<Button asChild className="w-full">
|
|
121
|
-
<Link to="/login">{t("goToLogin", "Go to Login")}</Link>
|
|
122
|
-
</Button>
|
|
123
|
-
</CardContent>
|
|
124
|
-
</Card>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
</div>
|
|
128
|
-
</Layout>
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return (
|
|
133
|
-
<Layout>
|
|
134
|
-
<div className="min-h-screen bg-muted/30 py-12">
|
|
135
|
-
<div className="container mx-auto px-4">
|
|
136
|
-
{/* Hero Section */}
|
|
137
|
-
<div className="text-center mb-12">
|
|
138
|
-
<h1 className="text-4xl font-bold text-foreground mb-4">
|
|
139
|
-
{t("title", "Forgot Password")}
|
|
140
|
-
</h1>
|
|
141
|
-
<div className="w-16 h-1 bg-primary mx-auto mb-6"></div>
|
|
142
|
-
<p className="text-lg text-muted-foreground max-w-xl mx-auto">
|
|
143
|
-
{step === "request"
|
|
144
|
-
? t(
|
|
145
|
-
"descriptionRequest",
|
|
146
|
-
"Enter your username and we'll send you a code to reset your password.",
|
|
147
|
-
)
|
|
148
|
-
: t(
|
|
149
|
-
"descriptionReset",
|
|
150
|
-
"Enter the code sent to your email and your new password.",
|
|
151
|
-
)}
|
|
152
|
-
</p>
|
|
153
|
-
</div>
|
|
154
|
-
|
|
155
|
-
<div className="max-w-md mx-auto">
|
|
156
|
-
<Card>
|
|
157
|
-
<CardHeader>
|
|
158
|
-
<CardTitle className="flex items-center gap-2">
|
|
159
|
-
<KeyRound className="w-5 h-5 text-primary" />
|
|
160
|
-
{step === "request"
|
|
161
|
-
? t("cardTitleRequest", "Request Reset Code")
|
|
162
|
-
: t("cardTitleReset", "Reset Password")}
|
|
163
|
-
</CardTitle>
|
|
164
|
-
<CardDescription>
|
|
165
|
-
{step === "request"
|
|
166
|
-
? t("cardDescRequest", "Step 1 of 2: Request a reset code")
|
|
167
|
-
: t(
|
|
168
|
-
"cardDescReset",
|
|
169
|
-
"Step 2 of 2: Enter code and new password",
|
|
170
|
-
)}
|
|
171
|
-
</CardDescription>
|
|
172
|
-
</CardHeader>
|
|
173
|
-
<CardContent>
|
|
174
|
-
{step === "request" ? (
|
|
175
|
-
// Step 1: Request Code
|
|
176
|
-
<form onSubmit={handleRequestCode} className="space-y-6">
|
|
177
|
-
<div>
|
|
178
|
-
<Label htmlFor="username">
|
|
179
|
-
{t("username", "Username")} *
|
|
180
|
-
</Label>
|
|
181
|
-
<Input
|
|
182
|
-
id="username"
|
|
183
|
-
type="text"
|
|
184
|
-
value={username}
|
|
185
|
-
onChange={(e) => setUsername(e.target.value)}
|
|
186
|
-
placeholder={t(
|
|
187
|
-
"usernamePlaceholder",
|
|
188
|
-
"Enter your username",
|
|
189
|
-
)}
|
|
190
|
-
required
|
|
191
|
-
className="mt-1"
|
|
192
|
-
autoComplete="username"
|
|
193
|
-
/>
|
|
194
|
-
</div>
|
|
195
|
-
|
|
196
|
-
{error && (
|
|
197
|
-
<div className="p-4 bg-red-50 border border-red-200 rounded-lg">
|
|
198
|
-
<p className="text-red-800 text-sm font-medium">
|
|
199
|
-
{error}
|
|
200
|
-
</p>
|
|
201
|
-
</div>
|
|
202
|
-
)}
|
|
203
|
-
|
|
204
|
-
<Button
|
|
205
|
-
type="submit"
|
|
206
|
-
size="lg"
|
|
207
|
-
className="w-full"
|
|
208
|
-
disabled={isSubmitting}
|
|
209
|
-
>
|
|
210
|
-
{isSubmitting ? (
|
|
211
|
-
<>
|
|
212
|
-
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2" />
|
|
213
|
-
{t("sending", "Sending...")}
|
|
214
|
-
</>
|
|
215
|
-
) : (
|
|
216
|
-
t("sendCode", "Send Reset Code")
|
|
217
|
-
)}
|
|
218
|
-
</Button>
|
|
219
|
-
|
|
220
|
-
<div className="text-center">
|
|
221
|
-
<Link
|
|
222
|
-
to="/login"
|
|
223
|
-
className="text-sm text-muted-foreground hover:text-primary inline-flex items-center gap-1"
|
|
224
|
-
>
|
|
225
|
-
<ArrowLeft className="w-4 h-4" />
|
|
226
|
-
{t("backToLogin", "Back to Login")}
|
|
227
|
-
</Link>
|
|
228
|
-
</div>
|
|
229
|
-
</form>
|
|
230
|
-
) : (
|
|
231
|
-
// Step 2: Reset Password
|
|
232
|
-
<form onSubmit={handleResetPassword} className="space-y-6">
|
|
233
|
-
<div className="p-3 bg-muted rounded-lg text-sm">
|
|
234
|
-
<span className="text-muted-foreground">
|
|
235
|
-
{t("codeFor", "Reset code for:")}{" "}
|
|
236
|
-
</span>
|
|
237
|
-
<span className="font-medium">{username}</span>
|
|
238
|
-
</div>
|
|
239
|
-
|
|
240
|
-
<div>
|
|
241
|
-
<Label htmlFor="code">{t("code", "Reset Code")} *</Label>
|
|
242
|
-
<Input
|
|
243
|
-
id="code"
|
|
244
|
-
type="text"
|
|
245
|
-
value={code}
|
|
246
|
-
onChange={(e) => setCode(e.target.value)}
|
|
247
|
-
placeholder={t("codePlaceholder", "Enter 6-digit code")}
|
|
248
|
-
required
|
|
249
|
-
className="mt-1"
|
|
250
|
-
maxLength={6}
|
|
251
|
-
/>
|
|
252
|
-
</div>
|
|
253
|
-
|
|
254
|
-
<div>
|
|
255
|
-
<Label htmlFor="newPassword">
|
|
256
|
-
{t("newPassword", "New Password")} *
|
|
257
|
-
</Label>
|
|
258
|
-
<div className="relative">
|
|
259
|
-
<Input
|
|
260
|
-
id="newPassword"
|
|
261
|
-
type={showPassword ? "text" : "password"}
|
|
262
|
-
value={newPassword}
|
|
263
|
-
onChange={(e) => setNewPassword(e.target.value)}
|
|
264
|
-
placeholder={t(
|
|
265
|
-
"newPasswordPlaceholder",
|
|
266
|
-
"Enter new password",
|
|
267
|
-
)}
|
|
268
|
-
required
|
|
269
|
-
className="mt-1 pr-10"
|
|
270
|
-
autoComplete="new-password"
|
|
271
|
-
/>
|
|
272
|
-
<button
|
|
273
|
-
type="button"
|
|
274
|
-
onClick={() => setShowPassword(!showPassword)}
|
|
275
|
-
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
|
276
|
-
>
|
|
277
|
-
{showPassword ? (
|
|
278
|
-
<EyeOff className="w-4 h-4" />
|
|
279
|
-
) : (
|
|
280
|
-
<Eye className="w-4 h-4" />
|
|
281
|
-
)}
|
|
282
|
-
</button>
|
|
283
|
-
</div>
|
|
284
|
-
<p className="text-xs text-muted-foreground mt-1">
|
|
285
|
-
{t(
|
|
286
|
-
"passwordRequirements",
|
|
287
|
-
"At least 8 characters, 1 letter and 1 number",
|
|
288
|
-
)}
|
|
289
|
-
</p>
|
|
290
|
-
</div>
|
|
291
|
-
|
|
292
|
-
<div>
|
|
293
|
-
<Label htmlFor="confirmPassword">
|
|
294
|
-
{t("confirmPassword", "Confirm Password")} *
|
|
295
|
-
</Label>
|
|
296
|
-
<Input
|
|
297
|
-
id="confirmPassword"
|
|
298
|
-
type={showPassword ? "text" : "password"}
|
|
299
|
-
value={confirmPassword}
|
|
300
|
-
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
301
|
-
placeholder={t(
|
|
302
|
-
"confirmPasswordPlaceholder",
|
|
303
|
-
"Confirm new password",
|
|
304
|
-
)}
|
|
305
|
-
required
|
|
306
|
-
className="mt-1"
|
|
307
|
-
autoComplete="new-password"
|
|
308
|
-
/>
|
|
309
|
-
</div>
|
|
310
|
-
|
|
311
|
-
{error && (
|
|
312
|
-
<div className="p-4 bg-red-50 border border-red-200 rounded-lg">
|
|
313
|
-
<p className="text-red-800 text-sm font-medium">
|
|
314
|
-
{error}
|
|
315
|
-
</p>
|
|
316
|
-
</div>
|
|
317
|
-
)}
|
|
318
|
-
|
|
319
|
-
<Button
|
|
320
|
-
type="submit"
|
|
321
|
-
size="lg"
|
|
322
|
-
className="w-full"
|
|
323
|
-
disabled={isSubmitting}
|
|
324
|
-
>
|
|
325
|
-
{isSubmitting ? (
|
|
326
|
-
<>
|
|
327
|
-
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2" />
|
|
328
|
-
{t("resetting", "Resetting...")}
|
|
329
|
-
</>
|
|
330
|
-
) : (
|
|
331
|
-
t("resetPassword", "Reset Password")
|
|
332
|
-
)}
|
|
333
|
-
</Button>
|
|
334
|
-
|
|
335
|
-
<div className="flex justify-between">
|
|
336
|
-
<button
|
|
337
|
-
type="button"
|
|
338
|
-
onClick={() => {
|
|
339
|
-
setStep("request");
|
|
340
|
-
setCode("");
|
|
341
|
-
setNewPassword("");
|
|
342
|
-
setConfirmPassword("");
|
|
343
|
-
setError(null);
|
|
344
|
-
}}
|
|
345
|
-
className="text-sm text-muted-foreground hover:text-primary"
|
|
346
|
-
>
|
|
347
|
-
{t("changeUsername", "Change username")}
|
|
348
|
-
</button>
|
|
349
|
-
<button
|
|
350
|
-
type="button"
|
|
351
|
-
onClick={() =>
|
|
352
|
-
handleRequestCode({
|
|
353
|
-
preventDefault: () => {},
|
|
354
|
-
} as React.FormEvent)
|
|
355
|
-
}
|
|
356
|
-
className="text-sm text-primary hover:underline"
|
|
357
|
-
disabled={isSubmitting}
|
|
358
|
-
>
|
|
359
|
-
{t("resendCode", "Resend code")}
|
|
360
|
-
</button>
|
|
361
|
-
</div>
|
|
362
|
-
</form>
|
|
363
|
-
)}
|
|
364
|
-
</CardContent>
|
|
365
|
-
</Card>
|
|
366
|
-
</div>
|
|
367
|
-
</div>
|
|
368
|
-
</div>
|
|
369
|
-
</Layout>
|
|
370
|
-
);
|
|
371
|
-
}
|
|
@@ -1,183 +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 { LogIn, Eye, EyeOff } from "lucide-react";
|
|
14
|
-
|
|
15
|
-
export default function LoginPage() {
|
|
16
|
-
const { t } = useTranslation("login");
|
|
17
|
-
usePageTitle({ title: t("title") });
|
|
18
|
-
|
|
19
|
-
const navigate = useNavigate();
|
|
20
|
-
const { login, isAuthenticated } = useAuth();
|
|
21
|
-
|
|
22
|
-
const [formData, setFormData] = useState({
|
|
23
|
-
username: "",
|
|
24
|
-
password: "",
|
|
25
|
-
});
|
|
26
|
-
const [showPassword, setShowPassword] = useState(false);
|
|
27
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
28
|
-
const [error, setError] = useState<string | null>(null);
|
|
29
|
-
|
|
30
|
-
// Redirect when authenticated (works for both initial load and after login)
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
if (isAuthenticated) {
|
|
33
|
-
navigate("/", { replace: true });
|
|
34
|
-
}
|
|
35
|
-
}, [isAuthenticated, navigate]);
|
|
36
|
-
|
|
37
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
38
|
-
e.preventDefault();
|
|
39
|
-
setIsSubmitting(true);
|
|
40
|
-
setError(null);
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
await login(formData.username, formData.password);
|
|
44
|
-
toast.success(t("toastSuccessTitle", "Welcome back!"), {
|
|
45
|
-
description: t("toastSuccessDesc", "You have successfully logged in."),
|
|
46
|
-
});
|
|
47
|
-
navigate("/", { replace: true });
|
|
48
|
-
} catch (err) {
|
|
49
|
-
const errorMessage = getErrorMessage(err, t("errorGeneric"));
|
|
50
|
-
setError(errorMessage);
|
|
51
|
-
toast.error(t("toastErrorTitle", "Login failed"), {
|
|
52
|
-
description: errorMessage,
|
|
53
|
-
});
|
|
54
|
-
} finally {
|
|
55
|
-
setIsSubmitting(false);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
60
|
-
setFormData((prev) => ({
|
|
61
|
-
...prev,
|
|
62
|
-
[e.target.name]: e.target.value,
|
|
63
|
-
}));
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<Layout>
|
|
68
|
-
<div className="min-h-screen bg-muted/30 py-12">
|
|
69
|
-
<div className="container mx-auto px-4">
|
|
70
|
-
{/* Hero Section */}
|
|
71
|
-
<div className="text-center mb-12">
|
|
72
|
-
<h1 className="text-4xl font-bold text-foreground mb-4">
|
|
73
|
-
{t("title")}
|
|
74
|
-
</h1>
|
|
75
|
-
<div className="w-16 h-1 bg-primary mx-auto mb-6"></div>
|
|
76
|
-
<p className="text-lg text-muted-foreground max-w-xl mx-auto">
|
|
77
|
-
{t("description")}
|
|
78
|
-
</p>
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
|
-
<div className="max-w-md mx-auto">
|
|
82
|
-
<Card>
|
|
83
|
-
<CardHeader>
|
|
84
|
-
<CardTitle className="flex items-center gap-2">
|
|
85
|
-
<LogIn className="w-5 h-5 text-primary" />
|
|
86
|
-
{t("cardTitle")}
|
|
87
|
-
</CardTitle>
|
|
88
|
-
</CardHeader>
|
|
89
|
-
<CardContent>
|
|
90
|
-
<form onSubmit={handleSubmit} className="space-y-6">
|
|
91
|
-
<div>
|
|
92
|
-
<Label htmlFor="username">{t("username")} *</Label>
|
|
93
|
-
<Input
|
|
94
|
-
id="username"
|
|
95
|
-
name="username"
|
|
96
|
-
type="text"
|
|
97
|
-
value={formData.username}
|
|
98
|
-
onChange={handleChange}
|
|
99
|
-
placeholder={t("usernamePlaceholder")}
|
|
100
|
-
required
|
|
101
|
-
className="mt-1"
|
|
102
|
-
autoComplete="username"
|
|
103
|
-
/>
|
|
104
|
-
</div>
|
|
105
|
-
|
|
106
|
-
<div>
|
|
107
|
-
<div className="flex items-center justify-between">
|
|
108
|
-
<Label htmlFor="password">{t("password")} *</Label>
|
|
109
|
-
<Link
|
|
110
|
-
to="/forgot-password"
|
|
111
|
-
className="text-sm text-primary hover:underline"
|
|
112
|
-
>
|
|
113
|
-
{t("forgotPassword", "Forgot password?")}
|
|
114
|
-
</Link>
|
|
115
|
-
</div>
|
|
116
|
-
<div className="relative">
|
|
117
|
-
<Input
|
|
118
|
-
id="password"
|
|
119
|
-
name="password"
|
|
120
|
-
type={showPassword ? "text" : "password"}
|
|
121
|
-
value={formData.password}
|
|
122
|
-
onChange={handleChange}
|
|
123
|
-
placeholder={t("passwordPlaceholder")}
|
|
124
|
-
required
|
|
125
|
-
className="mt-1 pr-10"
|
|
126
|
-
autoComplete="current-password"
|
|
127
|
-
/>
|
|
128
|
-
<button
|
|
129
|
-
type="button"
|
|
130
|
-
onClick={() => setShowPassword(!showPassword)}
|
|
131
|
-
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
|
132
|
-
>
|
|
133
|
-
{showPassword ? (
|
|
134
|
-
<EyeOff className="w-4 h-4" />
|
|
135
|
-
) : (
|
|
136
|
-
<Eye className="w-4 h-4" />
|
|
137
|
-
)}
|
|
138
|
-
</button>
|
|
139
|
-
</div>
|
|
140
|
-
</div>
|
|
141
|
-
|
|
142
|
-
{error && (
|
|
143
|
-
<div className="p-4 bg-red-50 border border-red-200 rounded-lg">
|
|
144
|
-
<p className="text-red-800 text-sm font-medium">
|
|
145
|
-
{error}
|
|
146
|
-
</p>
|
|
147
|
-
</div>
|
|
148
|
-
)}
|
|
149
|
-
|
|
150
|
-
<Button
|
|
151
|
-
type="submit"
|
|
152
|
-
size="lg"
|
|
153
|
-
className="w-full"
|
|
154
|
-
disabled={isSubmitting}
|
|
155
|
-
>
|
|
156
|
-
{isSubmitting ? (
|
|
157
|
-
<>
|
|
158
|
-
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2" />
|
|
159
|
-
{t("submitting")}
|
|
160
|
-
</>
|
|
161
|
-
) : (
|
|
162
|
-
t("submit")
|
|
163
|
-
)}
|
|
164
|
-
</Button>
|
|
165
|
-
|
|
166
|
-
<div className="text-center text-sm text-muted-foreground">
|
|
167
|
-
{t("noAccount")}{" "}
|
|
168
|
-
<Link
|
|
169
|
-
to="/register"
|
|
170
|
-
className="text-primary hover:underline font-medium"
|
|
171
|
-
>
|
|
172
|
-
{t("registerLink")}
|
|
173
|
-
</Link>
|
|
174
|
-
</div>
|
|
175
|
-
</form>
|
|
176
|
-
</CardContent>
|
|
177
|
-
</Card>
|
|
178
|
-
</div>
|
|
179
|
-
</div>
|
|
180
|
-
</div>
|
|
181
|
-
</Layout>
|
|
182
|
-
);
|
|
183
|
-
}
|