@promakeai/cli 0.2.13 → 0.3.1

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.
@@ -2,15 +2,12 @@
2
2
  "name": "login-page-split",
3
3
  "type": "registry:page",
4
4
  "title": "Login Page Split",
5
- "description": "Split-screen login page with form on the left and full-height image on the right. Features email/password fields, remember me checkbox, sign up and forgot password links. Uses customerClient for API and useAuthStore for state management.",
5
+ "description": "Split-screen login page with form on the left and full-height image on the right. Features username/password fields, sign up and forgot password links. Uses useAuth hook from auth-core for authentication.",
6
6
  "registryDependencies": [
7
- "input",
8
- "button",
9
- "checkbox",
10
7
  "auth-core",
11
8
  "api"
12
9
  ],
13
- "usage": "import LoginPageSplit from '@/modules/login-page-split';\n\n<LoginPageSplit\n image=\"/images/login-bg.jpg\"\n/>\n\n• Installed at: src/modules/login-page-split/\n• Customize text: src/modules/login-page-split/lang/*.json\n• API Integration:\n - customerClient.auth.login() for authentication\n - useAuthStore for state management\n - Automatically sets token after login\n• Supports remember me (7 days vs default)\n• Redirects to previous page after login\n• Add to your router as a page component",
10
+ "usage": "import LoginPageSplit from '@/modules/login-page-split';\n\n<LoginPageSplit\n image=\"/images/login-bg.jpg\"\n/>\n\n• Installed at: src/modules/login-page-split/\n• Customize text: src/modules/login-page-split/lang/*.json\n• Uses useAuth() hook from auth-core:\n const { login } = useAuth();\n await login(username, password);\n• Automatically sets token after login\n• Redirects to previous page after login\n• Add to your router as a page component",
14
11
  "route": {
15
12
  "path": "/login",
16
13
  "componentName": "LoginPageSplit"
@@ -26,19 +23,19 @@
26
23
  "path": "login-page-split/login-page-split.tsx",
27
24
  "type": "registry:page",
28
25
  "target": "$modules$/login-page-split/login-page-split.tsx",
29
- "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useLocation } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useAuthStore } from \"@/modules/auth-core/auth-store\";\r\nimport { customerClient } from \"@/modules/api/customer-client\";\r\nimport { getErrorMessage } from \"@/modules/api/get-error-message\";\r\n\r\ninterface LoginPageSplitProps {\r\n image?: string;\r\n}\r\n\r\nexport function LoginPageSplit({\r\n image = \"/images/placeholder.png\",\r\n}: LoginPageSplitProps) {\r\n const { t } = useTranslation(\"login-page-split\");\r\n const navigate = useNavigate();\r\n const location = useLocation();\r\n const setAuth = useAuthStore((state) => state.setAuth);\r\n\r\n const [email, setEmail] = useState(\"\");\r\n const [password, setPassword] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n // Get redirect URL from location state or default to home\r\n const from = (location.state as { from?: string })?.from || \"/\";\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n setIsLoading(true);\r\n\r\n try {\r\n const response = await customerClient.auth.login({\r\n username: email,\r\n password,\r\n });\r\n\r\n // Set auth state\r\n setAuth(\r\n { username: email, email: (response as any).email || email },\r\n {\r\n accessToken: response.accessToken,\r\n refreshToken: response.refreshToken,\r\n idToken: response.idToken,\r\n encryptionKey: response.encryptionKey,\r\n expiresAt: response.expiresIn\r\n ? Date.now() + response.expiresIn * 1000\r\n : undefined,\r\n }\r\n );\r\n\r\n // Set token for API client\r\n customerClient.setToken(response.accessToken);\r\n\r\n toast.success(t(\"loginSuccess\", \"Login successful!\"));\r\n navigate(from, { replace: true });\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"loginError\", \"Login failed. Please check your credentials.\")\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <section className=\"w-full md:grid md:min-h-screen md:grid-cols-2\">\r\n <div className=\"flex items-center justify-center px-4 py-12\">\r\n <div className=\"mx-auto grid w-full max-w-sm gap-6\">\r\n <Logo />\r\n <hr />\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"title\", \"Login\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"subtitle\", \"Enter your details below to login\")}\r\n </p>\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-3 text-sm text-red-600 bg-red-50 dark:bg-red-950 dark:text-red-400 rounded-md\">\r\n {error}\r\n </div>\r\n )}\r\n\r\n <form onSubmit={handleSubmit} className=\"grid gap-4\">\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"email-split\">{t(\"email\", \"Email\")}</Label>\r\n <Input\r\n required\r\n id=\"email-split\"\r\n type=\"email\"\r\n autoComplete=\"username\"\r\n placeholder={t(\"emailPlaceholder\", \"email@example.com\")}\r\n value={email}\r\n onChange={(e) => setEmail(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"password-split\">{t(\"password\", \"Password\")}</Label>\r\n <Input\r\n required\r\n id=\"password-split\"\r\n type=\"password\"\r\n placeholder=\"••••••••••\"\r\n autoComplete=\"current-password\"\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\r\n {isLoading ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"loggingIn\", \"Logging in...\")}\r\n </>\r\n ) : (\r\n t(\"login\", \"Login\")\r\n )}\r\n </Button>\r\n </form>\r\n\r\n <div className=\"flex flex-col gap-4 text-sm\">\r\n <p>\r\n {t(\"noAccount\", \"Don't have an account?\")}{\" \"}\r\n <Link to=\"/register\" className=\"underline\">\r\n {t(\"signUp\", \"Sign up\")}\r\n </Link>\r\n </p>\r\n <Link to=\"/forgot-password\" className=\"underline\">\r\n {t(\"forgotPassword\", \"Forgot your password?\")}\r\n </Link>\r\n </div>\r\n <hr />\r\n <p className=\"text-sm text-muted-foreground\">\r\n © {new Date().getFullYear()} {t(\"copyright\", \"All rights reserved.\")}\r\n </p>\r\n </div>\r\n </div>\r\n <div className=\"hidden p-4 md:block\">\r\n <img\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n width=\"1920\"\r\n height=\"1080\"\r\n alt={t(\"imageAlt\", \"Login background\")}\r\n src={image}\r\n className=\"size-full rounded-lg border bg-muted object-cover object-center\"\r\n />\r\n </div>\r\n </section>\r\n );\r\n}\r\n\r\nexport default LoginPageSplit;\r\n"
26
+ "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useLocation } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { getErrorMessage } from \"@/modules/api\";\r\n\r\ninterface LoginPageSplitProps {\r\n image?: string;\r\n}\r\n\r\nexport function LoginPageSplit({\r\n image = \"/images/placeholder.png\",\r\n}: LoginPageSplitProps) {\r\n const { t } = useTranslation(\"login-page-split\");\r\n const navigate = useNavigate();\r\n const location = useLocation();\r\n const { login } = useAuth();\r\n\r\n const [username, setUsername] = useState(\"\");\r\n const [password, setPassword] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n // Get redirect URL from location state or default to home\r\n const from = (location.state as { from?: string })?.from || \"/\";\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n setIsLoading(true);\r\n\r\n try {\r\n await login(username, password);\r\n toast.success(t(\"loginSuccess\", \"Login successful!\"));\r\n navigate(from, { replace: true });\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"loginError\", \"Login failed. Please check your credentials.\")\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <section className=\"w-full md:grid md:min-h-screen md:grid-cols-2\">\r\n <div className=\"flex items-center justify-center px-4 py-12\">\r\n <div className=\"mx-auto grid w-full max-w-sm gap-6\">\r\n <Logo />\r\n <hr />\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"title\", \"Login\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"subtitle\", \"Enter your details below to login\")}\r\n </p>\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-3 text-sm text-red-600 bg-red-50 dark:bg-red-950 dark:text-red-400 rounded-md\">\r\n {error}\r\n </div>\r\n )}\r\n\r\n <form onSubmit={handleSubmit} className=\"grid gap-4\">\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"username-split\">{t(\"username\", \"Username\")}</Label>\r\n <Input\r\n required\r\n id=\"username-split\"\r\n type=\"text\"\r\n autoComplete=\"username\"\r\n placeholder={t(\"usernamePlaceholder\", \"Enter your username\")}\r\n value={username}\r\n onChange={(e) => setUsername(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"password-split\">{t(\"password\", \"Password\")}</Label>\r\n <Input\r\n required\r\n id=\"password-split\"\r\n type=\"password\"\r\n placeholder=\"••••••••••\"\r\n autoComplete=\"current-password\"\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\r\n {isLoading ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"loggingIn\", \"Logging in...\")}\r\n </>\r\n ) : (\r\n t(\"login\", \"Login\")\r\n )}\r\n </Button>\r\n </form>\r\n\r\n <div className=\"flex flex-col gap-4 text-sm\">\r\n <p>\r\n {t(\"noAccount\", \"Don't have an account?\")}{\" \"}\r\n <Link to=\"/register\" className=\"underline\">\r\n {t(\"signUp\", \"Sign up\")}\r\n </Link>\r\n </p>\r\n <Link to=\"/forgot-password\" className=\"underline\">\r\n {t(\"forgotPassword\", \"Forgot your password?\")}\r\n </Link>\r\n </div>\r\n <hr />\r\n <p className=\"text-sm text-muted-foreground\">\r\n © {new Date().getFullYear()} {t(\"copyright\", \"All rights reserved.\")}\r\n </p>\r\n </div>\r\n </div>\r\n <div className=\"hidden p-4 md:block\">\r\n <img\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n width=\"1920\"\r\n height=\"1080\"\r\n alt={t(\"imageAlt\", \"Login background\")}\r\n src={image}\r\n className=\"size-full rounded-lg border bg-muted object-cover object-center\"\r\n />\r\n </div>\r\n </section>\r\n );\r\n}\r\n\r\nexport default LoginPageSplit;\r\n"
30
27
  },
31
28
  {
32
29
  "path": "login-page-split/lang/en.json",
33
30
  "type": "registry:lang",
34
31
  "target": "$modules$/login-page-split/lang/en.json",
35
- "content": "{\r\n \"title\": \"Login\",\r\n \"subtitle\": \"Enter your details below to login\",\r\n \"email\": \"Email\",\r\n \"emailPlaceholder\": \"email@example.com\",\r\n \"password\": \"Password\",\r\n \"login\": \"Login\",\r\n \"loginGoogle\": \"Login with Google\",\r\n \"noAccount\": \"Don't have an account?\",\r\n \"signUp\": \"Sign up\",\r\n \"forgotPassword\": \"Forgot your password?\",\r\n \"copyright\": \"All rights reserved.\",\r\n \"imageAlt\": \"Login background\"\r\n}\r\n"
32
+ "content": "{\r\n \"title\": \"Login\",\r\n \"subtitle\": \"Enter your details below to login\",\r\n \"username\": \"Username\",\r\n \"usernamePlaceholder\": \"Enter your username\",\r\n \"email\": \"Email\",\r\n \"emailPlaceholder\": \"email@example.com\",\r\n \"password\": \"Password\",\r\n \"login\": \"Login\",\r\n \"loggingIn\": \"Logging in...\",\r\n \"loginSuccess\": \"Login successful!\",\r\n \"loginError\": \"Login failed. Please check your credentials.\",\r\n \"loginGoogle\": \"Login with Google\",\r\n \"noAccount\": \"Don't have an account?\",\r\n \"signUp\": \"Sign up\",\r\n \"forgotPassword\": \"Forgot your password?\",\r\n \"copyright\": \"All rights reserved.\",\r\n \"imageAlt\": \"Login background\"\r\n}\r\n"
36
33
  },
