@promakeai/cli 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,13 +30,13 @@
30
30
  "path": "order-confirmation-page/lang/en.json",
31
31
  "type": "registry:lang",
32
32
  "target": "$modules$/order-confirmation-page/lang/en.json",
33
- "content": "{\r\n \"title\": \"Order Confirmed!\",\r\n \"orderNotFound\": \"Order Not Found\",\r\n \"orderNotFoundDescription\": \"We couldn't find the order details. Please check your email for confirmation.\",\r\n \"continueShopping\": \"Continue Shopping\",\r\n \"cashOnDelivery\": \"Cash on Delivery\",\r\n \"bankTransfer\": \"Bank Transfer\",\r\n \"creditCard\": \"Credit Card\",\r\n \"thankYou\": \"Thank you for your order! We've received your order and will process it shortly.\",\r\n \"orderInformation\": \"Order Information\",\r\n \"orderId\": \"Order ID\",\r\n \"orderDate\": \"Order Date\",\r\n \"totalAmount\": \"Total Amount\",\r\n \"paymentMethod\": \"Payment Method\",\r\n \"paymentStatus\": \"Payment Status\",\r\n \"checking\": \"Checking...\",\r\n \"statusError\": \"Error\",\r\n \"paymentSucceeded\": \"Paid\",\r\n \"processing\": \"Processing\",\r\n \"requiresPayment\": \"Requires Payment\",\r\n \"orderStatus\": \"Order Status\",\r\n \"pending\": \"Pending\",\r\n \"deliveryInformation\": \"Delivery Information\",\r\n \"address\": \"Address\",\r\n \"notes\": \"Notes\",\r\n \"orderItems\": \"Order Items\",\r\n \"qty\": \"Qty\",\r\n \"total\": \"Total\",\r\n \"paymentInstructions\": \"Payment Instructions\",\r\n \"bankTransferDetails\": \"Bank Transfer Details\",\r\n \"bankDetailsEmail\": \"Bank details will be sent via email. Please complete the transfer within 48 hours.\",\r\n \"deliveryPayment\": \"Payment on Delivery\",\r\n \"cashOnDeliveryInfo\": \"Cash on Delivery\",\r\n \"codInstructions\": \"Pay when your order arrives. Our delivery team will contact you before delivery.\",\r\n \"backToHome\": \"Back to Home\"\r\n}\r\n"
33
+ "content": "{\r\n \"title\": \"Order Confirmed!\",\r\n \"orderNotFound\": \"Order Not Found\",\r\n \"orderNotFoundDescription\": \"We couldn't find the order details. Please check your email for confirmation.\",\r\n \"continueShopping\": \"Continue Shopping\",\r\n \"cashOnDelivery\": \"Cash on Delivery\",\r\n \"bankTransfer\": \"Bank Transfer\",\r\n \"creditCard\": \"Credit Card\",\r\n \"thankYou\": \"Thank you for your order! We've received your order and will process it shortly.\",\r\n \"orderInformation\": \"Order Information\",\r\n \"orderId\": \"Order ID\",\r\n \"orderDate\": \"Order Date\",\r\n \"totalAmount\": \"Total Amount\",\r\n \"paymentMethod\": \"Payment Method\",\r\n \"paymentStatus\": \"Payment Status\",\r\n \"checking\": \"Checking...\",\r\n \"statusError\": \"Error\",\r\n \"paymentSucceeded\": \"Paid\",\r\n \"processing\": \"Processing\",\r\n \"requiresPayment\": \"Requires Payment\",\r\n \"orderStatus\": \"Order Status\",\r\n \"pending\": \"Pending\",\r\n \"deliveryInformation\": \"Delivery Information\",\r\n \"address\": \"Address\",\r\n \"notes\": \"Notes\",\r\n \"orderItems\": \"Order Items\",\r\n \"qty\": \"Qty\",\r\n \"total\": \"Total\",\r\n \"paymentInstructions\": \"Payment Instructions\",\r\n \"bankTransferDetails\": \"Bank Transfer Details\",\r\n \"bankDetailsEmail\": \"Bank details will be sent via email. Please complete the transfer within 48 hours.\",\r\n \"deliveryPayment\": \"Payment on Delivery\",\r\n \"cashOnDeliveryInfo\": \"Cash on Delivery\",\r\n \"codInstructions\": \"Pay when your order arrives. Our delivery team will contact you before delivery.\",\r\n \"backToHome\": \"Back to Home\",\r\n \"loading\": \"Loading Order...\"\r\n}\r\n"
34
34
  },
