@promakeai/cli 0.5.4 → 0.5.6

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.
@@ -24,7 +24,7 @@
24
24
  "path": "contact-page-split/contact-page-split.tsx",
25
25
  "type": "registry:component",
26
26
  "target": "$modules$/contact-page-split/contact-page-split.tsx",
27
- "content": "import React, { useRef, useState } from \"react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { Mail, Phone, MapPin, Clock } from \"lucide-react\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { useApiService } from \"@/lib/api\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Textarea } from \"@/components/ui/textarea\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport constants from \"@/constants/constants.json\";\r\nimport { FormFileInput } from \"@/components/FormFileInput\";\r\n\r\ninterface ContactPageSplitProps {\r\n className?: string;\r\n}\r\n\r\nexport function ContactPageSplit({ className }: ContactPageSplitProps) {\r\n const { t } = useTranslation(\"contact-page-split\");\r\n usePageTitle({ title: t(\"pageTitle\", \"Contact Us\") });\r\n const apiService = useApiService();\r\n\r\n const [formData, setFormData] = useState({\r\n name: \"\",\r\n email: \"\",\r\n phone: \"\",\r\n message: \"\",\r\n attachments: [] as File[]\r\n });\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [submitStatus, setSubmitStatus] = useState<\"idle\" | \"success\" | \"error\">(\"idle\");\r\n\r\n\r\n const handleFileUploadChange = (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const selectedFiles = Array.from(e.target.files || []) as File[];\r\n const fileMaxFiles = constants.file?.maxFiles || 5;\r\n\r\n const remainingSlots = fileMaxFiles - formData.attachments.length;\r\n\r\n // If the limit is exceeded, alert and do not add any files\r\n if (selectedFiles.length > remainingSlots) {\r\n alert(t(\"maxFilesLimit\", { max: fileMaxFiles }));\r\n e.target.value = ''; // Clear the input\r\n return;\r\n }\r\n\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: [...prev.attachments, ...selectedFiles]\r\n }));\r\n\r\n e.target.value = ''; // Clear the input\r\n }\r\n\r\n const handleRemoveFile = (index: number) => {\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: prev.attachments.filter((_, i) => i !== index)\r\n }));\r\n };\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setIsSubmitting(true);\r\n setSubmitStatus(\"idle\");\r\n\r\n try {\r\n await apiService.submitFormWithFile(\r\n formData,\r\n {\r\n email_subject1: \"Thank you for contacting us\",\r\n email_subject2: \"New Contact Form Submission\",\r\n fields: [\r\n { name: \"name\", required: true },\r\n { name: \"email\", required: true },\r\n { name: \"phone\", required: false },\r\n { name: \"message\", required: true },\r\n { name: \"attachments\", required: false },\r\n ],\r\n },\r\n constants.site.defaultLanguage\r\n );\r\n\r\n setSubmitStatus(\"success\");\r\n setFormData({ name: \"\", email: \"\", phone: \"\", message: \"\", attachments: [] });\r\n setTimeout(() => setSubmitStatus(\"idle\"), 5000);\r\n } catch {\r\n setSubmitStatus(\"error\");\r\n setTimeout(() => setSubmitStatus(\"idle\"), 5000);\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\r\n setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));\r\n };\r\n\r\n return (\r\n <Layout>\r\n <div className={cn(\"min-h-[calc(100vh-200px)] py-8 md:py-12\", className)}>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"grid lg:grid-cols-2 h-full rounded-xl overflow-hidden shadow-lg\">\r\n {/* Left Side - Info & Image */}\r\n <div className=\"relative bg-primary text-primary-foreground p-8 lg:p-12 flex flex-col justify-center\">\r\n {/* Background Pattern */}\r\n <div className=\"absolute inset-0 bg-[linear-gradient(to_right,rgba(255,255,255,0.1)_1px,transparent_1px),linear-gradient(to_bottom,rgba(255,255,255,0.1)_1px,transparent_1px)] bg-[size:4rem_4rem]\" />\r\n\r\n <div className=\"relative z-10 max-w-lg\">\r\n <h1 className=\"text-3xl lg:text-4xl font-bold mb-4\">\r\n {t(\"title\", \"Let's Start a Conversation\")}\r\n </h1>\r\n <p className=\"text-primary-foreground/80 mb-8\">\r\n {t(\"subtitle\", \"Have a project in mind? We'd love to hear about it. Get in touch and let's create something amazing together.\")}\r\n </p>\r\n\r\n <div className=\"space-y-6\">\r\n <div className=\"flex items-start gap-4\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\r\n <Mail className=\"h-5 w-5\" />\r\n </div>\r\n <div>\r\n <p className=\"font-semibold\">{t(\"emailLabel\", \"Email\")}</p>\r\n <p className=\"text-primary-foreground/70\">{constants.email || \"hello@example.com\"}</p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-4\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\r\n <Phone className=\"h-5 w-5\" />\r\n </div>\r\n <div>\r\n <p className=\"font-semibold\">{t(\"phoneLabel\", \"Phone\")}</p>\r\n <p className=\"text-primary-foreground/70\">{constants.phone || \"+1 234 567 890\"}</p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-4\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\r\n <MapPin className=\"h-5 w-5\" />\r\n </div>\r\n <div>\r\n <p className=\"font-semibold\">{t(\"addressLabel\", \"Address\")}</p>\r\n <p className=\"text-primary-foreground/70\">\r\n {constants.address?.line1 || \"123 Main Street\"}<br />\r\n {constants.address?.city || \"New York\"}, {constants.address?.country || \"USA\"}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-4\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\r\n <Clock className=\"h-5 w-5\" />\r\n </div>\r\n <div>\r\n <p className=\"font-semibold\">{t(\"hoursLabel\", \"Business Hours\")}</p>\r\n <p className=\"text-primary-foreground/70\">{t(\"hours\", \"Mon - Fri: 9:00 AM - 6:00 PM\")}</p>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Right Side - Form */}\r\n <div className=\"p-8 lg:p-12 flex items-center justify-center bg-background\">\r\n <div className=\"w-full max-w-md\">\r\n <h2 className=\"text-2xl font-bold mb-2\">{t(\"formTitle\", \"Send us a message\")}</h2>\r\n <p className=\"text-muted-foreground mb-8\">\r\n {t(\"formSubtitle\", \"Fill out the form below and we'll get back to you as soon as possible.\")}\r\n </p>\r\n\r\n <form onSubmit={handleSubmit} className=\"space-y-5\">\r\n <div>\r\n <Label htmlFor=\"name\">{t(\"nameLabel\", \"Full Name\")} *</Label>\r\n <Input\r\n id=\"name\"\r\n name=\"name\"\r\n value={formData.name}\r\n onChange={handleChange}\r\n placeholder={t(\"namePlaceholder\", \"John Doe\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"email\">{t(\"emailInputLabel\", \"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\", \"john@example.com\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"phone\">{t(\"phoneInputLabel\", \"Phone\")}</Label>\r\n <Input\r\n id=\"phone\"\r\n name=\"phone\"\r\n type=\"tel\"\r\n value={formData.phone}\r\n onChange={handleChange}\r\n placeholder={t(\"phonePlaceholder\", \"+1 234 567 890\")}\r\n className=\"mt-1\"\r\n />\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"message\">{t(\"messageLabel\", \"Message\")} *</Label>\r\n <Textarea\r\n id=\"message\"\r\n name=\"message\"\r\n value={formData.message}\r\n onChange={handleChange}\r\n placeholder={t(\"messagePlaceholder\", \"Tell us about your project...\")}\r\n required\r\n rows={5}\r\n className=\"mt-1 resize-none\"\r\n />\r\n </div>\r\n <FormFileInput\r\n files={formData.attachments}\r\n onFilesChange={handleFileUploadChange}\r\n handleRemoveFile={handleRemoveFile}\r\n maxFiles={constants.file?.maxFiles || 5}\r\n accept={constants.file?.accept || \".pdf,.doc,.docx,.jpg,.jpeg,.png\"}\r\n disabled={isSubmitting}\r\n uploadButtonText={t(\"addFiles\")}\r\n maxFilesReachedText={t(\"maxFilesReached\")}\r\n />\r\n {submitStatus === \"success\" && (\r\n <div className=\"p-4 bg-green-500/10 border border-green-500/30 rounded-lg\">\r\n <p className=\"text-green-600 dark:text-green-400 text-sm font-medium\">\r\n {t(\"success\", \"Message sent! We'll be in touch soon.\")}\r\n </p>\r\n </div>\r\n )}\r\n\r\n {submitStatus === \"error\" && (\r\n <div className=\"p-4 bg-destructive/10 border border-destructive/30 rounded-lg\">\r\n <p className=\"text-destructive text-sm font-medium\">\r\n {t(\"error\", \"Something went wrong. Please try again.\")}\r\n </p>\r\n </div>\r\n )}\r\n\r\n <Button type=\"submit\" size=\"lg\" className=\"w-full\" disabled={isSubmitting}>\r\n {isSubmitting ? t(\"sending\", \"Sending...\") : t(\"submit\", \"Send Message\")}\r\n </Button>\r\n </form>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default ContactPageSplit;\r\n"
27
+ "content": "import React, { useState } from \"react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { Mail, Phone, MapPin, Clock } from \"lucide-react\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { useApiService } from \"@/lib/api\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Textarea } from \"@/components/ui/textarea\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport constants from \"@/constants/constants.json\";\r\nimport { FormFileInput } from \"@/components/FormFileInput\";\r\nimport { FormField } from \"@/components/FormField\";\r\n\r\ninterface ContactPageSplitProps {\r\n className?: string;\r\n}\r\n\r\nexport function ContactPageSplit({ className }: ContactPageSplitProps) {\r\n const { t } = useTranslation(\"contact-page-split\");\r\n usePageTitle({ title: t(\"pageTitle\", \"Contact Us\") });\r\n const apiService = useApiService();\r\n\r\n const [formData, setFormData] = useState({\r\n name: \"\",\r\n email: \"\",\r\n phone: \"\",\r\n message: \"\",\r\n attachments: [] as File[]\r\n });\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [submitStatus, setSubmitStatus] = useState<\"idle\" | \"success\" | \"error\">(\"idle\");\r\n\r\n\r\n const handleFileUploadChange = (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const selectedFiles = Array.from(e.target.files || []) as File[];\r\n const fileMaxFiles = constants.file?.maxFiles || 5;\r\n\r\n const remainingSlots = fileMaxFiles - formData.attachments.length;\r\n\r\n // If the limit is exceeded, alert and do not add any files\r\n if (selectedFiles.length > remainingSlots) {\r\n alert(t(\"maxFilesLimit\", { max: fileMaxFiles }));\r\n e.target.value = ''; // Clear the input\r\n return;\r\n }\r\n\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: [...prev.attachments, ...selectedFiles]\r\n }));\r\n\r\n e.target.value = ''; // Clear the input\r\n }\r\n\r\n const handleRemoveFile = (index: number) => {\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: prev.attachments.filter((_, i) => i !== index)\r\n }));\r\n };\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setIsSubmitting(true);\r\n setSubmitStatus(\"idle\");\r\n\r\n try {\r\n await apiService.submitFormWithFile(\r\n formData,\r\n {\r\n email_subject1: \"Thank you for contacting us\",\r\n email_subject2: \"New Contact Form Submission\",\r\n fields: [\r\n { name: \"name\", required: true },\r\n { name: \"email\", required: true },\r\n { name: \"phone\", required: false },\r\n { name: \"message\", required: true },\r\n { name: \"attachments\", required: false },\r\n ],\r\n },\r\n constants.site.defaultLanguage\r\n );\r\n\r\n setSubmitStatus(\"success\");\r\n setFormData({ name: \"\", email: \"\", phone: \"\", message: \"\", attachments: [] });\r\n setTimeout(() => setSubmitStatus(\"idle\"), 5000);\r\n } catch {\r\n setSubmitStatus(\"error\");\r\n setTimeout(() => setSubmitStatus(\"idle\"), 5000);\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\r\n setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));\r\n };\r\n\r\n return (\r\n <Layout>\r\n <div className={cn(\"min-h-[calc(100vh-200px)] py-8 md:py-12\", className)}>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"grid lg:grid-cols-2 h-full rounded-xl overflow-hidden shadow-lg\">\r\n {/* Left Side - Info & Image */}\r\n <div className=\"relative bg-primary text-primary-foreground p-8 lg:p-12 flex flex-col justify-center\">\r\n {/* Background Pattern */}\r\n <div className=\"absolute inset-0 bg-[linear-gradient(to_right,rgba(255,255,255,0.1)_1px,transparent_1px),linear-gradient(to_bottom,rgba(255,255,255,0.1)_1px,transparent_1px)] bg-[size:4rem_4rem]\" />\r\n\r\n <div className=\"relative z-10 max-w-lg\">\r\n <h1 className=\"text-3xl lg:text-4xl font-bold mb-4\">\r\n {t(\"title\", \"Let's Start a Conversation\")}\r\n </h1>\r\n <p className=\"text-primary-foreground/80 mb-8\">\r\n {t(\"subtitle\", \"Have a project in mind? We'd love to hear about it. Get in touch and let's create something amazing together.\")}\r\n </p>\r\n\r\n <div className=\"space-y-6\">\r\n <div className=\"flex items-start gap-4\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\r\n <Mail className=\"h-5 w-5\" />\r\n </div>\r\n <div>\r\n <p className=\"font-semibold\">{t(\"emailLabel\", \"Email\")}</p>\r\n <p className=\"text-primary-foreground/70\">{constants.email || \"hello@example.com\"}</p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-4\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\r\n <Phone className=\"h-5 w-5\" />\r\n </div>\r\n <div>\r\n <p className=\"font-semibold\">{t(\"phoneLabel\", \"Phone\")}</p>\r\n <p className=\"text-primary-foreground/70\">{constants.phone || \"+1 234 567 890\"}</p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-4\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\r\n <MapPin className=\"h-5 w-5\" />\r\n </div>\r\n <div>\r\n <p className=\"font-semibold\">{t(\"addressLabel\", \"Address\")}</p>\r\n <p className=\"text-primary-foreground/70\">\r\n {constants.address?.line1 || \"123 Main Street\"}<br />\r\n {constants.address?.city || \"New York\"}, {constants.address?.country || \"USA\"}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-4\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\r\n <Clock className=\"h-5 w-5\" />\r\n </div>\r\n <div>\r\n <p className=\"font-semibold\">{t(\"hoursLabel\", \"Business Hours\")}</p>\r\n <p className=\"text-primary-foreground/70\">{t(\"hours\", \"Mon - Fri: 9:00 AM - 6:00 PM\")}</p>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Right Side - Form */}\r\n <div className=\"p-8 lg:p-12 flex items-center justify-center bg-background\">\r\n <div className=\"w-full max-w-md\">\r\n <h2 className=\"text-2xl font-bold mb-2\">{t(\"formTitle\", \"Send us a message\")}</h2>\r\n <p className=\"text-muted-foreground mb-8\">\r\n {t(\"formSubtitle\", \"Fill out the form below and we'll get back to you as soon as possible.\")}\r\n </p>\r\n\r\n <form onSubmit={handleSubmit} className=\"space-y-5\">\r\n <FormField label={t(\"nameLabel\", \"Full Name\")} htmlFor=\"name\" required>\r\n <Input\r\n id=\"name\"\r\n name=\"name\"\r\n value={formData.name}\r\n onChange={handleChange}\r\n placeholder={t(\"namePlaceholder\", \"John Doe\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </FormField>\r\n\r\n <FormField label={t(\"emailInputLabel\", \"Email\")} htmlFor=\"email\" required>\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\", \"john@example.com\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </FormField>\r\n\r\n <FormField label={t(\"phoneInputLabel\", \"Phone\")} htmlFor=\"phone\">\r\n <Input\r\n id=\"phone\"\r\n name=\"phone\"\r\n type=\"tel\"\r\n value={formData.phone}\r\n onChange={handleChange}\r\n placeholder={t(\"phonePlaceholder\", \"+1 234 567 890\")}\r\n className=\"mt-1\"\r\n />\r\n </FormField>\r\n <FormField label={t(\"messageLabel\", \"Message\")} htmlFor=\"message\" required>\r\n <Textarea\r\n id=\"message\"\r\n name=\"message\"\r\n value={formData.message}\r\n onChange={handleChange}\r\n placeholder={t(\"messagePlaceholder\", \"Tell us about your project...\")}\r\n required\r\n rows={5}\r\n className=\"mt-1 resize-none\"\r\n />\r\n </FormField>\r\n <FormFileInput\r\n files={formData.attachments}\r\n onFilesChange={handleFileUploadChange}\r\n handleRemoveFile={handleRemoveFile}\r\n maxFiles={constants.file?.maxFiles || 5}\r\n accept={constants.file?.accept || \".pdf,.doc,.docx,.jpg,.jpeg,.png\"}\r\n disabled={isSubmitting}\r\n uploadButtonText={t(\"addFiles\")}\r\n maxFilesReachedText={t(\"maxFilesReached\")}\r\n />\r\n {submitStatus === \"success\" && (\r\n <div className=\"p-4 bg-green-500/10 border border-green-500/30 rounded-lg\">\r\n <p className=\"text-green-600 dark:text-green-400 text-sm font-medium\">\r\n {t(\"success\", \"Message sent! We'll be in touch soon.\")}\r\n </p>\r\n </div>\r\n )}\r\n\r\n {submitStatus === \"error\" && (\r\n <div className=\"p-4 bg-destructive/10 border border-destructive/30 rounded-lg\">\r\n <p className=\"text-destructive text-sm font-medium\">\r\n {t(\"error\", \"Something went wrong. Please try again.\")}\r\n </p>\r\n </div>\r\n )}\r\n\r\n <Button type=\"submit\" size=\"lg\" className=\"w-full\" disabled={isSubmitting}>\r\n {isSubmitting ? t(\"sending\", \"Sending...\") : t(\"submit\", \"Send Message\")}\r\n </Button>\r\n </form>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default ContactPageSplit;\r\n"
28
28
  },
