@promakeai/cli 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +412 -201
- package/dist/registry/about-page.json +2 -2
- package/dist/registry/about-section.json +2 -2
- package/dist/registry/announcement-bar.json +2 -2
- package/dist/registry/blog-list-page.json +1 -1
- package/dist/registry/blog-section.json +6 -4
- package/dist/registry/cards-carousel-section.json +1 -1
- package/dist/registry/cart-drawer.json +5 -4
- package/dist/registry/case-study-page.json +2 -2
- package/dist/registry/coming-soon-page-minimal.json +1 -1
- package/dist/registry/coming-soon-page.json +1 -1
- package/dist/registry/contact-info-grid.json +2 -2
- package/dist/registry/contact-page-centered.json +2 -2
- package/dist/registry/contact-page-split.json +2 -2
- package/dist/registry/contact-page.json +2 -2
- package/dist/registry/cta-section.json +2 -2
- package/dist/registry/docs/blog-section.md +3 -1
- package/dist/registry/docs/cart-drawer.md +9 -9
- package/dist/registry/docs/favorites-blog-block.md +10 -3
- package/dist/registry/docs/favorites-blog-page.md +38 -0
- package/dist/registry/docs/favorites-ecommerce-block.md +10 -3
- package/dist/registry/docs/favorites-ecommerce-page.md +38 -0
- package/dist/registry/docs/login-page.md +6 -16
- package/dist/registry/docs/payment-success-block.md +8 -1
- package/dist/registry/docs/post-detail-page.md +39 -0
- package/dist/registry/docs/product-card-detailed.md +7 -11
- package/dist/registry/docs/product-detail-page.md +39 -0
- package/dist/registry/docs/product-detail-section.md +7 -13
- package/dist/registry/docs/product-quick-view.md +4 -2
- package/dist/registry/ecommerce-core.json +2 -2
- package/dist/registry/faq-categorized.json +2 -2
- package/dist/registry/faq-simple.json +2 -2
- package/dist/registry/favorites-blog-block.json +1 -1
- package/dist/registry/favorites-blog-page.json +48 -0
- package/dist/registry/favorites-ecommerce-block.json +1 -1
- package/dist/registry/favorites-ecommerce-page.json +48 -0
- package/dist/registry/feature-section.json +2 -2
- package/dist/registry/footer.json +2 -2
- package/dist/registry/header-ecommerce.json +1 -1
- package/dist/registry/hero-carousel.json +3 -3
- package/dist/registry/hero-cta.json +2 -2
- package/dist/registry/hero-gradient.json +2 -2
- package/dist/registry/hero.json +2 -2
- package/dist/registry/index.json +9 -0
- package/dist/registry/landing-page-app.json +1 -1
- package/dist/registry/landing-page-saas.json +1 -1
- package/dist/registry/login-page-split.json +1 -1
- package/dist/registry/login-page.json +8 -6
- package/dist/registry/logo-cloud.json +1 -1
- package/dist/registry/payment-success-block.json +7 -3
- package/dist/registry/portfolio-page.json +2 -2
- package/dist/registry/post-card.json +1 -1
- package/dist/registry/post-detail-page.json +48 -0
- package/dist/registry/pricing-page.json +1 -1
- package/dist/registry/pricing-section.json +2 -2
- package/dist/registry/product-card-detailed.json +5 -4
- package/dist/registry/product-detail-page.json +48 -0
- package/dist/registry/product-detail-section.json +5 -4
- package/dist/registry/product-quick-view.json +5 -4
- package/dist/registry/products-page.json +1 -1
- package/dist/registry/reading-progress.json +1 -1
- package/dist/registry/team-page.json +1 -1
- package/dist/registry/testimonials-carousel.json +2 -2
- package/dist/registry/testimonials-grid.json +2 -2
- package/dist/registry/timeline-section.json +2 -2
- package/dist/registry/video-hero.json +1 -1
- package/package.json +53 -51
- package/template/eslint.config.js +5 -4
- package/template/src/components/ui/sidebar.tsx +2 -4
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
"name": "login-page",
|
|
3
3
|
"type": "registry:page",
|
|
4
4
|
"title": "Login Page",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Login page with email/password form, forgot password link, and create account link. Centered card layout with responsive design. Integrated with auth-core for authentication.",
|
|
6
6
|
"registryDependencies": [
|
|
7
7
|
"button",
|
|
8
8
|
"input",
|
|
9
|
-
"label"
|
|
9
|
+
"label",
|
|
10
|
+
"auth-core",
|
|
11
|
+
"api"
|
|
10
12
|
],
|
|
11
|
-
"usage": "import LoginPage from '@/modules/login-page';\n\n
|
|
13
|
+
"usage": "import LoginPage from '@/modules/login-page';\n\n<LoginPage />\n\n• Installed at: src/modules/login-page/\n• Customize text: src/modules/login-page/lang/*.json\n• Integrated with auth-core for API authentication\n• On success, redirects to previous page or home",
|
|
12
14
|
"route": {
|
|
13
15
|
"path": "/login",
|
|
14
16
|
"componentName": "LoginPage"
|
|
@@ -24,19 +26,19 @@
|
|
|
24
26
|
"path": "login-page/login-page.tsx",
|
|
25
27
|
"type": "registry:page",
|
|
26
28
|
"target": "$modules$/login-page/login-page.tsx",
|
|
27
|
-
"content": "import { useState } from \"react\";\r\nimport { Link } from \"react-router\";\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 { cn } from \"@/lib/utils\";\r\
|
|
29
|
+
"content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useLocation } from \"react-router\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { toast } from \"sonner\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { useAuthStore } from \"@/modules/auth-core/auth-store\";\r\nimport { customerClient } from \"@/modules/api/customer-client\";\r\nimport { getErrorMessage } from \"@/modules/api/get-error-message\";\r\n\r\ninterface LoginPageProps {\r\n className?: string;\r\n}\r\n\r\nexport function LoginPage({ className }: LoginPageProps) {\r\n const { t } = useTranslation(\"login-page\");\r\n usePageTitle({ title: t(\"title\", \"Sign In\") });\r\n const navigate = useNavigate();\r\n const location = useLocation();\r\n const setAuth = useAuthStore((state) => state.setAuth);\r\n\r\n const [email, setEmail] = useState(\"\");\r\n const [password, setPassword] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const from = (location.state as { from?: string })?.from || \"/\";\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n setIsLoading(true);\r\n\r\n try {\r\n const response = await customerClient.auth.login({\r\n username: email,\r\n password,\r\n });\r\n\r\n setAuth(\r\n { username: email, email: (response as any).email || email },\r\n {\r\n accessToken: response.accessToken,\r\n refreshToken: response.refreshToken,\r\n idToken: response.idToken,\r\n encryptionKey: response.encryptionKey,\r\n expiresAt: response.expiresIn\r\n ? Date.now() + response.expiresIn * 1000\r\n : undefined,\r\n }\r\n );\r\n\r\n customerClient.setToken(response.accessToken);\r\n toast.success(t(\"loginSuccess\", \"Login successful!\"));\r\n navigate(from, { replace: true });\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"loginError\", \"Login failed. Please check your credentials.\")\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <section\r\n className={cn(\"flex min-h-screen bg-muted/30 px-4 py-16 md:py-32\", className)}\r\n >\r\n <form\r\n onSubmit={handleSubmit}\r\n className=\"bg-muted m-auto h-fit w-full max-w-sm overflow-hidden rounded-xl border shadow-md\"\r\n >\r\n <div className=\"bg-card -m-px rounded-xl border p-8 pb-6\">\r\n <div className=\"text-center\">\r\n <Link to=\"/\" aria-label=\"go home\" className=\"mx-auto block w-fit\">\r\n <Logo size=\"sm\" />\r\n </Link>\r\n <h1 className=\"mb-1 mt-4 text-xl font-semibold\">\r\n {t(\"title\", \"Sign In\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"subtitle\", \"Welcome back! Sign in to continue\")}\r\n </p>\r\n </div>\r\n\r\n {error && (\r\n <div className=\"mt-4 p-3 text-sm text-red-600 bg-red-50 dark:bg-red-950 dark:text-red-400 rounded-md\">\r\n {error}\r\n </div>\r\n )}\r\n\r\n <div className=\"mt-6 space-y-6\">\r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"email\" className=\"block text-sm\">\r\n {t(\"email\", \"Email\")}\r\n </Label>\r\n <Input\r\n type=\"email\"\r\n required\r\n name=\"email\"\r\n id=\"email\"\r\n value={email}\r\n onChange={(e) => setEmail(e.target.value)}\r\n placeholder={t(\"emailPlaceholder\", \"you@example.com\")}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <div className=\"flex items-center justify-between\">\r\n <Label htmlFor=\"password\" className=\"text-sm\">\r\n {t(\"password\", \"Password\")}\r\n </Label>\r\n <Button asChild variant=\"link\" size=\"sm\" className=\"h-auto p-0\">\r\n <Link to=\"/forgot-password\" className=\"text-xs\">\r\n {t(\"forgotPassword\", \"Forgot password?\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n <Input\r\n type=\"password\"\r\n required\r\n name=\"password\"\r\n id=\"password\"\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n placeholder=\"••••••••\"\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\r\n {isLoading ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"signingIn\", \"Signing in...\")}\r\n </>\r\n ) : (\r\n t(\"signIn\", \"Sign In\")\r\n )}\r\n </Button>\r\n </div>\r\n </div>\r\n\r\n <div className=\"p-3\">\r\n <p className=\"text-center text-sm text-muted-foreground\">\r\n {t(\"noAccount\", \"Don't have an account?\")}\r\n <Button asChild variant=\"link\" className=\"px-2\">\r\n <Link to=\"/register\">{t(\"createAccount\", \"Create account\")}</Link>\r\n </Button>\r\n </p>\r\n </div>\r\n </form>\r\n </section>\r\n );\r\n}\r\n\r\nexport default LoginPage;\r\n"
|
|
28
30
|
},
|
|
29
31
|
{
|
|
30
32
|
"path": "login-page/lang/en.json",
|
|
31
33
|
"type": "registry:lang",
|
|
32
34
|
"target": "$modules$/login-page/lang/en.json",
|
|
33
|
-
"content": "{\r\n \"title\": \"Sign In\",\r\n \"subtitle\": \"Welcome back! Sign in to continue\",\r\n \"email\": \"Email\",\r\n \"emailPlaceholder\": \"you@example.com\",\r\n \"password\": \"Password\",\r\n \"forgotPassword\": \"Forgot password?\",\r\n \"signIn\": \"Sign In\",\r\n \"
|
|
35
|
+
"content": "{\r\n \"title\": \"Sign In\",\r\n \"subtitle\": \"Welcome back! Sign in to continue\",\r\n \"email\": \"Email\",\r\n \"emailPlaceholder\": \"you@example.com\",\r\n \"password\": \"Password\",\r\n \"forgotPassword\": \"Forgot password?\",\r\n \"signIn\": \"Sign In\",\r\n \"signingIn\": \"Signing in...\",\r\n \"noAccount\": \"Don't have an account?\",\r\n \"createAccount\": \"Create account\",\r\n \"loginSuccess\": \"Login successful!\",\r\n \"loginError\": \"Login failed. Please check your credentials.\"\r\n}\r\n"
|
|
34
36
|
},
|
|
35
37
|
{
|
|
36
38
|
"path": "login-page/lang/tr.json",
|
|
37
39
|
"type": "registry:lang",
|
|
38
40
|
"target": "$modules$/login-page/lang/tr.json",
|
|
39
|
-
"content": "{\r\n \"title\": \"Giriş Yap\",\r\n \"subtitle\": \"Tekrar hoş geldiniz! Devam etmek için giriş yapın\",\r\n \"email\": \"E-posta\",\r\n \"emailPlaceholder\": \"ornek@email.com\",\r\n \"password\": \"Şifre\",\r\n \"forgotPassword\": \"Şifremi unuttum\",\r\n \"signIn\": \"Giriş Yap\",\r\n \"
|
|
41
|
+
"content": "{\r\n \"title\": \"Giriş Yap\",\r\n \"subtitle\": \"Tekrar hoş geldiniz! Devam etmek için giriş yapın\",\r\n \"email\": \"E-posta\",\r\n \"emailPlaceholder\": \"ornek@email.com\",\r\n \"password\": \"Şifre\",\r\n \"forgotPassword\": \"Şifremi unuttum\",\r\n \"signIn\": \"Giriş Yap\",\r\n \"signingIn\": \"Giriş yapılıyor...\",\r\n \"noAccount\": \"Hesabınız yok mu?\",\r\n \"createAccount\": \"Hesap oluştur\",\r\n \"loginSuccess\": \"Giriş başarılı!\",\r\n \"loginError\": \"Giriş başarısız. Lütfen bilgilerinizi kontrol edin.\"\r\n}\r\n"
|
|
40
42
|
}
|
|
41
43
|
],
|
|
42
44
|
"exports": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"path": "logo-cloud/logo-cloud.tsx",
|
|
12
12
|
"type": "registry:component",
|
|
13
13
|
"target": "$modules$/logo-cloud/logo-cloud.tsx",
|
|
14
|
-
"content": "\"use client\";\r\n\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface Logo {\r\n name: string;\r\n image: string;\r\n url?: string;\r\n}\r\n\r\ninterface LogoCloudProps {\r\n logos?: Logo[];\r\n title?: string;\r\n grayscale?: boolean;\r\n animate?: boolean;\r\n speed?: \"slow\" | \"normal\" | \"fast\";\r\n pauseOnHover?: boolean;\r\n className?: string;\r\n}\r\n\r\nconst defaultLogos: Logo[] = [\r\n { name: \"Company 1\", image: \"/images/placeholder.png\" },\r\n { name: \"Company 2\", image: \"/images/placeholder.png\" },\r\n { name: \"Company 3\", image: \"/images/placeholder.png\" },\r\n { name: \"Company 4\", image: \"/images/placeholder.png\" },\r\n { name: \"Company 5\", image: \"/images/placeholder.png\" },\r\n { name: \"Company 6\", image: \"/images/placeholder.png\" },\r\n];\r\n\r\nconst speedDuration = {\r\n slow: \"40s\",\r\n normal: \"25s\",\r\n fast: \"15s\",\r\n};\r\n\r\nexport function LogoCloud({\r\n logos = defaultLogos,\r\n title,\r\n grayscale = true,\r\n animate = true,\r\n speed = \"slow\",\r\n pauseOnHover = true,\r\n className,\r\n}: LogoCloudProps) {\r\n const { t } = useTranslation(\"logo-cloud\");\r\n\r\n const displayTitle = title ?? t(\"title\");\r\n\r\n const renderLogo = (logo: Logo, index: number) => {\r\n const imageElement = (\r\n <img\r\n src={logo.image}\r\n alt={logo.name}\r\n className={cn(\r\n \"h-10 md:h-12 w-auto object-contain transition-all duration-300\",\r\n grayscale && \"grayscale opacity-60 hover:grayscale-0 hover:opacity-100\"\r\n )}\r\n draggable={false}\r\n />\r\n );\r\n\r\n if (logo.url) {\r\n return (\r\n <a\r\n key={index}\r\n href={logo.url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n className=\"flex items-center justify-center px-8 md:px-12 shrink-0\"\r\n >\r\n {imageElement}\r\n </a>\r\n );\r\n }\r\n\r\n return (\r\n <div key={index} className=\"flex items-center justify-center px-8 md:px-12 shrink-0\">\r\n {imageElement}\r\n </div>\r\n );\r\n };\r\n\r\n return (\r\n <section className={cn(\"py-12 md:py-16 overflow-hidden\", className)}>\r\n <div className=\"container max-w-[var(--container-max-width)] mx-auto px-4\">\r\n {displayTitle && (\r\n <p className=\"text-center text-sm font-medium text-muted-foreground uppercase tracking-wider mb-8\">\r\n {displayTitle}\r\n </p>\r\n )}\r\n </div>\r\n\r\n {/* Marquee Container */}\r\n <div\r\n className={cn(\r\n \"relative w-full\",\r\n pauseOnHover && \"[&:hover_.marquee-content]:pause\"\r\n )}\r\n >\r\n {/* Gradient Masks */}\r\n <div className=\"absolute left-0 top-0 bottom-0 w-20 md:w-32 bg-gradient-to-r from-background to-transparent z-10 pointer-events-none\" />\r\n <div className=\"absolute right-0 top-0 bottom-0 w-20 md:w-32 bg-gradient-to-l from-background to-transparent z-10 pointer-events-none\" />\r\n\r\n {/* Scrolling Content */}\r\n <div className=\"flex overflow-hidden\">\r\n <div\r\n className={cn(\r\n \"marquee-content flex items-center\",\r\n animate && \"animate-marquee\"\r\n )}\r\n style={{\r\n [\"--marquee-duration\" as string]: speedDuration[speed],\r\n }}\r\n >\r\n {/* First set of logos */}\r\n {logos.map((logo, index) => renderLogo(logo, index))}\r\n {/* Duplicate for seamless loop */}\r\n {logos.map((logo, index) => renderLogo(logo, index + logos.length))}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <style>{`\r\n @keyframes marquee {\r\n 0% {\r\n transform: translateX(0);\r\n }\r\n 100% {\r\n transform: translateX(-50%);\r\n }\r\n }\r\n .animate-marquee {\r\n animation: marquee var(--marquee-duration, 25s) linear infinite;\r\n }\r\n .pause {\r\n animation-play-state: paused !important;\r\n }\r\n `}</style>\r\n </section>\r\n );\r\n}\r\n"
|
|
14
|
+
"content": "\"use client\";\r\n\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface Logo {\r\n name: string;\r\n image: string;\r\n url?: string;\r\n}\r\n\r\ninterface LogoCloudProps {\r\n logos?: Logo[];\r\n title?: string;\r\n grayscale?: boolean;\r\n animate?: boolean;\r\n speed?: \"slow\" | \"normal\" | \"fast\";\r\n pauseOnHover?: boolean;\r\n className?: string;\r\n}\r\n\r\nconst defaultLogos: Logo[] = [\r\n { name: \"Company 1\", image: \"/images/placeholder.png\" },\r\n { name: \"Company 2\", image: \"/images/placeholder.png\" },\r\n { name: \"Company 3\", image: \"/images/placeholder.png\" },\r\n { name: \"Company 4\", image: \"/images/placeholder.png\" },\r\n { name: \"Company 5\", image: \"/images/placeholder.png\" },\r\n { name: \"Company 6\", image: \"/images/placeholder.png\" },\r\n];\r\n\r\nconst speedDuration = {\r\n slow: \"40s\",\r\n normal: \"25s\",\r\n fast: \"15s\",\r\n};\r\n\r\nexport function LogoCloud({\r\n logos = defaultLogos,\r\n title,\r\n grayscale = true,\r\n animate = true,\r\n speed = \"slow\",\r\n pauseOnHover = true,\r\n className,\r\n}: LogoCloudProps) {\r\n const { t } = useTranslation(\"logo-cloud\");\r\n\r\n const displayTitle = title ?? t(\"title\");\r\n\r\n const renderLogo = (logo: Logo, index: number) => {\r\n const imageElement = (\r\n <img\r\n src={logo.image}\r\n alt={logo.name}\r\n className={cn(\r\n \"h-10 md:h-12 w-auto object-contain transition-all duration-300 rounded-[var(--radius)]\",\r\n grayscale && \"grayscale opacity-60 hover:grayscale-0 hover:opacity-100\"\r\n )}\r\n draggable={false}\r\n />\r\n );\r\n\r\n if (logo.url) {\r\n return (\r\n <a\r\n key={index}\r\n href={logo.url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n className=\"flex items-center justify-center px-8 md:px-12 shrink-0\"\r\n >\r\n {imageElement}\r\n </a>\r\n );\r\n }\r\n\r\n return (\r\n <div key={index} className=\"flex items-center justify-center px-8 md:px-12 shrink-0\">\r\n {imageElement}\r\n </div>\r\n );\r\n };\r\n\r\n return (\r\n <section className={cn(\"py-12 md:py-16 overflow-hidden\", className)}>\r\n <div className=\"container max-w-[var(--container-max-width)] mx-auto px-4\">\r\n {displayTitle && (\r\n <p className=\"text-center text-sm font-medium text-muted-foreground uppercase tracking-wider mb-8\">\r\n {displayTitle}\r\n </p>\r\n )}\r\n </div>\r\n\r\n {/* Marquee Container */}\r\n <div\r\n className={cn(\r\n \"relative w-full\",\r\n pauseOnHover && \"[&:hover_.marquee-content]:pause\"\r\n )}\r\n >\r\n {/* Gradient Masks */}\r\n <div className=\"absolute left-0 top-0 bottom-0 w-20 md:w-32 bg-gradient-to-r from-background to-transparent z-10 pointer-events-none\" />\r\n <div className=\"absolute right-0 top-0 bottom-0 w-20 md:w-32 bg-gradient-to-l from-background to-transparent z-10 pointer-events-none\" />\r\n\r\n {/* Scrolling Content */}\r\n <div className=\"flex overflow-hidden\">\r\n <div\r\n className={cn(\r\n \"marquee-content flex items-center\",\r\n animate && \"animate-marquee\"\r\n )}\r\n style={{\r\n [\"--marquee-duration\" as string]: speedDuration[speed],\r\n }}\r\n >\r\n {/* First set of logos */}\r\n {logos.map((logo, index) => renderLogo(logo, index))}\r\n {/* Duplicate for seamless loop */}\r\n {logos.map((logo, index) => renderLogo(logo, index + logos.length))}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <style>{`\r\n @keyframes marquee {\r\n 0% {\r\n transform: translateX(0);\r\n }\r\n 100% {\r\n transform: translateX(-50%);\r\n }\r\n }\r\n .animate-marquee {\r\n animation: marquee var(--marquee-duration, 25s) linear infinite;\r\n }\r\n .pause {\r\n animation-play-state: paused !important;\r\n }\r\n `}</style>\r\n </section>\r\n );\r\n}\r\n"
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
"path": "logo-cloud/index.ts",
|
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
"name": "payment-success-block",
|
|
3
3
|
"type": "registry:block",
|
|
4
4
|
"title": "Payment Success Block",
|
|
5
|
-
"description": "Payment verification UI with loading, success, and failed states. Displays order details and navigation options",
|
|
6
|
-
"registryDependencies": [
|
|
5
|
+
"description": "Payment verification UI with loading, success, and failed states. Displays order details and navigation options. Uses formatPrice from ecommerce-core.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"button",
|
|
8
|
+
"card",
|
|
9
|
+
"ecommerce-core"
|
|
10
|
+
],
|
|
7
11
|
"usage": "import { PaymentSuccessBlock } from '@/modules/payment-success-block';\n\n<PaymentSuccessBlock status=\"success\" orderId={orderId} />\n\n• Installed at: src/modules/payment-success-block/\n• Handles loading, success, and failed states\n• Use in payment callback/confirmation pages",
|
|
8
12
|
"files": [
|
|
9
13
|
{
|
|
@@ -16,7 +20,7 @@
|
|
|
16
20
|
"path": "payment-success-block/payment-success-block.tsx",
|
|
17
21
|
"type": "registry:block",
|
|
18
22
|
"target": "$modules$/payment-success-block/payment-success-block.tsx",
|
|
19
|
-
"content": "import { Link } from \"react-router\";\nimport {\n CheckCircle,\n XCircle,\n Loader2,\n ShoppingBag,\n Package,\n} from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { useTranslation } from \"react-i18next\";\n\ntype PaymentStatus = \"loading\" | \"success\" | \"failed\";\n\ninterface OrderDetails {\n id?: string;\n totalAmount?: number;\n currency?: string;\n paymentMethod?: string;\n paymentStatus?: string;\n status?: string;\n}\n\ninterface PaymentSuccessBlockProps {\n status: PaymentStatus;\n orderDetails?: OrderDetails | null;\n errorMessage?: string;\n onRetry?: () => void;\n}\n\nexport function PaymentSuccessBlock({\n status,\n orderDetails,\n errorMessage,\n onRetry,\n}: PaymentSuccessBlockProps) {\n const { t } = useTranslation(\"payment-success-block\");\n\n // Loading State\n if (status === \"loading\") {\n return (\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\n <div className=\"max-w-md mx-auto text-center\">\n <Card>\n <CardContent className=\"pt-8 pb-8\">\n <Loader2 className=\"w-16 h-16 text-primary mx-auto mb-4 animate-spin\" />\n <h1 className=\"text-2xl font-bold mb-2\">\n {t(\"verifyingPayment\", \"Verifying Payment\")}\n </h1>\n <p className=\"text-muted-foreground\">\n {t(\n \"pleaseWait\",\n \"Please wait while we verify your payment...\"\n )}\n </p>\n </CardContent>\n </Card>\n </div>\n </div>\n );\n }\n\n // Failed State\n if (status === \"failed\") {\n return (\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\n <div className=\"max-w-md mx-auto text-center\">\n <Card>\n <CardContent className=\"pt-8 pb-8\">\n <XCircle className=\"w-16 h-16 text-destructive mx-auto mb-4\" />\n <h1 className=\"text-2xl font-bold mb-2\">\n {t(\"paymentFailed\", \"Payment Failed\")}\n </h1>\n\n {/* Error Message */}\n {errorMessage && (\n <div className=\"bg-destructive/10 border border-destructive/30 rounded-lg p-4 mb-4\">\n <p className=\"text-sm text-destructive\">{errorMessage}</p>\n </div>\n )}\n\n <p className=\"text-muted-foreground mb-6\">\n {t(\n \"paymentFailedDescription\",\n \"We couldn't verify your payment. Please try again or contact support.\"\n )}\n </p>\n\n <div className=\"flex flex-col gap-3\">\n {onRetry && (\n <Button onClick={onRetry}>\n {t(\"tryAgain\", \"Try Again\")}\n </Button>\n )}\n <Button variant=\"outline\" asChild>\n <Link to=\"/contact\">\n {t(\"contactSupport\", \"Contact Support\")}\n </Link>\n </Button>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n );\n }\n\n // Success State\n return (\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\n <div className=\"max-w-lg mx-auto text-center\">\n <Card>\n <CardContent className=\"pt-8 pb-8\">\n <CheckCircle className=\"w-20 h-20 text-green-600 dark:text-green-400 mx-auto mb-6\" />\n <h1 className=\"text-3xl font-bold mb-2\">\n {t(\"thankYou\", \"Thank You!\")}\n </h1>\n <p className=\"text-xl text-muted-foreground mb-6\">\n {t(\"orderConfirmed\", \"Your order has been confirmed.\")}\n </p>\n\n {orderDetails && (\n <div className=\"bg-muted/50 rounded-lg p-4 mb-6 text-left\">\n <h3 className=\"font-semibold mb-2\">\n {t(\"orderDetails\", \"Order Details\")}\n </h3>\n <div className=\"text-sm space-y-1\">\n {orderDetails.id && (\n <p>\n <span className=\"text-muted-foreground\">\n {t(\"orderId\", \"Order ID\")}:\n </span>{\" \"}\n <span className=\"font-medium\">{orderDetails.id}</span>\n </p>\n )}\n {orderDetails.totalAmount !== undefined && orderDetails.currency && (\n <p>\n <span className=\"text-muted-foreground\">\n {t(\"total\", \"Total\")}:\n </span>{\" \"}\n <span className=\"font-medium\">\n {orderDetails.
|
|
23
|
+
"content": "import { Link } from \"react-router\";\nimport {\n CheckCircle,\n XCircle,\n Loader2,\n ShoppingBag,\n Package,\n} from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { useTranslation } from \"react-i18next\";\nimport { formatPrice } from \"@/modules/ecommerce-core\";\n\ntype PaymentStatus = \"loading\" | \"success\" | \"failed\";\n\ninterface OrderDetails {\n id?: string;\n totalAmount?: number;\n currency?: string;\n paymentMethod?: string;\n paymentStatus?: string;\n status?: string;\n}\n\ninterface PaymentSuccessBlockProps {\n status: PaymentStatus;\n orderDetails?: OrderDetails | null;\n errorMessage?: string;\n onRetry?: () => void;\n}\n\nexport function PaymentSuccessBlock({\n status,\n orderDetails,\n errorMessage,\n onRetry,\n}: PaymentSuccessBlockProps) {\n const { t } = useTranslation(\"payment-success-block\");\n\n // Loading State\n if (status === \"loading\") {\n return (\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\n <div className=\"max-w-md mx-auto text-center\">\n <Card>\n <CardContent className=\"pt-8 pb-8\">\n <Loader2 className=\"w-16 h-16 text-primary mx-auto mb-4 animate-spin\" />\n <h1 className=\"text-2xl font-bold mb-2\">\n {t(\"verifyingPayment\", \"Verifying Payment\")}\n </h1>\n <p className=\"text-muted-foreground\">\n {t(\n \"pleaseWait\",\n \"Please wait while we verify your payment...\"\n )}\n </p>\n </CardContent>\n </Card>\n </div>\n </div>\n );\n }\n\n // Failed State\n if (status === \"failed\") {\n return (\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\n <div className=\"max-w-md mx-auto text-center\">\n <Card>\n <CardContent className=\"pt-8 pb-8\">\n <XCircle className=\"w-16 h-16 text-destructive mx-auto mb-4\" />\n <h1 className=\"text-2xl font-bold mb-2\">\n {t(\"paymentFailed\", \"Payment Failed\")}\n </h1>\n\n {/* Error Message */}\n {errorMessage && (\n <div className=\"bg-destructive/10 border border-destructive/30 rounded-lg p-4 mb-4\">\n <p className=\"text-sm text-destructive\">{errorMessage}</p>\n </div>\n )}\n\n <p className=\"text-muted-foreground mb-6\">\n {t(\n \"paymentFailedDescription\",\n \"We couldn't verify your payment. Please try again or contact support.\"\n )}\n </p>\n\n <div className=\"flex flex-col gap-3\">\n {onRetry && (\n <Button onClick={onRetry}>\n {t(\"tryAgain\", \"Try Again\")}\n </Button>\n )}\n <Button variant=\"outline\" asChild>\n <Link to=\"/contact\">\n {t(\"contactSupport\", \"Contact Support\")}\n </Link>\n </Button>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n );\n }\n\n // Success State\n return (\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\n <div className=\"max-w-lg mx-auto text-center\">\n <Card>\n <CardContent className=\"pt-8 pb-8\">\n <CheckCircle className=\"w-20 h-20 text-green-600 dark:text-green-400 mx-auto mb-6\" />\n <h1 className=\"text-3xl font-bold mb-2\">\n {t(\"thankYou\", \"Thank You!\")}\n </h1>\n <p className=\"text-xl text-muted-foreground mb-6\">\n {t(\"orderConfirmed\", \"Your order has been confirmed.\")}\n </p>\n\n {orderDetails && (\n <div className=\"bg-muted/50 rounded-lg p-4 mb-6 text-left\">\n <h3 className=\"font-semibold mb-2\">\n {t(\"orderDetails\", \"Order Details\")}\n </h3>\n <div className=\"text-sm space-y-1\">\n {orderDetails.id && (\n <p>\n <span className=\"text-muted-foreground\">\n {t(\"orderId\", \"Order ID\")}:\n </span>{\" \"}\n <span className=\"font-medium\">{orderDetails.id}</span>\n </p>\n )}\n {orderDetails.totalAmount !== undefined && orderDetails.currency && (\n <p>\n <span className=\"text-muted-foreground\">\n {t(\"total\", \"Total\")}:\n </span>{\" \"}\n <span className=\"font-medium\">\n {formatPrice(orderDetails.totalAmount, orderDetails.currency)}\n </span>\n </p>\n )}\n {orderDetails.paymentMethod && (\n <p>\n <span className=\"text-muted-foreground\">\n {t(\"paymentMethod\", \"Payment Method\")}:\n </span>{\" \"}\n <span className=\"font-medium capitalize\">\n {orderDetails.paymentMethod}\n </span>\n </p>\n )}\n {orderDetails.paymentStatus && (\n <p>\n <span className=\"text-muted-foreground\">\n {t(\"paymentStatus\", \"Payment Status\")}:\n </span>{\" \"}\n <span className=\"font-medium capitalize text-green-600 dark:text-green-400\">\n {orderDetails.paymentStatus}\n </span>\n </p>\n )}\n {orderDetails.status && (\n <p>\n <span className=\"text-muted-foreground\">\n {t(\"orderStatus\", \"Order Status\")}:\n </span>{\" \"}\n <span className=\"font-medium capitalize\">\n {orderDetails.status}\n </span>\n </p>\n )}\n </div>\n </div>\n )}\n\n <p className=\"text-sm text-muted-foreground mb-6\">\n {t(\n \"confirmationEmailSent\",\n \"A confirmation email has been sent to your email address.\"\n )}\n </p>\n\n <div className=\"flex flex-col gap-3\">\n <Button asChild size=\"lg\">\n <Link to=\"/orders\">\n <Package className=\"w-4 h-4 mr-2\" />\n {t(\"viewMyOrders\", \"View My Orders\")}\n </Link>\n </Button>\n <Button variant=\"outline\" asChild>\n <Link to=\"/products\">\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\n {t(\"continueShopping\", \"Continue Shopping\")}\n </Link>\n </Button>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n );\n}\n"
|
|
20
24
|
},
|
|
21
25
|
{
|
|
22
26
|
"path": "payment-success-block/lang/en.json",
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
"path": "portfolio-page/lang/en.json",
|
|
27
27
|
"type": "registry:lang",
|
|
28
28
|
"target": "$modules$/portfolio-page/lang/en.json",
|
|
29
|
-
"content": "{\r\n \"pageTitle\": \"Portfolio\",\r\n \"title\": \"Our Portfolio\",\r\n \"subtitle\": \"
|
|
29
|
+
"content": "{\r\n \"pageTitle\": \"Portfolio\",\r\n \"title\": \"Our Portfolio\",\r\n \"subtitle\": \"Customize these projects with Promake based on your work.\",\r\n \"noItems\": \"No projects found in this category.\"\r\n}\r\n"
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
"path": "portfolio-page/lang/tr.json",
|
|
33
33
|
"type": "registry:lang",
|
|
34
34
|
"target": "$modules$/portfolio-page/lang/tr.json",
|
|
35
|
-
"content": "{\r\n \"pageTitle\": \"Portfolyo\",\r\n \"title\": \"Portfolyomuz\",\r\n \"subtitle\": \"Bu projeleri
|
|
35
|
+
"content": "{\r\n \"pageTitle\": \"Portfolyo\",\r\n \"title\": \"Portfolyomuz\",\r\n \"subtitle\": \"Bu projeleri çalışmalarınıza göre Promake ile özelleştirin.\",\r\n \"noItems\": \"Bu kategoride proje bulunamadı.\"\r\n}\r\n"
|
|
36
36
|
}
|
|
37
37
|
],
|
|
38
38
|
"exports": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"path": "post-card/post-card.tsx",
|
|
19
19
|
"type": "registry:component",
|
|
20
20
|
"target": "$modules$/post-card/post-card.tsx",
|
|
21
|
-
"content": "import { Link } from \"react-router\";\nimport { Calendar, Eye, Clock, ArrowRight } from \"lucide-react\";\nimport {\n Card,\n CardContent,\n CardFooter,\n CardHeader,\n} from \"@/components/ui/card\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { useTranslation } from \"react-i18next\";\nimport type { Post } from \"@/modules/blog-core/types\";\n\ninterface PostCardProps {\n post: Post;\n layout?: \"grid\" | \"list\";\n showExcerpt?: boolean;\n className?: string;\n}\n\nexport function PostCard({\n post,\n layout = \"grid\",\n showExcerpt = true,\n className = \"\",\n}: PostCardProps) {\n const { t } = useTranslation(\"post-card\");\n\n const formatDate = (dateString: string) => {\n return new Date(dateString).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n };\n\n
|
|
21
|
+
"content": "import { Link } from \"react-router\";\nimport { Calendar, Eye, Clock, ArrowRight } from \"lucide-react\";\nimport {\n Card,\n CardContent,\n CardFooter,\n CardHeader,\n} from \"@/components/ui/card\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { useTranslation } from \"react-i18next\";\nimport type { Post } from \"@/modules/blog-core/types\";\n\ninterface CardWrapperProps {\n children: React.ReactNode;\n className?: string;\n}\n\nfunction CardWrapper({ children, className = \"\" }: CardWrapperProps) {\n return (\n <Card\n className={`group overflow-hidden border-0 p-0 shadow-sm hover:shadow-lg transition-all duration-300 h-full ${className}`}\n >\n {children}\n </Card>\n );\n}\n\ninterface PostCardProps {\n post: Post;\n layout?: \"grid\" | \"list\";\n showExcerpt?: boolean;\n className?: string;\n}\n\nexport function PostCard({\n post,\n layout = \"grid\",\n showExcerpt = true,\n className = \"\",\n}: PostCardProps) {\n const { t } = useTranslation(\"post-card\");\n\n const formatDate = (dateString: string) => {\n return new Date(dateString).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n };\n\n if (layout === \"list\") {\n return (\n <CardWrapper className={className}>\n <div className=\"flex flex-col md:flex-row\">\n {post.featured_image && (\n <div className=\"md:w-1/3 flex-shrink-0\">\n <Link to={`/blog/${post.slug}`}>\n <img\n src={post.featured_image}\n alt={post.title}\n className=\"w-full h-48 md:h-full object-cover\"\n onError={(e) => {\n e.currentTarget.src = \"/images/placeholder.png\";\n }}\n />\n </Link>\n </div>\n )}\n\n <div className=\"flex-1 flex flex-col\">\n <CardHeader className=\"pt-6 pb-3\">\n {post.categories && post.categories.length > 0 && (\n <div className=\"flex flex-wrap gap-1 mb-2\">\n {post.categories.slice(0, 2).map((category) => (\n <Badge\n key={category.slug}\n variant=\"secondary\"\n className=\"text-xs\"\n >\n <Link to={`/blog?categories=${category.slug}`}>\n {category.name}\n </Link>\n </Badge>\n ))}\n </div>\n )}\n\n <h3 className=\"text-xl font-semibold line-clamp-2 group-hover:text-primary transition-colors\">\n <Link to={`/blog/${post.slug}`}>{post.title}</Link>\n </h3>\n </CardHeader>\n\n <CardContent className=\"flex-1 pb-3\">\n {showExcerpt && post.excerpt && (\n <p className=\"text-muted-foreground text-sm line-clamp-3 mb-4\">\n {post.excerpt}\n </p>\n )}\n\n <div className=\"flex flex-wrap items-center gap-4 text-xs text-muted-foreground\">\n <div className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n <span>{formatDate(post.published_at)}</span>\n </div>\n\n {post.read_time > 0 && (\n <div className=\"flex items-center gap-1\">\n <Clock className=\"h-3 w-3\" />\n <span>\n {post.read_time} {t(\"minRead\", \"min\")}\n </span>\n </div>\n )}\n\n {post.view_count > 0 && (\n <div className=\"flex items-center gap-1\">\n <Eye className=\"h-3 w-3\" />\n <span>{post.view_count.toLocaleString()}</span>\n </div>\n )}\n </div>\n </CardContent>\n\n <CardFooter className=\"pt-0 pb-6\">\n <Button variant=\"ghost\" size=\"sm\" asChild>\n <Link to={`/blog/${post.slug}`}>\n {t(\"readMore\", \"Read More\")}\n <ArrowRight className=\"h-3 w-3 ml-1\" />\n </Link>\n </Button>\n </CardFooter>\n </div>\n </div>\n </CardWrapper>\n );\n }\n\n return (\n <CardWrapper className={className}>\n <div className=\"flex flex-col h-full\">\n {post.featured_image && (\n <div className=\"aspect-video overflow-hidden\">\n <Link to={`/blog/${post.slug}`}>\n <img\n src={post.featured_image}\n alt={post.title}\n className=\"w-full h-full object-cover group-hover:scale-105 transition-transform duration-300\"\n onError={(e) => {\n e.currentTarget.src = \"/images/placeholder.png\";\n }}\n />\n </Link>\n </div>\n )}\n\n <div className=\"flex flex-col flex-1\">\n <CardHeader className=\"pt-6 pb-3\">\n {post.categories && post.categories.length > 0 && (\n <div className=\"flex flex-wrap gap-1 mb-2\">\n {post.categories.slice(0, 2).map((category) => (\n <Badge\n key={category.slug}\n variant=\"secondary\"\n className=\"text-xs\"\n >\n <Link to={`/blog?categories=${category.slug}`}>\n {category.name}\n </Link>\n </Badge>\n ))}\n </div>\n )}\n\n <h3 className=\"text-lg font-semibold line-clamp-2 group-hover:text-primary transition-colors\">\n <Link to={`/blog/${post.slug}`}>{post.title}</Link>\n </h3>\n </CardHeader>\n\n <CardContent className=\"flex-1 pb-3\">\n {showExcerpt && post.excerpt && (\n <p className=\"text-muted-foreground text-sm line-clamp-3 mb-4\">\n {post.excerpt}\n </p>\n )}\n\n <div className=\"flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n <div className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n <span>{formatDate(post.published_at)}</span>\n </div>\n\n {post.read_time > 0 && (\n <div className=\"flex items-center gap-1\">\n <Clock className=\"h-3 w-3\" />\n <span>\n {post.read_time} {t(\"minRead\", \"min\")}\n </span>\n </div>\n )}\n </div>\n </CardContent>\n\n <CardFooter className=\"pt-0 pb-6 mt-auto\">\n <div className=\"flex items-center justify-between w-full\">\n <Button variant=\"ghost\" size=\"sm\" asChild>\n <Link to={`/blog/${post.slug}`}>\n {t(\"readMore\", \"Read More\")}\n <ArrowRight className=\"h-3 w-3 ml-1\" />\n </Link>\n </Button>\n\n {post.view_count > 0 && (\n <div className=\"flex items-center gap-1 text-xs text-muted-foreground\">\n <Eye className=\"h-3 w-3\" />\n <span>{post.view_count.toLocaleString()}</span>\n </div>\n )}\n </div>\n </CardFooter>\n </div>\n </div>\n </CardWrapper>\n );\n}\n"
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"path": "post-card/lang/en.json",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "post-detail-page",
|
|
3
|
+
"type": "registry:page",
|
|
4
|
+
"title": "Post Detail Page",
|
|
5
|
+
"description": "Blog post detail page that fetches post data by slug from URL params. Uses usePostBySlug hook from blog-core and renders PostDetailBlock. Includes loading skeleton, error handling for not found posts, and automatic page title.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"blog-core",
|
|
8
|
+
"post-detail-block"
|
|
9
|
+
],
|
|
10
|
+
"usage": "import { PostDetailPage } from '@/modules/post-detail-page';\n\n<Route path=\"/blog/:slug\" element={<PostDetailPage />} />\n\n• Uses usePostBySlug() from blog-core\n• Fetches post by slug from URL params\n• Shows loading skeleton while fetching\n• Handles post not found state",
|
|
11
|
+
"route": {
|
|
12
|
+
"path": "/blog/:slug",
|
|
13
|
+
"componentName": "PostDetailPage"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
{
|
|
17
|
+
"path": "post-detail-page/index.ts",
|
|
18
|
+
"type": "registry:index",
|
|
19
|
+
"target": "$modules$/post-detail-page/index.ts",
|
|
20
|
+
"content": "export * from './post-detail-page';\r\nexport { PostDetailPage as default } from './post-detail-page';\r\n"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": "post-detail-page/post-detail-page.tsx",
|
|
24
|
+
"type": "registry:page",
|
|
25
|
+
"target": "$modules$/post-detail-page/post-detail-page.tsx",
|
|
26
|
+
"content": "import { useParams } from \"react-router\";\r\nimport { usePostBySlug } from \"@/modules/blog-core\";\r\nimport { PostDetailBlock } from \"@/modules/post-detail-block\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\n\r\nexport function PostDetailPage() {\r\n const { t } = useTranslation(\"post-detail-page\");\r\n const { slug } = useParams<{ slug: string }>();\r\n const { post, loading, error } = usePostBySlug(slug || \"\");\r\n\r\n usePageTitle({ title: post?.title || t(\"loading\", \"Loading...\") });\r\n\r\n if (loading) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <div className=\"animate-pulse space-y-4\">\r\n <div className=\"h-8 bg-muted rounded w-1/3\"></div>\r\n <div className=\"h-4 bg-muted rounded w-1/4\"></div>\r\n <div className=\"h-64 bg-muted rounded\"></div>\r\n <div className=\"space-y-2\">\r\n <div className=\"h-4 bg-muted rounded\"></div>\r\n <div className=\"h-4 bg-muted rounded\"></div>\r\n <div className=\"h-4 bg-muted rounded w-3/4\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n if (error || !post) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8 text-center\">\r\n <h1 className=\"text-2xl font-bold mb-4\">{t(\"notFound\", \"Post Not Found\")}</h1>\r\n <p className=\"text-muted-foreground\">{t(\"notFoundDescription\", \"The post you're looking for doesn't exist or has been removed.\")}</p>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n return (\r\n <Layout>\r\n <PostDetailBlock post={post} />\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default PostDetailPage;\r\n"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"path": "post-detail-page/lang/en.json",
|
|
30
|
+
"type": "registry:lang",
|
|
31
|
+
"target": "$modules$/post-detail-page/lang/en.json",
|
|
32
|
+
"content": "{\r\n \"loading\": \"Loading...\",\r\n \"notFound\": \"Post Not Found\",\r\n \"notFoundDescription\": \"The post you're looking for doesn't exist or has been removed.\"\r\n}\r\n"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"path": "post-detail-page/lang/tr.json",
|
|
36
|
+
"type": "registry:lang",
|
|
37
|
+
"target": "$modules$/post-detail-page/lang/tr.json",
|
|
38
|
+
"content": "{\r\n \"loading\": \"Yükleniyor...\",\r\n \"notFound\": \"Yazı Bulunamadı\",\r\n \"notFoundDescription\": \"Aradığınız yazı mevcut değil veya kaldırılmış.\"\r\n}\r\n"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"exports": {
|
|
42
|
+
"types": [],
|
|
43
|
+
"variables": [
|
|
44
|
+
"PostDetailPage",
|
|
45
|
+
"default"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"path": "pricing-page/lang/en.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/pricing-page/lang/en.json",
|
|
31
|
-
"content": "{\r\n \"title\": \"Pricing\",\r\n \"label\": \"Pricing\",\r\n \"heading\": \"Simple, Transparent Pricing\",\r\n \"description\": \"
|
|
31
|
+
"content": "{\r\n \"title\": \"Pricing\",\r\n \"label\": \"Pricing\",\r\n \"heading\": \"Simple, Transparent Pricing\",\r\n \"description\": \"Customize this pricing description with Promake to match your plans.\",\r\n \"monthly\": \"Monthly\",\r\n \"annual\": \"Annual\",\r\n \"save\": \"Save 20%\",\r\n \"month\": \"mo\",\r\n \"billedAnnually\": \"Billed annually\",\r\n \"mostPopular\": \"Most Popular\",\r\n \"freeName\": \"Free\",\r\n \"freeDesc\": \"Perfect for getting started\",\r\n \"freeFeature1\": \"Feature included\",\r\n \"freeFeature2\": \"Feature included\",\r\n \"freeFeature3\": \"Feature included\",\r\n \"freeFeature4\": \"Feature included\",\r\n \"freeFeature5\": \"Feature not included\",\r\n \"freeFeature6\": \"Feature not included\",\r\n \"freeFeature7\": \"Feature not included\",\r\n \"freeCta\": \"Get Started\",\r\n \"proName\": \"Professional\",\r\n \"proDesc\": \"Best for growing businesses\",\r\n \"proFeature1\": \"All Free features\",\r\n \"proFeature2\": \"Feature included\",\r\n \"proFeature3\": \"Feature included\",\r\n \"proFeature4\": \"Feature included\",\r\n \"proFeature5\": \"Feature included\",\r\n \"proFeature6\": \"Feature included\",\r\n \"proFeature7\": \"Feature included\",\r\n \"proCta\": \"Start Free Trial\",\r\n \"enterpriseName\": \"Enterprise\",\r\n \"enterpriseDesc\": \"For large organizations\",\r\n \"enterpriseFeature1\": \"All Pro features\",\r\n \"enterpriseFeature2\": \"Feature included\",\r\n \"enterpriseFeature3\": \"Feature included\",\r\n \"enterpriseFeature4\": \"Feature included\",\r\n \"enterpriseFeature5\": \"Feature included\",\r\n \"enterpriseFeature6\": \"Feature included\",\r\n \"enterpriseFeature7\": \"Feature included\",\r\n \"enterpriseCta\": \"Contact Sales\",\r\n \"compareTitle\": \"Compare Plans\",\r\n \"feature\": \"Feature\",\r\n \"compProjects\": \"Projects\",\r\n \"compStorage\": \"Storage\",\r\n \"compUsers\": \"Team members\",\r\n \"compApi\": \"API requests\",\r\n \"compSupport\": \"Support\",\r\n \"unlimited\": \"Unlimited\",\r\n \"community\": \"Community\",\r\n \"email\": \"Email\",\r\n \"dedicated\": \"Dedicated\",\r\n \"faqTooltip\": \"Click to see frequently asked questions\",\r\n \"faqTitle\": \"Have questions?\",\r\n \"faqDesc\": \"Check out our FAQ or contact our sales team for more information.\",\r\n \"viewFaq\": \"View FAQ\",\r\n \"contactSales\": \"Contact Sales\"\r\n}\r\n"
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"path": "pricing-page/lang/tr.json",
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"path": "pricing-section/lang/en.json",
|
|
26
26
|
"type": "registry:lang",
|
|
27
27
|
"target": "$modules$/pricing-section/lang/en.json",
|
|
28
|
-
"content": "{\r\n \"label\": \"Pricing\",\r\n \"title\": \"
|
|
28
|
+
"content": "{\r\n \"label\": \"Pricing\",\r\n \"title\": \"Customize this pricing title with Promake\",\r\n \"subtitle\": \"Let Promake personalize this with pricing details for your services.\",\r\n \"perMonth\": \"/month\",\r\n \"popular\": \"Most Popular\",\r\n \"cta\": \"Get Started\",\r\n \"guarantee\": \"Lorem ipsum dolor sit amet. Update this guarantee text using Promake.\",\r\n \"starterName\": \"Starter\",\r\n \"starterDesc\": \"Work with Promake to tailor this plan description\",\r\n \"starterPrice\": \"$9\",\r\n \"starterFeature1\": \"Replace with your starter plan feature\",\r\n \"starterFeature2\": \"Promake can help customize this feature\",\r\n \"starterFeature3\": \"Placeholder feature text\",\r\n \"starterFeature4\": \"Have Promake update this with relevant feature\",\r\n \"proName\": \"Pro\",\r\n \"proDesc\": \"Edit this description via Promake for your mid-tier plan\",\r\n \"proPrice\": \"$29\",\r\n \"proFeature1\": \"Customize this plan feature\",\r\n \"proFeature2\": \"Use Promake to generate an appropriate feature description\",\r\n \"proFeature3\": \"Placeholder text for plan features\",\r\n \"proFeature4\": \"Replace with actual plan feature\",\r\n \"proFeature5\": \"Let Promake tailor this feature to your plan\",\r\n \"enterpriseName\": \"Advanced\",\r\n \"enterpriseDesc\": \"Promake can help personalize this for your advanced tier\",\r\n \"enterprisePrice\": \"$99\",\r\n \"enterpriseFeature1\": \"Advanced plan feature placeholder\",\r\n \"enterpriseFeature2\": \"Work with Promake to add advanced features\",\r\n \"enterpriseFeature3\": \"Customize advanced plan feature description\",\r\n \"enterpriseFeature4\": \"Update this content using Promake\",\r\n \"enterpriseFeature5\": \"Placeholder for advanced plan feature\",\r\n \"enterpriseFeature6\": \"Replace with an actual advanced plan feature\"\r\n}\r\n"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"path": "pricing-section/lang/tr.json",
|
|
32
32
|
"type": "registry:lang",
|
|
33
33
|
"target": "$modules$/pricing-section/lang/tr.json",
|
|
34
|
-
"content": "{\r\n \"label\": \"Fiyatlandırma\",\r\n \"title\": \"
|
|
34
|
+
"content": "{\r\n \"label\": \"Fiyatlandırma\",\r\n \"title\": \"Bu fiyatlandırma başlığını Promake ile özelleştirin\",\r\n \"subtitle\": \"Promake ile hizmetleriniz için fiyatlandırma detaylarıyla kişiselleştirin.\",\r\n \"perMonth\": \"/ay\",\r\n \"popular\": \"En Popüler\",\r\n \"cta\": \"Başlayın\",\r\n \"guarantee\": \"Lorem ipsum dolor sit amet. Bu garanti metnini Promake kullanarak güncelleyin.\",\r\n \"starterName\": \"Başlangıç\",\r\n \"starterDesc\": \"Promake ile bu plan açıklamasını uyarlayın\",\r\n \"starterPrice\": \"99₺\",\r\n \"starterFeature1\": \"Başlangıç planı özelliğinizle değiştirin\",\r\n \"starterFeature2\": \"Promake bu özelliği özelleştirmenize yardımcı olabilir\",\r\n \"starterFeature3\": \"Placeholder özellik metni\",\r\n \"starterFeature4\": \"Promake'ten bunu ilgili özellikle güncellemesini isteyin\",\r\n \"proName\": \"Pro\",\r\n \"proDesc\": \"Bu açıklamayı orta seviye planınız için Promake üzerinden düzenleyin\",\r\n \"proPrice\": \"299₺\",\r\n \"proFeature1\": \"Bu plan özelliğini özelleştirin\",\r\n \"proFeature2\": \"Uygun özellik açıklaması üretmek için Promake kullanın\",\r\n \"proFeature3\": \"Plan özellikleri için placeholder metin\",\r\n \"proFeature4\": \"Gerçek plan özelliğiyle değiştirin\",\r\n \"proFeature5\": \"Promake ile bu özelliği planınıza göre uyarlayın\",\r\n \"enterpriseName\": \"Gelişmiş\",\r\n \"enterpriseDesc\": \"Promake bunu gelişmiş seviyeniz için kişiselleştirmenize yardımcı olabilir\",\r\n \"enterprisePrice\": \"999₺\",\r\n \"enterpriseFeature1\": \"Gelişmiş plan özelliği placeholder\",\r\n \"enterpriseFeature2\": \"Promake ile gelişmiş özellikler ekleyin\",\r\n \"enterpriseFeature3\": \"Gelişmiş plan özelliği açıklamasını özelleştirin\",\r\n \"enterpriseFeature4\": \"Bu içeriği Promake kullanarak güncelleyin\",\r\n \"enterpriseFeature5\": \"Gelişmiş plan özelliği için placeholder\",\r\n \"enterpriseFeature6\": \"Gerçek gelişmiş plan özelliğiyle değiştirin\"\r\n}\r\n"
|
|
35
35
|
}
|
|
36
36
|
],
|
|
37
37
|
"exports": {
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
"name": "product-card-detailed",
|
|
3
3
|
"type": "registry:component",
|
|
4
4
|
"title": "Product Card Detailed",
|
|
5
|
-
"description": "Detailed product card with wishlist
|
|
5
|
+
"description": "Detailed product card with wishlist toggle, hover scale effect, price with optional discount, description, and Add to Cart/Buy Now action buttons. Integrated with ecommerce-core for cart and favorites.",
|
|
6
6
|
"dependencies": [
|
|
7
7
|
"lucide-react"
|
|
8
8
|
],
|
|
9
9
|
"registryDependencies": [
|
|
10
|
-
"button"
|
|
10
|
+
"button",
|
|
11
|
+
"ecommerce-core"
|
|
11
12
|
],
|
|
12
|
-
"usage": "import { ProductCardDetailed } from '@/modules/product-card-detailed';\n\n<ProductCardDetailed
|
|
13
|
+
"usage": "import { ProductCardDetailed } from '@/modules/product-card-detailed';\nimport type { Product } from '@/modules/ecommerce-core';\n\n<ProductCardDetailed product={product} />\n\n• Uses useCart and useFavorites from ecommerce-core\n• Wishlist toggle shows filled heart when favorited",
|
|
13
14
|
"files": [
|
|
14
15
|
{
|
|
15
16
|
"path": "product-card-detailed/index.ts",
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
"path": "product-card-detailed/product-card-detailed.tsx",
|
|
22
23
|
"type": "registry:component",
|
|
23
24
|
"target": "$modules$/product-card-detailed/product-card-detailed.tsx",
|
|
24
|
-
"content": "import { Link } from \"react-router\";\r\nimport { Heart } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { useTranslation } from \"react-i18next\";\r\n\r\ninterface ProductCardDetailedProps {\r\n
|
|
25
|
+
"content": "import { Link, useNavigate } from \"react-router\";\r\nimport { Heart } from \"lucide-react\";\r\nimport { toast } from \"sonner\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport {\r\n useCart,\r\n useFavorites,\r\n formatPrice,\r\n type Product,\r\n} from \"@/modules/ecommerce-core\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface ProductCardDetailedProps {\r\n product: Product;\r\n className?: string;\r\n}\r\n\r\nexport function ProductCardDetailed({\r\n product,\r\n className,\r\n}: ProductCardDetailedProps) {\r\n const { t } = useTranslation(\"product-card-detailed\");\r\n const navigate = useNavigate();\r\n const { addItem } = useCart();\r\n const { isFavorite, addToFavorites, removeFromFavorites } = useFavorites();\r\n const currency = (constants.site as any).currency || \"USD\";\r\n\r\n if (!product) {\r\n return null;\r\n }\r\n\r\n const price = product.on_sale && product.sale_price\r\n ? product.sale_price\r\n : product.price;\r\n\r\n const handleAddToCart = () => {\r\n addItem(product);\r\n toast.success(t(\"addedToCart\", \"Added to cart!\"));\r\n };\r\n\r\n const handleBuyNow = () => {\r\n addItem(product);\r\n navigate(\"/checkout\");\r\n };\r\n\r\n const handleToggleFavorite = () => {\r\n if (isFavorite(product.id)) {\r\n removeFromFavorites(product.id);\r\n } else {\r\n addToFavorites(product);\r\n }\r\n };\r\n\r\n return (\r\n <div\r\n className={cn(\r\n \"group relative block overflow-hidden h-full flex flex-col\",\r\n className\r\n )}\r\n >\r\n <div className=\"relative\">\r\n <button\r\n onClick={handleToggleFavorite}\r\n className=\"absolute end-4 top-4 z-10 rounded-full bg-background p-1.5 transition hover:text-primary\"\r\n >\r\n <span className=\"sr-only\">{t(\"wishlist\", \"Wishlist\")}</span>\r\n <Heart\r\n className={cn(\r\n \"size-4\",\r\n isFavorite(product.id) && \"fill-current text-red-500\"\r\n )}\r\n />\r\n </button>\r\n\r\n <Link to={`/products/${product.slug}`}>\r\n <img\r\n src={product.images[0] || \"/images/placeholder.png\"}\r\n alt={product.name}\r\n className=\"h-64 w-full object-cover transition duration-500 group-hover:scale-105 sm:h-72\"\r\n />\r\n </Link>\r\n </div>\r\n\r\n <div className=\"relative border border-border bg-background p-6 flex-1 flex flex-col\">\r\n <p>\r\n {formatPrice(price, currency)}\r\n {product.on_sale && product.sale_price && (\r\n <span className=\"text-muted-foreground line-through ml-2\">\r\n {formatPrice(product.price, currency)}\r\n </span>\r\n )}\r\n </p>\r\n\r\n <h3 className=\"mt-1.5 text-lg font-medium\">\r\n <Link to={`/products/${product.slug}`} className=\"hover:underline\">\r\n {product.name}\r\n </Link>\r\n </h3>\r\n\r\n <div className=\"mt-1.5 min-h-[4.5rem]\">\r\n {product.description && (\r\n <p className=\"line-clamp-3 text-muted-foreground text-sm\">\r\n {product.description}\r\n </p>\r\n )}\r\n </div>\r\n\r\n <div className=\"mt-auto pt-4 flex flex-col gap-3\">\r\n <Button\r\n className=\"w-full transition hover:scale-105\"\r\n onClick={handleBuyNow}\r\n >\r\n {t(\"buyNow\", \"Buy Now\")}\r\n </Button>\r\n <Button\r\n variant=\"secondary\"\r\n className=\"w-full transition hover:scale-105\"\r\n onClick={handleAddToCart}\r\n >\r\n {t(\"addToCart\", \"Add to Cart\")}\r\n </Button>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n}\r\n"
|
|
25
26
|
},
|
|
26
27
|
{
|
|
27
28
|
"path": "product-card-detailed/lang/en.json",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "product-detail-page",
|
|
3
|
+
"type": "registry:page",
|
|
4
|
+
"title": "Product Detail Page",
|
|
5
|
+
"description": "Product detail page that fetches product data by slug from URL params. Uses useProductBySlug hook from ecommerce-core and renders ProductDetailBlock. Includes loading skeleton, error handling for not found products, and automatic page title.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"ecommerce-core",
|
|
8
|
+
"product-detail-block"
|
|
9
|
+
],
|
|
10
|
+
"usage": "import { ProductDetailPage } from '@/modules/product-detail-page';\n\n<Route path=\"/products/:slug\" element={<ProductDetailPage />} />\n\n• Uses useProductBySlug() from ecommerce-core\n• Fetches product by slug from URL params\n• Shows loading skeleton while fetching\n• Handles product not found state",
|
|
11
|
+
"route": {
|
|
12
|
+
"path": "/products/:slug",
|
|
13
|
+
"componentName": "ProductDetailPage"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
{
|
|
17
|
+
"path": "product-detail-page/index.ts",
|
|
18
|
+
"type": "registry:index",
|
|
19
|
+
"target": "$modules$/product-detail-page/index.ts",
|
|
20
|
+
"content": "export * from './product-detail-page';\r\nexport { ProductDetailPage as default } from './product-detail-page';\r\n"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": "product-detail-page/product-detail-page.tsx",
|
|
24
|
+
"type": "registry:page",
|
|
25
|
+
"target": "$modules$/product-detail-page/product-detail-page.tsx",
|
|
26
|
+
"content": "import { useParams } from \"react-router\";\r\nimport { useProductBySlug } from \"@/modules/ecommerce-core\";\r\nimport { ProductDetailBlock } from \"@/modules/product-detail-block\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\n\r\nexport function ProductDetailPage() {\r\n const { t } = useTranslation(\"product-detail-page\");\r\n const { slug } = useParams<{ slug: string }>();\r\n const { product, loading, error } = useProductBySlug(slug || \"\");\r\n\r\n usePageTitle({ title: product?.name || t(\"loading\", \"Loading...\") });\r\n\r\n if (loading) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <div className=\"animate-pulse\">\r\n <div className=\"grid lg:grid-cols-2 gap-12\">\r\n <div className=\"aspect-square bg-muted rounded-lg\"></div>\r\n <div className=\"space-y-4\">\r\n <div className=\"h-6 bg-muted rounded w-1/4\"></div>\r\n <div className=\"h-10 bg-muted rounded w-3/4\"></div>\r\n <div className=\"h-4 bg-muted rounded w-1/3\"></div>\r\n <div className=\"h-8 bg-muted rounded w-1/4\"></div>\r\n <div className=\"space-y-2\">\r\n <div className=\"h-4 bg-muted rounded\"></div>\r\n <div className=\"h-4 bg-muted rounded\"></div>\r\n <div className=\"h-4 bg-muted rounded w-2/3\"></div>\r\n </div>\r\n <div className=\"h-12 bg-muted rounded w-1/2\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n if (error || !product) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8 text-center\">\r\n <h1 className=\"text-2xl font-bold mb-4\">{t(\"notFound\", \"Product Not Found\")}</h1>\r\n <p className=\"text-muted-foreground\">{t(\"notFoundDescription\", \"The product you're looking for doesn't exist or has been removed.\")}</p>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <ProductDetailBlock product={product} />\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default ProductDetailPage;\r\n"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"path": "product-detail-page/lang/en.json",
|
|
30
|
+
"type": "registry:lang",
|
|
31
|
+
"target": "$modules$/product-detail-page/lang/en.json",
|
|
32
|
+
"content": "{\r\n \"loading\": \"Loading...\",\r\n \"notFound\": \"Product Not Found\",\r\n \"notFoundDescription\": \"The product you're looking for doesn't exist or has been removed.\"\r\n}\r\n"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"path": "product-detail-page/lang/tr.json",
|
|
36
|
+
"type": "registry:lang",
|
|
37
|
+
"target": "$modules$/product-detail-page/lang/tr.json",
|
|
38
|
+
"content": "{\r\n \"loading\": \"Yükleniyor...\",\r\n \"notFound\": \"Ürün Bulunamadı\",\r\n \"notFoundDescription\": \"Aradığınız ürün mevcut değil veya kaldırılmış.\"\r\n}\r\n"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"exports": {
|
|
42
|
+
"types": [],
|
|
43
|
+
"variables": [
|
|
44
|
+
"ProductDetailPage",
|
|
45
|
+
"default"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
"name": "product-detail-section",
|
|
3
3
|
"type": "registry:component",
|
|
4
4
|
"title": "Product Detail Section",
|
|
5
|
-
"description": "Product detail UI section with image, title, brand, price, description,
|
|
5
|
+
"description": "Product detail UI section with image, title, brand, price, description, rating stars, social share buttons, and add to cart/wishlist actions. Integrated with ecommerce-core for cart and favorites.",
|
|
6
6
|
"dependencies": [
|
|
7
7
|
"lucide-react"
|
|
8
8
|
],
|
|
9
9
|
"registryDependencies": [
|
|
10
10
|
"button",
|
|
11
|
-
"select"
|
|
11
|
+
"select",
|
|
12
|
+
"ecommerce-core"
|
|
12
13
|
],
|
|
13
|
-
"usage": "import { ProductDetailSection } from '@/modules/product-detail-section';\n\n<ProductDetailSection
|
|
14
|
+
"usage": "import { ProductDetailSection } from '@/modules/product-detail-section';\nimport type { Product } from '@/modules/ecommerce-core';\n\n<ProductDetailSection product={product} />\n\n• Uses useCart and useFavorites from ecommerce-core\n• Wishlist toggle shows filled heart when favorited",
|
|
14
15
|
"files": [
|
|
15
16
|
{
|
|
16
17
|
"path": "product-detail-section/index.ts",
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
"path": "product-detail-section/product-detail-section.tsx",
|
|
23
24
|
"type": "registry:component",
|
|
24
25
|
"target": "$modules$/product-detail-section/product-detail-section.tsx",
|
|
25
|
-
"content": "import {
|
|
26
|
+
"content": "import { Star, Heart, Facebook, Twitter, MessageCircle } from \"lucide-react\";\r\nimport { toast } from \"sonner\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport {\r\n useCart,\r\n useFavorites,\r\n formatPrice,\r\n type Product,\r\n} from \"@/modules/ecommerce-core\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface ProductDetailSectionProps {\r\n product: Product;\r\n className?: string;\r\n}\r\n\r\nexport function ProductDetailSection({\r\n product,\r\n className,\r\n}: ProductDetailSectionProps) {\r\n const { t } = useTranslation(\"product-detail-section\");\r\n const { addItem } = useCart();\r\n const { isFavorite, addToFavorites, removeFromFavorites } = useFavorites();\r\n const currency = (constants.site as any).currency || \"USD\";\r\n\r\n if (!product) {\r\n return null;\r\n }\r\n\r\n const price = product.on_sale && product.sale_price\r\n ? product.sale_price\r\n : product.price;\r\n\r\n const handleAddToCart = () => {\r\n addItem(product);\r\n toast.success(t(\"addedToCart\", \"Added to cart!\"));\r\n };\r\n\r\n const handleToggleFavorite = () => {\r\n if (isFavorite(product.id)) {\r\n removeFromFavorites(product.id);\r\n } else {\r\n addToFavorites(product);\r\n }\r\n };\r\n\r\n const renderStars = (rating: number) => {\r\n return Array.from({ length: 5 }, (_, i) => (\r\n <Star\r\n key={i}\r\n className={cn(\r\n \"w-4 h-4\",\r\n i < Math.floor(rating) ? \"fill-primary text-primary\" : \"text-primary\"\r\n )}\r\n />\r\n ));\r\n };\r\n\r\n return (\r\n <section className={cn(\"py-24\", className)}>\r\n <div className=\"container px-5 mx-auto\">\r\n <div className=\"lg:w-4/5 mx-auto flex flex-wrap\">\r\n <img\r\n alt={product.name}\r\n className=\"lg:w-1/2 w-full lg:h-auto h-64 object-cover object-center rounded\"\r\n src={product.images[0] || \"/images/placeholder.png\"}\r\n />\r\n <div className=\"lg:w-1/2 w-full lg:pl-10 lg:py-6 mt-6 lg:mt-0\">\r\n {product.brand && (\r\n <h2 className=\"text-sm text-muted-foreground tracking-widest uppercase\">\r\n {product.brand}\r\n </h2>\r\n )}\r\n <h1 className=\"text-3xl font-medium mb-1\">{product.name}</h1>\r\n\r\n <div className=\"flex mb-4\">\r\n <span className=\"flex items-center\">\r\n {renderStars(product.rating)}\r\n <span className=\"text-muted-foreground ml-3\">\r\n {product.review_count} {t(\"reviews\", \"Reviews\")}\r\n </span>\r\n </span>\r\n <span className=\"flex ml-3 pl-3 py-2 border-l-2 border-border space-x-2\">\r\n <a\r\n href=\"#\"\r\n className=\"text-muted-foreground hover:text-foreground\"\r\n >\r\n <Facebook className=\"w-5 h-5\" />\r\n </a>\r\n <a\r\n href=\"#\"\r\n className=\"text-muted-foreground hover:text-foreground\"\r\n >\r\n <Twitter className=\"w-5 h-5\" />\r\n </a>\r\n <a\r\n href=\"#\"\r\n className=\"text-muted-foreground hover:text-foreground\"\r\n >\r\n <MessageCircle className=\"w-5 h-5\" />\r\n </a>\r\n </span>\r\n </div>\r\n\r\n <p className=\"leading-relaxed text-muted-foreground\">\r\n {product.description}\r\n </p>\r\n\r\n {product.category_name && (\r\n <p className=\"mt-4 text-sm text-muted-foreground\">\r\n {t(\"category\", \"Category\")}: {product.category_name}\r\n </p>\r\n )}\r\n\r\n <div className=\"flex mt-6 items-center pb-5 border-b-2 border-border mb-5\">\r\n {product.stock > 0 ? (\r\n <span className=\"text-sm text-green-600 dark:text-green-400\">\r\n {t(\"inStock\", \"In Stock\")} ({product.stock})\r\n </span>\r\n ) : (\r\n <span className=\"text-sm text-red-600 dark:text-red-400\">\r\n {t(\"outOfStock\", \"Out of Stock\")}\r\n </span>\r\n )}\r\n </div>\r\n\r\n <div className=\"flex items-center\">\r\n <span className=\"font-medium text-2xl\">\r\n {formatPrice(price, currency)}\r\n {product.on_sale && product.sale_price && (\r\n <span className=\"text-lg text-muted-foreground line-through ml-2\">\r\n {formatPrice(product.price, currency)}\r\n </span>\r\n )}\r\n </span>\r\n <Button\r\n onClick={handleAddToCart}\r\n className=\"ml-auto\"\r\n disabled={product.stock === 0}\r\n >\r\n {t(\"addToCart\", \"Add to Cart\")}\r\n </Button>\r\n <Button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n onClick={handleToggleFavorite}\r\n className=\"rounded-full ml-4\"\r\n >\r\n <Heart\r\n className={cn(\r\n \"w-5 h-5\",\r\n isFavorite(product.id) && \"fill-current text-red-500\"\r\n )}\r\n />\r\n </Button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </section>\r\n );\r\n}\r\n"
|
|
26
27
|
},
|
|
27
28
|
{
|
|
28
29
|
"path": "product-detail-section/lang/en.json",
|