35
35
  {
36
36
  "path": "order-confirmation-page/lang/tr.json",
37
37
  "type": "registry:lang",
38
38
  "target": "$modules$/order-confirmation-page/lang/tr.json",
39
- "content": "{\r\n \"title\": \"Sipariş Onaylandı!\",\r\n \"orderNotFound\": \"Sipariş Bulunamadı\",\r\n \"orderNotFoundDescription\": \"Sipariş detaylarını bulamadık. Lütfen onay için e-postanızı kontrol edin.\",\r\n \"continueShopping\": \"Alışverişe Devam Et\",\r\n \"cashOnDelivery\": \"Kapıda Ödeme\",\r\n \"bankTransfer\": \"Havale/EFT\",\r\n \"creditCard\": \"Kredi Kartı\",\r\n \"thankYou\": \"Siparişiniz için teşekkürler! Siparişinizi aldık ve en kısa sürede işleme alacağız.\",\r\n \"orderInformation\": \"Sipariş Bilgileri\",\r\n \"orderId\": \"Sipariş No\",\r\n \"orderDate\": \"Sipariş Tarihi\",\r\n \"totalAmount\": \"Toplam Tutar\",\r\n \"paymentMethod\": \"Ödeme Yöntemi\",\r\n \"paymentStatus\": \"Ödeme Durumu\",\r\n \"checking\": \"Kontrol ediliyor...\",\r\n \"statusError\": \"Hata\",\r\n \"paymentSucceeded\": \"Ödendi\",\r\n \"processing\": \"İşleniyor\",\r\n \"requiresPayment\": \"Ödeme Gerekli\",\r\n \"orderStatus\": \"Sipariş Durumu\",\r\n \"pending\": \"Beklemede\",\r\n \"deliveryInformation\": \"Teslimat Bilgileri\",\r\n \"address\": \"Adres\",\r\n \"notes\": \"Notlar\",\r\n \"orderItems\": \"Sipariş Ürünleri\",\r\n \"qty\": \"Adet\",\r\n \"total\": \"Toplam\",\r\n \"paymentInstructions\": \"Ödeme Talimatları\",\r\n \"bankTransferDetails\": \"Havale/EFT Bilgileri\",\r\n \"bankDetailsEmail\": \"Banka bilgileri e-posta ile gönderilecektir. Lütfen transferi 48 saat içinde tamamlayın.\",\r\n \"deliveryPayment\": \"Teslimatta Ödeme\",\r\n \"cashOnDeliveryInfo\": \"Kapıda Ödeme\",\r\n \"codInstructions\": \"Siparişiniz geldiğinde ödeme yapın. Teslimat ekibimiz teslimat öncesi sizinle iletişime geçecektir.\",\r\n \"backToHome\": \"Ana Sayfaya Dön\"\r\n}\r\n"
39
+ "content": "{\r\n \"title\": \"Sipariş Onaylandı!\",\r\n \"orderNotFound\": \"Sipariş Bulunamadı\",\r\n \"orderNotFoundDescription\": \"Sipariş detaylarını bulamadık. Lütfen onay için e-postanızı kontrol edin.\",\r\n \"continueShopping\": \"Alışverişe Devam Et\",\r\n \"cashOnDelivery\": \"Kapıda Ödeme\",\r\n \"bankTransfer\": \"Havale/EFT\",\r\n \"creditCard\": \"Kredi Kartı\",\r\n \"thankYou\": \"Siparişiniz için teşekkürler! Siparişinizi aldık ve en kısa sürede işleme alacağız.\",\r\n \"orderInformation\": \"Sipariş Bilgileri\",\r\n \"orderId\": \"Sipariş No\",\r\n \"orderDate\": \"Sipariş Tarihi\",\r\n \"totalAmount\": \"Toplam Tutar\",\r\n \"paymentMethod\": \"Ödeme Yöntemi\",\r\n \"paymentStatus\": \"Ödeme Durumu\",\r\n \"checking\": \"Kontrol ediliyor...\",\r\n \"statusError\": \"Hata\",\r\n \"paymentSucceeded\": \"Ödendi\",\r\n \"processing\": \"İşleniyor\",\r\n \"requiresPayment\": \"Ödeme Gerekli\",\r\n \"orderStatus\": \"Sipariş Durumu\",\r\n \"pending\": \"Beklemede\",\r\n \"deliveryInformation\": \"Teslimat Bilgileri\",\r\n \"address\": \"Adres\",\r\n \"notes\": \"Notlar\",\r\n \"orderItems\": \"Sipariş Ürünleri\",\r\n \"qty\": \"Adet\",\r\n \"total\": \"Toplam\",\r\n \"paymentInstructions\": \"Ödeme Talimatları\",\r\n \"bankTransferDetails\": \"Havale/EFT Bilgileri\",\r\n \"bankDetailsEmail\": \"Banka bilgileri e-posta ile gönderilecektir. Lütfen transferi 48 saat içinde tamamlayın.\",\r\n \"deliveryPayment\": \"Teslimatta Ödeme\",\r\n \"cashOnDeliveryInfo\": \"Kapıda Ödeme\",\r\n \"codInstructions\": \"Siparişiniz geldiğinde ödeme yapın. Teslimat ekibimiz teslimat öncesi sizinle iletişime geçecektir.\",\r\n \"backToHome\": \"Ana Sayfaya Dön\",\r\n \"loading\": \"Sipariş Yükleniyor...\"\r\n}\r\n"
40
40
  }
41
41
  ],
42
42
  "exports": {
@@ -24,13 +24,13 @@
24
24
  "path": "post-detail-block/lang/en.json",
25
25
  "type": "registry:lang",
26
26
  "target": "$modules$/post-detail-block/lang/en.json",
27
- "content": "{\r\n \"backToBlog\": \"Back to Blog\",\r\n \"minRead\": \"min read\",\r\n \"views\": \"views\",\r\n \"share\": \"Share\",\r\n \"addToFavorites\": \"Add to Favorites\",\r\n \"removeFromFavorites\": \"Remove from Favorites\",\r\n \"tags\": \"Tags\"\r\n}\r\n"
27
+ "content": "{\r\n \"backToBlog\": \"Back to Blog\",\r\n \"minRead\": \"min read\",\r\n \"views\": \"views\",\r\n \"share\": \"Share\",\r\n \"addToFavorites\": \"Add to Favorites\",\r\n \"removeFromFavorites\": \"Remove from Favorites\",\r\n \"tags\": \"Tags\",\r\n \"authorDescription\": \"Author of this post\"\r\n}\r\n"
28
28
  },
29
29
  {
30
30
  "path": "post-detail-block/lang/tr.json",
31
31
  "type": "registry:lang",
32
32
  "target": "$modules$/post-detail-block/lang/tr.json",
33
- "content": "{\r\n \"backToBlog\": \"Blog'a Dön\",\r\n \"minRead\": \"dk okuma\",\r\n \"views\": \"görüntülenme\",\r\n \"share\": \"Paylaş\",\r\n \"addToFavorites\": \"Favorilere Ekle\",\r\n \"removeFromFavorites\": \"Favorilerden Çıkar\",\r\n \"tags\": \"Etiketler\"\r\n}\r\n"
33
+ "content": "{\r\n \"backToBlog\": \"Blog'a Dön\",\r\n \"minRead\": \"dk okuma\",\r\n \"views\": \"görüntülenme\",\r\n \"share\": \"Paylaş\",\r\n \"addToFavorites\": \"Favorilere Ekle\",\r\n \"removeFromFavorites\": \"Favorilerden Çıkar\",\r\n \"tags\": \"Etiketler\",\r\n \"authorDescription\": \"Bu yazının yazarı\"\r\n}\r\n"
34
34
  }
35
35
  ],
36
36
  "exports": {
@@ -28,13 +28,13 @@
28
28
  "path": "product-card-detailed/lang/en.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/product-card-detailed/lang/en.json",
31
- "content": "{\r\n \"wishlist\": \"Wishlist\",\r\n \"addToCart\": \"Add to Cart\",\r\n \"buyNow\": \"Buy Now\"\r\n}\r\n"
31
+ "content": "{\r\n \"wishlist\": \"Wishlist\",\r\n \"addToCart\": \"Add to Cart\",\r\n \"buyNow\": \"Buy Now\",\r\n \"addedToCart\": \"Added to cart!\"\r\n}\r\n"
32
32
  },