29
29
  {
30
30
  "path": "contact-page-split/lang/en.json",
@@ -22,7 +22,7 @@
22
22
  "path": "contact-page/contact-page.tsx",
23
23
  "type": "registry:page",
24
24
  "target": "$modules$/contact-page/contact-page.tsx",
25
- "content": "import React, { useState, useMemo, useRef } from \"react\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { useApiService } from \"@/lib/api\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Textarea } from \"@/components/ui/textarea\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\r\nimport {\r\n Mail,\r\n Phone,\r\n MapPin,\r\n MessageSquare,\r\n Facebook,\r\n Twitter,\r\n Instagram,\r\n Linkedin,\r\n Upload,\r\n} from \"lucide-react\";\r\nimport constants from \"@/constants/constants.json\";\r\nimport { FadeIn, SlideInLeft, SlideInRight } from \"@/modules/animations\";\r\nimport { FormFileInput } from \"@/components/FormFileInput\";\r\n\r\nconst socialIcons: Record<string, React.ElementType> = {\r\n facebook: Facebook,\r\n twitter: Twitter,\r\n instagram: Instagram,\r\n linkedin: Linkedin,\r\n};\r\n\r\nexport function ContactPage() {\r\n const { t } = useTranslation(\"contact-page\");\r\n usePageTitle({ title: t(\"title\") });\r\n\r\n const apiService = useApiService();\r\n\r\n const socialLinks = useMemo(() => {\r\n const socialMedia = constants.socialMedia as\r\n | Record<string, string>\r\n | undefined;\r\n if (!socialMedia) return [];\r\n return Object.entries(socialMedia)\r\n .filter(([platform, url]) => url && socialIcons[platform])\r\n .map(([platform, url]) => ({\r\n platform,\r\n url,\r\n Icon: socialIcons[platform],\r\n }));\r\n }, []);\r\n\r\n const [formData, setFormData] = useState({\r\n name: \"\",\r\n email: \"\",\r\n phone: \"\",\r\n subject: \"\",\r\n message: \"\",\r\n attachments: [] as File[],\r\n });\r\n const fileInputRef = useRef<HTMLInputElement>(null);\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [submitStatus, setSubmitStatus] = useState<\r\n \"idle\" | \"success\" | \"error\"\r\n >(\"idle\");\r\n const fileAcceptTypes = constants.file?.accept || \".pdf,.doc,.docx,.jpg,.jpeg,.png\";\r\n const fileMaxFiles = constants.file?.maxFiles || 5;\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setIsSubmitting(true);\r\n setSubmitStatus(\"idle\");\r\n setIsSubmitting(false);\r\n try {\r\n const currentLanguage = constants.site.defaultLanguage;\r\n\r\n await apiService.submitFormWithFile(\r\n formData,\r\n {\r\n email_subject1: \"Thank you for contacting us\",\r\n email_subject2: \"New Contact Form Submission\",\r\n fields: [\r\n { name: \"name\", required: true },\r\n { name: \"email\", required: true },\r\n { name: \"phone\", required: false },\r\n { name: \"subject\", required: false },\r\n { name: \"message\", required: true },\r\n { name: \"attachments\", required: false },\r\n ],\r\n },\r\n currentLanguage\r\n );\r\n\r\n setSubmitStatus(\"success\");\r\n setFormData({\r\n name: \"\",\r\n email: \"\",\r\n phone: \"\",\r\n subject: \"\",\r\n message: \"\",\r\n attachments: [],\r\n });\r\n\r\n setTimeout(() => {\r\n setSubmitStatus(\"idle\");\r\n }, 5000);\r\n } catch (error: unknown) {\r\n console.error(\"Form submission failed:\", error);\r\n setSubmitStatus(\"error\");\r\n\r\n setTimeout(() => {\r\n setSubmitStatus(\"idle\");\r\n }, 5000);\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n const handleFileUploadChange = (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const selectedFiles = Array.from(e.target.files || []) as File[];\r\n const remainingSlots = fileMaxFiles - formData.attachments.length;\r\n\r\n if (selectedFiles.length > remainingSlots) {\r\n alert(t(\"maxFilesLimit\", { max: fileMaxFiles }));\r\n e.target.value = ''; // Clear the input value\r\n return;\r\n }\r\n\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: [...prev.attachments, ...selectedFiles]\r\n }));\r\n\r\n e.target.value = ''; // Clear the input\r\n }\r\n const handleRemoveFile = (index: number) => {\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: prev.attachments.filter((_, i) => i !== index)\r\n }));\r\n };\r\n const handleChange = (\r\n e: React.ChangeEvent<\r\n HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement\r\n >\r\n ) => {\r\n setFormData((prev) => ({\r\n ...prev,\r\n [e.target.name]: e.target.value,\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 max-w-6xl\">\r\n {/* Hero Section */}\r\n <FadeIn className=\"text-center mb-12\">\r\n <h1 className=\"text-4xl font-bold text-foreground mb-4\">\r\n {t(\"title\")}\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-3xl mx-auto\">\r\n {t(\"description\")}\r\n </p>\r\n </FadeIn>\r\n\r\n <div className=\"grid lg:grid-cols-3 gap-8\">\r\n {/* Contact Information */}\r\n <SlideInLeft className=\"lg:col-span-1 space-y-6\">\r\n {/* Company Info */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"flex items-center gap-2\">\r\n <MessageSquare className=\"w-5 h-5 text-primary\" />\r\n {t(\"getInTouch\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent className=\"space-y-4\">\r\n <div className=\"flex items-start gap-3\">\r\n <Mail className=\"w-5 h-5 text-primary mt-1\" />\r\n <div>\r\n <p className=\"font-medium text-foreground\">\r\n {t(\"email\")}\r\n </p>\r\n <p className=\"text-muted-foreground\">{constants.email}</p>\r\n <p className=\"text-sm text-muted-foreground mt-1\">\r\n {t(\"emailResponse\")}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-3\">\r\n <Phone className=\"w-5 h-5 text-primary mt-1\" />\r\n <div>\r\n <p className=\"font-medium text-foreground\">\r\n {t(\"phone\")}\r\n </p>\r\n <p className=\"text-muted-foreground\">{constants.phone}</p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-3\">\r\n <MapPin className=\"w-5 h-5 text-primary mt-1\" />\r\n <div>\r\n <p className=\"font-medium text-foreground\">\r\n {t(\"address\")}\r\n </p>\r\n <div className=\"text-muted-foreground\">\r\n <p>{constants.address.line1}</p>\r\n {constants.address.line2 && (\r\n <p>{constants.address.line2}</p>\r\n )}\r\n <p>\r\n {constants.address.city}, {constants.address.state}{\" \"}\r\n {constants.address.postalCode}\r\n </p>\r\n {constants.address.country && (\r\n <p>{constants.address.country}</p>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Social Media Links */}\r\n {socialLinks.length > 0 && (\r\n <div className=\"pt-4 border-t\">\r\n <p className=\"font-medium text-foreground mb-3\">\r\n {t(\"followUs\")}\r\n </p>\r\n <div className=\"flex gap-2\">\r\n {socialLinks.map(({ platform, url, Icon }) => (\r\n <a\r\n key={platform}\r\n href={url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n className=\"h-10 w-10 flex items-center justify-center rounded-md border text-muted-foreground hover:text-primary hover:border-primary transition-colors\"\r\n >\r\n <Icon className=\"h-5 w-5\" />\r\n </a>\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n </CardContent>\r\n </Card>\r\n\r\n {/* Support Info */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"needSupport\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <p className=\"text-muted-foreground mb-4\">\r\n {t(\"supportDescription\")}\r\n </p>\r\n <div className=\"space-y-2\">\r\n <p className=\"font-medium text-foreground\">\r\n {t(\"supportEmail\")}\r\n </p>\r\n <p className=\"text-muted-foreground\">{constants.email}</p>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </SlideInLeft>\r\n\r\n {/* Contact Form */}\r\n <SlideInRight className=\"lg:col-span-2\">\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"sendMessage\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <form onSubmit={handleSubmit} className=\"space-y-6\">\r\n {/* Full Name */}\r\n <div>\r\n <Label htmlFor=\"name\">{t(\"fullName\")} *</Label>\r\n <Input\r\n id=\"name\"\r\n name=\"name\"\r\n type=\"text\"\r\n value={formData.name}\r\n onChange={handleChange}\r\n placeholder={t(\"fullNamePlaceholder\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </div>\r\n\r\n {/* Email */}\r\n <div>\r\n <Label htmlFor=\"email\">{t(\"emailAddress\")} *</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\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </div>\r\n\r\n {/* Phone */}\r\n <div>\r\n <Label htmlFor=\"phone\">{t(\"phoneNumber\")}</Label>\r\n <Input\r\n id=\"phone\"\r\n name=\"phone\"\r\n type=\"tel\"\r\n value={formData.phone}\r\n onChange={handleChange}\r\n placeholder={t(\"phonePlaceholder\")}\r\n className=\"mt-1\"\r\n />\r\n </div>\r\n\r\n {/* Subject */}\r\n <div>\r\n <Label htmlFor=\"subject\">{t(\"subject\")}</Label>\r\n <Input\r\n id=\"subject\"\r\n name=\"subject\"\r\n type=\"text\"\r\n value={formData.subject}\r\n onChange={handleChange}\r\n placeholder={t(\"subjectPlaceholder\")}\r\n className=\"mt-1\"\r\n />\r\n </div>\r\n\r\n {/* Message */}\r\n <div>\r\n <Label htmlFor=\"message\">{t(\"message\")} *</Label>\r\n <Textarea\r\n id=\"message\"\r\n name=\"message\"\r\n value={formData.message}\r\n onChange={handleChange}\r\n placeholder={t(\"messagePlaceholder\")}\r\n required\r\n rows={6}\r\n className=\"mt-1 resize-none\"\r\n />\r\n </div>\r\n <FormFileInput\r\n files={formData.attachments}\r\n onFilesChange={handleFileUploadChange}\r\n handleRemoveFile={handleRemoveFile}\r\n maxFiles={constants.file?.maxFiles || 5}\r\n accept={constants.file?.accept || \".pdf,.doc,.docx,.jpg,.jpeg,.png\"}\r\n disabled={isSubmitting}\r\n uploadButtonText={t(\"addFiles\")}\r\n maxFilesReachedText={t(\"maxFilesReached\")}\r\n />\r\n\r\n {submitStatus === \"success\" && (\r\n <div className=\"p-4 bg-green-500/10 dark:bg-green-500/20 border border-green-500/30 rounded-lg\">\r\n <p className=\"text-green-600 dark:text-green-400 text-sm font-medium\">\r\n {t(\"success\")}\r\n </p>\r\n </div>\r\n )}\r\n\r\n {submitStatus === \"error\" && (\r\n <div className=\"p-4 bg-destructive/10 border border-destructive/30 rounded-lg\">\r\n <p className=\"text-destructive text-sm font-medium\">\r\n {t(\"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-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"sending\")}\r\n </>\r\n ) : (\r\n t(\"submit\")\r\n )}\r\n </Button>\r\n </form>\r\n </CardContent>\r\n </Card>\r\n </SlideInRight>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default ContactPage;\r\n"
25
+ "content": "import React, { useState, useMemo } from \"react\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { useApiService } from \"@/lib/api\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Textarea } from \"@/components/ui/textarea\";\r\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\r\nimport {\r\n Mail,\r\n Phone,\r\n MapPin,\r\n MessageSquare,\r\n Facebook,\r\n Twitter,\r\n Instagram,\r\n Linkedin,\r\n} from \"lucide-react\";\r\nimport constants from \"@/constants/constants.json\";\r\nimport { FadeIn, SlideInLeft, SlideInRight } from \"@/modules/animations\";\r\nimport { FormFileInput } from \"@/components/FormFileInput\";\r\nimport { FormField } from \"@/components/FormField\";\r\n\r\nconst socialIcons: Record<string, React.ElementType> = {\r\n facebook: Facebook,\r\n twitter: Twitter,\r\n instagram: Instagram,\r\n linkedin: Linkedin,\r\n};\r\n\r\nexport function ContactPage() {\r\n const { t } = useTranslation(\"contact-page\");\r\n usePageTitle({ title: t(\"title\") });\r\n\r\n const apiService = useApiService();\r\n\r\n const socialLinks = useMemo(() => {\r\n const socialMedia = constants.socialMedia as\r\n | Record<string, string>\r\n | undefined;\r\n if (!socialMedia) return [];\r\n return Object.entries(socialMedia)\r\n .filter(([platform, url]) => url && socialIcons[platform])\r\n .map(([platform, url]) => ({\r\n platform,\r\n url,\r\n Icon: socialIcons[platform],\r\n }));\r\n }, []);\r\n\r\n const [formData, setFormData] = useState({\r\n name: \"\",\r\n email: \"\",\r\n phone: \"\",\r\n subject: \"\",\r\n message: \"\",\r\n attachments: [] as File[],\r\n });\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [submitStatus, setSubmitStatus] = useState<\r\n \"idle\" | \"success\" | \"error\"\r\n >(\"idle\");\r\n const fileMaxFiles = constants.file?.maxFiles || 5;\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setIsSubmitting(true);\r\n setSubmitStatus(\"idle\");\r\n try {\r\n const currentLanguage = constants.site.defaultLanguage;\r\n\r\n await apiService.submitFormWithFile(\r\n formData,\r\n {\r\n email_subject1: \"Thank you for contacting us\",\r\n email_subject2: \"New Contact Form Submission\",\r\n fields: [\r\n { name: \"name\", required: true },\r\n { name: \"email\", required: true },\r\n { name: \"phone\", required: false },\r\n { name: \"subject\", required: false },\r\n { name: \"message\", required: true },\r\n { name: \"attachments\", required: false },\r\n ],\r\n },\r\n currentLanguage\r\n );\r\n\r\n setSubmitStatus(\"success\");\r\n setFormData({\r\n name: \"\",\r\n email: \"\",\r\n phone: \"\",\r\n subject: \"\",\r\n message: \"\",\r\n attachments: [],\r\n });\r\n\r\n setTimeout(() => {\r\n setSubmitStatus(\"idle\");\r\n }, 5000);\r\n } catch (error: unknown) {\r\n console.error(\"Form submission failed:\", error);\r\n setSubmitStatus(\"error\");\r\n\r\n setTimeout(() => {\r\n setSubmitStatus(\"idle\");\r\n }, 5000);\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n const handleFileUploadChange = (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const selectedFiles = Array.from(e.target.files || []) as File[];\r\n const remainingSlots = fileMaxFiles - formData.attachments.length;\r\n\r\n if (selectedFiles.length > remainingSlots) {\r\n alert(t(\"maxFilesLimit\", { max: fileMaxFiles }));\r\n e.target.value = ''; // Clear the input value\r\n return;\r\n }\r\n\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: [...prev.attachments, ...selectedFiles]\r\n }));\r\n\r\n e.target.value = ''; // Clear the input\r\n }\r\n const handleRemoveFile = (index: number) => {\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: prev.attachments.filter((_, i) => i !== index)\r\n }));\r\n };\r\n const handleChange = (\r\n e: React.ChangeEvent<\r\n HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement\r\n >\r\n ) => {\r\n setFormData((prev) => ({\r\n ...prev,\r\n [e.target.name]: e.target.value,\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 max-w-6xl\">\r\n {/* Hero Section */}\r\n <FadeIn className=\"text-center mb-12\">\r\n <h1 className=\"text-4xl font-bold text-foreground mb-4\">\r\n {t(\"title\")}\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-3xl mx-auto\">\r\n {t(\"description\")}\r\n </p>\r\n </FadeIn>\r\n\r\n <div className=\"grid lg:grid-cols-3 gap-8\">\r\n {/* Contact Information */}\r\n <SlideInLeft className=\"lg:col-span-1 space-y-6\">\r\n {/* Company Info */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"flex items-center gap-2\">\r\n <MessageSquare className=\"w-5 h-5 text-primary\" />\r\n {t(\"getInTouch\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent className=\"space-y-4\">\r\n <div className=\"flex items-start gap-3\">\r\n <Mail className=\"w-5 h-5 text-primary mt-1\" />\r\n <div>\r\n <p className=\"font-medium text-foreground\">\r\n {t(\"email\")}\r\n </p>\r\n <p className=\"text-muted-foreground\">{constants.email}</p>\r\n <p className=\"text-sm text-muted-foreground mt-1\">\r\n {t(\"emailResponse\")}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-3\">\r\n <Phone className=\"w-5 h-5 text-primary mt-1\" />\r\n <div>\r\n <p className=\"font-medium text-foreground\">\r\n {t(\"phone\")}\r\n </p>\r\n <p className=\"text-muted-foreground\">{constants.phone}</p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-3\">\r\n <MapPin className=\"w-5 h-5 text-primary mt-1\" />\r\n <div>\r\n <p className=\"font-medium text-foreground\">\r\n {t(\"address\")}\r\n </p>\r\n <div className=\"text-muted-foreground\">\r\n <p>{constants.address.line1}</p>\r\n {constants.address.line2 && (\r\n <p>{constants.address.line2}</p>\r\n )}\r\n <p>\r\n {constants.address.city}, {constants.address.state}{\" \"}\r\n {constants.address.postalCode}\r\n </p>\r\n {constants.address.country && (\r\n <p>{constants.address.country}</p>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Social Media Links */}\r\n {socialLinks.length > 0 && (\r\n <div className=\"pt-4 border-t\">\r\n <p className=\"font-medium text-foreground mb-3\">\r\n {t(\"followUs\")}\r\n </p>\r\n <div className=\"flex gap-2\">\r\n {socialLinks.map(({ platform, url, Icon }) => (\r\n <a\r\n key={platform}\r\n href={url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n className=\"h-10 w-10 flex items-center justify-center rounded-md border text-muted-foreground hover:text-primary hover:border-primary transition-colors\"\r\n >\r\n <Icon className=\"h-5 w-5\" />\r\n </a>\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n </CardContent>\r\n </Card>\r\n\r\n {/* Support Info */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"needSupport\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <p className=\"text-muted-foreground mb-4\">\r\n {t(\"supportDescription\")}\r\n </p>\r\n <div className=\"space-y-2\">\r\n <p className=\"font-medium text-foreground\">\r\n {t(\"supportEmail\")}\r\n </p>\r\n <p className=\"text-muted-foreground\">{constants.email}</p>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </SlideInLeft>\r\n\r\n {/* Contact Form */}\r\n <SlideInRight className=\"lg:col-span-2\">\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"sendMessage\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <form onSubmit={handleSubmit} className=\"space-y-6\">\r\n {/* Full Name */}\r\n <FormField label={t(\"fullName\")} htmlFor=\"name\" required>\r\n <Input\r\n id=\"name\"\r\n name=\"name\"\r\n type=\"text\"\r\n value={formData.name}\r\n onChange={handleChange}\r\n placeholder={t(\"fullNamePlaceholder\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </FormField>\r\n {/* Email */}\r\n <FormField label={t(\"emailAddress\")} htmlFor=\"email\" required>\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\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </FormField>\r\n {/* Phone */}\r\n <FormField label={t(\"phoneNumber\")} htmlFor=\"phone\">\r\n <Input\r\n id=\"phone\"\r\n name=\"phone\"\r\n type=\"tel\"\r\n value={formData.phone}\r\n onChange={handleChange}\r\n placeholder={t(\"phonePlaceholder\")}\r\n className=\"mt-1\"\r\n />\r\n </FormField>\r\n {/* Subject */}\r\n <FormField label={t(\"subject\")} htmlFor=\"subject\" >\r\n <Input\r\n id=\"subject\"\r\n name=\"subject\"\r\n type=\"text\"\r\n value={formData.subject}\r\n onChange={handleChange}\r\n placeholder={t(\"subjectPlaceholder\")}\r\n className=\"mt-1\"\r\n />\r\n </FormField>\r\n {/* Message */}\r\n <FormField label={t(\"message\")} htmlFor=\"message\" required>\r\n <Textarea\r\n id=\"message\"\r\n name=\"message\"\r\n value={formData.message}\r\n onChange={handleChange}\r\n placeholder={t(\"messagePlaceholder\")}\r\n required\r\n rows={6}\r\n className=\"mt-1 resize-none\"\r\n />\r\n </FormField>\r\n <FormFileInput\r\n files={formData.attachments}\r\n onFilesChange={handleFileUploadChange}\r\n handleRemoveFile={handleRemoveFile}\r\n maxFiles={constants.file?.maxFiles || 5}\r\n accept={constants.file?.accept || \".pdf,.doc,.docx,.jpg,.jpeg,.png\"}\r\n disabled={isSubmitting}\r\n uploadButtonText={t(\"addFiles\")}\r\n maxFilesReachedText={t(\"maxFilesReached\")}\r\n />\r\n\r\n {submitStatus === \"success\" && (\r\n <div className=\"p-4 bg-green-500/10 dark:bg-green-500/20 border border-green-500/30 rounded-lg\">\r\n <p className=\"text-green-600 dark:text-green-400 text-sm font-medium\">\r\n {t(\"success\")}\r\n </p>\r\n </div>\r\n )}\r\n\r\n {submitStatus === \"error\" && (\r\n <div className=\"p-4 bg-destructive/10 border border-destructive/30 rounded-lg\">\r\n <p className=\"text-destructive text-sm font-medium\">\r\n {t(\"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-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"sending\")}\r\n </>\r\n ) : (\r\n t(\"submit\")\r\n )}\r\n </Button>\r\n </form>\r\n </CardContent>\r\n </Card>\r\n </SlideInRight>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default ContactPage;\r\n"
26
26
  },