37
34
  {
38
35
  "path": "login-page-split/lang/tr.json",
39
36
  "type": "registry:lang",
40
37
  "target": "$modules$/login-page-split/lang/tr.json",
41
- "content": "{\r\n \"title\": \"Giriş Yap\",\r\n \"subtitle\": \"Giriş yapmak için bilgilerinizi girin\",\r\n \"email\": \"E-posta\",\r\n \"emailPlaceholder\": \"eposta@ornek.com\",\r\n \"password\": \"Şifre\",\r\n \"login\": \"Giriş Yap\",\r\n \"loginGoogle\": \"Google ile Giriş Yap\",\r\n \"noAccount\": \"Hesabınız yok mu?\",\r\n \"signUp\": \"Kayıt Ol\",\r\n \"forgotPassword\": \"Şifrenizi mi unuttunuz?\",\r\n \"copyright\": \"Tüm hakları saklıdır.\",\r\n \"imageAlt\": \"Giriş arka planı\"\r\n}\r\n"
38
+ "content": "{\r\n \"title\": \"Giriş Yap\",\r\n \"subtitle\": \"Giriş yapmak için bilgilerinizi girin\",\r\n \"username\": \"Kullanıcı Adı\",\r\n \"usernamePlaceholder\": \"Kullanıcı adınızı girin\",\r\n \"email\": \"E-posta\",\r\n \"emailPlaceholder\": \"eposta@ornek.com\",\r\n \"password\": \"Şifre\",\r\n \"login\": \"Giriş Yap\",\r\n \"loggingIn\": \"Giriş yapılıyor...\",\r\n \"loginSuccess\": \"Giriş başarılı!\",\r\n \"loginError\": \"Giriş başarısız. Lütfen bilgilerinizi kontrol edin.\",\r\n \"loginGoogle\": \"Google ile Giriş Yap\",\r\n \"noAccount\": \"Hesabınız yok mu?\",\r\n \"signUp\": \"Kayıt Ol\",\r\n \"forgotPassword\": \"Şifrenizi mi unuttunuz?\",\r\n \"copyright\": \"Tüm hakları saklıdır.\",\r\n \"imageAlt\": \"Giriş arka planı\"\r\n}\r\n"
42
39
  }
43
40
  ],
44
41
  "exports": {
@@ -2,15 +2,12 @@
2
2
  "name": "login-page",
3
3
  "type": "registry:page",
4
4
  "title": "Login Page",
5
- "description": "Login page with email/password form, forgot password link, and create account link. Centered card layout with responsive design. Integrated with auth-core for authentication.",
5
+ "description": "Login page with username/password form, forgot password link, and create account link. Centered card layout with responsive design. Uses useAuth hook from auth-core for authentication.",
6
6
  "registryDependencies": [
7
- "button",
8
- "input",
9
- "label",
10
7
  "auth-core",
11
8
  "api"
12
9
  ],
13
- "usage": "import LoginPage from '@/modules/login-page';\n\n<LoginPage />\n\n• Installed at: src/modules/login-page/\n• Customize text: src/modules/login-page/lang/*.json\n• Integrated with auth-core for API authentication\n• On success, redirects to previous page or home",
10
+ "usage": "import LoginPage from '@/modules/login-page';\n\n<LoginPage />\n\n• Installed at: src/modules/login-page/\n• Customize text: src/modules/login-page/lang/*.json\n• Uses useAuth() hook from auth-core:\n const { login } = useAuth();\n await login(username, password);\n• On success, redirects to previous page or home\n• Add to your router as a page component",
14
11
  "route": {
15
12
  "path": "/login",
16
13
  "componentName": "LoginPage"
@@ -26,19 +23,19 @@
26
23
  "path": "login-page/login-page.tsx",
27
24
  "type": "registry:page",
28
25
  "target": "$modules$/login-page/login-page.tsx",
29
- "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useLocation } from \"react-router\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { toast } from \"sonner\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { useAuthStore } from \"@/modules/auth-core/auth-store\";\r\nimport { customerClient } from \"@/modules/api/customer-client\";\r\nimport { getErrorMessage } from \"@/modules/api/get-error-message\";\r\n\r\ninterface LoginPageProps {\r\n className?: string;\r\n}\r\n\r\nexport function LoginPage({ className }: LoginPageProps) {\r\n const { t } = useTranslation(\"login-page\");\r\n usePageTitle({ title: t(\"title\", \"Sign In\") });\r\n const navigate = useNavigate();\r\n const location = useLocation();\r\n const setAuth = useAuthStore((state) => state.setAuth);\r\n\r\n const [email, setEmail] = useState(\"\");\r\n const [password, setPassword] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const from = (location.state as { from?: string })?.from || \"/\";\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n setIsLoading(true);\r\n\r\n try {\r\n const response = await customerClient.auth.login({\r\n username: email,\r\n password,\r\n });\r\n\r\n setAuth(\r\n { username: email, email: (response as any).email || email },\r\n {\r\n accessToken: response.accessToken,\r\n refreshToken: response.refreshToken,\r\n idToken: response.idToken,\r\n encryptionKey: response.encryptionKey,\r\n expiresAt: response.expiresIn\r\n ? Date.now() + response.expiresIn * 1000\r\n : undefined,\r\n }\r\n );\r\n\r\n customerClient.setToken(response.accessToken);\r\n toast.success(t(\"loginSuccess\", \"Login successful!\"));\r\n navigate(from, { replace: true });\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"loginError\", \"Login failed. Please check your credentials.\")\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <section\r\n className={cn(\"flex min-h-screen bg-muted/30 px-4 py-16 md:py-32\", className)}\r\n >\r\n <form\r\n onSubmit={handleSubmit}\r\n className=\"bg-muted m-auto h-fit w-full max-w-sm overflow-hidden rounded-xl border shadow-md\"\r\n >\r\n <div className=\"bg-card -m-px rounded-xl border p-8 pb-6\">\r\n <div className=\"text-center\">\r\n <Link to=\"/\" aria-label=\"go home\" className=\"mx-auto block w-fit\">\r\n <Logo size=\"sm\" />\r\n </Link>\r\n <h1 className=\"mb-1 mt-4 text-xl font-semibold\">\r\n {t(\"title\", \"Sign In\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"subtitle\", \"Welcome back! Sign in to continue\")}\r\n </p>\r\n </div>\r\n\r\n {error && (\r\n <div className=\"mt-4 p-3 text-sm text-red-600 bg-red-50 dark:bg-red-950 dark:text-red-400 rounded-md\">\r\n {error}\r\n </div>\r\n )}\r\n\r\n <div className=\"mt-6 space-y-6\">\r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"email\" className=\"block text-sm\">\r\n {t(\"email\", \"Email\")}\r\n </Label>\r\n <Input\r\n type=\"email\"\r\n required\r\n name=\"email\"\r\n id=\"email\"\r\n value={email}\r\n onChange={(e) => setEmail(e.target.value)}\r\n placeholder={t(\"emailPlaceholder\", \"you@example.com\")}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <div className=\"flex items-center justify-between\">\r\n <Label htmlFor=\"password\" className=\"text-sm\">\r\n {t(\"password\", \"Password\")}\r\n </Label>\r\n <Button asChild variant=\"link\" size=\"sm\" className=\"h-auto p-0\">\r\n <Link to=\"/forgot-password\" className=\"text-xs\">\r\n {t(\"forgotPassword\", \"Forgot password?\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n <Input\r\n type=\"password\"\r\n required\r\n name=\"password\"\r\n id=\"password\"\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n placeholder=\"••••••••\"\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\r\n {isLoading ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"signingIn\", \"Signing in...\")}\r\n </>\r\n ) : (\r\n t(\"signIn\", \"Sign In\")\r\n )}\r\n </Button>\r\n </div>\r\n </div>\r\n\r\n <div className=\"p-3\">\r\n <p className=\"text-center text-sm text-muted-foreground\">\r\n {t(\"noAccount\", \"Don't have an account?\")}\r\n <Button asChild variant=\"link\" className=\"px-2\">\r\n <Link to=\"/register\">{t(\"createAccount\", \"Create account\")}</Link>\r\n </Button>\r\n </p>\r\n </div>\r\n </form>\r\n </section>\r\n );\r\n}\r\n\r\nexport default LoginPage;\r\n"
26
+ "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useLocation } from \"react-router\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { toast } from \"sonner\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { getErrorMessage } from \"@/modules/api\";\r\n\r\ninterface LoginPageProps {\r\n className?: string;\r\n}\r\n\r\nexport function LoginPage({ className }: LoginPageProps) {\r\n const { t } = useTranslation(\"login-page\");\r\n usePageTitle({ title: t(\"title\", \"Sign In\") });\r\n const navigate = useNavigate();\r\n const location = useLocation();\r\n const { login } = useAuth();\r\n\r\n const [username, setUsername] = useState(\"\");\r\n const [password, setPassword] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const from = (location.state as { from?: string })?.from || \"/\";\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n setIsLoading(true);\r\n\r\n try {\r\n await login(username, password);\r\n toast.success(t(\"loginSuccess\", \"Login successful!\"));\r\n navigate(from, { replace: true });\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"loginError\", \"Login failed. Please check your credentials.\")\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <section\r\n className={cn(\"flex min-h-screen bg-muted/30 px-4 py-16 md:py-32\", className)}\r\n >\r\n <form\r\n onSubmit={handleSubmit}\r\n className=\"bg-muted m-auto h-fit w-full max-w-sm overflow-hidden rounded-xl border shadow-md\"\r\n >\r\n <div className=\"bg-card -m-px rounded-xl border p-8 pb-6\">\r\n <div className=\"text-center\">\r\n <Link to=\"/\" aria-label=\"go home\" className=\"mx-auto block w-fit\">\r\n <Logo size=\"sm\" />\r\n </Link>\r\n <h1 className=\"mb-1 mt-4 text-xl font-semibold\">\r\n {t(\"title\", \"Sign In\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"subtitle\", \"Welcome back! Sign in to continue\")}\r\n </p>\r\n </div>\r\n\r\n {error && (\r\n <div className=\"mt-4 p-3 text-sm text-red-600 bg-red-50 dark:bg-red-950 dark:text-red-400 rounded-md\">\r\n {error}\r\n </div>\r\n )}\r\n\r\n <div className=\"mt-6 space-y-6\">\r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"username\" className=\"block text-sm\">\r\n {t(\"username\", \"Username\")}\r\n </Label>\r\n <Input\r\n type=\"text\"\r\n required\r\n name=\"username\"\r\n id=\"username\"\r\n autoComplete=\"username\"\r\n value={username}\r\n onChange={(e) => setUsername(e.target.value)}\r\n placeholder={t(\"usernamePlaceholder\", \"Enter your username\")}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <div className=\"flex items-center justify-between\">\r\n <Label htmlFor=\"password\" className=\"text-sm\">\r\n {t(\"password\", \"Password\")}\r\n </Label>\r\n <Button asChild variant=\"link\" size=\"sm\" className=\"h-auto p-0\">\r\n <Link to=\"/forgot-password\" className=\"text-xs\">\r\n {t(\"forgotPassword\", \"Forgot password?\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n <Input\r\n type=\"password\"\r\n required\r\n name=\"password\"\r\n id=\"password\"\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n placeholder=\"••••••••\"\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\r\n {isLoading ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"signingIn\", \"Signing in...\")}\r\n </>\r\n ) : (\r\n t(\"signIn\", \"Sign In\")\r\n )}\r\n </Button>\r\n </div>\r\n </div>\r\n\r\n <div className=\"p-3\">\r\n <p className=\"text-center text-sm text-muted-foreground\">\r\n {t(\"noAccount\", \"Don't have an account?\")}\r\n <Button asChild variant=\"link\" className=\"px-2\">\r\n <Link to=\"/register\">{t(\"createAccount\", \"Create account\")}</Link>\r\n </Button>\r\n </p>\r\n </div>\r\n </form>\r\n </section>\r\n );\r\n}\r\n\r\nexport default LoginPage;\r\n"
30
27
  },
31
28
  {
32
29
  "path": "login-page/lang/en.json",
33
30
  "type": "registry:lang",
34
31
  "target": "$modules$/login-page/lang/en.json",
35
- "content": "{\r\n \"title\": \"Sign In\",\r\n \"subtitle\": \"Welcome back! Sign in to continue\",\r\n \"email\": \"Email\",\r\n \"emailPlaceholder\": \"you@example.com\",\r\n \"password\": \"Password\",\r\n \"forgotPassword\": \"Forgot password?\",\r\n \"signIn\": \"Sign In\",\r\n \"signingIn\": \"Signing in...\",\r\n \"noAccount\": \"Don't have an account?\",\r\n \"createAccount\": \"Create account\",\r\n \"loginSuccess\": \"Login successful!\",\r\n \"loginError\": \"Login failed. Please check your credentials.\"\r\n}\r\n"
32
+ "content": "{\r\n \"title\": \"Sign In\",\r\n \"subtitle\": \"Welcome back! Sign in to continue\",\r\n \"username\": \"Username\",\r\n \"usernamePlaceholder\": \"Enter your username\",\r\n \"email\": \"Email\",\r\n \"emailPlaceholder\": \"you@example.com\",\r\n \"password\": \"Password\",\r\n \"forgotPassword\": \"Forgot password?\",\r\n \"signIn\": \"Sign In\",\r\n \"signingIn\": \"Signing in...\",\r\n \"noAccount\": \"Don't have an account?\",\r\n \"createAccount\": \"Create account\",\r\n \"loginSuccess\": \"Login successful!\",\r\n \"loginError\": \"Login failed. Please check your credentials.\"\r\n}\r\n"
36
33
  },
37
34
  {
38
35
  "path": "login-page/lang/tr.json",
39
36
  "type": "registry:lang",
40
37
  "target": "$modules$/login-page/lang/tr.json",
41
- "content": "{\r\n \"title\": \"Giriş Yap\",\r\n \"subtitle\": \"Tekrar hoş geldiniz! Devam etmek için giriş yapın\",\r\n \"email\": \"E-posta\",\r\n \"emailPlaceholder\": \"ornek@email.com\",\r\n \"password\": \"Şifre\",\r\n \"forgotPassword\": \"Şifremi unuttum\",\r\n \"signIn\": \"Giriş Yap\",\r\n \"signingIn\": \"Giriş yapılıyor...\",\r\n \"noAccount\": \"Hesabınız yok mu?\",\r\n \"createAccount\": \"Hesap oluştur\",\r\n \"loginSuccess\": \"Giriş başarılı!\",\r\n \"loginError\": \"Giriş başarısız. Lütfen bilgilerinizi kontrol edin.\"\r\n}\r\n"
38
+ "content": "{\r\n \"title\": \"Giriş Yap\",\r\n \"subtitle\": \"Tekrar hoş geldiniz! Devam etmek için giriş yapın\",\r\n \"username\": \"Kullanıcı Adı\",\r\n \"usernamePlaceholder\": \"Kullanıcı adınızı girin\",\r\n \"email\": \"E-posta\",\r\n \"emailPlaceholder\": \"ornek@email.com\",\r\n \"password\": \"Şifre\",\r\n \"forgotPassword\": \"Şifremi unuttum\",\r\n \"signIn\": \"Giriş Yap\",\r\n \"signingIn\": \"Giriş yapılıyor...\",\r\n \"noAccount\": \"Hesabınız yok mu?\",\r\n \"createAccount\": \"Hesap oluştur\",\r\n \"loginSuccess\": \"Giriş başarılı!\",\r\n \"loginError\": \"Giriş başarısız. Lütfen bilgilerinizi kontrol edin.\"\r\n}\r\n"
42
39
  }
43
40
  ],