33
33
  {
34
34
  "path": "product-card-detailed/lang/tr.json",
35
35
  "type": "registry:lang",
36
36
  "target": "$modules$/product-card-detailed/lang/tr.json",
37
- "content": "{\r\n \"wishlist\": \"Favorilere Ekle\",\r\n \"addToCart\": \"Sepete Ekle\",\r\n \"buyNow\": \"Hemen Al\"\r\n}\r\n"
37
+ "content": "{\r\n \"wishlist\": \"Favorilere Ekle\",\r\n \"addToCart\": \"Sepete Ekle\",\r\n \"buyNow\": \"Hemen Al\",\r\n \"addedToCart\": \"Sepete eklendi!\"\r\n}\r\n"
38
38
  }
39
39
  ],
40
40
  "exports": {
@@ -29,13 +29,13 @@
29
29
  "path": "product-detail-section/lang/en.json",
30
30
  "type": "registry:lang",
31
31
  "target": "$modules$/product-detail-section/lang/en.json",
32
- "content": "{\r\n \"reviews\": \"Reviews\",\r\n \"color\": \"Color\",\r\n \"size\": \"Size\",\r\n \"addToCart\": \"Add to Cart\"\r\n}\r\n"
32
+ "content": "{\r\n \"reviews\": \"Reviews\",\r\n \"color\": \"Color\",\r\n \"size\": \"Size\",\r\n \"addToCart\": \"Add to Cart\",\r\n \"addedToCart\": \"Added to cart!\",\r\n \"category\": \"Category\",\r\n \"inStock\": \"In Stock\",\r\n \"outOfStock\": \"Out of Stock\"\r\n}\r\n"
33
33
  },
34
34
  {
35
35
  "path": "product-detail-section/lang/tr.json",
36
36
  "type": "registry:lang",
37
37
  "target": "$modules$/product-detail-section/lang/tr.json",
38
- "content": "{\r\n \"reviews\": \"Değerlendirme\",\r\n \"color\": \"Renk\",\r\n \"size\": \"Beden\",\r\n \"addToCart\": \"Sepete Ekle\"\r\n}\r\n"
38
+ "content": "{\r\n \"reviews\": \"Değerlendirme\",\r\n \"color\": \"Renk\",\r\n \"size\": \"Beden\",\r\n \"addToCart\": \"Sepete Ekle\",\r\n \"addedToCart\": \"Sepete eklendi!\",\r\n \"category\": \"Kategori\",\r\n \"inStock\": \"Stokta\",\r\n \"outOfStock\": \"Stokta Yok\"\r\n}\r\n"
39
39
  }
40
40
  ],
41
41
  "exports": {
@@ -29,13 +29,13 @@
29
29
  "path": "product-quick-view/lang/en.json",
30
30
  "type": "registry:lang",
31
31
  "target": "$modules$/product-quick-view/lang/en.json",
32
- "content": "{\r\n \"size\": \"Size\",\r\n \"color\": \"Color\",\r\n \"quantity\": \"Quantity\",\r\n \"addToCart\": \"Add to Cart\",\r\n \"viewDetails\": \"View Full Details\"\r\n}\r\n"
32
+ "content": "{\r\n \"size\": \"Size\",\r\n \"color\": \"Color\",\r\n \"quantity\": \"Quantity\",\r\n \"addToCart\": \"Add to Cart\",\r\n \"viewDetails\": \"View Full Details\",\r\n \"addedToCart\": \"Added to cart!\",\r\n \"removedFromFavorites\": \"Removed from favorites\",\r\n \"addedToFavorites\": \"Added to favorites!\",\r\n \"category\": \"Category\"\r\n}\r\n"
33
33
  },
34
34
  {
35
35
  "path": "product-quick-view/lang/tr.json",
36
36
  "type": "registry:lang",
37
37
  "target": "$modules$/product-quick-view/lang/tr.json",
38
- "content": "{\r\n \"size\": \"Beden\",\r\n \"color\": \"Renk\",\r\n \"quantity\": \"Adet\",\r\n \"addToCart\": \"Sepete Ekle\",\r\n \"viewDetails\": \"Tüm Detayları Gör\"\r\n}\r\n"
38
+ "content": "{\r\n \"size\": \"Beden\",\r\n \"color\": \"Renk\",\r\n \"quantity\": \"Adet\",\r\n \"addToCart\": \"Sepete Ekle\",\r\n \"viewDetails\": \"Tüm Detayları Gör\",\r\n \"addedToCart\": \"Sepete eklendi!\",\r\n \"removedFromFavorites\": \"Favorilerden çıkarıldı\",\r\n \"addedToFavorites\": \"Favorilere eklendi!\",\r\n \"category\": \"Kategori\"\r\n}\r\n"
39
39
  }
40
40
  ],