27
27
  {
28
28
  "path": "contact-page/lang/en.json",
@@ -23,7 +23,7 @@
23
23
  "path": "forgot-password-page-split/forgot-password-page-split.tsx",
24
24
  "type": "registry:page",
25
25
  "target": "$modules$/forgot-password-page-split/forgot-password-page-split.tsx",
26
- "content": "import { useState } from \"react\";\r\nimport { Link } 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 { Mail, ArrowLeft, CheckCircle } from \"lucide-react\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { getErrorMessage } from \"@/modules/api\";\r\n\r\ninterface ForgotPasswordPageSplitProps {\r\n image?: string;\r\n}\r\n\r\nexport function ForgotPasswordPageSplit({\r\n image = \"/images/placeholder.png\",\r\n}: ForgotPasswordPageSplitProps) {\r\n const { t } = useTranslation(\"forgot-password-page-split\");\r\n usePageTitle({ title: t(\"title\", \"Forgot Password\") });\r\n const { forgotPassword } = useAuth();\r\n\r\n const [username, setUsername] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const [isEmailSent, setIsEmailSent] = useState(false);\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 forgotPassword(username);\r\n\r\n setIsEmailSent(true);\r\n toast.success(t(\"emailSent\", \"Reset link sent!\"), {\r\n description: t(\"checkInbox\", \"Please check your email inbox.\"),\r\n });\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"forgotPasswordError\", \"Failed to send reset link. 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 - email sent\r\n if (isEmailSent) {\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(\"checkYourEmail\", \"Check your email\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-2\">\r\n {t(\"emailSentTo\", \"We've sent a password reset link to\")}\r\n </p>\r\n <p className=\"text-sm font-medium mt-1\">{username}</p>\r\n </div>\r\n\r\n <div className=\"text-sm text-muted-foreground\">\r\n <p>{t(\"didntReceive\", \"Didn't receive the email?\")}</p>\r\n <Button\r\n variant=\"link\"\r\n className=\"p-0 h-auto\"\r\n onClick={() => {\r\n setIsEmailSent(false);\r\n setError(null);\r\n }}\r\n >\r\n {t(\"tryAgain\", \"Click here to try again\")}\r\n </Button>\r\n </div>\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\", \"Forgot 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 <Mail 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\", \"Forgot Password\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-1\">\r\n {t(\"subtitle\", \"Enter your username to reset your password\")}\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\", \"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\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(\"sending\", \"Sending...\")}\r\n </>\r\n ) : (\r\n t(\"sendLink\", \"Send Reset Link\")\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\", \"Forgot 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 ForgotPasswordPageSplit;\r\n"
26
+ "content": "import { useState } from \"react\";\r\nimport { Link } 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 { Logo } from \"@/components/Logo\";\r\nimport { Mail, 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\n\r\ninterface ForgotPasswordPageSplitProps {\r\n image?: string;\r\n}\r\n\r\nexport function ForgotPasswordPageSplit({\r\n image = \"/images/placeholder.png\",\r\n}: ForgotPasswordPageSplitProps) {\r\n const { t } = useTranslation(\"forgot-password-page-split\");\r\n usePageTitle({ title: t(\"title\", \"Forgot Password\") });\r\n const { forgotPassword } = useAuth();\r\n\r\n const [username, setUsername] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const [isEmailSent, setIsEmailSent] = useState(false);\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 forgotPassword(username);\r\n\r\n setIsEmailSent(true);\r\n toast.success(t(\"emailSent\", \"Reset link sent!\"), {\r\n description: t(\"checkInbox\", \"Please check your email inbox.\"),\r\n });\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"forgotPasswordError\", \"Failed to send reset link. 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 - email sent\r\n if (isEmailSent) {\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(\"checkYourEmail\", \"Check your email\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-2\">\r\n {t(\"emailSentTo\", \"We've sent a password reset link to\")}\r\n </p>\r\n <p className=\"text-sm font-medium mt-1\">{username}</p>\r\n </div>\r\n\r\n <div className=\"text-sm text-muted-foreground\">\r\n <p>{t(\"didntReceive\", \"Didn't receive the email?\")}</p>\r\n <Button\r\n variant=\"link\"\r\n className=\"p-0 h-auto\"\r\n onClick={() => {\r\n setIsEmailSent(false);\r\n setError(null);\r\n }}\r\n >\r\n {t(\"tryAgain\", \"Click here to try again\")}\r\n </Button>\r\n </div>\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\", \"Forgot 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 <Mail 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\", \"Forgot Password\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-1\">\r\n {t(\"subtitle\", \"Enter your username to reset your password\")}\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(\"username\", \"Username\")} htmlFor=\"username\" required>\r\n <Input\r\n required\r\n id=\"username\"\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 </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(\"sending\", \"Sending...\")}\r\n </>\r\n ) : (\r\n t(\"sendLink\", \"Send Reset Link\")\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\", \"Forgot 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 ForgotPasswordPageSplit;\r\n"
27
27
  },
