@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.
Files changed (161) hide show
  1. package/dist/index.js +214 -135
  2. package/dist/registry/about-page.json +5 -3
  3. package/dist/registry/about-section.json +2 -2
  4. package/dist/registry/announcement-bar.json +43 -0
  5. package/dist/registry/api.json +55 -0
  6. package/dist/registry/auth-core.json +43 -0
  7. package/dist/registry/auth.json +70 -0
  8. package/dist/registry/bento-grid-section.json +1 -1
  9. package/dist/registry/blog-list-page.json +3 -2
  10. package/dist/registry/blog-section.json +2 -2
  11. package/dist/registry/cart-drawer.json +1 -1
  12. package/dist/registry/cart-page.json +5 -4
  13. package/dist/registry/case-study-page.json +48 -0
  14. package/dist/registry/category-section.json +1 -1
  15. package/dist/registry/checkout-page.json +7 -5
  16. package/dist/registry/coming-soon-page-minimal.json +45 -0
  17. package/dist/registry/coming-soon-page.json +47 -0
  18. package/dist/registry/contact-info-grid.json +2 -2
  19. package/dist/registry/contact-page-centered.json +2 -2
  20. package/dist/registry/contact-page-map-overlay.json +4 -3
  21. package/dist/registry/contact-page-map-split.json +4 -3
  22. package/dist/registry/contact-page-split.json +3 -3
  23. package/dist/registry/contact-page.json +5 -3
  24. package/dist/registry/cookie-consent.json +43 -0
  25. package/dist/registry/cookies-page.json +4 -2
  26. package/dist/registry/cta-section.json +2 -2
  27. package/dist/registry/db.json +129 -0
  28. package/dist/registry/docs/about-page.md +5 -0
  29. package/dist/registry/docs/announcement-bar.md +38 -0
  30. package/dist/registry/docs/auth-core.md +64 -0
  31. package/dist/registry/docs/blog-list-page.md +1 -0
  32. package/dist/registry/docs/cart-page.md +1 -0
  33. package/dist/registry/docs/case-study-page.md +39 -0
  34. package/dist/registry/docs/checkout-page.md +3 -1
  35. package/dist/registry/docs/coming-soon-page-minimal.md +32 -0
  36. package/dist/registry/docs/coming-soon-page.md +37 -0
  37. package/dist/registry/docs/contact-page-map-overlay.md +2 -2
  38. package/dist/registry/docs/contact-page-map-split.md +2 -2
  39. package/dist/registry/docs/contact-page.md +5 -0
  40. package/dist/registry/docs/cookie-consent.md +37 -0
  41. package/dist/registry/docs/cookies-page.md +5 -0
  42. package/dist/registry/docs/ecommerce-core.md +4 -3
  43. package/dist/registry/docs/forgot-password-page-split.md +45 -0
  44. package/dist/registry/docs/forgot-password-page.md +46 -0
  45. package/dist/registry/docs/header-ecommerce.md +2 -0
  46. package/dist/registry/docs/hero-carousel.md +37 -0
  47. package/dist/registry/docs/landing-page-app.md +43 -0
  48. package/dist/registry/docs/landing-page-saas.md +41 -0
  49. package/dist/registry/docs/login-page-split.md +13 -4
  50. package/dist/registry/docs/login-page.md +17 -4
  51. package/dist/registry/docs/logo-cloud.md +33 -0
  52. package/dist/registry/docs/masonry-grid.md +37 -0
  53. package/dist/registry/docs/my-orders-page.md +44 -0
  54. package/dist/registry/docs/order-confirmation-page.md +41 -0
  55. package/dist/registry/docs/portfolio-page.md +38 -0
  56. package/dist/registry/docs/pricing-page.md +38 -0
  57. package/dist/registry/docs/privacy-page.md +5 -0
  58. package/dist/registry/docs/product-quick-view.md +37 -0
  59. package/dist/registry/docs/products-page.md +1 -0
  60. package/dist/registry/docs/reading-progress.md +31 -0
  61. package/dist/registry/docs/register-page-split.md +45 -0
  62. package/dist/registry/docs/register-page.md +46 -0
  63. package/dist/registry/docs/reset-password-page-split.md +45 -0
  64. package/dist/registry/docs/reset-password-page.md +36 -0
  65. package/dist/registry/docs/share-buttons.md +37 -0
  66. package/dist/registry/docs/team-page.md +38 -0
  67. package/dist/registry/docs/terms-page.md +5 -0
  68. package/dist/registry/docs/timeline-section.md +37 -0
  69. package/dist/registry/docs/video-hero.md +41 -0
  70. package/dist/registry/ecommerce-core.json +18 -2
  71. package/dist/registry/empty-page.json +1 -1
  72. package/dist/registry/faq-categorized.json +2 -2
  73. package/dist/registry/faq-simple.json +2 -2
  74. package/dist/registry/favorites-blog-block.json +1 -1
  75. package/dist/registry/favorites-ecommerce-block.json +1 -1
  76. package/dist/registry/feature-section.json +2 -2
  77. package/dist/registry/featured-products.json +1 -1
  78. package/dist/registry/footer-detailed.json +1 -1
  79. package/dist/registry/footer-minimal.json +3 -3
  80. package/dist/registry/footer.json +2 -2
  81. package/dist/registry/forgot-password-page-split.json +50 -0
  82. package/dist/registry/forgot-password-page.json +51 -0
  83. package/dist/registry/header-ecommerce.json +4 -2
  84. package/dist/registry/header-mega.json +2 -2
  85. package/dist/registry/header-minimal.json +1 -1
  86. package/dist/registry/header-simple.json +1 -1
  87. package/dist/registry/hero-carousel.json +45 -0
  88. package/dist/registry/hero-cta.json +2 -2
  89. package/dist/registry/hero-gradient.json +2 -2
  90. package/dist/registry/hero-profile.json +1 -1
  91. package/dist/registry/hero.json +2 -2
  92. package/dist/registry/index.json +24 -1
  93. package/dist/registry/landing-page-app.json +47 -0
  94. package/dist/registry/landing-page-saas.json +47 -0
  95. package/dist/registry/login-page-split.json +11 -7
  96. package/dist/registry/login-page.json +4 -4
  97. package/dist/registry/logo-cloud.json +41 -0
  98. package/dist/registry/masonry-grid.json +43 -0
  99. package/dist/registry/my-orders-page.json +52 -0
  100. package/dist/registry/order-confirmation-page.json +49 -0
  101. package/dist/registry/orders-list-block.json +1 -1
  102. package/dist/registry/payment-success-block.json +1 -1
  103. package/dist/registry/portfolio-page.json +45 -0
  104. package/dist/registry/post-detail-block.json +1 -1
  105. package/dist/registry/pricing-page.json +47 -0
  106. package/dist/registry/pricing-section.json +2 -2
  107. package/dist/registry/privacy-page.json +4 -2
  108. package/dist/registry/product-detail-block.json +1 -1
  109. package/dist/registry/product-quick-view.json +46 -0
  110. package/dist/registry/products-page.json +5 -4
  111. package/dist/registry/reading-progress.json +43 -0
  112. package/dist/registry/register-page-split.json +50 -0
  113. package/dist/registry/register-page.json +51 -0
  114. package/dist/registry/related-posts-block.json +1 -1
  115. package/dist/registry/reset-password-page-split.json +50 -0
  116. package/dist/registry/reset-password-page.json +39 -0
  117. package/dist/registry/share-buttons.json +46 -0
  118. package/dist/registry/team-page.json +47 -0
  119. package/dist/registry/terms-page.json +4 -2
  120. package/dist/registry/testimonials-carousel.json +2 -2
  121. package/dist/registry/testimonials-grid.json +2 -2
  122. package/dist/registry/timeline-section.json +43 -0
  123. package/dist/registry/video-hero.json +42 -0
  124. package/package.json +1 -1
  125. package/template/index.html +5 -5
  126. package/template/src/App.tsx +7 -24
  127. package/template/src/components/GoogleAnalytics.tsx +34 -0
  128. package/template/src/components/Layout.tsx +1 -5
  129. package/template/src/components/ScriptInjector.tsx +62 -0
  130. package/template/src/constants/constants.json +8 -2
  131. package/template/src/index.css +1 -0
  132. package/template/src/lang/en/index.json +1 -28
  133. package/template/src/lang/tr/index.json +1 -28
  134. package/template/src/pages/Index.tsx +1 -98
  135. package/template/src/components/Footer.tsx +0 -100
  136. package/template/src/components/Header.tsx +0 -79
  137. package/template/src/components/Hero.tsx +0 -69
  138. package/template/src/modules/api/USAGE.md +0 -515
  139. package/template/src/modules/api/customer-client.ts +0 -20
  140. package/template/src/modules/api/get-error-message.ts +0 -18
  141. package/template/src/modules/api/validation/en.json +0 -29
  142. package/template/src/modules/api/validation/tr.json +0 -29
  143. package/template/src/modules/auth/USAGE.md +0 -248
  144. package/template/src/modules/auth/auth-header-menu.tsx +0 -123
  145. package/template/src/modules/auth/auth-store.ts +0 -57
  146. package/template/src/modules/auth/forgot-password-page.tsx +0 -371
  147. package/template/src/modules/auth/login-page.tsx +0 -183
  148. package/template/src/modules/auth/register-page.tsx +0 -252
  149. package/template/src/modules/auth/use-auth.ts +0 -273
  150. package/template/src/modules/db/adapters/IDataAdapter.ts +0 -26
  151. package/template/src/modules/db/adapters/SqliteAdapter.ts +0 -364
  152. package/template/src/modules/db/adapters/index.ts +0 -2
  153. package/template/src/modules/db/config.ts +0 -59
  154. package/template/src/modules/db/core/DataManager.ts +0 -125
  155. package/template/src/modules/db/core/types.ts +0 -101
  156. package/template/src/modules/db/index.ts +0 -42
  157. package/template/src/modules/db/react/QueryProvider.tsx +0 -16
  158. package/template/src/modules/db/react/index.ts +0 -23
  159. package/template/src/modules/db/react/queryClient.ts +0 -64
  160. package/template/src/modules/db/react/useRepository.ts +0 -400
  161. 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
- }