41
41
  "exports": {
@@ -23,7 +23,7 @@
23
23
  "path": "reset-password-page-split/reset-password-page-split.tsx",
24
24
  "type": "registry:page",
25
25
  "target": "$modules$/reset-password-page-split/reset-password-page-split.tsx",
26
- "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useSearchParams } 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 { Logo } from \"@/components/Logo\";\r\nimport { KeyRound, ArrowLeft, CheckCircle } from \"lucide-react\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { getErrorMessage } from \"@/modules/api\";\r\nimport { FormField } from \"@/components/FormField\";\r\nimport { PasswordInput } from \"@/components/PasswordInput\";\r\n\r\ninterface ResetPasswordPageSplitProps {\r\n image?: string;\r\n}\r\n\r\nexport function ResetPasswordPageSplit({\r\n image = \"/images/placeholder.png\",\r\n}: ResetPasswordPageSplitProps) {\r\n const { t } = useTranslation(\"reset-password-page-split\");\r\n usePageTitle({ title: t(\"title\", \"Reset Password\") });\r\n const navigate = useNavigate();\r\n const [searchParams] = useSearchParams();\r\n const { resetPassword } = useAuth();\r\n\r\n // Get code and username from URL params\r\n const code = searchParams.get(\"code\") || \"\";\r\n const username = searchParams.get(\"username\") || searchParams.get(\"email\") || \"\";\r\n\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 const [isSuccess, setIsSuccess] = useState(false);\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 // Validate we have required params\r\n if (!code || !username) {\r\n setError(t(\"invalidLink\", \"Invalid or expired reset link. Please request a new one.\"));\r\n return;\r\n }\r\n\r\n setIsLoading(true);\r\n\r\n try {\r\n await resetPassword(username, code, password);\r\n\r\n setIsSuccess(true);\r\n toast.success(t(\"passwordResetSuccess\", \"Password reset successfully!\"));\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"resetPasswordError\", \"Failed to reset password. Please try again.\")\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n // Success state\r\n if (isSuccess) {\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 text-center\">\r\n <Logo />\r\n <hr />\r\n\r\n <div className=\"flex justify-center\">\r\n <div className=\"w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center\">\r\n <CheckCircle className=\"w-8 h-8 text-green-600 dark:text-green-400\" />\r\n </div>\r\n </div>\r\n\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"passwordReset\", \"Password Reset!\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-2\">\r\n {t(\"passwordResetDescription\", \"Your password has been reset successfully. You can now log in with your new password.\")}\r\n </p>\r\n </div>\r\n\r\n <Button onClick={() => navigate(\"/login\")} className=\"w-full\">\r\n {t(\"goToLogin\", \"Go to Login\")}\r\n </Button>\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\", \"Reset password 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\n // Invalid link state\r\n if (!code || !username) {\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 text-center\">\r\n <Logo />\r\n <hr />\r\n\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight text-red-600\">\r\n {t(\"invalidLinkTitle\", \"Invalid Reset Link\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-2\">\r\n {t(\"invalidLinkDescription\", \"This password reset link is invalid or has expired. Please request a new one.\")}\r\n </p>\r\n </div>\r\n\r\n <Button onClick={() => navigate(\"/forgot-password\")} className=\"w-full\">\r\n {t(\"requestNewLink\", \"Request New Link\")}\r\n </Button>\r\n\r\n <Link\r\n to=\"/login\"\r\n className=\"inline-flex items-center justify-center gap-2 text-sm text-muted-foreground hover:text-foreground\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to login\")}\r\n </Link>\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\", \"Reset password 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\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\r\n <div className=\"flex justify-center\">\r\n <div className=\"w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center\">\r\n <KeyRound className=\"w-6 h-6 text-primary\" />\r\n </div>\r\n </div>\r\n\r\n <div className=\"text-center\">\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"title\", \"Reset Password\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-1\">\r\n {t(\"subtitle\", \"Enter your new password below\")}\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 <FormField label={t(\"newPassword\", \"New Password\")} htmlFor=\"password\" required>\r\n <PasswordInput\r\n required\r\n id=\"password\"\r\n name=\"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 </FormField>\r\n </div>\r\n\r\n <div className=\"grid gap-2\">\r\n <FormField label={t(\"confirmPassword\", \"Confirm Password\")} htmlFor=\"confirm-password\" required>\r\n <PasswordInput\r\n required\r\n id=\"confirm-password\"\r\n name=\"confirm-password\"\r\n autoComplete=\"confirm-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 </FormField>\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(\"resetting\", \"Resetting...\")}\r\n </>\r\n ) : (\r\n t(\"resetPassword\", \"Reset Password\")\r\n )}\r\n </Button>\r\n </form>\r\n\r\n <Link\r\n to=\"/login\"\r\n className=\"inline-flex items-center justify-center gap-2 text-sm text-muted-foreground hover:text-foreground\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to login\")}\r\n </Link>\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\", \"Reset password 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 ResetPasswordPageSplit;\r\n"
26
+ "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useSearchParams } 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 { Logo } from \"@/components/Logo\";\r\nimport { KeyRound, ArrowLeft, CheckCircle } from \"lucide-react\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { getErrorMessage } from \"@/modules/api\";\r\nimport { FormField } from \"@/components/FormField\";\r\nimport { PasswordInput } from \"@/components/PasswordInput\";\r\n\r\ninterface ResetPasswordPageSplitProps {\r\n image?: string;\r\n}\r\n\r\nexport function ResetPasswordPageSplit({\r\n image = \"/images/placeholder.png\",\r\n}: ResetPasswordPageSplitProps) {\r\n const { t } = useTranslation(\"reset-password-page-split\");\r\n usePageTitle({ title: t(\"title\", \"Reset Password\") });\r\n const navigate = useNavigate();\r\n const [searchParams] = useSearchParams();\r\n const { resetPassword } = useAuth();\r\n\r\n // Get code and email from URL params\r\n const code = searchParams.get(\"code\") || \"\";\r\n const email = searchParams.get(\"email\") || \"\";\r\n\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 const [isSuccess, setIsSuccess] = useState(false);\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 // Validate we have required params\r\n if (!code || !email) {\r\n setError(t(\"invalidLink\", \"Invalid or expired reset link. Please request a new one.\"));\r\n return;\r\n }\r\n\r\n setIsLoading(true);\r\n\r\n try {\r\n await resetPassword(email, code, password);\r\n\r\n setIsSuccess(true);\r\n toast.success(t(\"passwordResetSuccess\", \"Password reset successfully!\"));\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"resetPasswordError\", \"Failed to reset password. Please try again.\")\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n // Success state\r\n if (isSuccess) {\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 text-center\">\r\n <Logo />\r\n <hr />\r\n\r\n <div className=\"flex justify-center\">\r\n <div className=\"w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center\">\r\n <CheckCircle className=\"w-8 h-8 text-green-600 dark:text-green-400\" />\r\n </div>\r\n </div>\r\n\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"passwordReset\", \"Password Reset!\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-2\">\r\n {t(\"passwordResetDescription\", \"Your password has been reset successfully. You can now log in with your new password.\")}\r\n </p>\r\n </div>\r\n\r\n <Button onClick={() => navigate(\"/login\")} className=\"w-full\">\r\n {t(\"goToLogin\", \"Go to Login\")}\r\n </Button>\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\", \"Reset password 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\n // Invalid link state\r\n if (!code || !email) {\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 text-center\">\r\n <Logo />\r\n <hr />\r\n\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight text-red-600\">\r\n {t(\"invalidLinkTitle\", \"Invalid Reset Link\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-2\">\r\n {t(\"invalidLinkDescription\", \"This password reset link is invalid or has expired. Please request a new one.\")}\r\n </p>\r\n </div>\r\n\r\n <Button onClick={() => navigate(\"/forgot-password\")} className=\"w-full\">\r\n {t(\"requestNewLink\", \"Request New Link\")}\r\n </Button>\r\n\r\n <Link\r\n to=\"/login\"\r\n className=\"inline-flex items-center justify-center gap-2 text-sm text-muted-foreground hover:text-foreground\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to login\")}\r\n </Link>\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\", \"Reset password 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\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\r\n <div className=\"flex justify-center\">\r\n <div className=\"w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center\">\r\n <KeyRound className=\"w-6 h-6 text-primary\" />\r\n </div>\r\n </div>\r\n\r\n <div className=\"text-center\">\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"title\", \"Reset Password\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-1\">\r\n {t(\"subtitle\", \"Enter your new password below\")}\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 <FormField label={t(\"newPassword\", \"New Password\")} htmlFor=\"password\" required>\r\n <PasswordInput\r\n required\r\n id=\"password\"\r\n name=\"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 </FormField>\r\n </div>\r\n\r\n <div className=\"grid gap-2\">\r\n <FormField label={t(\"confirmPassword\", \"Confirm Password\")} htmlFor=\"confirm-password\" required>\r\n <PasswordInput\r\n required\r\n id=\"confirm-password\"\r\n name=\"confirm-password\"\r\n autoComplete=\"confirm-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 </FormField>\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(\"resetting\", \"Resetting...\")}\r\n </>\r\n ) : (\r\n t(\"resetPassword\", \"Reset Password\")\r\n )}\r\n </Button>\r\n </form>\r\n\r\n <Link\r\n to=\"/login\"\r\n className=\"inline-flex items-center justify-center gap-2 text-sm text-muted-foreground hover:text-foreground\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to login\")}\r\n </Link>\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\", \"Reset password 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 ResetPasswordPageSplit;\r\n"
27
27
  },