28
28
  {
29
29
  "path": "forgot-password-page-split/lang/en.json",
@@ -23,7 +23,7 @@
23
23
  "path": "forgot-password-page/forgot-password-page.tsx",
24
24
  "type": "registry:page",
25
25
  "target": "$modules$/forgot-password-page/forgot-password-page.tsx",
26
- "content": "import { useState } from \"react\";\r\nimport { Link } 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\ntype Step = \"request\" | \"reset\" | \"success\";\r\n\r\nexport function ForgotPasswordPage() {\r\n const { t } = useTranslation(\"forgot-password-page\");\r\n usePageTitle({ title: t(\"title\", \"Forgot Password\") });\r\n\r\n const { forgotPassword, resetPassword } = useAuth();\r\n\r\n const [step, setStep] = useState<Step>(\"request\");\r\n const [username, setUsername] = useState(\"\");\r\n const [code, setCode] = useState(\"\");\r\n const [newPassword, setNewPassword] = useState(\"\");\r\n const [confirmPassword, setConfirmPassword] = useState(\"\");\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\r\n const handleRequestCode = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setIsSubmitting(true);\r\n setError(null);\r\n\r\n try {\r\n await forgotPassword(username);\r\n toast.success(t(\"codeSentTitle\", \"Code Sent!\"), {\r\n description: t(\r\n \"codeSentDesc\",\r\n \"A password reset code has been sent to your email.\",\r\n ),\r\n });\r\n setStep(\"reset\");\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"errorGeneric\", \"Failed to send reset code. Please try again.\"),\r\n );\r\n setError(errorMessage);\r\n toast.error(t(\"errorTitle\", \"Error\"), {\r\n description: errorMessage,\r\n });\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n const handleResetPassword = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n\r\n // Validate passwords match\r\n if (newPassword !== confirmPassword) {\r\n setError(t(\"passwordMismatch\", \"Passwords do not match\"));\r\n return;\r\n }\r\n\r\n setIsSubmitting(true);\r\n\r\n try {\r\n await resetPassword(username, code, newPassword);\r\n toast.success(t(\"resetSuccessTitle\", \"Password Reset!\"), {\r\n description: t(\r\n \"resetSuccessDesc\",\r\n \"Your password has been successfully reset.\",\r\n ),\r\n });\r\n setStep(\"success\");\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"errorResetGeneric\", \"Failed to reset password. Please try again.\"),\r\n );\r\n setError(errorMessage);\r\n toast.error(t(\"errorTitle\", \"Error\"), {\r\n description: errorMessage,\r\n });\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n // Success step\r\n if (step === \"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-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(\"successTitle\", \"Password Reset Successfully!\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"successDescription\",\r\n \"Your password has been changed. You can now login with your new password.\",\r\n )}\r\n </p>\r\n <Button asChild className=\"w-full\">\r\n <Link to=\"/login\">{t(\"goToLogin\", \"Go to Login\")}</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 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\", \"Forgot 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 {step === \"request\"\r\n ? t(\r\n \"descriptionRequest\",\r\n \"Enter your username and we'll send you a code to reset your password.\",\r\n )\r\n : t(\r\n \"descriptionReset\",\r\n \"Enter the code sent to your email and your new password.\",\r\n )}\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 {step === \"request\"\r\n ? t(\"cardTitleRequest\", \"Request Reset Code\")\r\n : t(\"cardTitleReset\", \"Reset Password\")}\r\n </CardTitle>\r\n <CardDescription>\r\n {step === \"request\"\r\n ? t(\"cardDescRequest\", \"Step 1 of 2: Request a reset code\")\r\n : t(\r\n \"cardDescReset\",\r\n \"Step 2 of 2: Enter code and new password\",\r\n )}\r\n </CardDescription>\r\n </CardHeader>\r\n <CardContent>\r\n {step === \"request\" ? (\r\n // Step 1: Request Code\r\n <form onSubmit={handleRequestCode} className=\"space-y-6\">\r\n <div>\r\n <Label htmlFor=\"username\">\r\n {t(\"username\", \"Username\")} *\r\n </Label>\r\n <Input\r\n id=\"username\"\r\n type=\"text\"\r\n value={username}\r\n onChange={(e) => setUsername(e.target.value)}\r\n placeholder={t(\r\n \"usernamePlaceholder\",\r\n \"Enter your username\",\r\n )}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"username\"\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(\"sending\", \"Sending...\")}\r\n </>\r\n ) : (\r\n t(\"sendCode\", \"Send Reset Code\")\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 ) : (\r\n // Step 2: Reset Password\r\n <form onSubmit={handleResetPassword} className=\"space-y-6\">\r\n <div className=\"p-3 bg-muted rounded-lg text-sm\">\r\n <span className=\"text-muted-foreground\">\r\n {t(\"codeFor\", \"Reset code for:\")}{\" \"}\r\n </span>\r\n <span className=\"font-medium\">{username}</span>\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"code\">{t(\"code\", \"Reset Code\")} *</Label>\r\n <Input\r\n id=\"code\"\r\n type=\"text\"\r\n value={code}\r\n onChange={(e) => setCode(e.target.value)}\r\n placeholder={t(\"codePlaceholder\", \"Enter 6-digit code\")}\r\n required\r\n className=\"mt-1\"\r\n maxLength={6}\r\n />\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"newPassword\">\r\n {t(\"newPassword\", \"New Password\")} *\r\n </Label>\r\n <div className=\"relative\">\r\n <Input\r\n id=\"newPassword\"\r\n type={showPassword ? \"text\" : \"password\"}\r\n value={newPassword}\r\n onChange={(e) => setNewPassword(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 />\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 />\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(\"resetting\", \"Resetting...\")}\r\n </>\r\n ) : (\r\n t(\"resetPassword\", \"Reset Password\")\r\n )}\r\n </Button>\r\n\r\n <div className=\"flex justify-between\">\r\n <button\r\n type=\"button\"\r\n onClick={() => {\r\n setStep(\"request\");\r\n setCode(\"\");\r\n setNewPassword(\"\");\r\n setConfirmPassword(\"\");\r\n setError(null);\r\n }}\r\n className=\"text-sm text-muted-foreground hover:text-primary\"\r\n >\r\n {t(\"changeUsername\", \"Change username\")}\r\n </button>\r\n <button\r\n type=\"button\"\r\n onClick={() =>\r\n handleRequestCode({\r\n preventDefault: () => {},\r\n } as React.FormEvent)\r\n }\r\n className=\"text-sm text-primary hover:underline\"\r\n disabled={isSubmitting}\r\n >\r\n {t(\"resendCode\", \"Resend code\")}\r\n </button>\r\n </div>\r\n </form>\r\n )}\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 ForgotPasswordPage;\r\n"
26
+ "content": "import { useState } from \"react\";\r\nimport { Link } 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 {\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, CheckCircle2 } from \"lucide-react\";\r\nimport { FormField } from \"@/components/FormField\";\r\nimport { PasswordInput } from \"@/components/PasswordInput\";\r\n\r\ntype Step = \"request\" | \"reset\" | \"success\";\r\n\r\nexport function ForgotPasswordPage() {\r\n const { t } = useTranslation(\"forgot-password-page\");\r\n usePageTitle({ title: t(\"title\", \"Forgot Password\") });\r\n\r\n const { forgotPassword, resetPassword } = useAuth();\r\n\r\n const [step, setStep] = useState<Step>(\"request\");\r\n const [username, setUsername] = useState(\"\");\r\n const [code, setCode] = useState(\"\");\r\n const [newPassword, setNewPassword] = useState(\"\");\r\n const [confirmPassword, setConfirmPassword] = useState(\"\");\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const handleRequestCode = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setIsSubmitting(true);\r\n setError(null);\r\n\r\n try {\r\n await forgotPassword(username);\r\n toast.success(t(\"codeSentTitle\", \"Code Sent!\"), {\r\n description: t(\r\n \"codeSentDesc\",\r\n \"A password reset code has been sent to your email.\",\r\n ),\r\n });\r\n setStep(\"reset\");\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"errorGeneric\", \"Failed to send reset code. Please try again.\"),\r\n );\r\n setError(errorMessage);\r\n toast.error(t(\"errorTitle\", \"Error\"), {\r\n description: errorMessage,\r\n });\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n const handleResetPassword = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n\r\n // Validate passwords match\r\n if (newPassword !== confirmPassword) {\r\n setError(t(\"passwordMismatch\", \"Passwords do not match\"));\r\n return;\r\n }\r\n\r\n setIsSubmitting(true);\r\n\r\n try {\r\n await resetPassword(username, code, newPassword);\r\n toast.success(t(\"resetSuccessTitle\", \"Password Reset!\"), {\r\n description: t(\r\n \"resetSuccessDesc\",\r\n \"Your password has been successfully reset.\",\r\n ),\r\n });\r\n setStep(\"success\");\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"errorResetGeneric\", \"Failed to reset password. Please try again.\"),\r\n );\r\n setError(errorMessage);\r\n toast.error(t(\"errorTitle\", \"Error\"), {\r\n description: errorMessage,\r\n });\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n // Success step\r\n if (step === \"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-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(\"successTitle\", \"Password Reset Successfully!\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"successDescription\",\r\n \"Your password has been changed. You can now login with your new password.\",\r\n )}\r\n </p>\r\n <Button asChild className=\"w-full\">\r\n <Link to=\"/login\">{t(\"goToLogin\", \"Go to Login\")}</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 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\", \"Forgot 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 {step === \"request\"\r\n ? t(\r\n \"descriptionRequest\",\r\n \"Enter your username and we'll send you a code to reset your password.\",\r\n )\r\n : t(\r\n \"descriptionReset\",\r\n \"Enter the code sent to your email and your new password.\",\r\n )}\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 {step === \"request\"\r\n ? t(\"cardTitleRequest\", \"Request Reset Code\")\r\n : t(\"cardTitleReset\", \"Reset Password\")}\r\n </CardTitle>\r\n <CardDescription>\r\n {step === \"request\"\r\n ? t(\"cardDescRequest\", \"Step 1 of 2: Request a reset code\")\r\n : t(\r\n \"cardDescReset\",\r\n \"Step 2 of 2: Enter code and new password\",\r\n )}\r\n </CardDescription>\r\n </CardHeader>\r\n <CardContent>\r\n {step === \"request\" ? (\r\n // Step 1: Request Code\r\n <form onSubmit={handleRequestCode} className=\"space-y-6\">\r\n <FormField label={t(\"username\", \"Username\")} htmlFor=\"username\" required>\r\n <Input\r\n id=\"username\"\r\n type=\"text\"\r\n value={username}\r\n onChange={(e) => setUsername(e.target.value)}\r\n placeholder={t(\"usernamePlaceholder\", \"Enter your username\")}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"username\"\r\n />\r\n </FormField>\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(\"sending\", \"Sending...\")}\r\n </>\r\n ) : (\r\n t(\"sendCode\", \"Send Reset Code\")\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 ) : (\r\n // Step 2: Reset Password\r\n <form onSubmit={handleResetPassword} className=\"space-y-6\">\r\n <div className=\"p-3 bg-muted rounded-lg text-sm\">\r\n <span className=\"text-muted-foreground\">\r\n {t(\"codeFor\", \"Reset code for:\")}{\" \"}\r\n </span>\r\n <span className=\"font-medium\">{username}</span>\r\n </div>\r\n <FormField label={t(\"code\", \"Reset Code\")} htmlFor=\"code\" required>\r\n <Input\r\n id=\"code\"\r\n type=\"text\"\r\n value={code}\r\n onChange={(e) => setCode(e.target.value)}\r\n placeholder={t(\"codePlaceholder\", \"Enter 6-digit code\")}\r\n required\r\n className=\"mt-1\"\r\n maxLength={6}\r\n />\r\n </FormField>\r\n <FormField label={t(\"newPassword\", \"New Password\")} htmlFor=\"new-password\" required>\r\n <PasswordInput\r\n id=\"new-password\"\r\n name=\"new-password\"\r\n value={newPassword}\r\n onChange={(e) => setNewPassword(e.target.value)}\r\n placeholder={t(\"newPasswordPlaceholder\", \"Enter new password\")}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"new-password\"\r\n />\r\n </FormField>\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 <FormField label={t(\"confirmPassword\", \"Confirm Password\")} htmlFor=\"confirm-password\" required>\r\n <PasswordInput\r\n id=\"confirm-password\"\r\n value={confirmPassword}\r\n name=\"confirm-password\"\r\n onChange={(e) => setConfirmPassword(e.target.value)}\r\n placeholder={t(\"confirmPasswordPlaceholder\", \"Confirm new password\")}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"new-password\"\r\n />\r\n </FormField>\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(\"resetting\", \"Resetting...\")}\r\n </>\r\n ) : (\r\n t(\"resetPassword\", \"Reset Password\")\r\n )}\r\n </Button>\r\n\r\n <div className=\"flex justify-between\">\r\n <button\r\n type=\"button\"\r\n onClick={() => {\r\n setStep(\"request\");\r\n setCode(\"\");\r\n setNewPassword(\"\");\r\n setConfirmPassword(\"\");\r\n setError(null);\r\n }}\r\n className=\"text-sm text-muted-foreground hover:text-primary\"\r\n >\r\n {t(\"changeUsername\", \"Change username\")}\r\n </button>\r\n <button\r\n type=\"button\"\r\n onClick={() =>\r\n handleRequestCode({\r\n preventDefault: () => { },\r\n } as React.FormEvent)\r\n }\r\n className=\"text-sm text-primary hover:underline\"\r\n disabled={isSubmitting}\r\n >\r\n {t(\"resendCode\", \"Resend code\")}\r\n </button>\r\n </div>\r\n </form>\r\n )}\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 ForgotPasswordPage;\r\n"
27
27
  },