44
41
  "exports": {
@@ -27,7 +27,7 @@
27
27
  "path": "my-orders-page/my-orders-page.tsx",
28
28
  "type": "registry:page",
29
29
  "target": "$modules$/my-orders-page/my-orders-page.tsx",
30
- "content": "import { useEffect, useState } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport {\r\n Package,\r\n Loader2,\r\n ShoppingBag,\r\n ArrowLeft,\r\n Calendar,\r\n CreditCard,\r\n Truck,\r\n CheckCircle2,\r\n Clock,\r\n XCircle,\r\n} from \"lucide-react\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Card, CardContent } from \"@/components/ui/card\";\r\nimport { Badge } from \"@/components/ui/badge\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { customerClient } from \"@/modules/api/customer-client\";\r\n\r\ninterface OrderItem {\r\n name: string;\r\n description?: string;\r\n quantity: number;\r\n amount: number;\r\n img?: string;\r\n}\r\n\r\ninterface Order {\r\n id: string;\r\n created_at: string;\r\n status: string;\r\n payment_status: string;\r\n total_amount: number;\r\n currency: string;\r\n product_data?: OrderItem[];\r\n}\r\n\r\ntype PageStatus = \"loading\" | \"success\" | \"error\" | \"unauthorized\";\r\n\r\nexport function MyOrdersPage() {\r\n const { t } = useTranslation(\"my-orders-page\");\r\n usePageTitle({ title: t(\"title\", \"My Orders\") });\r\n\r\n const { isAuthenticated, token } = useAuth();\r\n\r\n const [status, setStatus] = useState<PageStatus>(\"loading\");\r\n const [orders, setOrders] = useState<Order[]>([]);\r\n\r\n useEffect(() => {\r\n if (!isAuthenticated || !token) {\r\n setStatus(\"unauthorized\");\r\n return;\r\n }\r\n\r\n const fetchOrders = async () => {\r\n try {\r\n const response = (await (customerClient as any).orders.getMyOrders()) as any;\r\n // Handle both 'orders' and 'transactions' keys from API\r\n const ordersList = response.orders || response.transactions || [];\r\n setOrders(ordersList);\r\n setStatus(\"success\");\r\n } catch (error: any) {\r\n console.error(\"Error fetching orders:\", error);\r\n\r\n if (error?.response?.status === 401) {\r\n setStatus(\"unauthorized\");\r\n } else {\r\n setStatus(\"error\");\r\n toast.error(t(\"loadError\", \"Failed to load orders. Please try again.\"));\r\n }\r\n }\r\n };\r\n\r\n fetchOrders();\r\n }, [isAuthenticated, token, t]);\r\n\r\n const getStatusIcon = (orderStatus: string) => {\r\n const statusLower = orderStatus.toLowerCase();\r\n if ([\"paid\", \"completed\", \"tamamlandı\"].includes(statusLower)) {\r\n return <CheckCircle2 className=\"w-4 h-4\" />;\r\n }\r\n if ([\"processing\", \"hazırlanıyor\"].includes(statusLower)) {\r\n return <Clock className=\"w-4 h-4\" />;\r\n }\r\n if ([\"shipped\", \"kargoda\"].includes(statusLower)) {\r\n return <Truck className=\"w-4 h-4\" />;\r\n }\r\n if ([\"cancelled\", \"iptal\", \"failed\"].includes(statusLower)) {\r\n return <XCircle className=\"w-4 h-4\" />;\r\n }\r\n return <Package className=\"w-4 h-4\" />;\r\n };\r\n\r\n const getStatusBadgeVariant = (\r\n orderStatus: string\r\n ): \"default\" | \"secondary\" | \"destructive\" | \"outline\" => {\r\n const statusLower = orderStatus.toLowerCase();\r\n if ([\"paid\", \"completed\", \"tamamlandı\"].includes(statusLower)) {\r\n return \"default\";\r\n }\r\n if ([\"processing\", \"hazırlanıyor\"].includes(statusLower)) {\r\n return \"secondary\";\r\n }\r\n if ([\"shipped\", \"kargoda\"].includes(statusLower)) {\r\n return \"outline\";\r\n }\r\n if ([\"cancelled\", \"iptal\", \"failed\"].includes(statusLower)) {\r\n return \"destructive\";\r\n }\r\n return \"secondary\";\r\n };\r\n\r\n const getPaymentStatusBadge = (\r\n paymentStatus: string\r\n ): \"default\" | \"secondary\" | \"destructive\" => {\r\n if (paymentStatus === \"paid\") return \"default\";\r\n if (paymentStatus === \"unpaid\" || paymentStatus === \"pending\")\r\n return \"secondary\";\r\n return \"destructive\";\r\n };\r\n\r\n const formatDate = (dateString: string) => {\r\n try {\r\n return new Date(dateString).toLocaleDateString(undefined, {\r\n year: \"numeric\",\r\n month: \"long\",\r\n day: \"numeric\",\r\n hour: \"2-digit\",\r\n minute: \"2-digit\",\r\n });\r\n } catch {\r\n return dateString;\r\n }\r\n };\r\n\r\n const formatCurrency = (amount: number, currency: string) => {\r\n return new Intl.NumberFormat(\"en-US\", {\r\n style: \"currency\",\r\n currency: currency.toUpperCase(),\r\n }).format(amount / 100);\r\n };\r\n\r\n // Unauthorized\r\n if (status === \"unauthorized\") {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-muted-foreground mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"loginRequired\", \"Login Required\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"loginRequiredDescription\",\r\n \"Please login to view your orders.\"\r\n )}\r\n </p>\r\n <div className=\"flex flex-col gap-3\">\r\n <Button asChild>\r\n <Link to=\"/login\">{t(\"login\", \"Login\")}</Link>\r\n </Button>\r\n <Button variant=\"outline\" asChild>\r\n <Link to=\"/register\">\r\n {t(\"createAccount\", \"Create Account\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Loading\r\n if (status === \"loading\") {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Loader2 className=\"w-16 h-16 text-primary mx-auto mb-4 animate-spin\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"loadingOrders\", \"Loading Orders\")}\r\n </h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\"pleaseWait\", \"Please wait...\")}\r\n </p>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Error\r\n if (status === \"error\") {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-red-500 mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"errorTitle\", \"Something went wrong\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"errorDescription\",\r\n \"We couldn't load your orders. Please try again.\"\r\n )}\r\n </p>\r\n <Button onClick={() => window.location.reload()}>\r\n {t(\"tryAgain\", \"Try Again\")}\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // No orders\r\n if (orders.length === 0) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-muted-foreground mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"noOrders\", \"No Orders Yet\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"noOrdersDescription\",\r\n \"You haven't placed any orders yet. Start shopping to see your orders here.\"\r\n )}\r\n </p>\r\n <Button asChild>\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"startShopping\", \"Start Shopping\")}\r\n </Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Success - Has orders\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n {/* Header */}\r\n <div className=\"flex items-center gap-4 mb-8\">\r\n <Button variant=\"ghost\" size=\"icon\" asChild>\r\n <Link to=\"/\">\r\n <ArrowLeft className=\"h-4 w-4\" />\r\n </Link>\r\n </Button>\r\n <div>\r\n <h1 className=\"text-3xl font-bold\">{t(\"title\", \"My Orders\")}</h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\"orderCount\", \"{{count}} order(s)\", { count: orders.length })}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n {/* Orders List */}\r\n <div className=\"space-y-6\">\r\n {orders.map((order) => (\r\n <Card key={order.id} className=\"overflow-hidden\">\r\n {/* Order Header */}\r\n <div className=\"bg-muted/50 px-6 py-4 border-b\">\r\n <div className=\"flex flex-col md:flex-row md:items-center md:justify-between gap-4\">\r\n <div className=\"flex flex-wrap items-center gap-x-6 gap-y-2 text-sm\">\r\n <div>\r\n <span className=\"text-muted-foreground\">\r\n {t(\"orderNumber\", \"Order\")}:\r\n </span>{\" \"}\r\n <span className=\"font-mono font-medium\">\r\n #{order.id.slice(0, 8)}\r\n </span>\r\n </div>\r\n <div className=\"flex items-center gap-1.5 text-muted-foreground\">\r\n <Calendar className=\"w-4 h-4\" />\r\n <span>{formatDate(order.created_at)}</span>\r\n </div>\r\n <div className=\"flex items-center gap-1.5 text-muted-foreground\">\r\n <CreditCard className=\"w-4 h-4\" />\r\n <Badge\r\n variant={getPaymentStatusBadge(order.payment_status)}\r\n className=\"text-xs\"\r\n >\r\n {order.payment_status === \"paid\"\r\n ? t(\"paid\", \"Paid\")\r\n : t(\"unpaid\", \"Unpaid\")}\r\n </Badge>\r\n </div>\r\n </div>\r\n <div className=\"flex items-center gap-3\">\r\n <Badge\r\n variant={getStatusBadgeVariant(order.status)}\r\n className=\"flex items-center gap-1.5\"\r\n >\r\n {getStatusIcon(order.status)}\r\n <span className=\"capitalize\">{order.status}</span>\r\n </Badge>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Order Items */}\r\n <CardContent className=\"p-6\">\r\n <div className=\"space-y-4\">\r\n {order.product_data?.map((item, index) => (\r\n <div\r\n key={index}\r\n className=\"flex gap-4 pb-4 last:pb-0 last:border-0 border-b border-border/50\"\r\n >\r\n {/* Product Image */}\r\n <div className=\"flex-shrink-0\">\r\n <img\r\n src={item.img || \"/images/placeholder.png\"}\r\n alt={item.name}\r\n className=\"w-20 h-20 md:w-24 md:h-24 object-cover rounded-lg border\"\r\n onError={(e) => {\r\n (e.target as HTMLImageElement).src =\r\n \"/images/placeholder.png\";\r\n }}\r\n />\r\n </div>\r\n\r\n {/* Product Details */}\r\n <div className=\"flex-1 min-w-0\">\r\n <h3 className=\"font-semibold text-base md:text-lg truncate\">\r\n {item.name}\r\n </h3>\r\n {item.description && (\r\n <p className=\"text-sm text-muted-foreground line-clamp-2 mt-1\">\r\n {item.description}\r\n </p>\r\n )}\r\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 mt-2 text-sm\">\r\n <span className=\"text-muted-foreground\">\r\n {t(\"quantity\", \"Qty\")}:{\" \"}\r\n <span className=\"font-medium text-foreground\">\r\n {item.quantity}\r\n </span>\r\n </span>\r\n <span className=\"font-semibold text-foreground\">\r\n {formatCurrency(item.amount, order.currency)}\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n\r\n {/* Order Total */}\r\n <div className=\"flex items-center justify-between mt-6 pt-4 border-t\">\r\n <span className=\"text-muted-foreground\">\r\n {t(\"total\", \"Total\")}\r\n </span>\r\n <span className=\"text-xl font-bold\">\r\n {formatCurrency(order.total_amount, order.currency)}\r\n </span>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n ))}\r\n </div>\r\n\r\n {/* Continue Shopping */}\r\n <div className=\"mt-8 text-center\">\r\n <Button variant=\"outline\" asChild>\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default MyOrdersPage;\r\n"
30
+ "content": "import { useEffect, useState } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport {\r\n Package,\r\n Loader2,\r\n ShoppingBag,\r\n ArrowLeft,\r\n Calendar,\r\n CreditCard,\r\n Truck,\r\n CheckCircle2,\r\n Clock,\r\n XCircle,\r\n} from \"lucide-react\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Card, CardContent } from \"@/components/ui/card\";\r\nimport { Badge } from \"@/components/ui/badge\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { customerClient } from \"@/modules/api\";\r\n\r\ninterface OrderItem {\r\n name: string;\r\n description?: string;\r\n quantity: number;\r\n amount: number;\r\n img?: string;\r\n}\r\n\r\ninterface Order {\r\n id: string;\r\n created_at: string;\r\n status: string;\r\n payment_status: string;\r\n total_amount: number;\r\n currency: string;\r\n product_data?: OrderItem[];\r\n}\r\n\r\ntype PageStatus = \"loading\" | \"success\" | \"error\" | \"unauthorized\";\r\n\r\nexport function MyOrdersPage() {\r\n const { t } = useTranslation(\"my-orders-page\");\r\n usePageTitle({ title: t(\"title\", \"My Orders\") });\r\n\r\n const { isAuthenticated, token } = useAuth();\r\n\r\n const [status, setStatus] = useState<PageStatus>(\"loading\");\r\n const [orders, setOrders] = useState<Order[]>([]);\r\n\r\n useEffect(() => {\r\n if (!isAuthenticated || !token) {\r\n setStatus(\"unauthorized\");\r\n return;\r\n }\r\n\r\n const fetchOrders = async () => {\r\n try {\r\n const response = (await (customerClient as any).orders.getMyOrders()) as any;\r\n // Handle both 'orders' and 'transactions' keys from API\r\n const ordersList = response.orders || response.transactions || [];\r\n setOrders(ordersList);\r\n setStatus(\"success\");\r\n } catch (error: any) {\r\n console.error(\"Error fetching orders:\", error);\r\n\r\n if (error?.response?.status === 401) {\r\n setStatus(\"unauthorized\");\r\n } else {\r\n setStatus(\"error\");\r\n toast.error(t(\"loadError\", \"Failed to load orders. Please try again.\"));\r\n }\r\n }\r\n };\r\n\r\n fetchOrders();\r\n }, [isAuthenticated, token, t]);\r\n\r\n const getStatusIcon = (orderStatus: string) => {\r\n const statusLower = orderStatus.toLowerCase();\r\n if ([\"paid\", \"completed\", \"tamamlandı\"].includes(statusLower)) {\r\n return <CheckCircle2 className=\"w-4 h-4\" />;\r\n }\r\n if ([\"processing\", \"hazırlanıyor\"].includes(statusLower)) {\r\n return <Clock className=\"w-4 h-4\" />;\r\n }\r\n if ([\"shipped\", \"kargoda\"].includes(statusLower)) {\r\n return <Truck className=\"w-4 h-4\" />;\r\n }\r\n if ([\"cancelled\", \"iptal\", \"failed\"].includes(statusLower)) {\r\n return <XCircle className=\"w-4 h-4\" />;\r\n }\r\n return <Package className=\"w-4 h-4\" />;\r\n };\r\n\r\n const getStatusBadgeVariant = (\r\n orderStatus: string\r\n ): \"default\" | \"secondary\" | \"destructive\" | \"outline\" => {\r\n const statusLower = orderStatus.toLowerCase();\r\n if ([\"paid\", \"completed\", \"tamamlandı\"].includes(statusLower)) {\r\n return \"default\";\r\n }\r\n if ([\"processing\", \"hazırlanıyor\"].includes(statusLower)) {\r\n return \"secondary\";\r\n }\r\n if ([\"shipped\", \"kargoda\"].includes(statusLower)) {\r\n return \"outline\";\r\n }\r\n if ([\"cancelled\", \"iptal\", \"failed\"].includes(statusLower)) {\r\n return \"destructive\";\r\n }\r\n return \"secondary\";\r\n };\r\n\r\n const getPaymentStatusBadge = (\r\n paymentStatus: string\r\n ): \"default\" | \"secondary\" | \"destructive\" => {\r\n if (paymentStatus === \"paid\") return \"default\";\r\n if (paymentStatus === \"unpaid\" || paymentStatus === \"pending\")\r\n return \"secondary\";\r\n return \"destructive\";\r\n };\r\n\r\n const formatDate = (dateString: string) => {\r\n try {\r\n return new Date(dateString).toLocaleDateString(undefined, {\r\n year: \"numeric\",\r\n month: \"long\",\r\n day: \"numeric\",\r\n hour: \"2-digit\",\r\n minute: \"2-digit\",\r\n });\r\n } catch {\r\n return dateString;\r\n }\r\n };\r\n\r\n const formatCurrency = (amount: number, currency: string) => {\r\n return new Intl.NumberFormat(\"en-US\", {\r\n style: \"currency\",\r\n currency: currency.toUpperCase(),\r\n }).format(amount / 100);\r\n };\r\n\r\n // Unauthorized\r\n if (status === \"unauthorized\") {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-muted-foreground mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"loginRequired\", \"Login Required\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"loginRequiredDescription\",\r\n \"Please login to view your orders.\"\r\n )}\r\n </p>\r\n <div className=\"flex flex-col gap-3\">\r\n <Button asChild>\r\n <Link to=\"/login\">{t(\"login\", \"Login\")}</Link>\r\n </Button>\r\n <Button variant=\"outline\" asChild>\r\n <Link to=\"/register\">\r\n {t(\"createAccount\", \"Create Account\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Loading\r\n if (status === \"loading\") {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Loader2 className=\"w-16 h-16 text-primary mx-auto mb-4 animate-spin\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"loadingOrders\", \"Loading Orders\")}\r\n </h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\"pleaseWait\", \"Please wait...\")}\r\n </p>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Error\r\n if (status === \"error\") {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-red-500 mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"errorTitle\", \"Something went wrong\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"errorDescription\",\r\n \"We couldn't load your orders. Please try again.\"\r\n )}\r\n </p>\r\n <Button onClick={() => window.location.reload()}>\r\n {t(\"tryAgain\", \"Try Again\")}\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // No orders\r\n if (orders.length === 0) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-muted-foreground mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"noOrders\", \"No Orders Yet\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"noOrdersDescription\",\r\n \"You haven't placed any orders yet. Start shopping to see your orders here.\"\r\n )}\r\n </p>\r\n <Button asChild>\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"startShopping\", \"Start Shopping\")}\r\n </Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Success - Has orders\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n {/* Header */}\r\n <div className=\"flex items-center gap-4 mb-8\">\r\n <Button variant=\"ghost\" size=\"icon\" asChild>\r\n <Link to=\"/\">\r\n <ArrowLeft className=\"h-4 w-4\" />\r\n </Link>\r\n </Button>\r\n <div>\r\n <h1 className=\"text-3xl font-bold\">{t(\"title\", \"My Orders\")}</h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\"orderCount\", \"{{count}} order(s)\", { count: orders.length })}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n {/* Orders List */}\r\n <div className=\"space-y-6\">\r\n {orders.map((order) => (\r\n <Card key={order.id} className=\"overflow-hidden\">\r\n {/* Order Header */}\r\n <div className=\"bg-muted/50 px-6 py-4 border-b\">\r\n <div className=\"flex flex-col md:flex-row md:items-center md:justify-between gap-4\">\r\n <div className=\"flex flex-wrap items-center gap-x-6 gap-y-2 text-sm\">\r\n <div>\r\n <span className=\"text-muted-foreground\">\r\n {t(\"orderNumber\", \"Order\")}:\r\n </span>{\" \"}\r\n <span className=\"font-mono font-medium\">\r\n #{order.id.slice(0, 8)}\r\n </span>\r\n </div>\r\n <div className=\"flex items-center gap-1.5 text-muted-foreground\">\r\n <Calendar className=\"w-4 h-4\" />\r\n <span>{formatDate(order.created_at)}</span>\r\n </div>\r\n <div className=\"flex items-center gap-1.5 text-muted-foreground\">\r\n <CreditCard className=\"w-4 h-4\" />\r\n <Badge\r\n variant={getPaymentStatusBadge(order.payment_status)}\r\n className=\"text-xs\"\r\n >\r\n {order.payment_status === \"paid\"\r\n ? t(\"paid\", \"Paid\")\r\n : t(\"unpaid\", \"Unpaid\")}\r\n </Badge>\r\n </div>\r\n </div>\r\n <div className=\"flex items-center gap-3\">\r\n <Badge\r\n variant={getStatusBadgeVariant(order.status)}\r\n className=\"flex items-center gap-1.5\"\r\n >\r\n {getStatusIcon(order.status)}\r\n <span className=\"capitalize\">{order.status}</span>\r\n </Badge>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Order Items */}\r\n <CardContent className=\"p-6\">\r\n <div className=\"space-y-4\">\r\n {order.product_data?.map((item, index) => (\r\n <div\r\n key={index}\r\n className=\"flex gap-4 pb-4 last:pb-0 last:border-0 border-b border-border/50\"\r\n >\r\n {/* Product Image */}\r\n <div className=\"flex-shrink-0\">\r\n <img\r\n src={item.img || \"/images/placeholder.png\"}\r\n alt={item.name}\r\n className=\"w-20 h-20 md:w-24 md:h-24 object-cover rounded-lg border\"\r\n onError={(e) => {\r\n (e.target as HTMLImageElement).src =\r\n \"/images/placeholder.png\";\r\n }}\r\n />\r\n </div>\r\n\r\n {/* Product Details */}\r\n <div className=\"flex-1 min-w-0\">\r\n <h3 className=\"font-semibold text-base md:text-lg truncate\">\r\n {item.name}\r\n </h3>\r\n {item.description && (\r\n <p className=\"text-sm text-muted-foreground line-clamp-2 mt-1\">\r\n {item.description}\r\n </p>\r\n )}\r\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 mt-2 text-sm\">\r\n <span className=\"text-muted-foreground\">\r\n {t(\"quantity\", \"Qty\")}:{\" \"}\r\n <span className=\"font-medium text-foreground\">\r\n {item.quantity}\r\n </span>\r\n </span>\r\n <span className=\"font-semibold text-foreground\">\r\n {formatCurrency(item.amount, order.currency)}\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n\r\n {/* Order Total */}\r\n <div className=\"flex items-center justify-between mt-6 pt-4 border-t\">\r\n <span className=\"text-muted-foreground\">\r\n {t(\"total\", \"Total\")}\r\n </span>\r\n <span className=\"text-xl font-bold\">\r\n {formatCurrency(order.total_amount, order.currency)}\r\n </span>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n ))}\r\n </div>\r\n\r\n {/* Continue Shopping */}\r\n <div className=\"mt-8 text-center\">\r\n <Button variant=\"outline\" asChild>\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default MyOrdersPage;\r\n"
31
31
  },