28
28
  {
29
29
  "path": "reset-password-page-split/lang/en.json",
@@ -23,7 +23,7 @@
23
23
  "path": "reset-password-page/reset-password-page.tsx",
24
24
  "type": "registry:page",
25
25
  "target": "$modules$/reset-password-page/reset-password-page.tsx",
26
- "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useSearchParams } 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 {\r\n Card,\r\n CardContent,\r\n CardHeader,\r\n CardTitle,\r\n CardDescription,\r\n} from \"@/components/ui/card\";\r\nimport { KeyRound, ArrowLeft, Eye, EyeOff, CheckCircle2 } from \"lucide-react\";\r\n\r\nexport function ResetPasswordPage() {\r\n const { t } = useTranslation(\"reset-password-page\");\r\n usePageTitle({ title: t(\"title\", \"Reset Password\") });\r\n const navigate = useNavigate();\r\n const [searchParams] = useSearchParams();\r\n const { resetPassword } = useAuth();\r\n\r\n const code = searchParams.get(\"code\") || \"\";\r\n const username =\r\n searchParams.get(\"username\") || searchParams.get(\"email\") || \"\";\r\n\r\n const [password, setPassword] = useState(\"\");\r\n const [confirmPassword, setConfirmPassword] = useState(\"\");\r\n const [showPassword, setShowPassword] = useState(false);\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const [isSuccess, setIsSuccess] = useState(false);\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n\r\n if (password !== confirmPassword) {\r\n setError(t(\"passwordMismatch\", \"Passwords do not match\"));\r\n return;\r\n }\r\n\r\n if (!code || !username) {\r\n setError(\r\n t(\r\n \"invalidLink\",\r\n \"Invalid or expired reset link. Please request a new one.\",\r\n ),\r\n );\r\n return;\r\n }\r\n\r\n setIsLoading(true);\r\n\r\n try {\r\n await resetPassword(username, code, password);\r\n setIsSuccess(true);\r\n toast.success(\r\n t(\"passwordResetSuccess\", \"Password reset successfully!\"),\r\n );\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\r\n \"resetPasswordError\",\r\n \"Failed to reset password. Please try again.\",\r\n ),\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n // Success state\r\n if (isSuccess) {\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-8 pb-8 text-center\">\r\n <CheckCircle2 className=\"w-16 h-16 text-green-500 mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"passwordReset\", \"Password Reset!\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"passwordResetDescription\",\r\n \"Your password has been reset successfully. You can now log in with your new password.\",\r\n )}\r\n </p>\r\n <Button asChild className=\"w-full\">\r\n <Link to=\"/login\">\r\n {t(\"goToLogin\", \"Go to Login\")}\r\n </Link>\r\n </Button>\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 // Invalid link state\r\n if (!code || !username) {\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-8 pb-8 text-center\">\r\n <h1 className=\"text-2xl font-bold mb-2 text-red-600\">\r\n {t(\"invalidLinkTitle\", \"Invalid Reset Link\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"invalidLinkDescription\",\r\n \"This password reset link is invalid or has expired. Please request a new one.\",\r\n )}\r\n </p>\r\n <Button\r\n onClick={() => navigate(\"/forgot-password\")}\r\n className=\"w-full mb-3\"\r\n >\r\n {t(\"requestNewLink\", \"Request New Link\")}\r\n </Button>\r\n <Link\r\n to=\"/login\"\r\n className=\"inline-flex items-center justify-center gap-1 text-sm text-muted-foreground hover:text-primary\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to Login\")}\r\n </Link>\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 <div className=\"text-center mb-12\">\r\n <h1 className=\"text-4xl font-bold text-foreground mb-4\">\r\n {t(\"title\", \"Reset Password\")}\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(\"subtitle\", \"Enter your new password below\")}\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 <KeyRound className=\"w-5 h-5 text-primary\" />\r\n {t(\"resetPassword\", \"Reset Password\")}\r\n </CardTitle>\r\n <CardDescription>\r\n {t(\"subtitle\", \"Enter your new password below\")}\r\n </CardDescription>\r\n </CardHeader>\r\n <CardContent>\r\n <form onSubmit={handleSubmit} className=\"space-y-6\">\r\n <div>\r\n <Label htmlFor=\"password\">\r\n {t(\"newPassword\", \"New Password\")} *\r\n </Label>\r\n <div className=\"relative\">\r\n <Input\r\n id=\"password\"\r\n type={showPassword ? \"text\" : \"password\"}\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n placeholder={t(\r\n \"newPasswordPlaceholder\",\r\n \"Enter new password\",\r\n )}\r\n required\r\n className=\"mt-1 pr-10\"\r\n autoComplete=\"new-password\"\r\n minLength={8}\r\n disabled={isLoading}\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 <p className=\"text-xs text-muted-foreground mt-1\">\r\n {t(\r\n \"passwordRequirements\",\r\n \"At least 8 characters, 1 letter and 1 number\",\r\n )}\r\n </p>\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 type={showPassword ? \"text\" : \"password\"}\r\n value={confirmPassword}\r\n onChange={(e) => setConfirmPassword(e.target.value)}\r\n placeholder={t(\r\n \"confirmPasswordPlaceholder\",\r\n \"Confirm new password\",\r\n )}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"new-password\"\r\n minLength={8}\r\n disabled={isLoading}\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={isLoading}\r\n >\r\n {isLoading ? (\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(\"resetting\", \"Resetting...\")}\r\n </>\r\n ) : (\r\n t(\"resetPassword\", \"Reset Password\")\r\n )}\r\n </Button>\r\n\r\n <div className=\"text-center\">\r\n <Link\r\n to=\"/login\"\r\n className=\"text-sm text-muted-foreground hover:text-primary inline-flex items-center gap-1\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to Login\")}\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 ResetPasswordPage;\r\n"
26
+ "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useSearchParams } 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 {\r\n Card,\r\n CardContent,\r\n CardHeader,\r\n CardTitle,\r\n CardDescription,\r\n} from \"@/components/ui/card\";\r\nimport { KeyRound, ArrowLeft, Eye, EyeOff, CheckCircle2 } from \"lucide-react\";\r\n\r\nexport function ResetPasswordPage() {\r\n const { t } = useTranslation(\"reset-password-page\");\r\n usePageTitle({ title: t(\"title\", \"Reset Password\") });\r\n const navigate = useNavigate();\r\n const [searchParams] = useSearchParams();\r\n const { resetPassword } = useAuth();\r\n\r\n const code = searchParams.get(\"code\") || \"\";\r\n const email = searchParams.get(\"email\") || \"\";\r\n\r\n const [password, setPassword] = useState(\"\");\r\n const [confirmPassword, setConfirmPassword] = useState(\"\");\r\n const [showPassword, setShowPassword] = useState(false);\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const [isSuccess, setIsSuccess] = useState(false);\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n\r\n if (password !== confirmPassword) {\r\n setError(t(\"passwordMismatch\", \"Passwords do not match\"));\r\n return;\r\n }\r\n\r\n if (!code || !email) {\r\n setError(\r\n t(\r\n \"invalidLink\",\r\n \"Invalid or expired reset link. Please request a new one.\",\r\n ),\r\n );\r\n return;\r\n }\r\n\r\n setIsLoading(true);\r\n\r\n try {\r\n await resetPassword(email, code, password);\r\n setIsSuccess(true);\r\n toast.success(\r\n t(\"passwordResetSuccess\", \"Password reset successfully!\"),\r\n );\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\r\n \"resetPasswordError\",\r\n \"Failed to reset password. Please try again.\",\r\n ),\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n // Success state\r\n if (isSuccess) {\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-8 pb-8 text-center\">\r\n <CheckCircle2 className=\"w-16 h-16 text-green-500 mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"passwordReset\", \"Password Reset!\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"passwordResetDescription\",\r\n \"Your password has been reset successfully. You can now log in with your new password.\",\r\n )}\r\n </p>\r\n <Button asChild className=\"w-full\">\r\n <Link to=\"/login\">\r\n {t(\"goToLogin\", \"Go to Login\")}\r\n </Link>\r\n </Button>\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 // Invalid link state\r\n if (!code || !email) {\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-8 pb-8 text-center\">\r\n <h1 className=\"text-2xl font-bold mb-2 text-red-600\">\r\n {t(\"invalidLinkTitle\", \"Invalid Reset Link\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"invalidLinkDescription\",\r\n \"This password reset link is invalid or has expired. Please request a new one.\",\r\n )}\r\n </p>\r\n <Button\r\n onClick={() => navigate(\"/forgot-password\")}\r\n className=\"w-full mb-3\"\r\n >\r\n {t(\"requestNewLink\", \"Request New Link\")}\r\n </Button>\r\n <Link\r\n to=\"/login\"\r\n className=\"inline-flex items-center justify-center gap-1 text-sm text-muted-foreground hover:text-primary\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to Login\")}\r\n </Link>\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 <div className=\"text-center mb-12\">\r\n <h1 className=\"text-4xl font-bold text-foreground mb-4\">\r\n {t(\"title\", \"Reset Password\")}\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(\"subtitle\", \"Enter your new password below\")}\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 <KeyRound className=\"w-5 h-5 text-primary\" />\r\n {t(\"resetPassword\", \"Reset Password\")}\r\n </CardTitle>\r\n <CardDescription>\r\n {t(\"subtitle\", \"Enter your new password below\")}\r\n </CardDescription>\r\n </CardHeader>\r\n <CardContent>\r\n <form onSubmit={handleSubmit} className=\"space-y-6\">\r\n <div>\r\n <Label htmlFor=\"password\">\r\n {t(\"newPassword\", \"New Password\")} *\r\n </Label>\r\n <div className=\"relative\">\r\n <Input\r\n id=\"password\"\r\n type={showPassword ? \"text\" : \"password\"}\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n placeholder={t(\r\n \"newPasswordPlaceholder\",\r\n \"Enter new password\",\r\n )}\r\n required\r\n className=\"mt-1 pr-10\"\r\n autoComplete=\"new-password\"\r\n minLength={8}\r\n disabled={isLoading}\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 <p className=\"text-xs text-muted-foreground mt-1\">\r\n {t(\r\n \"passwordRequirements\",\r\n \"At least 8 characters, 1 letter and 1 number\",\r\n )}\r\n </p>\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 type={showPassword ? \"text\" : \"password\"}\r\n value={confirmPassword}\r\n onChange={(e) => setConfirmPassword(e.target.value)}\r\n placeholder={t(\r\n \"confirmPasswordPlaceholder\",\r\n \"Confirm new password\",\r\n )}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"new-password\"\r\n minLength={8}\r\n disabled={isLoading}\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={isLoading}\r\n >\r\n {isLoading ? (\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(\"resetting\", \"Resetting...\")}\r\n </>\r\n ) : (\r\n t(\"resetPassword\", \"Reset Password\")\r\n )}\r\n </Button>\r\n\r\n <div className=\"text-center\">\r\n <Link\r\n to=\"/login\"\r\n className=\"text-sm text-muted-foreground hover:text-primary inline-flex items-center gap-1\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to Login\")}\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 ResetPasswordPage;\r\n"
27
27
  },