28
28
  {
29
29
  "path": "forgot-password-page/lang/en.json",
@@ -23,7 +23,7 @@
23
23
  "path": "login-page-split/login-page-split.tsx",
24
24
  "type": "registry:page",
25
25
  "target": "$modules$/login-page-split/login-page-split.tsx",
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"
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 { useTranslation } from \"react-i18next\";\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 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 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 <FormField label={t(\"username\", \"Username\")} htmlFor=\"username-split\">\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 </FormField>\r\n </div>\r\n <div className=\"grid gap-2\">\r\n <FormField label={t(\"password\", \"Password\")} htmlFor=\"password-split\">\r\n <PasswordInput\r\n id=\"password-split\"\r\n name=\"password\"\r\n value={password}\r\n disabled={isLoading}\r\n onChange={(e) => setPassword(e.target.value)}\r\n placeholder=\"••••••••••\"\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"current-password\"\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(\"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"
27
27
  },
28
28
  {
29
29
  "path": "login-page-split/lang/en.json",
@@ -23,7 +23,7 @@
23
23
  "path": "login-page/login-page.tsx",
24
24
  "type": "registry:page",
25
25
  "target": "$modules$/login-page/login-page.tsx",
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 <div className=\"mx-auto block w-fit\">\r\n <Logo size=\"sm\" />\r\n </div>\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"
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\nimport { FormField } from \"@/components/FormField\";\r\nimport { PasswordInput } from \"@/components/PasswordInput\";\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 <div className=\"mx-auto block w-fit\">\r\n <Logo size=\"sm\" />\r\n </div>\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 <FormField label={t(\"username\", \"Username\")} htmlFor=\"username\">\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 </FormField>\r\n <FormField\r\n label={t(\"password\", \"Password\")}\r\n htmlFor=\"password\"\r\n labelAction={\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 }\r\n >\r\n <PasswordInput\r\n id=\"password\"\r\n name=\"password\"\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n placeholder=\"••••••••\"\r\n disabled={isLoading}\r\n required\r\n />\r\n </FormField>\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"
27
27
  },