32
32
  {
33
33
  "path": "my-orders-page/lang/en.json",
@@ -24,7 +24,7 @@
24
24
  "path": "order-confirmation-page/order-confirmation-page.tsx",
25
25
  "type": "registry:page",
26
26
  "target": "$modules$/order-confirmation-page/order-confirmation-page.tsx",
27
- "content": "import { useEffect, useState } from \"react\";\r\nimport { Link, useParams, useSearchParams } from \"react-router\";\r\nimport {\r\n CheckCircle,\r\n Package,\r\n Truck,\r\n CreditCard,\r\n ArrowLeft,\r\n Clock,\r\n AlertCircle,\r\n Loader2,\r\n ShoppingBag,\r\n} from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\r\nimport { Badge } from \"@/components/ui/badge\";\r\nimport { Separator } from \"@/components/ui/separator\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { formatPrice } from \"@/modules/ecommerce-core\";\r\nimport { customerClient } from \"@/modules/api/customer-client\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface CheckoutData {\r\n items: any[];\r\n total: number;\r\n customerInfo: {\r\n firstName: string;\r\n lastName: string;\r\n email: string;\r\n phone: string;\r\n address: string;\r\n city: string;\r\n postalCode: string;\r\n country: string;\r\n notes?: string;\r\n };\r\n paymentMethod: string;\r\n paymentProvider: string;\r\n}\r\n\r\nexport function OrderConfirmationPage() {\r\n const { t } = useTranslation(\"order-confirmation-page\");\r\n usePageTitle({ title: t(\"title\", \"Order Confirmation\") });\r\n\r\n const { orderId } = useParams<{ orderId: string }>();\r\n const [searchParams] = useSearchParams();\r\n const sessionId = searchParams.get(\"session_id\");\r\n\r\n const currency = (constants as any).site?.currency || \"USD\";\r\n\r\n const [checkoutData, setCheckoutData] = useState<CheckoutData | null>(null);\r\n const [paymentStatus, setPaymentStatus] = useState<string | null>(null);\r\n const [isCheckingStatus, setIsCheckingStatus] = useState(false);\r\n const [statusError, setStatusError] = useState<string | null>(null);\r\n\r\n // Check if this is a Stripe payment\r\n const isStripePayment = orderId?.startsWith(\"stripe-\") || !!sessionId;\r\n\r\n useEffect(() => {\r\n window.scrollTo(0, 0);\r\n\r\n // Load checkout data from localStorage\r\n const savedData = localStorage.getItem(\"pending_checkout\");\r\n if (savedData) {\r\n try {\r\n setCheckoutData(JSON.parse(savedData));\r\n // Clear after loading\r\n localStorage.removeItem(\"pending_checkout\");\r\n } catch (e) {\r\n console.error(\"Failed to parse checkout data:\", e);\r\n }\r\n }\r\n\r\n // If Stripe payment, check status\r\n if (isStripePayment && (sessionId || orderId)) {\r\n checkStripePaymentStatus();\r\n }\r\n }, []);\r\n\r\n const checkStripePaymentStatus = async () => {\r\n const sid = sessionId || orderId?.replace(\"stripe-\", \"\");\r\n if (!sid) return;\r\n\r\n setIsCheckingStatus(true);\r\n setStatusError(null);\r\n\r\n try {\r\n const response = await (customerClient as any).payment.status({ sessionId: sid });\r\n setPaymentStatus(response.status);\r\n } catch (error: any) {\r\n console.error(\"Payment status check error:\", error);\r\n setStatusError(error.message || \"An error occurred\");\r\n } finally {\r\n setIsCheckingStatus(false);\r\n }\r\n };\r\n\r\n // No checkout data found\r\n if (!checkoutData) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-muted-foreground mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"orderNotFound\", \"Order Not Found\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"orderNotFoundDescription\",\r\n \"We couldn't find the order details. Please check your email for confirmation.\"\r\n )}\r\n </p>\r\n <Button asChild>\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n </Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n const { items, total, customerInfo, paymentMethod, paymentProvider } = checkoutData;\r\n\r\n const getPaymentMethodDisplay = () => {\r\n if (paymentProvider === \"cash_on_delivery\" || paymentMethod === \"cash\") {\r\n return {\r\n icon: <Truck className=\"w-4 h-4 text-green-600\" />,\r\n label: t(\"cashOnDelivery\", \"Cash on Delivery\"),\r\n };\r\n }\r\n if (paymentProvider === \"bank_transfer\" || paymentMethod === \"transfer\") {\r\n return {\r\n icon: <CreditCard className=\"w-4 h-4 text-primary\" />,\r\n label: t(\"bankTransfer\", \"Bank Transfer\"),\r\n };\r\n }\r\n return {\r\n icon: <CreditCard className=\"w-4 h-4 text-blue-600\" />,\r\n label: t(\"creditCard\", \"Credit Card\"),\r\n };\r\n };\r\n\r\n const paymentDisplay = getPaymentMethodDisplay();\r\n\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <div className=\"max-w-4xl mx-auto\">\r\n {/* Success Header */}\r\n <div className=\"text-center mb-8\">\r\n <div className=\"w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center mx-auto mb-4\">\r\n <CheckCircle className=\"w-8 h-8 text-green-600 dark:text-green-400\" />\r\n </div>\r\n <h1 className=\"text-3xl font-bold text-green-600 dark:text-green-400 mb-2\">\r\n {t(\"title\", \"Order Confirmed!\")}\r\n </h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\r\n \"thankYou\",\r\n \"Thank you for your order! We've received your order and will process it shortly.\"\r\n )}\r\n </p>\r\n </div>\r\n\r\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-8\">\r\n {/* Order Details */}\r\n <div className=\"lg:col-span-2 space-y-6\">\r\n {/* Order Info */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"flex items-center gap-2\">\r\n <Package className=\"w-5 h-5\" />\r\n {t(\"orderInformation\", \"Order Information\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent className=\"space-y-4\">\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n {orderId && (\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"orderId\", \"Order ID\")}\r\n </p>\r\n <p className=\"font-semibold font-mono\">#{orderId.slice(0, 12)}</p>\r\n </div>\r\n )}\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"orderDate\", \"Order Date\")}\r\n </p>\r\n <p className=\"font-semibold\">\r\n {new Date().toLocaleDateString()}\r\n </p>\r\n </div>\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"totalAmount\", \"Total Amount\")}\r\n </p>\r\n <p className=\"font-semibold text-lg\">\r\n {formatPrice(total, currency)}\r\n </p>\r\n </div>\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"paymentMethod\", \"Payment Method\")}\r\n </p>\r\n <div className=\"flex items-center gap-2\">\r\n {paymentDisplay.icon}\r\n <span className=\"font-semibold\">{paymentDisplay.label}</span>\r\n </div>\r\n </div>\r\n {isStripePayment && (\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"paymentStatus\", \"Payment Status\")}\r\n </p>\r\n <div className=\"flex items-center gap-2\">\r\n {isCheckingStatus ? (\r\n <div className=\"flex items-center gap-2 text-muted-foreground\">\r\n <Loader2 className=\"w-4 h-4 animate-spin\" />\r\n <span className=\"text-sm\">{t(\"checking\", \"Checking...\")}</span>\r\n </div>\r\n ) : statusError ? (\r\n <Badge variant=\"destructive\" className=\"flex items-center gap-1\">\r\n <AlertCircle className=\"w-3 h-3\" />\r\n {t(\"statusError\", \"Error\")}\r\n </Badge>\r\n ) : paymentStatus === \"succeeded\" ? (\r\n <Badge className=\"bg-green-100 text-green-800 border-green-200 flex items-center gap-1\">\r\n <CheckCircle className=\"w-3 h-3\" />\r\n {t(\"paymentSucceeded\", \"Paid\")}\r\n </Badge>\r\n ) : paymentStatus === \"processing\" ? (\r\n <Badge className=\"bg-blue-100 text-blue-800 border-blue-200 flex items-center gap-1\">\r\n <Loader2 className=\"w-3 h-3 animate-spin\" />\r\n {t(\"processing\", \"Processing\")}\r\n </Badge>\r\n ) : paymentStatus === \"requires_payment_method\" ? (\r\n <Badge className=\"bg-yellow-100 text-yellow-800 border-yellow-200 flex items-center gap-1\">\r\n <AlertCircle className=\"w-3 h-3\" />\r\n {t(\"requiresPayment\", \"Requires Payment\")}\r\n </Badge>\r\n ) : paymentStatus ? (\r\n <Badge variant=\"secondary\" className=\"flex items-center gap-1\">\r\n <Clock className=\"w-3 h-3\" />\r\n {paymentStatus}\r\n </Badge>\r\n ) : null}\r\n </div>\r\n </div>\r\n )}\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"orderStatus\", \"Order Status\")}\r\n </p>\r\n <Badge className=\"bg-yellow-100 text-yellow-800 border-yellow-200\">\r\n <Clock className=\"w-3 h-3 mr-1\" />\r\n {t(\"pending\", \"Pending\")}\r\n </Badge>\r\n </div>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n\r\n {/* Delivery Info */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"deliveryInformation\", \"Delivery Information\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"space-y-2\">\r\n <p className=\"font-semibold\">\r\n {customerInfo.firstName} {customerInfo.lastName}\r\n </p>\r\n <p className=\"text-sm text-muted-foreground\">{customerInfo.email}</p>\r\n <p className=\"text-sm text-muted-foreground\">{customerInfo.phone}</p>\r\n <div className=\"mt-3\">\r\n <p className=\"text-sm font-medium text-muted-foreground\">\r\n {t(\"address\", \"Address\")}:\r\n </p>\r\n <p className=\"text-sm\">{customerInfo.address}</p>\r\n <p className=\"text-sm\">\r\n {customerInfo.city} {customerInfo.postalCode}\r\n </p>\r\n </div>\r\n {customerInfo.notes && (\r\n <div className=\"mt-3\">\r\n <p className=\"text-sm font-medium text-muted-foreground\">\r\n {t(\"notes\", \"Notes\")}:\r\n </p>\r\n <p className=\"text-sm\">{customerInfo.notes}</p>\r\n </div>\r\n )}\r\n </div>\r\n </CardContent>\r\n </Card>\r\n\r\n {/* Order Items */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"orderItems\", \"Order Items\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"space-y-4\">\r\n {items.map((item: any, index: number) => (\r\n <div key={item.id || index} className=\"flex gap-4\">\r\n <img\r\n src={item.product?.images?.[0] || \"/images/placeholder.png\"}\r\n alt={item.product?.name || \"Product\"}\r\n className=\"w-16 h-16 object-cover rounded border\"\r\n />\r\n <div className=\"flex-1\">\r\n <h4 className=\"font-medium\">{item.product?.name}</h4>\r\n <div className=\"flex justify-between items-center mt-2\">\r\n <span className=\"text-sm\">\r\n {t(\"qty\", \"Qty\")}: {item.quantity}\r\n </span>\r\n <span className=\"font-semibold\">\r\n {formatPrice(\r\n (item.product?.on_sale\r\n ? item.product?.sale_price\r\n : item.product?.price) * item.quantity,\r\n currency\r\n )}\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n\r\n <Separator className=\"my-4\" />\r\n\r\n <div className=\"flex justify-between font-semibold text-lg\">\r\n <span>{t(\"total\", \"Total\")}</span>\r\n <span>{formatPrice(total, currency)}</span>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n\r\n {/* Sidebar */}\r\n <div className=\"space-y-6\">\r\n {/* Bank Transfer Instructions */}\r\n {(paymentProvider === \"bank_transfer\" || paymentMethod === \"transfer\") && (\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"text-lg\">\r\n {t(\"paymentInstructions\", \"Payment Instructions\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"p-4 bg-primary/10 border border-primary/20 rounded-lg\">\r\n <p className=\"text-sm font-medium text-primary mb-2\">\r\n {t(\"bankTransferDetails\", \"Bank Transfer Details\")}\r\n </p>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\r\n \"bankDetailsEmail\",\r\n \"Bank details will be sent via email. Please complete the transfer within 48 hours.\"\r\n )}\r\n </p>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n )}\r\n\r\n {/* Cash on Delivery Info */}\r\n {(paymentProvider === \"cash_on_delivery\" || paymentMethod === \"cash\") && (\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"text-lg\">\r\n {t(\"deliveryPayment\", \"Payment on Delivery\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg\">\r\n <p className=\"text-sm font-medium text-green-800 dark:text-green-200 mb-2\">\r\n {t(\"cashOnDeliveryInfo\", \"Cash on Delivery\")}\r\n </p>\r\n <p className=\"text-sm text-green-700 dark:text-green-300\">\r\n {t(\r\n \"codInstructions\",\r\n \"Pay when your order arrives. Our delivery team will contact you before delivery.\"\r\n )}\r\n </p>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n )}\r\n\r\n {/* Actions */}\r\n <Card>\r\n <CardContent className=\"pt-6 space-y-4\">\r\n <Button asChild className=\"w-full\">\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n </Link>\r\n </Button>\r\n\r\n <Button variant=\"outline\" asChild className=\"w-full\">\r\n <Link to=\"/\">\r\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\r\n {t(\"backToHome\", \"Back to Home\")}\r\n </Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default OrderConfirmationPage;\r\n"
27
+ "content": "import { useEffect, useState } from \"react\";\r\nimport { Link, useParams, useSearchParams } from \"react-router\";\r\nimport {\r\n CheckCircle,\r\n Package,\r\n Truck,\r\n CreditCard,\r\n ArrowLeft,\r\n Clock,\r\n AlertCircle,\r\n Loader2,\r\n ShoppingBag,\r\n} from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\r\nimport { Badge } from \"@/components/ui/badge\";\r\nimport { Separator } from \"@/components/ui/separator\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { formatPrice } from \"@/modules/ecommerce-core\";\r\nimport { customerClient } from \"@/modules/api\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface CheckoutData {\r\n items: any[];\r\n total: number;\r\n customerInfo: {\r\n firstName: string;\r\n lastName: string;\r\n email: string;\r\n phone: string;\r\n address: string;\r\n city: string;\r\n postalCode: string;\r\n country: string;\r\n notes?: string;\r\n };\r\n paymentMethod: string;\r\n paymentProvider: string;\r\n}\r\n\r\nexport function OrderConfirmationPage() {\r\n const { t } = useTranslation(\"order-confirmation-page\");\r\n usePageTitle({ title: t(\"title\", \"Order Confirmation\") });\r\n\r\n const { orderId } = useParams<{ orderId: string }>();\r\n const [searchParams] = useSearchParams();\r\n const sessionId = searchParams.get(\"session_id\");\r\n\r\n const currency = (constants as any).site?.currency || \"USD\";\r\n\r\n const [checkoutData, setCheckoutData] = useState<CheckoutData | null>(null);\r\n const [paymentStatus, setPaymentStatus] = useState<string | null>(null);\r\n const [isCheckingStatus, setIsCheckingStatus] = useState(false);\r\n const [statusError, setStatusError] = useState<string | null>(null);\r\n\r\n // Check if this is a Stripe payment\r\n const isStripePayment = orderId?.startsWith(\"stripe-\") || !!sessionId;\r\n\r\n useEffect(() => {\r\n window.scrollTo(0, 0);\r\n\r\n // Load checkout data from localStorage\r\n const savedData = localStorage.getItem(\"pending_checkout\");\r\n if (savedData) {\r\n try {\r\n setCheckoutData(JSON.parse(savedData));\r\n // Clear after loading\r\n localStorage.removeItem(\"pending_checkout\");\r\n } catch (e) {\r\n console.error(\"Failed to parse checkout data:\", e);\r\n }\r\n }\r\n\r\n // If Stripe payment, check status\r\n if (isStripePayment && (sessionId || orderId)) {\r\n checkStripePaymentStatus();\r\n }\r\n }, []);\r\n\r\n const checkStripePaymentStatus = async () => {\r\n const sid = sessionId || orderId?.replace(\"stripe-\", \"\");\r\n if (!sid) return;\r\n\r\n setIsCheckingStatus(true);\r\n setStatusError(null);\r\n\r\n try {\r\n const response = await (customerClient as any).payment.status({ sessionId: sid });\r\n setPaymentStatus(response.status);\r\n } catch (error: any) {\r\n console.error(\"Payment status check error:\", error);\r\n setStatusError(error.message || \"An error occurred\");\r\n } finally {\r\n setIsCheckingStatus(false);\r\n }\r\n };\r\n\r\n // No checkout data found\r\n if (!checkoutData) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-muted-foreground mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"orderNotFound\", \"Order Not Found\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"orderNotFoundDescription\",\r\n \"We couldn't find the order details. Please check your email for confirmation.\"\r\n )}\r\n </p>\r\n <Button asChild>\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n </Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n const { items, total, customerInfo, paymentMethod, paymentProvider } = checkoutData;\r\n\r\n const getPaymentMethodDisplay = () => {\r\n if (paymentProvider === \"cash_on_delivery\" || paymentMethod === \"cash\") {\r\n return {\r\n icon: <Truck className=\"w-4 h-4 text-green-600\" />,\r\n label: t(\"cashOnDelivery\", \"Cash on Delivery\"),\r\n };\r\n }\r\n if (paymentProvider === \"bank_transfer\" || paymentMethod === \"transfer\") {\r\n return {\r\n icon: <CreditCard className=\"w-4 h-4 text-primary\" />,\r\n label: t(\"bankTransfer\", \"Bank Transfer\"),\r\n };\r\n }\r\n return {\r\n icon: <CreditCard className=\"w-4 h-4 text-blue-600\" />,\r\n label: t(\"creditCard\", \"Credit Card\"),\r\n };\r\n };\r\n\r\n const paymentDisplay = getPaymentMethodDisplay();\r\n\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <div className=\"max-w-4xl mx-auto\">\r\n {/* Success Header */}\r\n <div className=\"text-center mb-8\">\r\n <div className=\"w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center mx-auto mb-4\">\r\n <CheckCircle className=\"w-8 h-8 text-green-600 dark:text-green-400\" />\r\n </div>\r\n <h1 className=\"text-3xl font-bold text-green-600 dark:text-green-400 mb-2\">\r\n {t(\"title\", \"Order Confirmed!\")}\r\n </h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\r\n \"thankYou\",\r\n \"Thank you for your order! We've received your order and will process it shortly.\"\r\n )}\r\n </p>\r\n </div>\r\n\r\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-8\">\r\n {/* Order Details */}\r\n <div className=\"lg:col-span-2 space-y-6\">\r\n {/* Order Info */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"flex items-center gap-2\">\r\n <Package className=\"w-5 h-5\" />\r\n {t(\"orderInformation\", \"Order Information\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent className=\"space-y-4\">\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n {orderId && (\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"orderId\", \"Order ID\")}\r\n </p>\r\n <p className=\"font-semibold font-mono\">#{orderId.slice(0, 12)}</p>\r\n </div>\r\n )}\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"orderDate\", \"Order Date\")}\r\n </p>\r\n <p className=\"font-semibold\">\r\n {new Date().toLocaleDateString()}\r\n </p>\r\n </div>\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"totalAmount\", \"Total Amount\")}\r\n </p>\r\n <p className=\"font-semibold text-lg\">\r\n {formatPrice(total, currency)}\r\n </p>\r\n </div>\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"paymentMethod\", \"Payment Method\")}\r\n </p>\r\n <div className=\"flex items-center gap-2\">\r\n {paymentDisplay.icon}\r\n <span className=\"font-semibold\">{paymentDisplay.label}</span>\r\n </div>\r\n </div>\r\n {isStripePayment && (\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"paymentStatus\", \"Payment Status\")}\r\n </p>\r\n <div className=\"flex items-center gap-2\">\r\n {isCheckingStatus ? (\r\n <div className=\"flex items-center gap-2 text-muted-foreground\">\r\n <Loader2 className=\"w-4 h-4 animate-spin\" />\r\n <span className=\"text-sm\">{t(\"checking\", \"Checking...\")}</span>\r\n </div>\r\n ) : statusError ? (\r\n <Badge variant=\"destructive\" className=\"flex items-center gap-1\">\r\n <AlertCircle className=\"w-3 h-3\" />\r\n {t(\"statusError\", \"Error\")}\r\n </Badge>\r\n ) : paymentStatus === \"succeeded\" ? (\r\n <Badge className=\"bg-green-100 text-green-800 border-green-200 flex items-center gap-1\">\r\n <CheckCircle className=\"w-3 h-3\" />\r\n {t(\"paymentSucceeded\", \"Paid\")}\r\n </Badge>\r\n ) : paymentStatus === \"processing\" ? (\r\n <Badge className=\"bg-blue-100 text-blue-800 border-blue-200 flex items-center gap-1\">\r\n <Loader2 className=\"w-3 h-3 animate-spin\" />\r\n {t(\"processing\", \"Processing\")}\r\n </Badge>\r\n ) : paymentStatus === \"requires_payment_method\" ? (\r\n <Badge className=\"bg-yellow-100 text-yellow-800 border-yellow-200 flex items-center gap-1\">\r\n <AlertCircle className=\"w-3 h-3\" />\r\n {t(\"requiresPayment\", \"Requires Payment\")}\r\n </Badge>\r\n ) : paymentStatus ? (\r\n <Badge variant=\"secondary\" className=\"flex items-center gap-1\">\r\n <Clock className=\"w-3 h-3\" />\r\n {paymentStatus}\r\n </Badge>\r\n ) : null}\r\n </div>\r\n </div>\r\n )}\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"orderStatus\", \"Order Status\")}\r\n </p>\r\n <Badge className=\"bg-yellow-100 text-yellow-800 border-yellow-200\">\r\n <Clock className=\"w-3 h-3 mr-1\" />\r\n {t(\"pending\", \"Pending\")}\r\n </Badge>\r\n </div>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n\r\n {/* Delivery Info */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"deliveryInformation\", \"Delivery Information\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"space-y-2\">\r\n <p className=\"font-semibold\">\r\n {customerInfo.firstName} {customerInfo.lastName}\r\n </p>\r\n <p className=\"text-sm text-muted-foreground\">{customerInfo.email}</p>\r\n <p className=\"text-sm text-muted-foreground\">{customerInfo.phone}</p>\r\n <div className=\"mt-3\">\r\n <p className=\"text-sm font-medium text-muted-foreground\">\r\n {t(\"address\", \"Address\")}:\r\n </p>\r\n <p className=\"text-sm\">{customerInfo.address}</p>\r\n <p className=\"text-sm\">\r\n {customerInfo.city} {customerInfo.postalCode}\r\n </p>\r\n </div>\r\n {customerInfo.notes && (\r\n <div className=\"mt-3\">\r\n <p className=\"text-sm font-medium text-muted-foreground\">\r\n {t(\"notes\", \"Notes\")}:\r\n </p>\r\n <p className=\"text-sm\">{customerInfo.notes}</p>\r\n </div>\r\n )}\r\n </div>\r\n </CardContent>\r\n </Card>\r\n\r\n {/* Order Items */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"orderItems\", \"Order Items\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"space-y-4\">\r\n {items.map((item: any, index: number) => (\r\n <div key={item.id || index} className=\"flex gap-4\">\r\n <img\r\n src={item.product?.images?.[0] || \"/images/placeholder.png\"}\r\n alt={item.product?.name || \"Product\"}\r\n className=\"w-16 h-16 object-cover rounded border\"\r\n />\r\n <div className=\"flex-1\">\r\n <h4 className=\"font-medium\">{item.product?.name}</h4>\r\n <div className=\"flex justify-between items-center mt-2\">\r\n <span className=\"text-sm\">\r\n {t(\"qty\", \"Qty\")}: {item.quantity}\r\n </span>\r\n <span className=\"font-semibold\">\r\n {formatPrice(\r\n (item.product?.on_sale\r\n ? item.product?.sale_price\r\n : item.product?.price) * item.quantity,\r\n currency\r\n )}\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n\r\n <Separator className=\"my-4\" />\r\n\r\n <div className=\"flex justify-between font-semibold text-lg\">\r\n <span>{t(\"total\", \"Total\")}</span>\r\n <span>{formatPrice(total, currency)}</span>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n\r\n {/* Sidebar */}\r\n <div className=\"space-y-6\">\r\n {/* Bank Transfer Instructions */}\r\n {(paymentProvider === \"bank_transfer\" || paymentMethod === \"transfer\") && (\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"text-lg\">\r\n {t(\"paymentInstructions\", \"Payment Instructions\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"p-4 bg-primary/10 border border-primary/20 rounded-lg\">\r\n <p className=\"text-sm font-medium text-primary mb-2\">\r\n {t(\"bankTransferDetails\", \"Bank Transfer Details\")}\r\n </p>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\r\n \"bankDetailsEmail\",\r\n \"Bank details will be sent via email. Please complete the transfer within 48 hours.\"\r\n )}\r\n </p>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n )}\r\n\r\n {/* Cash on Delivery Info */}\r\n {(paymentProvider === \"cash_on_delivery\" || paymentMethod === \"cash\") && (\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"text-lg\">\r\n {t(\"deliveryPayment\", \"Payment on Delivery\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg\">\r\n <p className=\"text-sm font-medium text-green-800 dark:text-green-200 mb-2\">\r\n {t(\"cashOnDeliveryInfo\", \"Cash on Delivery\")}\r\n </p>\r\n <p className=\"text-sm text-green-700 dark:text-green-300\">\r\n {t(\r\n \"codInstructions\",\r\n \"Pay when your order arrives. Our delivery team will contact you before delivery.\"\r\n )}\r\n </p>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n )}\r\n\r\n {/* Actions */}\r\n <Card>\r\n <CardContent className=\"pt-6 space-y-4\">\r\n <Button asChild className=\"w-full\">\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n </Link>\r\n </Button>\r\n\r\n <Button variant=\"outline\" asChild className=\"w-full\">\r\n <Link to=\"/\">\r\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\r\n {t(\"backToHome\", \"Back to Home\")}\r\n </Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default OrderConfirmationPage;\r\n"
28
28
  },