28
28
  {
29
29
  "path": "reset-password-page/lang/en.json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promakeai/cli",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "promake": "dist/index.js"
package/template/bun.lock CHANGED
@@ -5,8 +5,8 @@
5
5
  "name": "new",
6
6
  "dependencies": {
7
7
  "@hookform/resolvers": "^5.2.2",
8
- "@promakeai/customer-backend-client": "^1.1.0",
9
- "@promakeai/dbreact": "^1.0.8",
8
+ "@promakeai/customer-backend-client": "^1.2.0",
9
+ "@promakeai/dbreact": "^1.1.1",
10
10
  "@promakeai/inspector": "^1.7.4",
11
11
  "@radix-ui/react-accordion": "^1.2.12",
12
12
  "@radix-ui/react-alert-dialog": "^1.1.15",
@@ -229,15 +229,15 @@
229
229
 
230
230
  "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
231
231
 
232
- "@promakeai/customer-backend-client": ["@promakeai/customer-backend-client@1.1.0", "", { "dependencies": { "axios": "^1.7.0", "zod": "^4.1.13" }, "peerDependencies": { "@tanstack/react-query": ">=5.0.0", "react": ">=18.0.0" }, "optionalPeers": ["@tanstack/react-query", "react"] }, "sha512-Ql56YtwRQyJ9toFyfSkkn0deAdlVodNdK7d2xd9nVVAS7C2F8fxIuJ3xCtWtdLfXjponHhG2qdy9ILq8V4fpbA=="],
232
+ "@promakeai/customer-backend-client": ["@promakeai/customer-backend-client@1.2.0", "", { "dependencies": { "axios": "^1.7.0", "zod": "^4.1.13" }, "peerDependencies": { "@tanstack/react-query": ">=5.0.0", "react": ">=18.0.0" }, "optionalPeers": ["@tanstack/react-query", "react"] }, "sha512-w8uk1abno9W+HeYs0DXOCpGSO4kbdWCLkPNHy53j25qXCLk5xAnqE6wRio9BaADk5hkRC+Ghs428sce1AXWOgA=="],
233
233
 
234
- "@promakeai/dbreact": ["@promakeai/dbreact@1.0.8", "", { "dependencies": { "@promakeai/orm": "1.0.6" }, "peerDependencies": { "@tanstack/react-query": ">=5.0.0", "react": ">=19.0.0", "react-dom": ">=19.0.0", "sql.js": ">=1.11.0" } }, "sha512-QjoFNrCfrVgYlvROYYSV2PCeoiWvNOopHuqH36+YudlP2i2BbS02FQ5J2ELZcxGa1GiI94ikRoBKbvvTsGpqLg=="],
234
+ "@promakeai/dbreact": ["@promakeai/dbreact@1.1.1", "", { "dependencies": { "@promakeai/orm": "1.3.0" }, "peerDependencies": { "@tanstack/react-query": ">=5.0.0", "react": ">=19.0.0", "react-dom": ">=19.0.0", "sql.js": ">=1.11.0" } }, "sha512-xlnebJzy7u9ABLbJlAQ/p6arIfAFj4DbNvKWswD5PP4aEESoaBrLLk/QL/O3BXsyzLAB3GuzsMyuGkevrCybdw=="],
235
235
 
236
236
  "@promakeai/inspector": ["@promakeai/inspector@1.7.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@promakeai/inspector-types": "1.7.5", "clsx": "^2.1.1", "estree-walker": "^3.0.3", "lodash": "^4.17.21", "lucide-react": "^0.554.0", "magic-string": "^0.30.21", "react-colorful": "^5.6.1", "vite-plugin-component-debugger": "^2.2.0", "zustand": "^5.0.8" }, "peerDependencies": { "react": ">=18.0.0", "vite": ">=5.0.0" }, "optionalPeers": ["vite"] }, "sha512-dudt8Yuyxrvudatr1T61qY8f0UT8klcGphnpaLbZ98sZTuth+m9X3CePz/bgf6judDCtcX4zFzjI+8e7+ryukw=="],