28
28
  {
29
29
  "path": "login-page/lang/en.json",
@@ -20,7 +20,7 @@
20
20
  "path": "newsletter-section/newsletter-section.tsx",
21
21
  "type": "registry:component",
22
22
  "target": "$modules$/newsletter-section/newsletter-section.tsx",
23
- "content": "import { Button } from \"@/components/ui/button\";\r\nimport { Card, CardContent } from \"@/components/ui/card\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { useTranslation } from \"react-i18next\";\r\n\r\ninterface NewsletterSectionProps {\r\n image?: string;\r\n className?: string;\r\n}\r\n\r\nexport function NewsletterSection({\r\n image = \"/images/placeholder.png\",\r\n className,\r\n}: NewsletterSectionProps) {\r\n const { t } = useTranslation(\"newsletter-section\");\r\n\r\n const handleSubmit = (e: React.FormEvent) => {\r\n e.preventDefault();\r\n // Handle newsletter subscription\r\n };\r\n\r\n return (\r\n <section className={`px-4 py-20 bg-accent ${className || \"\"}`}>\r\n <div className=\"mx-auto max-w-6xl\">\r\n <Card className=\"overflow-hidden shadow-none p-0\">\r\n <CardContent className=\"p-0\">\r\n <div className=\"grid md:grid-cols-2 items-center\">\r\n <img\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n src={image}\r\n alt={t(\"imageAlt\", \"Newsletter\")}\r\n width={1200}\r\n height={800}\r\n className=\"h-64 w-full object-cover md:h-full\"\r\n />\r\n <form\r\n onSubmit={handleSubmit}\r\n className=\"flex flex-col gap-4 p-8 max-w-md w-full mx-auto\"\r\n >\r\n <div className=\"space-y-1 text-center\">\r\n <h2 className=\"text-2xl font-bold tracking-tight\">\r\n {t(\"title\", \"Get the inside scoop\")}\r\n </h2>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"description\", \"Join our newsletter to stay ahead.\")}\r\n </p>\r\n </div>\r\n <div className=\"grid gap-2 text-left\">\r\n <Label htmlFor=\"newsletter-email\">\r\n {t(\"emailLabel\", \"Email\")}\r\n </Label>\r\n <Input\r\n id=\"newsletter-email\"\r\n type=\"email\"\r\n placeholder={t(\"emailPlaceholder\", \"Enter your email\")}\r\n required\r\n />\r\n </div>\r\n <Button type=\"submit\">{t(\"subscribe\", \"Subscribe\")}</Button>\r\n </form>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </section>\r\n );\r\n}\r\n"
23
+ "content": "import { FormField } from \"@/components/FormField\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Card, CardContent } from \"@/components/ui/card\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { useTranslation } from \"react-i18next\";\r\n\r\ninterface NewsletterSectionProps {\r\n image?: string;\r\n className?: string;\r\n}\r\n\r\nexport function NewsletterSection({\r\n image = \"/images/placeholder.png\",\r\n className,\r\n}: NewsletterSectionProps) {\r\n const { t } = useTranslation(\"newsletter-section\");\r\n\r\n const handleSubmit = (e: React.FormEvent) => {\r\n e.preventDefault();\r\n // Handle newsletter subscription\r\n };\r\n\r\n return (\r\n <section className={`px-4 py-20 bg-accent ${className || \"\"}`}>\r\n <div className=\"mx-auto max-w-6xl\">\r\n <Card className=\"overflow-hidden shadow-none p-0\">\r\n <CardContent className=\"p-0\">\r\n <div className=\"grid md:grid-cols-2 items-center\">\r\n <img\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n src={image}\r\n alt={t(\"imageAlt\", \"Newsletter\")}\r\n width={1200}\r\n height={800}\r\n className=\"h-64 w-full object-cover md:h-full\"\r\n />\r\n <form\r\n onSubmit={handleSubmit}\r\n className=\"flex flex-col gap-4 p-8 max-w-md w-full mx-auto\"\r\n >\r\n <div className=\"space-y-1 text-center\">\r\n <h2 className=\"text-2xl font-bold tracking-tight\">\r\n {t(\"title\", \"Get the inside scoop\")}\r\n </h2>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"description\", \"Join our newsletter to stay ahead.\")}\r\n </p>\r\n </div>\r\n <div className=\"grid gap-2 text-left\">\r\n <FormField label={t(\"emailLabel\", \"Email\")} htmlFor=\"newsletter-email\" required>\r\n <Input\r\n id=\"newsletter-email\"\r\n type=\"email\"\r\n placeholder={t(\"emailPlaceholder\", \"Enter your email\")}\r\n required\r\n />\r\n </FormField>\r\n \r\n </div>\r\n <Button type=\"submit\">{t(\"subscribe\", \"Subscribe\")}</Button>\r\n </form>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </section>\r\n );\r\n}\r\n"
24
24
  },
