@promakeai/cli 0.0.6 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +31 -31
- package/dist/registry/about-page.json +4 -2
- package/dist/registry/about-section.json +2 -2
- package/dist/registry/announcement-bar.json +43 -0
- package/dist/registry/auth-core.json +43 -0
- package/dist/registry/auth.json +1 -1
- package/dist/registry/blog-list-page.json +3 -2
- package/dist/registry/blog-section.json +1 -1
- package/dist/registry/cart-page.json +3 -3
- package/dist/registry/case-study-page.json +48 -0
- package/dist/registry/checkout-page.json +6 -5
- package/dist/registry/coming-soon-page-minimal.json +45 -0
- package/dist/registry/coming-soon-page.json +47 -0
- package/dist/registry/contact-info-grid.json +1 -1
- package/dist/registry/contact-page-centered.json +2 -2
- package/dist/registry/contact-page-map-overlay.json +4 -3
- package/dist/registry/contact-page-map-split.json +4 -3
- package/dist/registry/contact-page-split.json +3 -3
- package/dist/registry/contact-page.json +5 -3
- package/dist/registry/cookie-consent.json +43 -0
- package/dist/registry/cookies-page.json +3 -1
- package/dist/registry/cta-section.json +1 -1
- package/dist/registry/db.json +1 -1
- package/dist/registry/docs/about-page.md +5 -0
- package/dist/registry/docs/announcement-bar.md +38 -0
- package/dist/registry/docs/auth-core.md +64 -0
- package/dist/registry/docs/blog-list-page.md +1 -0
- package/dist/registry/docs/case-study-page.md +39 -0
- package/dist/registry/docs/checkout-page.md +2 -1
- package/dist/registry/docs/coming-soon-page-minimal.md +32 -0
- package/dist/registry/docs/coming-soon-page.md +37 -0
- package/dist/registry/docs/contact-page-map-overlay.md +2 -2
- package/dist/registry/docs/contact-page-map-split.md +2 -2
- package/dist/registry/docs/contact-page.md +5 -0
- package/dist/registry/docs/cookie-consent.md +37 -0
- package/dist/registry/docs/cookies-page.md +5 -0
- package/dist/registry/docs/ecommerce-core.md +4 -3
- package/dist/registry/docs/forgot-password-page-split.md +45 -0
- package/dist/registry/docs/forgot-password-page.md +14 -5
- package/dist/registry/docs/header-ecommerce.md +1 -0
- package/dist/registry/docs/hero-carousel.md +37 -0
- package/dist/registry/docs/landing-page-app.md +43 -0
- package/dist/registry/docs/landing-page-saas.md +41 -0
- package/dist/registry/docs/login-page-split.md +13 -4
- package/dist/registry/docs/login-page.md +17 -4
- package/dist/registry/docs/logo-cloud.md +33 -0
- package/dist/registry/docs/masonry-grid.md +37 -0
- package/dist/registry/docs/my-orders-page.md +44 -0
- package/dist/registry/docs/order-confirmation-page.md +41 -0
- package/dist/registry/docs/portfolio-page.md +38 -0
- package/dist/registry/docs/pricing-page.md +38 -0
- package/dist/registry/docs/privacy-page.md +5 -0
- package/dist/registry/docs/product-quick-view.md +37 -0
- package/dist/registry/docs/reading-progress.md +31 -0
- package/dist/registry/docs/register-page-split.md +45 -0
- package/dist/registry/docs/register-page.md +14 -7
- package/dist/registry/docs/reset-password-page-split.md +45 -0
- package/dist/registry/docs/reset-password-page.md +36 -0
- package/dist/registry/docs/share-buttons.md +37 -0
- package/dist/registry/docs/team-page.md +38 -0
- package/dist/registry/docs/terms-page.md +5 -0
- package/dist/registry/docs/timeline-section.md +37 -0
- package/dist/registry/docs/video-hero.md +41 -0
- package/dist/registry/ecommerce-core.json +17 -1
- package/dist/registry/empty-page.json +1 -1
- package/dist/registry/faq-categorized.json +1 -1
- package/dist/registry/faq-simple.json +1 -1
- package/dist/registry/favorites-ecommerce-block.json +1 -1
- package/dist/registry/feature-section.json +2 -2
- package/dist/registry/footer.json +1 -1
- package/dist/registry/forgot-password-page-split.json +50 -0
- package/dist/registry/forgot-password-page.json +9 -7
- package/dist/registry/header-ecommerce.json +3 -2
- package/dist/registry/header-mega.json +1 -1
- package/dist/registry/hero-carousel.json +45 -0
- package/dist/registry/hero-cta.json +2 -2
- package/dist/registry/hero-gradient.json +1 -1
- package/dist/registry/hero.json +1 -1
- package/dist/registry/index.json +22 -2
- package/dist/registry/landing-page-app.json +47 -0
- package/dist/registry/landing-page-saas.json +47 -0
- package/dist/registry/login-page-split.json +11 -7
- package/dist/registry/login-page.json +4 -4
- package/dist/registry/logo-cloud.json +41 -0
- package/dist/registry/masonry-grid.json +43 -0
- package/dist/registry/my-orders-page.json +52 -0
- package/dist/registry/order-confirmation-page.json +49 -0
- package/dist/registry/portfolio-page.json +45 -0
- package/dist/registry/pricing-page.json +47 -0
- package/dist/registry/pricing-section.json +1 -1
- package/dist/registry/privacy-page.json +3 -1
- package/dist/registry/product-detail-block.json +1 -1
- package/dist/registry/product-quick-view.json +46 -0
- package/dist/registry/products-page.json +3 -3
- package/dist/registry/reading-progress.json +43 -0
- package/dist/registry/register-page-split.json +50 -0
- package/dist/registry/register-page.json +9 -7
- package/dist/registry/reset-password-page-split.json +50 -0
- package/dist/registry/reset-password-page.json +39 -0
- package/dist/registry/share-buttons.json +46 -0
- package/dist/registry/team-page.json +47 -0
- package/dist/registry/terms-page.json +3 -1
- package/dist/registry/testimonials-carousel.json +1 -1
- package/dist/registry/testimonials-grid.json +1 -1
- package/dist/registry/timeline-section.json +43 -0
- package/dist/registry/video-hero.json +42 -0
- package/package.json +1 -1
- package/template/index.html +5 -5
- package/template/src/App.tsx +4 -0
- package/template/src/components/GoogleAnalytics.tsx +34 -0
- package/template/src/components/Layout.tsx +1 -1
- package/template/src/components/ScriptInjector.tsx +62 -0
- package/template/src/constants/constants.json +8 -2
- package/template/src/pages/Index.tsx +5 -1
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
"name": "forgot-password-page",
|
|
3
3
|
"type": "registry:page",
|
|
4
4
|
"title": "Forgot Password Page",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Centered card password recovery page with Layout wrapper. Two-step flow: request reset code then enter code with new password. Uses useAuth hook from auth-core for API calls.",
|
|
6
6
|
"registryDependencies": [
|
|
7
7
|
"button",
|
|
8
8
|
"input",
|
|
9
|
-
"
|
|
9
|
+
"card",
|
|
10
|
+
"auth-core",
|
|
11
|
+
"api"
|
|
10
12
|
],
|
|
11
|
-
"usage": "import ForgotPasswordPage from '@/modules/forgot-password-page';\n\n<ForgotPasswordPage\n
|
|
13
|
+
"usage": "import ForgotPasswordPage from '@/modules/forgot-password-page';\n\n<ForgotPasswordPage />\n\n• Installed at: src/modules/forgot-password-page/\n• Customize text: src/modules/forgot-password-page/lang/*.json\n• Uses useAuth() hook from auth-core:\n const { forgotPassword, resetPassword } = useAuth();\n await forgotPassword(username);\n await resetPassword(username, code, newPassword);\n• Two-step flow: request code -> enter code with new password\n• Add to your router as a page component",
|
|
12
14
|
"route": {
|
|
13
15
|
"path": "/forgot-password",
|
|
14
16
|
"componentName": "ForgotPasswordPage"
|
|
@@ -18,25 +20,25 @@
|
|
|
18
20
|
"path": "forgot-password-page/index.ts",
|
|
19
21
|
"type": "registry:index",
|
|
20
22
|
"target": "$modules$/forgot-password-page/index.ts",
|
|
21
|
-
"content": "export
|
|
23
|
+
"content": "export * from './forgot-password-page';\r\nexport { default } from './forgot-password-page';\r\n"
|
|
22
24
|
},
|
|
23
25
|
{
|
|
24
26
|
"path": "forgot-password-page/forgot-password-page.tsx",
|
|
25
27
|
"type": "registry:page",
|
|
26
28
|
"target": "$modules$/forgot-password-page/forgot-password-page.tsx",
|
|
27
|
-
"content": "import { useState } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { useTranslation } from \"react-i18next\";\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\n\r\ninterface ForgotPasswordPageProps {\r\n onSubmit?: (email: string) => void;\r\n className?: string;\r\n}\r\n\r\nexport default function ForgotPasswordPage({\r\n onSubmit,\r\n className,\r\n}: ForgotPasswordPageProps) {\r\n const { t } = useTranslation(\"forgot-password-page\");\r\n const [email, setEmail] = useState(\"\");\r\n\r\n const handleSubmit = (e: React.FormEvent) => {\r\n e.preventDefault();\r\n onSubmit?.(email);\r\n };\r\n\r\n return (\r\n <section className={cn(\"flex min-h-screen bg-muted/30 px-4 py-16 md:py-32\", className)}>\r\n <form\r\n onSubmit={handleSubmit}\r\n className=\"bg-muted m-auto h-fit w-full max-w-sm overflow-hidden rounded-xl border shadow-md\"\r\n >\r\n <div className=\"bg-card -m-px rounded-xl border p-8 pb-6\">\r\n <div className=\"text-center\">\r\n <Link to=\"/\" aria-label=\"go home\" className=\"mx-auto block w-fit\">\r\n <Logo size=\"sm\" />\r\n </Link>\r\n <h1 className=\"mb-1 mt-4 text-xl font-semibold\">\r\n {t(\"title\", \"Forgot Password\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"subtitle\", \"Enter your email to reset your password\")}\r\n </p>\r\n </div>\r\n\r\n <div className=\"mt-6 space-y-6\">\r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"email\" className=\"block text-sm\">\r\n {t(\"email\", \"Email\")}\r\n </Label>\r\n <Input\r\n type=\"email\"\r\n required\r\n name=\"email\"\r\n id=\"email\"\r\n value={email}\r\n onChange={(e) => setEmail(e.target.value)}\r\n placeholder={t(\"emailPlaceholder\", \"you@example.com\")}\r\n />\r\n </div>\r\n\r\n <Button type=\"submit\" className=\"w-full\">\r\n {t(\"sendLink\", \"Send Reset Link\")}\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(\"rememberPassword\", \"Remember your password?\")}\r\n <Button asChild variant=\"link\" className=\"px-2\">\r\n <Link to=\"/login\">{t(\"signIn\", \"Sign in\")}</Link>\r\n </Button>\r\n </p>\r\n </div>\r\n </form>\r\n </section>\r\n );\r\n}\r\n"
|
|
29
|
+
"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/use-auth\";\r\nimport { getErrorMessage } from \"@/modules/api/get-error-message\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport {\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"
|
|
28
30
|
},
|
|
29
31
|
{
|
|
30
32
|
"path": "forgot-password-page/lang/en.json",
|
|
31
33
|
"type": "registry:lang",
|
|
32
34
|
"target": "$modules$/forgot-password-page/lang/en.json",
|
|
33
|
-
"content": "{\r\n \"title\": \"Forgot Password\",\r\n \"
|
|
35
|
+
"content": "{\r\n \"title\": \"Forgot Password\",\r\n \"descriptionRequest\": \"Enter your username and we'll send you a code to reset your password.\",\r\n \"descriptionReset\": \"Enter the code sent to your email and your new password.\",\r\n \"cardTitleRequest\": \"Request Reset Code\",\r\n \"cardTitleReset\": \"Reset Password\",\r\n \"cardDescRequest\": \"Step 1 of 2: Request a reset code\",\r\n \"cardDescReset\": \"Step 2 of 2: Enter code and new password\",\r\n \"username\": \"Username\",\r\n \"usernamePlaceholder\": \"Enter your username\",\r\n \"code\": \"Reset Code\",\r\n \"codePlaceholder\": \"Enter 6-digit code\",\r\n \"codeFor\": \"Reset code for:\",\r\n \"newPassword\": \"New Password\",\r\n \"newPasswordPlaceholder\": \"Enter new password\",\r\n \"confirmPassword\": \"Confirm Password\",\r\n \"confirmPasswordPlaceholder\": \"Confirm new password\",\r\n \"passwordRequirements\": \"At least 8 characters, 1 letter and 1 number\",\r\n \"passwordMismatch\": \"Passwords do not match\",\r\n \"sendCode\": \"Send Reset Code\",\r\n \"sending\": \"Sending...\",\r\n \"resetPassword\": \"Reset Password\",\r\n \"resetting\": \"Resetting...\",\r\n \"backToLogin\": \"Back to Login\",\r\n \"changeUsername\": \"Change username\",\r\n \"resendCode\": \"Resend code\",\r\n \"codeSentTitle\": \"Code Sent!\",\r\n \"codeSentDesc\": \"A password reset code has been sent to your email.\",\r\n \"errorTitle\": \"Error\",\r\n \"errorGeneric\": \"Failed to send reset code. Please try again.\",\r\n \"errorResetGeneric\": \"Failed to reset password. Please try again.\",\r\n \"resetSuccessTitle\": \"Password Reset!\",\r\n \"resetSuccessDesc\": \"Your password has been successfully reset.\",\r\n \"successTitle\": \"Password Reset Successfully!\",\r\n \"successDescription\": \"Your password has been changed. You can now login with your new password.\",\r\n \"goToLogin\": \"Go to Login\"\r\n}\r\n"
|
|
34
36
|
},
|
|
35
37
|
{
|
|
36
38
|
"path": "forgot-password-page/lang/tr.json",
|
|
37
39
|
"type": "registry:lang",
|
|
38
40
|
"target": "$modules$/forgot-password-page/lang/tr.json",
|
|
39
|
-
"content": "{\r\n \"title\": \"Şifremi Unuttum\",\r\n \"
|
|
41
|
+
"content": "{\r\n \"title\": \"Şifremi Unuttum\",\r\n \"descriptionRequest\": \"Kullanıcı adınızı girin, size şifre sıfırlama kodu göndereceğiz.\",\r\n \"descriptionReset\": \"E-postanıza gönderilen kodu ve yeni şifrenizi girin.\",\r\n \"cardTitleRequest\": \"Sıfırlama Kodu İste\",\r\n \"cardTitleReset\": \"Şifre Sıfırla\",\r\n \"cardDescRequest\": \"Adım 1/2: Sıfırlama kodu isteyin\",\r\n \"cardDescReset\": \"Adım 2/2: Kodu ve yeni şifreyi girin\",\r\n \"username\": \"Kullanıcı Adı\",\r\n \"usernamePlaceholder\": \"Kullanıcı adınızı girin\",\r\n \"code\": \"Sıfırlama Kodu\",\r\n \"codePlaceholder\": \"6 haneli kodu girin\",\r\n \"codeFor\": \"Sıfırlama kodu:\",\r\n \"newPassword\": \"Yeni Şifre\",\r\n \"newPasswordPlaceholder\": \"Yeni şifre girin\",\r\n \"confirmPassword\": \"Şifre Onayı\",\r\n \"confirmPasswordPlaceholder\": \"Yeni şifreyi onaylayın\",\r\n \"passwordRequirements\": \"En az 8 karakter, 1 harf ve 1 rakam\",\r\n \"passwordMismatch\": \"Şifreler eşleşmiyor\",\r\n \"sendCode\": \"Sıfırlama Kodu Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"resetPassword\": \"Şifreyi Sıfırla\",\r\n \"resetting\": \"Sıfırlanıyor...\",\r\n \"backToLogin\": \"Girişe Dön\",\r\n \"changeUsername\": \"Kullanıcı adını değiştir\",\r\n \"resendCode\": \"Kodu tekrar gönder\",\r\n \"codeSentTitle\": \"Kod Gönderildi!\",\r\n \"codeSentDesc\": \"E-postanıza şifre sıfırlama kodu gönderildi.\",\r\n \"errorTitle\": \"Hata\",\r\n \"errorGeneric\": \"Sıfırlama kodu gönderilemedi. Lütfen tekrar deneyin.\",\r\n \"errorResetGeneric\": \"Şifre sıfırlanamadı. Lütfen tekrar deneyin.\",\r\n \"resetSuccessTitle\": \"Şifre Sıfırlandı!\",\r\n \"resetSuccessDesc\": \"Şifreniz başarıyla sıfırlandı.\",\r\n \"successTitle\": \"Şifre Başarıyla Sıfırlandı!\",\r\n \"successDescription\": \"Şifreniz değiştirildi. Artık yeni şifrenizle giriş yapabilirsiniz.\",\r\n \"goToLogin\": \"Girişe Git\"\r\n}\r\n"
|
|
40
42
|
}
|
|
41
43
|
],
|
|
42
44
|
"exports": {
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
"description": "Full-featured e-commerce header with logo, main navigation, search bar with autocomplete dropdown, favorites icon with count badge, shopping cart icon with item count, user account dropdown menu, and mobile hamburger menu. Includes sticky positioning, language switcher, and responsive breakpoints. Dark mode support included.",
|
|
6
6
|
"registryDependencies": [
|
|
7
7
|
"ecommerce-core",
|
|
8
|
-
"cart-drawer"
|
|
8
|
+
"cart-drawer",
|
|
9
|
+
"auth-core"
|
|
9
10
|
],
|
|
10
11
|
"usage": "import { Header } from '@/modules/header-ecommerce';\n\n<Header />\n\n• Component is installed at: src/modules/header-ecommerce/\n• Customize navigation links in: src/modules/header-ecommerce/lang/*.json\n• Uses useCart and useFavorites from ecommerce-core for badges\n• Includes search, cart, favorites, and mobile menu",
|
|
11
12
|
"files": [
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
"path": "header-ecommerce/header-ecommerce.tsx",
|
|
20
21
|
"type": "registry:component",
|
|
21
22
|
"target": "$modules$/header-ecommerce/header-ecommerce.tsx",
|
|
22
|
-
"content": "import { useState } from \"react\";\nimport { Link, useNavigate } from \"react-router\";\nimport { ShoppingCart, Menu, Search, Heart, Package } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n Sheet,\n SheetHeader,\n SheetTitle,\n SheetContent,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\nimport { Logo } from \"@/components/Logo\";\nimport { AuthHeaderMenu } from \"@/modules/auth/auth-header-menu\";\nimport { CartDrawer } from \"@/modules/cart-drawer\";\nimport { useTranslation } from \"react-i18next\";\nimport constants from \"@/constants/constants.json\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\nimport {\n useCart,\n useFavorites,\n useSearch,\n formatPrice,\n} from \"@/modules/ecommerce-core\";\n\nexport function HeaderEcommerce() {\n const [mobileMenuOpen, setMobileMenuOpen] = useState(false);\n const [mobileSearchOpen, setMobileSearchOpen] = useState(false);\n const [desktopSearchOpen, setDesktopSearchOpen] = useState(false);\n const [showResults, setShowResults] = useState(false);\n const { itemCount, state, removeItem, isDrawerOpen, setDrawerOpen } = useCart();\n const { favoriteCount } = useFavorites();\n const navigate = useNavigate();\n const { t } = useTranslation(\"header-ecommerce\");\n\n const {\n searchTerm,\n setSearchTerm,\n results: searchResults,\n clearSearch,\n } = useSearch();\n\n const handleSearchSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (searchTerm.trim()) {\n navigate(`/products?search=${encodeURIComponent(searchTerm)}`);\n setShowResults(false);\n setDesktopSearchOpen(false);\n clearSearch();\n }\n };\n\n const handleSearchFocus = () => {\n setShowResults(true);\n };\n\n const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchTerm(e.target.value);\n setShowResults(true);\n };\n\n const navigation = [\n { name: t(\"home\"), href: \"/\" },\n { name: t(\"products\"), href: \"/products\" },\n { name: t(\"about\"), href: \"/about\" },\n { name: t(\"contact\"), href: \"/contact\" },\n ];\n\n return (\n <header className=\"sticky top-0 z-50 w-full border-b border-border/20 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-3 sm:px-4 lg:px-8\">\n <div className=\"flex h-14 sm:h-16 md:h-20 items-center justify-between gap-2\">\n {/* Logo */}\n <div className=\"flex-shrink-0 min-w-0\">\n <Logo size=\"sm\" className=\"text-base sm:text-xl lg:text-2xl\" />\n </div>\n\n {/* Desktop Navigation - Centered */}\n <nav className=\"hidden lg:flex items-center space-x-12 absolute left-1/2 transform -translate-x-1/2\">\n {navigation.map((item) => (\n <Link\n key={item.name}\n to={item.href}\n className=\"text-base font-medium transition-colors hover:text-primary relative group py-2\"\n >\n {item.name}\n <span className=\"absolute -bottom-1 left-0 w-0 h-0.5 bg-primary transition-all duration-300 group-hover:w-full\"></span>\n </Link>\n ))}\n </nav>\n\n {/* Search & Actions - Right Aligned */}\n <div className=\"flex items-center space-x-1 sm:space-x-2 lg:space-x-4 flex-shrink-0\">\n {/* Desktop Search - Modal */}\n <Dialog\n open={desktopSearchOpen}\n onOpenChange={setDesktopSearchOpen}\n >\n <DialogTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"hidden lg:flex h-10 w-10\"\n >\n <Search className=\"h-5 w-5\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-2xl\">\n <DialogHeader>\n <DialogTitle>\n {t(\"searchProducts\", \"Search Products\")}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4\">\n <form onSubmit={handleSearchSubmit}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-5 w-5\" />\n <Input\n type=\"search\"\n placeholder={t(\n \"searchPlaceholder\",\n \"Search for products...\"\n )}\n value={searchTerm}\n onChange={handleSearchChange}\n className=\"pl-11 h-12 text-base\"\n autoFocus\n />\n </div>\n </form>\n\n {/* Desktop Search Results */}\n {searchTerm.trim() && (\n <div className=\"max-h-[400px] overflow-y-auto rounded-lg border bg-card\">\n {searchResults.length > 0 ? (\n <div className=\"divide-y\">\n <div className=\"px-4 py-3 bg-muted/50\">\n <p className=\"text-sm font-medium text-muted-foreground\">\n {searchResults.length}{\" \"}\n {searchResults.length === 1\n ? \"result\"\n : \"results\"}{\" \"}\n found\n </p>\n </div>\n {searchResults.slice(0, 8).map((product: Product) => (\n <Link\n key={product.id}\n to={`/products/${product.slug}`}\n onClick={() => {\n setDesktopSearchOpen(false);\n clearSearch();\n }}\n className=\"flex items-center gap-4 p-4 hover:bg-muted/50 transition-colors\"\n >\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-16 h-16 object-cover rounded flex-shrink-0\"\n />\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-base line-clamp-1\">\n {product.name}\n </h4>\n <p className=\"text-sm text-muted-foreground capitalize\">\n {product.category}\n </p>\n <p className=\"text-base font-semibold text-primary mt-1\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </Link>\n ))}\n {searchResults.length > 8 && (\n <div className=\"px-4 py-3 bg-muted/30 text-center\">\n <button\n onClick={() => {\n navigate(\n `/products?search=${encodeURIComponent(\n searchTerm\n )}`\n );\n setDesktopSearchOpen(false);\n clearSearch();\n }}\n className=\"text-sm font-medium text-primary hover:underline\"\n >\n {t(\n \"viewAllResults\",\n `View all ${searchResults.length} results`\n )}\n </button>\n </div>\n )}\n </div>\n ) : (\n <div className=\"p-8 text-center\">\n <Search className=\"h-12 w-12 text-muted-foreground mx-auto mb-3 opacity-50\" />\n <p className=\"text-base text-muted-foreground\">\n {t(\"noResults\", \"No products found\")}\n </p>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {t(\n \"tryDifferentKeywords\",\n \"Try different keywords\"\n )}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n </DialogContent>\n </Dialog>\n\n {/* Search - Mobile (Hidden - moved to hamburger menu) */}\n <Dialog open={mobileSearchOpen} onOpenChange={setMobileSearchOpen}>\n <DialogTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"hidden\">\n <Search className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t(\"searchProducts\")}</DialogTitle>\n </DialogHeader>\n <form\n onSubmit={(e) => {\n e.preventDefault();\n if (searchTerm.trim()) {\n navigate(\n `/products?search=${encodeURIComponent(searchTerm)}`\n );\n setMobileSearchOpen(false);\n clearSearch();\n }\n }}\n className=\"space-y-4\"\n >\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4\" />\n <Input\n type=\"search\"\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n className=\"pl-10\"\n autoFocus\n />\n </div>\n <div className=\"flex gap-2\">\n <Button type=\"submit\" className=\"flex-1\">\n {t(\"searchButton\", \"Search\")}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n clearSearch();\n setMobileSearchOpen(false);\n }}\n >\n {t(\"cancel\", \"Cancel\")}\n </Button>\n </div>\n </form>\n\n {/* Mobile Search Results */}\n {searchTerm.trim() && (\n <div className=\"mt-4 max-h-64 overflow-y-auto\">\n {searchResults.length > 0 ? (\n <div className=\"space-y-2\">\n <p className=\"text-sm text-muted-foreground mb-2\">\n {searchResults.length} result\n {searchResults.length !== 1 ? \"s\" : \"\"} found\n </p>\n {searchResults.slice(0, 5).map((product: Product) => (\n <Link\n key={product.id}\n to={`/products/${product.slug}`}\n onClick={() => {\n setMobileSearchOpen(false);\n clearSearch();\n }}\n className=\"block p-2 rounded hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-10 h-10 object-cover rounded\"\n />\n <div className=\"flex-1\">\n <h4 className=\"font-medium text-sm\">\n {product.name}\n </h4>\n <p className=\"text-xs text-muted-foreground\">\n {product.category}\n </p>\n <p className=\"text-sm font-medium\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </div>\n </Link>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n {t(\"noResults\")}\n </p>\n )}\n </div>\n )}\n </DialogContent>\n </Dialog>\n\n {/* Wishlist - Desktop Only */}\n <Link to=\"/favorites\" className=\"hidden lg:block\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative h-10 w-10\"\n >\n <Heart className=\"h-5 w-5\" />\n {favoriteCount > 0 && (\n <Badge\n variant=\"destructive\"\n className=\"absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center p-0 text-[10px]\"\n >\n {favoriteCount}\n </Badge>\n )}\n </Button>\n </Link>\n\n {/* Cart - Desktop Only */}\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative h-10 w-10 hidden lg:flex\"\n asChild\n >\n <Link to=\"/cart\">\n <ShoppingCart className=\"h-5 w-5\" />\n {itemCount > 0 && (\n <Badge\n variant=\"destructive\"\n className=\"absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center p-0 text-[10px]\"\n >\n {itemCount}\n </Badge>\n )}\n </Link>\n </Button>\n\n {/* Auth - Desktop Only */}\n <div className=\"hidden lg:flex\">\n <AuthHeaderMenu variant=\"desktop\">\n <DropdownMenuItem asChild className=\"cursor-pointer\">\n <Link to=\"/orders\" className=\"flex items-center\">\n <Package className=\"mr-2 h-4 w-4\" />\n {t(\"myOrders\", \"My Orders\")}\n </Link>\n </DropdownMenuItem>\n </AuthHeaderMenu>\n </div>\n\n {/* Mobile Menu */}\n <Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>\n <SheetTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"lg:hidden h-8 w-8 sm:h-10 sm:w-10\"\n >\n <Menu className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n </Button>\n </SheetTrigger>\n <SheetContent side=\"right\" className=\"w-[300px] sm:w-[400px] px-6\">\n <SheetHeader>\n <SheetTitle>{t(\"menu\")}</SheetTitle>\n </SheetHeader>\n\n {/* Mobile Search in Hamburger */}\n <div className=\"mt-6 pb-4 border-b\">\n <form onSubmit={handleSearchSubmit}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4\" />\n <Input\n type=\"search\"\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={handleSearchChange}\n onFocus={handleSearchFocus}\n className=\"pl-10 h-11\"\n />\n </div>\n </form>\n\n {/* Search Results in Hamburger */}\n {showResults && searchTerm && (\n <div className=\"mt-3 max-h-[300px] overflow-y-auto rounded-lg border bg-card\">\n {searchResults.length > 0 ? (\n <div className=\"divide-y\">\n <div className=\"px-3 py-2 bg-muted/50\">\n <p className=\"text-xs font-medium text-muted-foreground\">\n {searchResults.length}{\" \"}\n {searchResults.length === 1\n ? \"result\"\n : \"results\"}\n </p>\n </div>\n {searchResults.slice(0, 5).map((product: Product) => (\n <Link\n key={product.id}\n to={`/products/${product.slug}`}\n onClick={() => {\n setMobileMenuOpen(false);\n clearSearch();\n setShowResults(false);\n }}\n className=\"flex items-center gap-3 p-3 hover:bg-muted/50 transition-colors\"\n >\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-14 h-14 object-cover rounded flex-shrink-0\"\n />\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-sm line-clamp-1\">\n {product.name}\n </h4>\n <p className=\"text-xs text-muted-foreground capitalize\">\n {product.category}\n </p>\n <p className=\"text-sm font-semibold text-primary mt-1\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </Link>\n ))}\n {searchResults.length > 5 && (\n <div className=\"px-3 py-2 bg-muted/30 text-center\">\n <button\n onClick={() => {\n navigate(\n `/products?search=${encodeURIComponent(\n searchTerm\n )}`\n );\n setMobileMenuOpen(false);\n clearSearch();\n setShowResults(false);\n }}\n className=\"text-xs font-medium text-primary hover:underline\"\n >\n {t(\n \"viewAllResults\",\n `View all ${searchResults.length} results`\n )}\n </button>\n </div>\n )}\n </div>\n ) : (\n <div className=\"p-6 text-center\">\n <Search className=\"h-8 w-8 text-muted-foreground mx-auto mb-2 opacity-50\" />\n <p className=\"text-sm text-muted-foreground\">\n {t(\"noResults\", \"No results found\")}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n\n <div className=\"flex flex-col space-y-4 mt-6\">\n {navigation.map((item) => (\n <Link\n key={item.name}\n to={item.href}\n className=\"text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n {item.name}\n </Link>\n ))}\n <div className=\"border-t pt-4 space-y-4\">\n <Link\n to=\"/favorites\"\n className=\"flex items-center justify-between text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <div className=\"flex items-center space-x-2\">\n <Heart className=\"h-5 w-5\" />\n <span>{t(\"favorites\")}</span>\n </div>\n <Badge variant=\"secondary\">{favoriteCount}</Badge>\n </Link>\n <Link\n to=\"/cart\"\n className=\"flex items-center justify-between w-full text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <div className=\"flex items-center space-x-2\">\n <ShoppingCart className=\"h-5 w-5\" />\n <span>{t(\"cart\")}</span>\n </div>\n <div className=\"flex flex-col items-end\">\n <Badge variant=\"secondary\">{itemCount}</Badge>\n <span className=\"text-xs text-muted-foreground\">\n {formatPrice(state.total, constants.site.currency)}\n </span>\n </div>\n </Link>\n\n {/* Auth - Mobile */}\n <AuthHeaderMenu\n variant=\"mobile\"\n onMenuClose={() => setMobileMenuOpen(false)}\n >\n <Link\n to=\"/orders\"\n className=\"flex items-center space-x-2 text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <Package className=\"h-5 w-5\" />\n <span>{t(\"myOrders\", \"My Orders\")}</span>\n </Link>\n </AuthHeaderMenu>\n </div>\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </div>\n </div>\n {/* Cart Drawer */}\n <CartDrawer\n open={isDrawerOpen}\n onOpenChange={setDrawerOpen}\n items={state.items.map((item) => ({\n id: item.id,\n name: item.product.name,\n href: `/products/${item.product.slug}`,\n price: item.product.on_sale && item.product.sale_price\n ? item.product.sale_price\n : item.product.price,\n quantity: item.quantity,\n image: item.product.images[0] || \"/images/placeholder.png\",\n imageAlt: item.product.name,\n }))}\n currency={constants.site.currency}\n onRemove={(id) => removeItem(id)}\n />\n </header>\n );\n}\n"
|
|
23
|
+
"content": "import { useState } from \"react\";\nimport { Link, useNavigate } from \"react-router\";\nimport { ShoppingCart, Menu, Search, Heart, Package, User, LogOut } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n Sheet,\n SheetHeader,\n SheetTitle,\n SheetContent,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Logo } from \"@/components/Logo\";\nimport { useAuth } from \"@/modules/auth-core\";\nimport { CartDrawer } from \"@/modules/cart-drawer\";\nimport { toast } from \"sonner\";\nimport { useTranslation } from \"react-i18next\";\nimport constants from \"@/constants/constants.json\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\nimport {\n useCart,\n useFavorites,\n useSearch,\n formatPrice,\n} from \"@/modules/ecommerce-core\";\n\nexport function HeaderEcommerce() {\n const [mobileMenuOpen, setMobileMenuOpen] = useState(false);\n const [mobileSearchOpen, setMobileSearchOpen] = useState(false);\n const [desktopSearchOpen, setDesktopSearchOpen] = useState(false);\n const [showResults, setShowResults] = useState(false);\n const { itemCount, state, removeItem, isDrawerOpen, setDrawerOpen } = useCart();\n const { favoriteCount } = useFavorites();\n const { isAuthenticated, user, logout } = useAuth();\n const navigate = useNavigate();\n const { t } = useTranslation(\"header-ecommerce\");\n\n const handleLogout = () => {\n logout();\n toast.success(t(\"logoutToastTitle\", \"Goodbye!\"), {\n description: t(\"logoutToastDesc\", \"You have been logged out successfully.\"),\n });\n };\n\n const {\n searchTerm,\n setSearchTerm,\n results: searchResults,\n clearSearch,\n } = useSearch();\n\n const handleSearchSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (searchTerm.trim()) {\n navigate(`/products?search=${encodeURIComponent(searchTerm)}`);\n setShowResults(false);\n setDesktopSearchOpen(false);\n clearSearch();\n }\n };\n\n const handleSearchFocus = () => {\n setShowResults(true);\n };\n\n const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchTerm(e.target.value);\n setShowResults(true);\n };\n\n const navigation = [\n { name: t(\"home\"), href: \"/\" },\n { name: t(\"products\"), href: \"/products\" },\n { name: t(\"about\"), href: \"/about\" },\n { name: t(\"contact\"), href: \"/contact\" },\n ];\n\n return (\n <header className=\"sticky top-0 z-50 w-full border-b border-border/20 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-3 sm:px-4 lg:px-8\">\n <div className=\"flex h-14 sm:h-16 md:h-20 items-center justify-between gap-2\">\n {/* Logo */}\n <div className=\"flex-shrink-0 min-w-0\">\n <Logo size=\"sm\" className=\"text-base sm:text-xl lg:text-2xl\" />\n </div>\n\n {/* Desktop Navigation - Centered */}\n <nav className=\"hidden lg:flex items-center space-x-12 absolute left-1/2 transform -translate-x-1/2\">\n {navigation.map((item) => (\n <Link\n key={item.name}\n to={item.href}\n className=\"text-base font-medium transition-colors hover:text-primary relative group py-2\"\n >\n {item.name}\n <span className=\"absolute -bottom-1 left-0 w-0 h-0.5 bg-primary transition-all duration-300 group-hover:w-full\"></span>\n </Link>\n ))}\n </nav>\n\n {/* Search & Actions - Right Aligned */}\n <div className=\"flex items-center space-x-1 sm:space-x-2 lg:space-x-4 flex-shrink-0\">\n {/* Desktop Search - Modal */}\n <Dialog\n open={desktopSearchOpen}\n onOpenChange={setDesktopSearchOpen}\n >\n <DialogTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"hidden lg:flex h-10 w-10\"\n >\n <Search className=\"h-5 w-5\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-2xl\">\n <DialogHeader>\n <DialogTitle>\n {t(\"searchProducts\", \"Search Products\")}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4\">\n <form onSubmit={handleSearchSubmit}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-5 w-5\" />\n <Input\n type=\"search\"\n placeholder={t(\n \"searchPlaceholder\",\n \"Search for products...\"\n )}\n value={searchTerm}\n onChange={handleSearchChange}\n className=\"pl-11 h-12 text-base\"\n autoFocus\n />\n </div>\n </form>\n\n {/* Desktop Search Results */}\n {searchTerm.trim() && (\n <div className=\"max-h-[400px] overflow-y-auto rounded-lg border bg-card\">\n {searchResults.length > 0 ? (\n <div className=\"divide-y\">\n <div className=\"px-4 py-3 bg-muted/50\">\n <p className=\"text-sm font-medium text-muted-foreground\">\n {searchResults.length}{\" \"}\n {searchResults.length === 1\n ? \"result\"\n : \"results\"}{\" \"}\n found\n </p>\n </div>\n {searchResults.slice(0, 8).map((product: Product) => (\n <Link\n key={product.id}\n to={`/products/${product.slug}`}\n onClick={() => {\n setDesktopSearchOpen(false);\n clearSearch();\n }}\n className=\"flex items-center gap-4 p-4 hover:bg-muted/50 transition-colors\"\n >\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-16 h-16 object-cover rounded flex-shrink-0\"\n />\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-base line-clamp-1\">\n {product.name}\n </h4>\n <p className=\"text-sm text-muted-foreground capitalize\">\n {product.category}\n </p>\n <p className=\"text-base font-semibold text-primary mt-1\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </Link>\n ))}\n {searchResults.length > 8 && (\n <div className=\"px-4 py-3 bg-muted/30 text-center\">\n <button\n onClick={() => {\n navigate(\n `/products?search=${encodeURIComponent(\n searchTerm\n )}`\n );\n setDesktopSearchOpen(false);\n clearSearch();\n }}\n className=\"text-sm font-medium text-primary hover:underline\"\n >\n {t(\n \"viewAllResults\",\n `View all ${searchResults.length} results`\n )}\n </button>\n </div>\n )}\n </div>\n ) : (\n <div className=\"p-8 text-center\">\n <Search className=\"h-12 w-12 text-muted-foreground mx-auto mb-3 opacity-50\" />\n <p className=\"text-base text-muted-foreground\">\n {t(\"noResults\", \"No products found\")}\n </p>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {t(\n \"tryDifferentKeywords\",\n \"Try different keywords\"\n )}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n </DialogContent>\n </Dialog>\n\n {/* Search - Mobile (Hidden - moved to hamburger menu) */}\n <Dialog open={mobileSearchOpen} onOpenChange={setMobileSearchOpen}>\n <DialogTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"hidden\">\n <Search className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t(\"searchProducts\")}</DialogTitle>\n </DialogHeader>\n <form\n onSubmit={(e) => {\n e.preventDefault();\n if (searchTerm.trim()) {\n navigate(\n `/products?search=${encodeURIComponent(searchTerm)}`\n );\n setMobileSearchOpen(false);\n clearSearch();\n }\n }}\n className=\"space-y-4\"\n >\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4\" />\n <Input\n type=\"search\"\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n className=\"pl-10\"\n autoFocus\n />\n </div>\n <div className=\"flex gap-2\">\n <Button type=\"submit\" className=\"flex-1\">\n {t(\"searchButton\", \"Search\")}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n clearSearch();\n setMobileSearchOpen(false);\n }}\n >\n {t(\"cancel\", \"Cancel\")}\n </Button>\n </div>\n </form>\n\n {/* Mobile Search Results */}\n {searchTerm.trim() && (\n <div className=\"mt-4 max-h-64 overflow-y-auto\">\n {searchResults.length > 0 ? (\n <div className=\"space-y-2\">\n <p className=\"text-sm text-muted-foreground mb-2\">\n {searchResults.length} result\n {searchResults.length !== 1 ? \"s\" : \"\"} found\n </p>\n {searchResults.slice(0, 5).map((product: Product) => (\n <Link\n key={product.id}\n to={`/products/${product.slug}`}\n onClick={() => {\n setMobileSearchOpen(false);\n clearSearch();\n }}\n className=\"block p-2 rounded hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-10 h-10 object-cover rounded\"\n />\n <div className=\"flex-1\">\n <h4 className=\"font-medium text-sm\">\n {product.name}\n </h4>\n <p className=\"text-xs text-muted-foreground\">\n {product.category}\n </p>\n <p className=\"text-sm font-medium\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </div>\n </Link>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n {t(\"noResults\")}\n </p>\n )}\n </div>\n )}\n </DialogContent>\n </Dialog>\n\n {/* Wishlist - Desktop Only */}\n <Link to=\"/favorites\" className=\"hidden lg:block\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative h-10 w-10\"\n >\n <Heart className=\"h-5 w-5\" />\n {favoriteCount > 0 && (\n <Badge\n variant=\"destructive\"\n className=\"absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center p-0 text-[10px]\"\n >\n {favoriteCount}\n </Badge>\n )}\n </Button>\n </Link>\n\n {/* Cart - Desktop Only */}\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative h-10 w-10 hidden lg:flex\"\n asChild\n >\n <Link to=\"/cart\">\n <ShoppingCart className=\"h-5 w-5\" />\n {itemCount > 0 && (\n <Badge\n variant=\"destructive\"\n className=\"absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center p-0 text-[10px]\"\n >\n {itemCount}\n </Badge>\n )}\n </Link>\n </Button>\n\n {/* Auth - Desktop Only */}\n <div className=\"hidden lg:flex\">\n {isAuthenticated ? (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"h-10 w-10\">\n <User className=\"h-5 w-5\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-56\">\n <DropdownMenuLabel className=\"font-normal\">\n <div className=\"flex flex-col space-y-1\">\n <p className=\"text-sm font-medium\">{user?.username}</p>\n {user?.email && (\n <p className=\"text-xs text-muted-foreground\">{user.email}</p>\n )}\n </div>\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuItem asChild className=\"cursor-pointer\">\n <Link to=\"/my-orders\" className=\"flex items-center\">\n <Package className=\"mr-2 h-4 w-4\" />\n {t(\"myOrders\", \"My Orders\")}\n </Link>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={handleLogout}\n className=\"text-red-600 focus:text-red-600 focus:bg-red-50 cursor-pointer\"\n >\n <LogOut className=\"mr-2 h-4 w-4\" />\n {t(\"logout\", \"Logout\")}\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n ) : (\n <Link to=\"/login\">\n <Button variant=\"ghost\" size=\"icon\" className=\"h-10 w-10\">\n <User className=\"h-5 w-5\" />\n </Button>\n </Link>\n )}\n </div>\n\n {/* Mobile Menu */}\n <Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>\n <SheetTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"lg:hidden h-8 w-8 sm:h-10 sm:w-10\"\n >\n <Menu className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n </Button>\n </SheetTrigger>\n <SheetContent side=\"right\" className=\"w-[300px] sm:w-[400px] px-6\">\n <SheetHeader>\n <SheetTitle>{t(\"menu\")}</SheetTitle>\n </SheetHeader>\n\n {/* Mobile Search in Hamburger */}\n <div className=\"mt-6 pb-4 border-b\">\n <form onSubmit={handleSearchSubmit}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4\" />\n <Input\n type=\"search\"\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={handleSearchChange}\n onFocus={handleSearchFocus}\n className=\"pl-10 h-11\"\n />\n </div>\n </form>\n\n {/* Search Results in Hamburger */}\n {showResults && searchTerm && (\n <div className=\"mt-3 max-h-[300px] overflow-y-auto rounded-lg border bg-card\">\n {searchResults.length > 0 ? (\n <div className=\"divide-y\">\n <div className=\"px-3 py-2 bg-muted/50\">\n <p className=\"text-xs font-medium text-muted-foreground\">\n {searchResults.length}{\" \"}\n {searchResults.length === 1\n ? \"result\"\n : \"results\"}\n </p>\n </div>\n {searchResults.slice(0, 5).map((product: Product) => (\n <Link\n key={product.id}\n to={`/products/${product.slug}`}\n onClick={() => {\n setMobileMenuOpen(false);\n clearSearch();\n setShowResults(false);\n }}\n className=\"flex items-center gap-3 p-3 hover:bg-muted/50 transition-colors\"\n >\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-14 h-14 object-cover rounded flex-shrink-0\"\n />\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-sm line-clamp-1\">\n {product.name}\n </h4>\n <p className=\"text-xs text-muted-foreground capitalize\">\n {product.category}\n </p>\n <p className=\"text-sm font-semibold text-primary mt-1\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </Link>\n ))}\n {searchResults.length > 5 && (\n <div className=\"px-3 py-2 bg-muted/30 text-center\">\n <button\n onClick={() => {\n navigate(\n `/products?search=${encodeURIComponent(\n searchTerm\n )}`\n );\n setMobileMenuOpen(false);\n clearSearch();\n setShowResults(false);\n }}\n className=\"text-xs font-medium text-primary hover:underline\"\n >\n {t(\n \"viewAllResults\",\n `View all ${searchResults.length} results`\n )}\n </button>\n </div>\n )}\n </div>\n ) : (\n <div className=\"p-6 text-center\">\n <Search className=\"h-8 w-8 text-muted-foreground mx-auto mb-2 opacity-50\" />\n <p className=\"text-sm text-muted-foreground\">\n {t(\"noResults\", \"No results found\")}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n\n <div className=\"flex flex-col space-y-4 mt-6\">\n {navigation.map((item) => (\n <Link\n key={item.name}\n to={item.href}\n className=\"text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n {item.name}\n </Link>\n ))}\n <div className=\"border-t pt-4 space-y-4\">\n <Link\n to=\"/favorites\"\n className=\"flex items-center justify-between text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <div className=\"flex items-center space-x-2\">\n <Heart className=\"h-5 w-5\" />\n <span>{t(\"favorites\")}</span>\n </div>\n <Badge variant=\"secondary\">{favoriteCount}</Badge>\n </Link>\n <Link\n to=\"/cart\"\n className=\"flex items-center justify-between w-full text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <div className=\"flex items-center space-x-2\">\n <ShoppingCart className=\"h-5 w-5\" />\n <span>{t(\"cart\")}</span>\n </div>\n <div className=\"flex flex-col items-end\">\n <Badge variant=\"secondary\">{itemCount}</Badge>\n <span className=\"text-xs text-muted-foreground\">\n {formatPrice(state.total, constants.site.currency)}\n </span>\n </div>\n </Link>\n\n {/* Auth - Mobile */}\n {isAuthenticated ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-center space-x-3 p-3 bg-muted/50 rounded-lg\">\n <div className=\"h-10 w-10 rounded-full bg-primary/10 flex items-center justify-center\">\n <User className=\"h-5 w-5 text-primary\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium truncate\">{user?.username}</p>\n {user?.email && (\n <p className=\"text-xs text-muted-foreground truncate\">{user.email}</p>\n )}\n </div>\n </div>\n <Link\n to=\"/my-orders\"\n className=\"flex items-center space-x-2 text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <Package className=\"h-5 w-5\" />\n <span>{t(\"myOrders\", \"My Orders\")}</span>\n </Link>\n <button\n onClick={() => {\n handleLogout();\n setMobileMenuOpen(false);\n }}\n className=\"flex items-center space-x-2 text-lg font-medium text-red-600 hover:text-red-700 transition-colors w-full\"\n >\n <LogOut className=\"h-5 w-5\" />\n <span>{t(\"logout\", \"Logout\")}</span>\n </button>\n </div>\n ) : (\n <Link\n to=\"/login\"\n className=\"flex items-center space-x-2 text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <User className=\"h-5 w-5\" />\n <span>{t(\"login\", \"Login\")}</span>\n </Link>\n )}\n </div>\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </div>\n </div>\n {/* Cart Drawer */}\n <CartDrawer\n open={isDrawerOpen}\n onOpenChange={setDrawerOpen}\n items={state.items.map((item) => ({\n id: item.id,\n name: item.product.name,\n href: `/products/${item.product.slug}`,\n price: item.product.on_sale && item.product.sale_price\n ? item.product.sale_price\n : item.product.price,\n quantity: item.quantity,\n image: item.product.images[0] || \"/images/placeholder.png\",\n imageAlt: item.product.name,\n }))}\n currency={constants.site.currency}\n onRemove={(id) => removeItem(id)}\n />\n </header>\n );\n}\n"
|
|
23
24
|
},
|
|
24
25
|
{
|
|
25
26
|
"path": "header-ecommerce/lang/en.json",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"path": "header-mega/lang/en.json",
|
|
30
30
|
"type": "registry:lang",
|
|
31
31
|
"target": "$modules$/header-mega/lang/en.json",
|
|
32
|
-
"content": "{\r\n \"home\": \"Home\",\r\n \"products\": \"Products\",\r\n \"allProducts\": \"All Products\",\r\n \"allProductsDesc\": \"
|
|
32
|
+
"content": "{\r\n \"home\": \"Home\",\r\n \"products\": \"Products\",\r\n \"allProducts\": \"All Products\",\r\n \"allProductsDesc\": \"Ask Promake to customize this menu description based on your product catalog\",\r\n \"featured\": \"Featured\",\r\n \"featuredDesc\": \"Replace this text with your featured products description\",\r\n \"newArrivals\": \"New Arrivals\",\r\n \"newArrivalsDesc\": \"This description will be customized by Promake for your new products\",\r\n \"onSale\": \"On Sale\",\r\n \"onSaleDesc\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit\",\r\n \"company\": \"About\",\r\n \"aboutUs\": \"About Us\",\r\n \"aboutUsDesc\": \"Ask Promake to replace this with your story and purpose\",\r\n \"contact\": \"Contact\",\r\n \"contactDesc\": \"Customize this contact menu description\",\r\n \"blog\": \"Blog\",\r\n \"blogDesc\": \"This text will be replaced with your blog description\",\r\n \"login\": \"Login\",\r\n \"signup\": \"Sign up\"\r\n}\r\n"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"path": "header-mega/lang/tr.json",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hero-carousel",
|
|
3
|
+
"type": "registry:component",
|
|
4
|
+
"title": "Hero Carousel",
|
|
5
|
+
"description": "Full-width hero carousel with auto-play and touch support. Features navigation dots, arrows, and configurable interval.",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"lucide-react"
|
|
8
|
+
],
|
|
9
|
+
"registryDependencies": [
|
|
10
|
+
"button"
|
|
11
|
+
],
|
|
12
|
+
"usage": "import { HeroCarousel } from '@/modules/hero-carousel';\n\n<HeroCarousel />\n\n• Installed at: src/modules/hero-carousel/\n• Customize content: lang/en/hero-carousel.json\n• Props: slides[], autoPlay, interval, showDots, showArrows, pauseOnHover",
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "hero-carousel/hero-carousel.tsx",
|
|
16
|
+
"type": "registry:component",
|
|
17
|
+
"target": "$modules$/hero-carousel/hero-carousel.tsx",
|
|
18
|
+
"content": "\"use client\";\n\nimport { useState, useEffect, useCallback, useRef } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { Link } from \"react-router\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\n\ninterface Slide {\n image: string;\n title: string;\n description: string;\n primaryButton?: {\n text: string;\n link: string;\n };\n secondaryButton?: {\n text: string;\n link: string;\n };\n}\n\ninterface HeroCarouselProps {\n slides?: Slide[];\n autoPlay?: boolean;\n interval?: number;\n showDots?: boolean;\n showArrows?: boolean;\n pauseOnHover?: boolean;\n className?: string;\n}\n\nexport function HeroCarousel({\n slides,\n autoPlay = true,\n interval = 5000,\n showDots = true,\n showArrows = true,\n pauseOnHover = true,\n className,\n}: HeroCarouselProps) {\n const { t } = useTranslation(\"hero-carousel\");\n const [currentSlide, setCurrentSlide] = useState(0);\n const [isPaused, setIsPaused] = useState(false);\n const [dragStart, setDragStart] = useState<number | null>(null);\n const [isDragging, setIsDragging] = useState(false);\n const containerRef = useRef<HTMLElement>(null);\n\n const defaultSlides: Slide[] = [\n {\n image: \"/images/placeholder.png\",\n title: t(\"slides.0.title\"),\n description: t(\"slides.0.description\"),\n primaryButton: { text: t(\"slides.0.primaryButton\"), link: \"/get-started\" },\n secondaryButton: { text: t(\"slides.0.secondaryButton\"), link: \"/learn-more\" },\n },\n {\n image: \"/images/placeholder.png\",\n title: t(\"slides.1.title\"),\n description: t(\"slides.1.description\"),\n primaryButton: { text: t(\"slides.1.primaryButton\"), link: \"/features\" },\n },\n {\n image: \"/images/placeholder.png\",\n title: t(\"slides.2.title\"),\n description: t(\"slides.2.description\"),\n primaryButton: { text: t(\"slides.2.primaryButton\"), link: \"/contact\" },\n secondaryButton: { text: t(\"slides.2.secondaryButton\"), link: \"/demo\" },\n },\n ];\n\n const displaySlides = slides ?? defaultSlides;\n\n const goToSlide = useCallback((index: number) => {\n setCurrentSlide(index);\n }, []);\n\n const nextSlide = useCallback(() => {\n setCurrentSlide((prev) => (prev + 1) % displaySlides.length);\n }, [displaySlides.length]);\n\n const prevSlide = useCallback(() => {\n setCurrentSlide((prev) => (prev - 1 + displaySlides.length) % displaySlides.length);\n }, [displaySlides.length]);\n\n // Auto-play\n useEffect(() => {\n if (!autoPlay || isPaused) return;\n\n const timer = setInterval(nextSlide, interval);\n return () => clearInterval(timer);\n }, [autoPlay, interval, isPaused, nextSlide]);\n\n // Mouse drag handlers\n const handleMouseDown = (e: React.MouseEvent) => {\n setIsDragging(true);\n setDragStart(e.clientX);\n };\n\n const handleMouseMove = (e: React.MouseEvent) => {\n if (!isDragging || dragStart === null) return;\n e.preventDefault();\n };\n\n const handleMouseUp = (e: React.MouseEvent) => {\n if (!isDragging || dragStart === null) {\n setIsDragging(false);\n return;\n }\n\n const diff = dragStart - e.clientX;\n\n if (Math.abs(diff) > 50) {\n if (diff > 0) {\n nextSlide();\n } else {\n prevSlide();\n }\n }\n\n setIsDragging(false);\n setDragStart(null);\n };\n\n const handleMouseLeave = () => {\n if (isDragging) {\n setIsDragging(false);\n setDragStart(null);\n }\n };\n\n // Touch handlers for swipe\n const handleTouchStart = (e: React.TouchEvent) => {\n setDragStart(e.touches[0].clientX);\n };\n\n const handleTouchEnd = (e: React.TouchEvent) => {\n if (dragStart === null) return;\n\n const touchEnd = e.changedTouches[0].clientX;\n const diff = dragStart - touchEnd;\n\n if (Math.abs(diff) > 50) {\n if (diff > 0) {\n nextSlide();\n } else {\n prevSlide();\n }\n }\n\n setDragStart(null);\n };\n\n // Wheel handler for trackpad two-finger swipe\n const handleWheel = useCallback((e: WheelEvent) => {\n // Only handle horizontal scroll (trackpad two-finger swipe)\n if (Math.abs(e.deltaX) > Math.abs(e.deltaY) && Math.abs(e.deltaX) > 30) {\n e.preventDefault();\n if (e.deltaX > 0) {\n nextSlide();\n } else {\n prevSlide();\n }\n }\n }, [nextSlide, prevSlide]);\n\n // Add wheel event listener\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n let lastWheelTime = 0;\n const throttledWheel = (e: WheelEvent) => {\n const now = Date.now();\n if (now - lastWheelTime > 500) {\n handleWheel(e);\n lastWheelTime = now;\n }\n };\n\n container.addEventListener(\"wheel\", throttledWheel, { passive: false });\n return () => container.removeEventListener(\"wheel\", throttledWheel);\n }, [handleWheel]);\n\n return (\n <section\n ref={containerRef}\n className={cn(\n \"relative w-full h-[500px] md:h-[600px] lg:h-[700px] overflow-hidden select-none\",\n isDragging ? \"cursor-grabbing\" : \"cursor-grab\",\n className\n )}\n onMouseEnter={() => pauseOnHover && setIsPaused(true)}\n onMouseLeave={() => {\n pauseOnHover && setIsPaused(false);\n handleMouseLeave();\n }}\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleMouseUp}\n onTouchStart={handleTouchStart}\n onTouchEnd={handleTouchEnd}\n >\n {/* Slides */}\n {displaySlides.map((slide, index) => (\n <div\n key={index}\n className={cn(\n \"absolute inset-0 transition-opacity duration-700\",\n index === currentSlide ? \"opacity-100 z-10\" : \"opacity-0 z-0\"\n )}\n >\n {/* Background Image */}\n <img\n src={slide.image}\n alt={slide.title}\n className=\"absolute inset-0 w-full h-full object-cover pointer-events-none\"\n draggable={false}\n />\n\n {/* Overlay */}\n <div className=\"absolute inset-0 bg-black/50\" />\n\n {/* Content */}\n <div className=\"relative z-10 h-full flex items-center\">\n <div className=\"container max-w-[var(--container-max-width)] mx-auto px-6 md:px-24 lg:px-32\">\n <div\n className={cn(\n \"max-w-2xl transition-all duration-700 delay-200\",\n index === currentSlide\n ? \"translate-y-0 opacity-100\"\n : \"translate-y-8 opacity-0\"\n )}\n >\n <h1 className=\"text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-4 md:mb-6\">\n {slide.title}\n </h1>\n <p className=\"text-lg md:text-xl text-white/90 mb-6 md:mb-8\">\n {slide.description}\n </p>\n <div className=\"flex flex-wrap gap-4\">\n {slide.primaryButton && (\n <Link to={slide.primaryButton.link}>\n <Button size=\"lg\" className=\"text-base\">\n {slide.primaryButton.text}\n </Button>\n </Link>\n )}\n {slide.secondaryButton && (\n <Link to={slide.secondaryButton.link}>\n <Button size=\"lg\" variant=\"outline\" className=\"text-base bg-white/10 border-white/30 text-white hover:bg-white/20\">\n {slide.secondaryButton.text}\n </Button>\n </Link>\n )}\n </div>\n </div>\n </div>\n </div>\n </div>\n ))}\n\n {/* Navigation Arrows */}\n {showArrows && (\n <>\n <button\n onClick={prevSlide}\n className=\"absolute left-4 top-1/2 -translate-y-1/2 z-20 w-12 h-12 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center text-white hover:bg-white/30 transition-colors\"\n aria-label=\"Previous slide\"\n >\n <ChevronLeft className=\"w-6 h-6\" />\n </button>\n <button\n onClick={nextSlide}\n className=\"absolute right-4 top-1/2 -translate-y-1/2 z-20 w-12 h-12 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center text-white hover:bg-white/30 transition-colors\"\n aria-label=\"Next slide\"\n >\n <ChevronRight className=\"w-6 h-6\" />\n </button>\n </>\n )}\n\n {/* Dots */}\n {showDots && (\n <div className=\"absolute bottom-6 left-1/2 -translate-x-1/2 z-20 flex gap-2\">\n {displaySlides.map((_, index) => (\n <button\n key={index}\n onClick={() => goToSlide(index)}\n className={cn(\n \"w-3 h-3 rounded-full transition-all duration-300\",\n index === currentSlide\n ? \"bg-white w-8\"\n : \"bg-white/50 hover:bg-white/70\"\n )}\n aria-label={`Go to slide ${index + 1}`}\n />\n ))}\n </div>\n )}\n </section>\n );\n}\n"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"path": "hero-carousel/index.ts",
|
|
22
|
+
"type": "registry:index",
|
|
23
|
+
"target": "$modules$/hero-carousel/index.ts",
|
|
24
|
+
"content": "export * from \"./hero-carousel\";\r\n"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"path": "hero-carousel/lang/en.json",
|
|
28
|
+
"type": "registry:lang",
|
|
29
|
+
"target": "$modules$/hero-carousel/lang/en.json",
|
|
30
|
+
"content": "{\r\n \"slides\": [\r\n {\r\n \"title\": \"Build Something Amazing\",\r\n \"description\": \"Ask Promake to customize these slides based on your brand message and call-to-action.\",\r\n \"primaryButton\": \"Get Started\",\r\n \"secondaryButton\": \"Learn More\"\r\n },\r\n {\r\n \"title\": \"Powerful Features\",\r\n \"description\": \"Discover tools and features that help you achieve more with less effort.\",\r\n \"primaryButton\": \"Explore Features\"\r\n },\r\n {\r\n \"title\": \"Ready to Transform?\",\r\n \"description\": \"Join thousands of satisfied customers who have already made the switch.\",\r\n \"primaryButton\": \"Contact Us\",\r\n \"secondaryButton\": \"See Demo\"\r\n }\r\n ]\r\n}\r\n"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"path": "hero-carousel/lang/tr.json",
|
|
34
|
+
"type": "registry:lang",
|
|
35
|
+
"target": "$modules$/hero-carousel/lang/tr.json",
|
|
36
|
+
"content": "{\r\n \"slides\": [\r\n {\r\n \"title\": \"Harika Bir Şey İnşa Edin\",\r\n \"description\": \"Bu slaytları marka mesajınıza ve eylem çağrınıza göre özelleştirmek için Promake'e sorun.\",\r\n \"primaryButton\": \"Başlayın\",\r\n \"secondaryButton\": \"Daha Fazla Bilgi\"\r\n },\r\n {\r\n \"title\": \"Güçlü Özellikler\",\r\n \"description\": \"Daha az çabayla daha fazlasını başarmanıza yardımcı olan araçları ve özellikleri keşfedin.\",\r\n \"primaryButton\": \"Özellikleri Keşfet\"\r\n },\r\n {\r\n \"title\": \"Dönüşüme Hazır mısınız?\",\r\n \"description\": \"Geçiş yapan binlerce memnun müşteriye katılın.\",\r\n \"primaryButton\": \"Bize Ulaşın\",\r\n \"secondaryButton\": \"Demo İzle\"\r\n }\r\n ]\r\n}\r\n"
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"exports": {
|
|
40
|
+
"types": [],
|
|
41
|
+
"variables": [
|
|
42
|
+
"HeroCarousel"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
"path": "hero-cta/hero-cta.tsx",
|
|
19
19
|
"type": "registry:component",
|
|
20
20
|
"target": "$modules$/hero-cta/hero-cta.tsx",
|
|
21
|
-
"content": "import { Link } from \"react-router\";\r\nimport { ArrowRight, Star } from \"lucide-react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { Avatar, AvatarImage, AvatarFallback } from \"@/components/ui/avatar\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface HeroCtaProps {\r\n className?: string;\r\n}\r\n\r\nexport function HeroCta({ className }: HeroCtaProps) {\r\n const { t } = useTranslation(\"hero-cta\");\r\n\r\n const avatars = [\r\n { src: \"/images/placeholder.png\", alt: \"User 1\", fallback: \"U1\" },\r\n { src: \"/images/placeholder.png\", alt: \"User 2\", fallback: \"U2\" },\r\n { src: \"/images/placeholder.png\", alt: \"User 3\", fallback: \"U3\" },\r\n { src: \"/images/placeholder.png\", alt: \"User 4\", fallback: \"U4\" },\r\n { src: \"/images/placeholder.png\", alt: \"User 5\", fallback: \"U5\" },\r\n ];\r\n\r\n return (\r\n <section className={cn(\"py-
|
|
21
|
+
"content": "import { Link } from \"react-router\";\r\nimport { ArrowRight, Star } from \"lucide-react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { Avatar, AvatarImage, AvatarFallback } from \"@/components/ui/avatar\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface HeroCtaProps {\r\n className?: string;\r\n}\r\n\r\nexport function HeroCta({ className }: HeroCtaProps) {\r\n const { t } = useTranslation(\"hero-cta\");\r\n\r\n const avatars = [\r\n { src: \"/images/placeholder.png\", alt: \"User 1\", fallback: \"U1\" },\r\n { src: \"/images/placeholder.png\", alt: \"User 2\", fallback: \"U2\" },\r\n { src: \"/images/placeholder.png\", alt: \"User 3\", fallback: \"U3\" },\r\n { src: \"/images/placeholder.png\", alt: \"User 4\", fallback: \"U4\" },\r\n { src: \"/images/placeholder.png\", alt: \"User 5\", fallback: \"U5\" },\r\n ];\r\n\r\n return (\r\n <section className={cn(\"py-8 md:py-12 lg:py-26\", className)}>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"grid items-center gap-10 lg:grid-cols-2 lg:gap-16\">\r\n {/* Content Column */}\r\n <div className=\"flex flex-col items-center text-center lg:items-start lg:text-left\">\r\n <h1 className=\"mb-6 text-2xl font-bold text-pretty sm:text-3xl lg:text-4xl xl:text-5xl leading-tight\">\r\n {t(\"heading\")}\r\n </h1>\r\n <p className=\"mb-8 max-w-xl text-muted-foreground text-base lg:text-lg\">\r\n {t(\"description\")}\r\n </p>\r\n\r\n {/* Social Proof */}\r\n <div className=\"mb-10 flex flex-col items-center gap-4 sm:flex-row lg:items-start\">\r\n <div className=\"flex -space-x-3\">\r\n {avatars.map((avatar, index) => (\r\n <Avatar key={index} className=\"h-10 w-10 border-2 border-background\">\r\n <AvatarImage src={avatar.src} alt={avatar.alt} />\r\n <AvatarFallback className=\"text-xs\">{avatar.fallback}</AvatarFallback>\r\n </Avatar>\r\n ))}\r\n </div>\r\n <div>\r\n <div className=\"flex items-center gap-1\">\r\n {[...Array(5)].map((_, index) => (\r\n <Star\r\n key={index}\r\n className=\"h-4 w-4 fill-yellow-400 text-yellow-400\"\r\n />\r\n ))}\r\n <span className=\"ml-1 font-semibold text-sm\">5.0</span>\r\n </div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"reviews\")}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n {/* CTA Buttons */}\r\n <div className=\"flex w-full flex-col gap-3 sm:flex-row sm:w-auto\">\r\n <Button asChild size=\"lg\" className=\"w-full sm:w-auto\">\r\n <Link to=\"/register\">\r\n {t(\"primaryCta\")}\r\n </Link>\r\n </Button>\r\n <Button asChild variant=\"outline\" size=\"lg\" className=\"w-full sm:w-auto\">\r\n <Link to=\"/about\">\r\n {t(\"secondaryCta\")}\r\n <ArrowRight className=\"ml-2 h-4 w-4\" />\r\n </Link>\r\n </Button>\r\n </div>\r\n </div>\r\n\r\n {/* Image Column */}\r\n <div className=\"relative order-first lg:order-last\">\r\n <div className=\"aspect-[4/3] overflow-hidden rounded-2xl bg-muted\">\r\n <img\r\n src=\"/images/placeholder.png\"\r\n alt={t(\"imageAlt\")}\r\n className=\"h-full w-full object-cover\"\r\n onError={(e) => {\r\n e.currentTarget.style.display = \"none\";\r\n }}\r\n />\r\n </div>\r\n {/* Decorative gradient blur */}\r\n <div className=\"absolute -z-10 -bottom-4 -right-4 h-72 w-72 rounded-full bg-primary/20 blur-3xl\" />\r\n </div>\r\n </div>\r\n </div>\r\n </section>\r\n );\r\n}\r\n"
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"path": "hero-cta/lang/en.json",
|
|
25
25
|
"type": "registry:lang",
|
|
26
26
|
"target": "$modules$/hero-cta/lang/en.json",
|
|
27
|
-
"content": "{\r\n \"heading\": \"
|
|
27
|
+
"content": "{\r\n \"heading\": \"Ask Promake to customize this hero heading based on your site\",\r\n \"description\": \"This is where your hero section description will appear. Ask Promake to replace this text with content relevant to your industry and services. Lorem ipsum dolor sit amet.\",\r\n \"reviews\": \"Ask Promake to customize this review count\",\r\n \"primaryCta\": \"Get Started\",\r\n \"secondaryCta\": \"Learn More\",\r\n \"imageAlt\": \"Hero image\"\r\n}\r\n"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "hero-cta/lang/tr.json",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"path": "hero-gradient/lang/en.json",
|
|
23
23
|
"type": "registry:lang",
|
|
24
24
|
"target": "$modules$/hero-gradient/lang/en.json",
|
|
25
|
-
"content": "{\r\n \"badge\": \"
|
|
25
|
+
"content": "{\r\n \"badge\": \"Ask Promake to customize this badge text\",\r\n \"headingLine1\": \"Ask Promake to replace this heading\",\r\n \"headingLine2\": \"with your site headline\",\r\n \"description\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ask Promake to customize this description based on your site and audience. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"primaryCta\": \"Get Started\",\r\n \"secondaryCta\": \"Contact Sales\",\r\n \"users\": \"Active Users\",\r\n \"uptime\": \"Uptime\",\r\n \"support\": \"Support\"\r\n}\r\n"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"path": "hero-gradient/lang/tr.json",
|
package/dist/registry/hero.json
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"path": "hero/lang/en.json",
|
|
23
23
|
"type": "registry:lang",
|
|
24
24
|
"target": "$modules$/hero/lang/en.json",
|
|
25
|
-
"content": "{\r\n \"discover\": \"Discover\",\r\n \"amazing\": \"Inspiring\",\r\n \"content\": \"Stories\",\r\n \"subtitle\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
|
25
|
+
"content": "{\r\n \"discover\": \"Discover\",\r\n \"amazing\": \"Inspiring\",\r\n \"content\": \"Stories\",\r\n \"subtitle\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ask Promake to replace this content with your actual blog description.\",\r\n \"readLatest\": \"Start Reading\",\r\n \"exploreTopics\": \"Explore Topics\"\r\n}\r\n"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"path": "hero/lang/tr.json",
|
package/dist/registry/index.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
[
|
|
2
2
|
"about-page",
|
|
3
3
|
"about-section",
|
|
4
|
+
"announcement-bar",
|
|
4
5
|
"api",
|
|
5
|
-
"auth",
|
|
6
|
+
"auth-core",
|
|
6
7
|
"animations",
|
|
7
8
|
"bento-grid-section",
|
|
8
9
|
"blog-core",
|
|
@@ -11,8 +12,11 @@
|
|
|
11
12
|
"cards-carousel-section",
|
|
12
13
|
"cart-drawer",
|
|
13
14
|
"cart-page",
|
|
15
|
+
"case-study-page",
|
|
14
16
|
"category-section",
|
|
15
17
|
"checkout-page",
|
|
18
|
+
"coming-soon-page",
|
|
19
|
+
"coming-soon-page-minimal",
|
|
16
20
|
"contact-info-grid",
|
|
17
21
|
"contact-page",
|
|
18
22
|
"contact-page-centered",
|
|
@@ -20,6 +24,7 @@
|
|
|
20
24
|
"contact-page-map-split",
|
|
21
25
|
"contact-page-split",
|
|
22
26
|
"content-section",
|
|
27
|
+
"cookie-consent",
|
|
23
28
|
"cookies-page",
|
|
24
29
|
"cta-section",
|
|
25
30
|
"db",
|
|
@@ -34,6 +39,7 @@
|
|
|
34
39
|
"footer",
|
|
35
40
|
"footer-detailed",
|
|
36
41
|
"footer-minimal",
|
|
42
|
+
"forgot-password-page",
|
|
37
43
|
"google-map",
|
|
38
44
|
"header-centered-pill",
|
|
39
45
|
"header-ecommerce",
|
|
@@ -41,20 +47,27 @@
|
|
|
41
47
|
"header-minimal",
|
|
42
48
|
"header-simple",
|
|
43
49
|
"hero",
|
|
50
|
+
"hero-carousel",
|
|
44
51
|
"hero-cta",
|
|
45
52
|
"hero-gradient",
|
|
46
53
|
"hero-grid",
|
|
47
54
|
"hero-profile",
|
|
55
|
+
"landing-page-app",
|
|
56
|
+
"landing-page-saas",
|
|
48
57
|
"login-page",
|
|
49
58
|
"login-page-split",
|
|
59
|
+
"logo-cloud",
|
|
60
|
+
"masonry-grid",
|
|
50
61
|
"newsletter-section",
|
|
51
62
|
"order-card-compact",
|
|
52
63
|
"order-detail-block",
|
|
53
64
|
"orders-list-block",
|
|
54
65
|
"payment-success-block",
|
|
66
|
+
"portfolio-page",
|
|
55
67
|
"post-card",
|
|
56
68
|
"post-detail-block",
|
|
57
69
|
"pricing-card",
|
|
70
|
+
"pricing-page",
|
|
58
71
|
"pricing-section",
|
|
59
72
|
"privacy-page",
|
|
60
73
|
"product-card",
|
|
@@ -62,12 +75,19 @@
|
|
|
62
75
|
"product-card-hover",
|
|
63
76
|
"product-detail-block",
|
|
64
77
|
"product-detail-section",
|
|
78
|
+
"product-quick-view",
|
|
65
79
|
"products-page",
|
|
80
|
+
"reading-progress",
|
|
81
|
+
"register-page",
|
|
66
82
|
"related-posts-block",
|
|
67
83
|
"related-products-block",
|
|
68
84
|
"service-card",
|
|
85
|
+
"share-buttons",
|
|
69
86
|
"skill-card",
|
|
87
|
+
"team-page",
|
|
70
88
|
"terms-page",
|
|
71
89
|
"testimonials-carousel",
|
|
72
|
-
"testimonials-grid"
|
|
90
|
+
"testimonials-grid",
|
|
91
|
+
"timeline-section",
|
|
92
|
+
"video-hero"
|
|
73
93
|
]
|