237
237
 
238
238
  "@promakeai/inspector-types": ["@promakeai/inspector-types@1.7.5", "", {}, "sha512-4swpzEDymv0abrVN0KrbdtSss0OAVPzCzd7MOivSLFLlLQuKmsbyxFJkKY1qTQYCNjEjkhpnWuRsayK21I23qA=="],
239
239
 
240
- "@promakeai/orm": ["@promakeai/orm@1.0.6", "", {}, "sha512-ZZsCiv5e/+Y2YfKuZu/Wkhhb2cDW1jqYum1vjEOLjbMCbuutJULEp2ZqQ/mIzK9GPAxez+lzp7onZixjqUPkAQ=="],
240
+ "@promakeai/orm": ["@promakeai/orm@1.3.0", "", {}, "sha512-4V6/w8/M6H63tmsENYFdsX51NqbRpPyFvUSRY/U8xtp9dSq+ch7GubQBS+U7m43gW8ap7x8DX/fYoWdimgDz7g=="],
241
241
 
242
242
  "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
243
243
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@promakeai/template",
3
3
  "private": true,
4
- "version": "0.1.7",
4
+ "version": "0.3.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -15,8 +15,8 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@hookform/resolvers": "^5.2.2",
18
- "@promakeai/dbreact": "^1.0.8",
19
- "@promakeai/customer-backend-client": "^1.1.0",
18
+ "@promakeai/dbreact": "^1.1.1",
19
+ "@promakeai/customer-backend-client": "^1.2.0",
20
20
  "@promakeai/inspector": "^1.7.4",
21
21
  "@radix-ui/react-accordion": "^1.2.12",