25
25
  {
26
26
  "path": "newsletter-section/lang/en.json",
@@ -23,7 +23,7 @@
23
23
  "path": "register-page-split/register-page-split.tsx",
24
24
  "type": "registry:page",
25
25
  "target": "$modules$/register-page-split/register-page-split.tsx",
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"
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 { Logo } from \"@/components/Logo\";\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 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 <FormField label={t(\"username\", \"Username\")} htmlFor=\"username\" required>\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 </FormField>\r\n </div>\r\n\r\n <div className=\"grid gap-2\">\r\n <FormField label={t(\"email\", \"Email\")} htmlFor=\"email\" required>\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 </FormField>\r\n </div>\r\n\r\n <div className=\"grid gap-2\">\r\n <FormField label={t(\"password\", \"Password\")} htmlFor=\"password\" required>\r\n <PasswordInput\r\n required\r\n name=\"password\"\r\n id=\"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=\"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 </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(\"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"
27
27
  },
28
28
  {
29
29
  "path": "register-page-split/lang/en.json",
@@ -23,7 +23,7 @@
23
23
  "path": "register-page/register-page.tsx",
24
24
  "type": "registry:page",
25
25
  "target": "$modules$/register-page/register-page.tsx",
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 <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 );\r\n }\r\n\r\n return (\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 );\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 { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\r\nimport { UserPlus, CheckCircle } from \"lucide-react\";\r\nimport { PasswordInput } from \"@/components/PasswordInput\";\r\nimport { FormField } from \"@/components/FormField\";\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 [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 <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 );\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 <FormField label={t(\"username\", \"Username\")} htmlFor=\"username\" required>\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 </FormField>\r\n <FormField label={t(\"email\", \"Email\")} htmlFor=\"email\" required>\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 </FormField>\r\n <FormField label={t(\"password\", \"Password\")} htmlFor=\"password\" required>\r\n <PasswordInput id=\"password\"\r\n name=\"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 </FormField>\r\n\r\n <FormField label={t(\"confirmPassword\", \"Confirm Password\")} htmlFor=\"confirmPassword\" required\r\n >\r\n <PasswordInput\r\n id=\"confirmPassword\"\r\n name=\"confirmPassword\"\r\n value={formData.confirmPassword}\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 />\r\n </FormField>\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 </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 );\r\n}\r\n\r\nexport default RegisterPage;\r\n"
27
27
  },
28
28
  {
29
29
  "path": "register-page/lang/en.json",