29
29
  {
30
30
  "path": "order-confirmation-page/lang/en.json",
@@ -2,14 +2,12 @@
2
2
  "name": "register-page-split",
3
3
  "type": "registry:page",
4
4
  "title": "Register Page Split",
5
- "description": "Split-screen registration page with form on the left and full-height image on the right. Features username, email, password fields with confirmation. Uses customerClient for API calls.",
5
+ "description": "Split-screen registration page with form on the left and full-height image on the right. Features username, email, password fields with confirmation. Uses useAuth hook from auth-core for registration.",
6
6
  "registryDependencies": [
7
- "button",
8
- "input",
9
7
  "auth-core",
10
8
  "api"
11
9
  ],
12
- "usage": "import RegisterPageSplit from '@/modules/register-page-split';\n\n<RegisterPageSplit\n image=\"/images/register-bg.jpg\"\n/>\n\n• Installed at: src/modules/register-page-split/\n• Customize text: src/modules/register-page-split/lang/*.json\n• API Integration:\n - customerClient.auth.register() for registration\n - Shows success state after registration\n• Add to your router as a page component",
10
+ "usage": "import RegisterPageSplit from '@/modules/register-page-split';\n\n<RegisterPageSplit\n image=\"/images/register-bg.jpg\"\n/>\n\n• Installed at: src/modules/register-page-split/\n• Customize text: src/modules/register-page-split/lang/*.json\n• Uses useAuth() hook from auth-core:\n const { register } = useAuth();\n await register(username, email, password);\n Shows success state after registration\n• Add to your router as a page component",
13
11
  "route": {
14
12
  "path": "/register",
15
13
  "componentName": "RegisterPageSplit"
@@ -25,7 +23,7 @@
25
23
  "path": "register-page-split/register-page-split.tsx",
26
24
  "type": "registry:page",
27
25
  "target": "$modules$/register-page-split/register-page-split.tsx",
28
- "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { customerClient } from \"@/modules/api/customer-client\";\r\nimport { getErrorMessage } from \"@/modules/api/get-error-message\";\r\n\r\ninterface RegisterPageSplitProps {\r\n image?: string;\r\n}\r\n\r\nexport function RegisterPageSplit({\r\n image = \"/images/placeholder.png\",\r\n}: RegisterPageSplitProps) {\r\n const { t } = useTranslation(\"register-page-split\");\r\n usePageTitle({ title: t(\"title\", \"Create Account\") });\r\n const navigate = useNavigate();\r\n\r\n const [username, setUsername] = useState(\"\");\r\n const [email, setEmail] = useState(\"\");\r\n const [password, setPassword] = useState(\"\");\r\n const [confirmPassword, setConfirmPassword] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n\r\n // Validate passwords match\r\n if (password !== confirmPassword) {\r\n setError(t(\"passwordMismatch\", \"Passwords do not match\"));\r\n return;\r\n }\r\n\r\n setIsLoading(true);\r\n\r\n try {\r\n await customerClient.auth.register({\r\n username,\r\n email,\r\n password,\r\n });\r\n\r\n toast.success(t(\"registerSuccess\", \"Account created successfully!\"), {\r\n description: t(\"checkEmail\", \"Please check your email to verify your account.\"),\r\n });\r\n\r\n // Navigate to login page after successful registration\r\n navigate(\"/login\", {\r\n state: {\r\n message: t(\"verifyEmail\", \"Please verify your email before logging in.\"),\r\n email\r\n }\r\n });\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"registerError\", \"Registration failed. Please try again.\")\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <section className=\"w-full md:grid md:min-h-screen md:grid-cols-2\">\r\n <div className=\"flex items-center justify-center px-4 py-12\">\r\n <div className=\"mx-auto grid w-full max-w-sm gap-6\">\r\n <Logo />\r\n <hr />\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"title\", \"Create Account\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"subtitle\", \"Sign up to get started\")}\r\n </p>\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-3 text-sm text-red-600 bg-red-50 dark:bg-red-950 dark:text-red-400 rounded-md\">\r\n {error}\r\n </div>\r\n )}\r\n\r\n <form onSubmit={handleSubmit} className=\"grid gap-4\">\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"username\">{t(\"username\", \"Username\")}</Label>\r\n <Input\r\n required\r\n id=\"username\"\r\n type=\"text\"\r\n autoComplete=\"username\"\r\n placeholder={t(\"usernamePlaceholder\", \"johndoe\")}\r\n value={username}\r\n onChange={(e) => setUsername(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"email\">{t(\"email\", \"Email\")}</Label>\r\n <Input\r\n required\r\n id=\"email\"\r\n type=\"email\"\r\n autoComplete=\"email\"\r\n placeholder={t(\"emailPlaceholder\", \"you@example.com\")}\r\n value={email}\r\n onChange={(e) => setEmail(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"password\">{t(\"password\", \"Password\")}</Label>\r\n <Input\r\n required\r\n id=\"password\"\r\n type=\"password\"\r\n autoComplete=\"new-password\"\r\n placeholder=\"••••••••\"\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n disabled={isLoading}\r\n minLength={8}\r\n />\r\n </div>\r\n\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"confirm-password\">{t(\"confirmPassword\", \"Confirm Password\")}</Label>\r\n <Input\r\n required\r\n id=\"confirm-password\"\r\n type=\"password\"\r\n autoComplete=\"new-password\"\r\n placeholder=\"••••••••\"\r\n value={confirmPassword}\r\n onChange={(e) => setConfirmPassword(e.target.value)}\r\n disabled={isLoading}\r\n minLength={8}\r\n />\r\n </div>\r\n\r\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\r\n {isLoading ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"creatingAccount\", \"Creating account...\")}\r\n </>\r\n ) : (\r\n t(\"signUp\", \"Sign Up\")\r\n )}\r\n </Button>\r\n </form>\r\n\r\n <div className=\"text-sm text-center\">\r\n <p>\r\n {t(\"hasAccount\", \"Already have an account?\")}{\" \"}\r\n <Link to=\"/login\" className=\"underline\">\r\n {t(\"signIn\", \"Sign in\")}\r\n </Link>\r\n </p>\r\n </div>\r\n\r\n <hr />\r\n <p className=\"text-sm text-muted-foreground\">\r\n © {new Date().getFullYear()} {t(\"copyright\", \"All rights reserved.\")}\r\n </p>\r\n </div>\r\n </div>\r\n <div className=\"hidden p-4 md:block\">\r\n <img\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n width=\"1920\"\r\n height=\"1080\"\r\n alt={t(\"imageAlt\", \"Register background\")}\r\n src={image}\r\n className=\"size-full rounded-lg border bg-muted object-cover object-center\"\r\n />\r\n </div>\r\n </section>\r\n );\r\n}\r\n\r\nexport default RegisterPageSplit;\r\n"
26
+ "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { getErrorMessage } from \"@/modules/api\";\r\n\r\ninterface RegisterPageSplitProps {\r\n image?: string;\r\n}\r\n\r\nexport function RegisterPageSplit({\r\n image = \"/images/placeholder.png\",\r\n}: RegisterPageSplitProps) {\r\n const { t } = useTranslation(\"register-page-split\");\r\n usePageTitle({ title: t(\"title\", \"Create Account\") });\r\n const navigate = useNavigate();\r\n const { register } = useAuth();\r\n\r\n const [username, setUsername] = useState(\"\");\r\n const [email, setEmail] = useState(\"\");\r\n const [password, setPassword] = useState(\"\");\r\n const [confirmPassword, setConfirmPassword] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n\r\n // Validate passwords match\r\n if (password !== confirmPassword) {\r\n setError(t(\"passwordMismatch\", \"Passwords do not match\"));\r\n return;\r\n }\r\n\r\n setIsLoading(true);\r\n\r\n try {\r\n await register(username, email, password);\r\n\r\n toast.success(t(\"registerSuccess\", \"Account created successfully!\"), {\r\n description: t(\"checkEmail\", \"Please check your email to verify your account.\"),\r\n });\r\n\r\n // Navigate to login page after successful registration\r\n navigate(\"/login\", {\r\n state: {\r\n message: t(\"verifyEmail\", \"Please verify your email before logging in.\"),\r\n email\r\n }\r\n });\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"registerError\", \"Registration failed. Please try again.\")\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <section className=\"w-full md:grid md:min-h-screen md:grid-cols-2\">\r\n <div className=\"flex items-center justify-center px-4 py-12\">\r\n <div className=\"mx-auto grid w-full max-w-sm gap-6\">\r\n <Logo />\r\n <hr />\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"title\", \"Create Account\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"subtitle\", \"Sign up to get started\")}\r\n </p>\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-3 text-sm text-red-600 bg-red-50 dark:bg-red-950 dark:text-red-400 rounded-md\">\r\n {error}\r\n </div>\r\n )}\r\n\r\n <form onSubmit={handleSubmit} className=\"grid gap-4\">\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"username\">{t(\"username\", \"Username\")}</Label>\r\n <Input\r\n required\r\n id=\"username\"\r\n type=\"text\"\r\n autoComplete=\"username\"\r\n placeholder={t(\"usernamePlaceholder\", \"johndoe\")}\r\n value={username}\r\n onChange={(e) => setUsername(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"email\">{t(\"email\", \"Email\")}</Label>\r\n <Input\r\n required\r\n id=\"email\"\r\n type=\"email\"\r\n autoComplete=\"email\"\r\n placeholder={t(\"emailPlaceholder\", \"you@example.com\")}\r\n value={email}\r\n onChange={(e) => setEmail(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"password\">{t(\"password\", \"Password\")}</Label>\r\n <Input\r\n required\r\n id=\"password\"\r\n type=\"password\"\r\n autoComplete=\"new-password\"\r\n placeholder=\"••••••••\"\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n disabled={isLoading}\r\n minLength={8}\r\n />\r\n </div>\r\n\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"confirm-password\">{t(\"confirmPassword\", \"Confirm Password\")}</Label>\r\n <Input\r\n required\r\n id=\"confirm-password\"\r\n type=\"password\"\r\n autoComplete=\"new-password\"\r\n placeholder=\"••••••••\"\r\n value={confirmPassword}\r\n onChange={(e) => setConfirmPassword(e.target.value)}\r\n disabled={isLoading}\r\n minLength={8}\r\n />\r\n </div>\r\n\r\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\r\n {isLoading ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"creatingAccount\", \"Creating account...\")}\r\n </>\r\n ) : (\r\n t(\"signUp\", \"Sign Up\")\r\n )}\r\n </Button>\r\n </form>\r\n\r\n <div className=\"text-sm text-center\">\r\n <p>\r\n {t(\"hasAccount\", \"Already have an account?\")}{\" \"}\r\n <Link to=\"/login\" className=\"underline\">\r\n {t(\"signIn\", \"Sign in\")}\r\n </Link>\r\n </p>\r\n </div>\r\n\r\n <hr />\r\n <p className=\"text-sm text-muted-foreground\">\r\n © {new Date().getFullYear()} {t(\"copyright\", \"All rights reserved.\")}\r\n </p>\r\n </div>\r\n </div>\r\n <div className=\"hidden p-4 md:block\">\r\n <img\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n width=\"1920\"\r\n height=\"1080\"\r\n alt={t(\"imageAlt\", \"Register background\")}\r\n src={image}\r\n className=\"size-full rounded-lg border bg-muted object-cover object-center\"\r\n />\r\n </div>\r\n </section>\r\n );\r\n}\r\n\r\nexport default RegisterPageSplit;\r\n"
29
27
  },
30
28
  {
31
29
  "path": "register-page-split/lang/en.json",
@@ -4,9 +4,6 @@
4
4
  "title": "Register Page",
5
5
  "description": "Centered card registration page with Layout wrapper. Features username, email, password fields with confirmation, success state after registration. Uses useAuth hook from auth-core for API calls.",
6
6
  "registryDependencies": [
7
- "button",
8
- "input",
9
- "card",
10
7
  "auth-core",
11
8
  "api"
12
9
  ],
@@ -26,7 +23,7 @@
26
23
  "path": "register-page/register-page.tsx",
27
24
  "type": "registry:page",
28
25
  "target": "$modules$/register-page/register-page.tsx",
29
- "content": "import { useState, useEffect } from \"react\";\r\nimport { Link, useNavigate } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useAuth } from \"@/modules/auth-core/use-auth\";\r\nimport { getErrorMessage } from \"@/modules/api/get-error-message\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\r\nimport { UserPlus, Eye, EyeOff, CheckCircle } from \"lucide-react\";\r\n\r\nexport function RegisterPage() {\r\n const { t } = useTranslation(\"register-page\");\r\n usePageTitle({ title: t(\"title\", \"Create Account\") });\r\n\r\n const navigate = useNavigate();\r\n const { register, isAuthenticated } = useAuth();\r\n\r\n const [formData, setFormData] = useState({\r\n username: \"\",\r\n email: \"\",\r\n password: \"\",\r\n confirmPassword: \"\",\r\n });\r\n const [showPassword, setShowPassword] = useState(false);\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const [success, setSuccess] = useState(false);\r\n\r\n // Redirect if already authenticated\r\n useEffect(() => {\r\n if (isAuthenticated) {\r\n navigate(\"/\", { replace: true });\r\n }\r\n }, [isAuthenticated, navigate]);\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setIsSubmitting(true);\r\n setError(null);\r\n\r\n // Validate passwords match\r\n if (formData.password !== formData.confirmPassword) {\r\n setError(t(\"passwordMismatch\", \"Passwords do not match\"));\r\n toast.error(t(\"toastErrorTitle\", \"Registration failed\"), {\r\n description: t(\"passwordMismatch\", \"Passwords do not match\"),\r\n });\r\n setIsSubmitting(false);\r\n return;\r\n }\r\n\r\n try {\r\n await register(formData.username, formData.email, formData.password);\r\n setSuccess(true);\r\n toast.success(t(\"toastSuccessTitle\", \"Account created!\"), {\r\n description: t(\r\n \"toastSuccessDesc\",\r\n \"Please check your email to verify your account.\",\r\n ),\r\n });\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(err, t(\"errorGeneric\", \"Registration failed. Please try again.\"));\r\n setError(errorMessage);\r\n toast.error(t(\"toastErrorTitle\", \"Registration failed\"), {\r\n description: errorMessage,\r\n });\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\r\n setFormData((prev) => ({\r\n ...prev,\r\n [e.target.name]: e.target.value,\r\n }));\r\n };\r\n\r\n if (success) {\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen bg-muted/30 py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"max-w-md mx-auto\">\r\n <Card>\r\n <CardContent className=\"pt-6\">\r\n <div className=\"text-center space-y-4\">\r\n <CheckCircle className=\"w-16 h-16 text-green-500 mx-auto\" />\r\n <h2 className=\"text-2xl font-bold text-foreground\">\r\n {t(\"successTitle\", \"Account Created!\")}\r\n </h2>\r\n <p className=\"text-muted-foreground\">\r\n {t(\"successMessage\", \"Please check your email to verify your account.\")}\r\n </p>\r\n <Button asChild className=\"mt-4\">\r\n <Link to=\"/login\">{t(\"goToLogin\", \"Go to Login\")}</Link>\r\n </Button>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen bg-muted/30 py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n {/* Hero Section */}\r\n <div className=\"text-center mb-12\">\r\n <h1 className=\"text-4xl font-bold text-foreground mb-4\">\r\n {t(\"title\", \"Create Account\")}\r\n </h1>\r\n <div className=\"w-16 h-1 bg-primary mx-auto mb-6\"></div>\r\n <p className=\"text-lg text-muted-foreground max-w-xl mx-auto\">\r\n {t(\"description\", \"Create an account to get started\")}\r\n </p>\r\n </div>\r\n\r\n <div className=\"max-w-md mx-auto\">\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"flex items-center gap-2\">\r\n <UserPlus className=\"w-5 h-5 text-primary\" />\r\n {t(\"cardTitle\", \"Sign Up\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <form onSubmit={handleSubmit} className=\"space-y-6\">\r\n <div>\r\n <Label htmlFor=\"username\">{t(\"username\", \"Username\")} *</Label>\r\n <Input\r\n id=\"username\"\r\n name=\"username\"\r\n type=\"text\"\r\n value={formData.username}\r\n onChange={handleChange}\r\n placeholder={t(\"usernamePlaceholder\", \"Enter your username\")}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"username\"\r\n />\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"email\">{t(\"email\", \"Email\")} *</Label>\r\n <Input\r\n id=\"email\"\r\n name=\"email\"\r\n type=\"email\"\r\n value={formData.email}\r\n onChange={handleChange}\r\n placeholder={t(\"emailPlaceholder\", \"Enter your email\")}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"email\"\r\n />\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"password\">{t(\"password\", \"Password\")} *</Label>\r\n <div className=\"relative\">\r\n <Input\r\n id=\"password\"\r\n name=\"password\"\r\n type={showPassword ? \"text\" : \"password\"}\r\n value={formData.password}\r\n onChange={handleChange}\r\n placeholder={t(\"passwordPlaceholder\", \"Enter password\")}\r\n required\r\n className=\"mt-1 pr-10\"\r\n autoComplete=\"new-password\"\r\n />\r\n <button\r\n type=\"button\"\r\n onClick={() => setShowPassword(!showPassword)}\r\n className=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\r\n >\r\n {showPassword ? (\r\n <EyeOff className=\"w-4 h-4\" />\r\n ) : (\r\n <Eye className=\"w-4 h-4\" />\r\n )}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"confirmPassword\">\r\n {t(\"confirmPassword\", \"Confirm Password\")} *\r\n </Label>\r\n <Input\r\n id=\"confirmPassword\"\r\n name=\"confirmPassword\"\r\n type={showPassword ? \"text\" : \"password\"}\r\n value={formData.confirmPassword}\r\n onChange={handleChange}\r\n placeholder={t(\"confirmPasswordPlaceholder\", \"Confirm your password\")}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"new-password\"\r\n />\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-4 bg-red-50 border border-red-200 rounded-lg\">\r\n <p className=\"text-red-800 text-sm font-medium\">\r\n {error}\r\n </p>\r\n </div>\r\n )}\r\n\r\n <Button\r\n type=\"submit\"\r\n size=\"lg\"\r\n className=\"w-full\"\r\n disabled={isSubmitting}\r\n >\r\n {isSubmitting ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"submitting\", \"Creating account...\")}\r\n </>\r\n ) : (\r\n t(\"submit\", \"Create Account\")\r\n )}\r\n </Button>\r\n\r\n <div className=\"text-center text-sm text-muted-foreground\">\r\n {t(\"hasAccount\", \"Already have an account?\")}{\" \"}\r\n <Link\r\n to=\"/login\"\r\n className=\"text-primary hover:underline font-medium\"\r\n >\r\n {t(\"loginLink\", \"Sign in\")}\r\n </Link>\r\n </div>\r\n </form>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default RegisterPage;\r\n"
26
+ "content": "import { useState, useEffect } from \"react\";\r\nimport { Link, useNavigate } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { getErrorMessage } from \"@/modules/api\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\r\nimport { UserPlus, Eye, EyeOff, CheckCircle } from \"lucide-react\";\r\n\r\nexport function RegisterPage() {\r\n const { t } = useTranslation(\"register-page\");\r\n usePageTitle({ title: t(\"title\", \"Create Account\") });\r\n\r\n const navigate = useNavigate();\r\n const { register, isAuthenticated } = useAuth();\r\n\r\n const [formData, setFormData] = useState({\r\n username: \"\",\r\n email: \"\",\r\n password: \"\",\r\n confirmPassword: \"\",\r\n });\r\n const [showPassword, setShowPassword] = useState(false);\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const [success, setSuccess] = useState(false);\r\n\r\n // Redirect if already authenticated\r\n useEffect(() => {\r\n if (isAuthenticated) {\r\n navigate(\"/\", { replace: true });\r\n }\r\n }, [isAuthenticated, navigate]);\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setIsSubmitting(true);\r\n setError(null);\r\n\r\n // Validate passwords match\r\n if (formData.password !== formData.confirmPassword) {\r\n setError(t(\"passwordMismatch\", \"Passwords do not match\"));\r\n toast.error(t(\"toastErrorTitle\", \"Registration failed\"), {\r\n description: t(\"passwordMismatch\", \"Passwords do not match\"),\r\n });\r\n setIsSubmitting(false);\r\n return;\r\n }\r\n\r\n try {\r\n await register(formData.username, formData.email, formData.password);\r\n setSuccess(true);\r\n toast.success(t(\"toastSuccessTitle\", \"Account created!\"), {\r\n description: t(\r\n \"toastSuccessDesc\",\r\n \"Please check your email to verify your account.\",\r\n ),\r\n });\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(err, t(\"errorGeneric\", \"Registration failed. Please try again.\"));\r\n setError(errorMessage);\r\n toast.error(t(\"toastErrorTitle\", \"Registration failed\"), {\r\n description: errorMessage,\r\n });\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\r\n setFormData((prev) => ({\r\n ...prev,\r\n [e.target.name]: e.target.value,\r\n }));\r\n };\r\n\r\n if (success) {\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen bg-muted/30 py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"max-w-md mx-auto\">\r\n <Card>\r\n <CardContent className=\"pt-6\">\r\n <div className=\"text-center space-y-4\">\r\n <CheckCircle className=\"w-16 h-16 text-green-500 mx-auto\" />\r\n <h2 className=\"text-2xl font-bold text-foreground\">\r\n {t(\"successTitle\", \"Account Created!\")}\r\n </h2>\r\n <p className=\"text-muted-foreground\">\r\n {t(\"successMessage\", \"Please check your email to verify your account.\")}\r\n </p>\r\n <Button asChild className=\"mt-4\">\r\n <Link to=\"/login\">{t(\"goToLogin\", \"Go to Login\")}</Link>\r\n </Button>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen bg-muted/30 py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n {/* Hero Section */}\r\n <div className=\"text-center mb-12\">\r\n <h1 className=\"text-4xl font-bold text-foreground mb-4\">\r\n {t(\"title\", \"Create Account\")}\r\n </h1>\r\n <div className=\"w-16 h-1 bg-primary mx-auto mb-6\"></div>\r\n <p className=\"text-lg text-muted-foreground max-w-xl mx-auto\">\r\n {t(\"description\", \"Create an account to get started\")}\r\n </p>\r\n </div>\r\n\r\n <div className=\"max-w-md mx-auto\">\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"flex items-center gap-2\">\r\n <UserPlus className=\"w-5 h-5 text-primary\" />\r\n {t(\"cardTitle\", \"Sign Up\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <form onSubmit={handleSubmit} className=\"space-y-6\">\r\n <div>\r\n <Label htmlFor=\"username\">{t(\"username\", \"Username\")} *</Label>\r\n <Input\r\n id=\"username\"\r\n name=\"username\"\r\n type=\"text\"\r\n value={formData.username}\r\n onChange={handleChange}\r\n placeholder={t(\"usernamePlaceholder\", \"Enter your username\")}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"username\"\r\n />\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"email\">{t(\"email\", \"Email\")} *</Label>\r\n <Input\r\n id=\"email\"\r\n name=\"email\"\r\n type=\"email\"\r\n value={formData.email}\r\n onChange={handleChange}\r\n placeholder={t(\"emailPlaceholder\", \"Enter your email\")}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"email\"\r\n />\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"password\">{t(\"password\", \"Password\")} *</Label>\r\n <div className=\"relative\">\r\n <Input\r\n id=\"password\"\r\n name=\"password\"\r\n type={showPassword ? \"text\" : \"password\"}\r\n value={formData.password}\r\n onChange={handleChange}\r\n placeholder={t(\"passwordPlaceholder\", \"Enter password\")}\r\n required\r\n className=\"mt-1 pr-10\"\r\n autoComplete=\"new-password\"\r\n />\r\n <button\r\n type=\"button\"\r\n onClick={() => setShowPassword(!showPassword)}\r\n className=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\r\n >\r\n {showPassword ? (\r\n <EyeOff className=\"w-4 h-4\" />\r\n ) : (\r\n <Eye className=\"w-4 h-4\" />\r\n )}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"confirmPassword\">\r\n {t(\"confirmPassword\", \"Confirm Password\")} *\r\n </Label>\r\n <Input\r\n id=\"confirmPassword\"\r\n name=\"confirmPassword\"\r\n type={showPassword ? \"text\" : \"password\"}\r\n value={formData.confirmPassword}\r\n onChange={handleChange}\r\n placeholder={t(\"confirmPasswordPlaceholder\", \"Confirm your password\")}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"new-password\"\r\n />\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-4 bg-red-50 border border-red-200 rounded-lg\">\r\n <p className=\"text-red-800 text-sm font-medium\">\r\n {error}\r\n </p>\r\n </div>\r\n )}\r\n\r\n <Button\r\n type=\"submit\"\r\n size=\"lg\"\r\n className=\"w-full\"\r\n disabled={isSubmitting}\r\n >\r\n {isSubmitting ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"submitting\", \"Creating account...\")}\r\n </>\r\n ) : (\r\n t(\"submit\", \"Create Account\")\r\n )}\r\n </Button>\r\n\r\n <div className=\"text-center text-sm text-muted-foreground\">\r\n {t(\"hasAccount\", \"Already have an account?\")}{\" \"}\r\n <Link\r\n to=\"/login\"\r\n className=\"text-primary hover:underline font-medium\"\r\n >\r\n {t(\"loginLink\", \"Sign in\")}\r\n </Link>\r\n </div>\r\n </form>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default RegisterPage;\r\n"
30
27
  },
31
28
  {
32
29
  "path": "register-page/lang/en.json",