22
22
  "@radix-ui/react-alert-dialog": "^1.1.15",
@@ -61,6 +61,10 @@
61
61
  "instagram": "",
62
62
  "linkedin": ""
63
63
  },
64
+ "database": {
65
+ "adapter": "sqlite",
66
+ "apiEndpoint": "/database"
67
+ },
64
68
  "file":{
65
69
  "maxFiles": 5,
66
70
  "accept": ".pdf,.doc,.docx,.jpg,.jpeg,.png"
@@ -14,6 +14,7 @@ export {
14
14
  useDbUpdate,
15
15
  useDbDelete,
16
16
  SqliteAdapter,
17
+ RestAdapter,
17
18
  parseJSONSchema,
18
19
  } from "@promakeai/dbreact";
19
20
 
@@ -1,6 +1,12 @@
1
1
  import { useEffect, useState, type ReactNode } from "react";
2
2
  import { useTranslation } from "react-i18next";
3
- import { DbProvider, SqliteAdapter, parseJSONSchema } from "@promakeai/dbreact";
3
+ import {
4
+ DbProvider,
5
+ SqliteAdapter,
6
+ RestAdapter,
7
+ parseJSONSchema,
8
+ } from "@promakeai/dbreact";
9
+ import type { IDataAdapter } from "@promakeai/dbreact";
4
10
  import constants from "@/constants/constants.json";
5
11
  import schemaJson from "./schema.json";
6
12
 
@@ -11,10 +17,27 @@ interface AppDbProviderProps {
11
17
  }
12
18
 
13
19
  const DEFAULT_LANG = constants?.site?.defaultLanguage || "en";
20
+ const DB_CONFIG = (constants as any)?.database;
21
+ const API_BASE_URL = (constants as any)?.api?.baseUrl;
22
+
23
+ /**
24
+ * Read auth token from localStorage (where auth-core's Zustand store persists).
25
+ * Returns null when auth-core is not installed or user is not logged in.
26
+ */
27
+ function getAuthToken(): string | null {
28
+ try {
29
+ const raw = localStorage.getItem("auth-storage");
30
+ if (!raw) return null;
31
+ const parsed = JSON.parse(raw);
32
+ return parsed?.state?.tokens?.accessToken ?? null;
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
14
37
 
15
38
  export function AppDbProvider({ children }: AppDbProviderProps) {
16
39
  const { i18n } = useTranslation();
17
- const [adapter, setAdapter] = useState<SqliteAdapter | null>(null);
40
+ const [adapter, setAdapter] = useState<IDataAdapter | null>(null);
18
41
  const [error, setError] = useState<Error | null>(null);
19
42
 
20
43
  useEffect(() => {
@@ -22,27 +45,43 @@ export function AppDbProvider({ children }: AppDbProviderProps) {
22
45
 
23
46
  async function loadDb() {
24
47
  try {
25
- const response = await fetch("/data/database.db");
26
- if (!response.ok) {
27
- throw new Error(
28
- `Failed to load database: ${response.status} ${response.statusText}`
29
- );
30
- }
48
+ if (DB_CONFIG?.adapter === "rest") {
49
+ // REST API mode
50
+ const restAdapter = new RestAdapter({
51
+ baseUrl: API_BASE_URL || "",
52
+ databasePrefix: DB_CONFIG?.apiEndpoint || "/database",
53
+ schema,
54
+ defaultLang: DEFAULT_LANG,
55
+ getToken: getAuthToken,
56
+ });
31
57
 
32
- const buffer = await response.arrayBuffer();
33
- if (cancelled) return;
58
+ if (!cancelled) {
59
+ setAdapter(restAdapter);
60
+ }
61
+ } else {
62
+ // SQLite mode (default)
63
+ const response = await fetch("/data/database.db");
64
+ if (!response.ok) {
65
+ throw new Error(
66
+ `Failed to load database: ${response.status} ${response.statusText}`
67
+ );
68
+ }
34
69
 
35
- const dbAdapter = new SqliteAdapter({
36
- schema,
37
- defaultLang: DEFAULT_LANG,
38
- wasmPath: "/sql-wasm.wasm",
39
- });
70
+ const buffer = await response.arrayBuffer();
71
+ if (cancelled) return;
40
72
 
41
- await dbAdapter.connect();
42
- await dbAdapter.import(new Uint8Array(buffer));
73
+ const dbAdapter = new SqliteAdapter({
74
+ schema,
75
+ defaultLang: DEFAULT_LANG,
76
+ wasmPath: "/sql-wasm.wasm",
77
+ });
43
78
 
44
- if (!cancelled) {
45
- setAdapter(dbAdapter);
79
+ await dbAdapter.connect();
80
+ await dbAdapter.import(new Uint8Array(buffer));
81
+
82
+ if (!cancelled) {
83
+ setAdapter(dbAdapter);
84
+ }
46
85
  }
47
86
  } catch (err) {
48
87
  if (!cancelled) {
@@ -7,6 +7,11 @@
7
7
  "defaultLanguage": "en",
8
8
  "tables": {
9
9
  "blog_categories": {
10
+ "$permissions": {
11
+ "anon": ["read"],
12
+ "user": ["read"],
13
+ "admin": ["read", "create", "update", "delete"]
14
+ },
10
15
  "id": {
11
16
  "type": "id"
12
17
  },
@@ -35,6 +40,11 @@
35
40
  }
36
41
  },
37
42
  "posts": {
43
+ "$permissions": {
44
+ "anon": ["read"],
45
+ "user": ["read"],
46
+ "admin": ["read", "create", "update", "delete"]
47
+ },
38
48
  "id": {
39
49
  "type": "id"
40
50
  },
@@ -118,6 +128,11 @@
118
128
  }
119
129
  },
120
130
  "product_categories": {
131
+ "$permissions": {
132
+ "anon": ["read"],
133
+ "user": ["read"],
134
+ "admin": ["read", "create", "update", "delete"]
135
+ },
121
136
  "id": {
122
137
  "type": "id"
123
138
  },
@@ -150,6 +165,11 @@
150
165
  }
151
166
  },
152
167
  "products": {
168
+ "$permissions": {
169
+ "anon": ["read"],
170
+ "user": ["read"],
171
+ "admin": ["read", "create", "update", "delete"]
172
+ },
153
173
  "id": {
154
174
  "type": "id"
155
175
  },