@promakeai/cli 0.0.5
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 +212 -0
- package/dist/registry/about-page.json +45 -0
- package/dist/registry/about-section.json +40 -0
- package/dist/registry/animations.json +69 -0
- package/dist/registry/bento-grid-section.json +42 -0
- package/dist/registry/blog-core.json +74 -0
- package/dist/registry/blog-list-page.json +48 -0
- package/dist/registry/blog-section.json +43 -0
- package/dist/registry/cards-carousel-section.json +46 -0
- package/dist/registry/cart-drawer.json +43 -0
- package/dist/registry/cart-page.json +47 -0
- package/dist/registry/category-section.json +43 -0
- package/dist/registry/checkout-page.json +47 -0
- package/dist/registry/contact-info-grid.json +40 -0
- package/dist/registry/contact-page-centered.json +50 -0
- package/dist/registry/contact-page-map-overlay.json +54 -0
- package/dist/registry/contact-page-map-split.json +54 -0
- package/dist/registry/contact-page-split.json +49 -0
- package/dist/registry/contact-page.json +45 -0
- package/dist/registry/content-section.json +40 -0
- package/dist/registry/cookies-page.json +45 -0
- package/dist/registry/cta-section.json +40 -0
- package/dist/registry/docs/about-page.md +32 -0
- package/dist/registry/docs/about-section.md +33 -0
- package/dist/registry/docs/animations.md +44 -0
- package/dist/registry/docs/bento-grid-section.md +40 -0
- package/dist/registry/docs/blog-core.md +37 -0
- package/dist/registry/docs/blog-list-page.md +38 -0
- package/dist/registry/docs/blog-section.md +39 -0
- package/dist/registry/docs/cards-carousel-section.md +39 -0
- package/dist/registry/docs/cart-drawer.md +42 -0
- package/dist/registry/docs/cart-page.md +37 -0
- package/dist/registry/docs/category-section.md +34 -0
- package/dist/registry/docs/checkout-page.md +38 -0
- package/dist/registry/docs/contact-info-grid.md +33 -0
- package/dist/registry/docs/contact-page-centered.md +41 -0
- package/dist/registry/docs/contact-page-map-overlay.md +44 -0
- package/dist/registry/docs/contact-page-map-split.md +44 -0
- package/dist/registry/docs/contact-page-split.md +40 -0
- package/dist/registry/docs/contact-page.md +33 -0
- package/dist/registry/docs/content-section.md +35 -0
- package/dist/registry/docs/cookies-page.md +32 -0
- package/dist/registry/docs/cta-section.md +32 -0
- package/dist/registry/docs/ecommerce-core.md +41 -0
- package/dist/registry/docs/empty-page.md +31 -0
- package/dist/registry/docs/faq-categorized.md +38 -0
- package/dist/registry/docs/faq-simple.md +38 -0
- package/dist/registry/docs/favorites-blog-block.md +38 -0
- package/dist/registry/docs/favorites-ecommerce-block.md +38 -0
- package/dist/registry/docs/feature-section.md +33 -0
- package/dist/registry/docs/featured-products.md +38 -0
- package/dist/registry/docs/footer-detailed.md +33 -0
- package/dist/registry/docs/footer-minimal.md +32 -0
- package/dist/registry/docs/footer.md +32 -0
- package/dist/registry/docs/google-map.md +36 -0
- package/dist/registry/docs/header-centered-pill.md +37 -0
- package/dist/registry/docs/header-ecommerce.md +38 -0
- package/dist/registry/docs/header-mega.md +40 -0
- package/dist/registry/docs/header-minimal.md +38 -0
- package/dist/registry/docs/header-simple.md +32 -0
- package/dist/registry/docs/hero-cta.md +38 -0
- package/dist/registry/docs/hero-gradient.md +33 -0
- package/dist/registry/docs/hero-grid.md +40 -0
- package/dist/registry/docs/hero-profile.md +33 -0
- package/dist/registry/docs/hero.md +32 -0
- package/dist/registry/docs/login-page-split.md +40 -0
- package/dist/registry/docs/login-page.md +39 -0
- package/dist/registry/docs/newsletter-section.md +40 -0
- package/dist/registry/docs/order-card-compact.md +37 -0
- package/dist/registry/docs/order-detail-block.md +37 -0
- package/dist/registry/docs/orders-list-block.md +40 -0
- package/dist/registry/docs/payment-success-block.md +32 -0
- package/dist/registry/docs/post-card.md +37 -0
- package/dist/registry/docs/post-detail-block.md +37 -0
- package/dist/registry/docs/pricing-card.md +37 -0
- package/dist/registry/docs/pricing-section.md +39 -0
- package/dist/registry/docs/privacy-page.md +32 -0
- package/dist/registry/docs/product-card-detailed.md +42 -0
- package/dist/registry/docs/product-card-hover.md +35 -0
- package/dist/registry/docs/product-card.md +37 -0
- package/dist/registry/docs/product-detail-block.md +37 -0
- package/dist/registry/docs/product-detail-section.md +45 -0
- package/dist/registry/docs/products-page.md +39 -0
- package/dist/registry/docs/related-posts-block.md +38 -0
- package/dist/registry/docs/related-products-block.md +38 -0
- package/dist/registry/docs/service-card.md +34 -0
- package/dist/registry/docs/skill-card.md +33 -0
- package/dist/registry/docs/terms-page.md +32 -0
- package/dist/registry/docs/testimonials-carousel.md +40 -0
- package/dist/registry/docs/testimonials-grid.md +39 -0
- package/dist/registry/ecommerce-core.json +95 -0
- package/dist/registry/empty-page.json +45 -0
- package/dist/registry/faq-categorized.json +42 -0
- package/dist/registry/faq-simple.json +42 -0
- package/dist/registry/favorites-blog-block.json +43 -0
- package/dist/registry/favorites-ecommerce-block.json +43 -0
- package/dist/registry/feature-section.json +40 -0
- package/dist/registry/featured-products.json +43 -0
- package/dist/registry/footer-detailed.json +43 -0
- package/dist/registry/footer-minimal.json +40 -0
- package/dist/registry/footer.json +40 -0
- package/dist/registry/google-map.json +31 -0
- package/dist/registry/header-centered-pill.json +45 -0
- package/dist/registry/header-ecommerce.json +42 -0
- package/dist/registry/header-mega.json +47 -0
- package/dist/registry/header-minimal.json +45 -0
- package/dist/registry/header-simple.json +43 -0
- package/dist/registry/hero-cta.json +42 -0
- package/dist/registry/hero-gradient.json +40 -0
- package/dist/registry/hero-grid.json +42 -0
- package/dist/registry/hero-profile.json +62 -0
- package/dist/registry/hero.json +40 -0
- package/dist/registry/index.json +70 -0
- package/dist/registry/login-page-split.json +47 -0
- package/dist/registry/login-page.json +49 -0
- package/dist/registry/newsletter-section.json +44 -0
- package/dist/registry/order-card-compact.json +42 -0
- package/dist/registry/order-detail-block.json +42 -0
- package/dist/registry/orders-list-block.json +45 -0
- package/dist/registry/payment-success-block.json +40 -0
- package/dist/registry/post-card.json +42 -0
- package/dist/registry/post-detail-block.json +42 -0
- package/dist/registry/pricing-card.json +40 -0
- package/dist/registry/pricing-section.json +43 -0
- package/dist/registry/privacy-page.json +45 -0
- package/dist/registry/product-card-detailed.json +45 -0
- package/dist/registry/product-card-hover.json +40 -0
- package/dist/registry/product-card.json +42 -0
- package/dist/registry/product-detail-block.json +42 -0
- package/dist/registry/product-detail-section.json +46 -0
- package/dist/registry/products-page.json +48 -0
- package/dist/registry/related-posts-block.json +43 -0
- package/dist/registry/related-products-block.json +43 -0
- package/dist/registry/service-card.json +28 -0
- package/dist/registry/skill-card.json +28 -0
- package/dist/registry/terms-page.json +45 -0
- package/dist/registry/testimonials-carousel.json +44 -0
- package/dist/registry/testimonials-grid.json +43 -0
- package/package.json +52 -0
- package/template/.env +6 -0
- package/template/.prettierignore +3 -0
- package/template/.prettierrc +1 -0
- package/template/README.md +73 -0
- package/template/bun.lock +1007 -0
- package/template/components.json +22 -0
- package/template/eslint.config.js +32 -0
- package/template/index.html +285 -0
- package/template/package.json +92 -0
- package/template/promake.json +6 -0
- package/template/public/_redirects +1 -0
- package/template/public/data/database.db +0 -0
- package/template/public/favicon.svg +1 -0
- package/template/public/images/placeholder.png +0 -0
- package/template/public/robots.txt +14 -0
- package/template/scripts/init-db.ts +131 -0
- package/template/src/App.tsx +33 -0
- package/template/src/components/Footer.tsx +100 -0
- package/template/src/components/Header.tsx +79 -0
- package/template/src/components/Hero.tsx +69 -0
- package/template/src/components/LanguageSwitcher.tsx +47 -0
- package/template/src/components/Layout.tsx +25 -0
- package/template/src/components/Logo.tsx +64 -0
- package/template/src/components/ThemeSwitcher.tsx +58 -0
- package/template/src/components/ui/accordion.tsx +64 -0
- package/template/src/components/ui/alert-dialog.tsx +155 -0
- package/template/src/components/ui/alert.tsx +66 -0
- package/template/src/components/ui/aspect-ratio.tsx +11 -0
- package/template/src/components/ui/avatar.tsx +51 -0
- package/template/src/components/ui/badge.tsx +46 -0
- package/template/src/components/ui/breadcrumb.tsx +109 -0
- package/template/src/components/ui/button-group.tsx +83 -0
- package/template/src/components/ui/button.tsx +62 -0
- package/template/src/components/ui/calendar.tsx +220 -0
- package/template/src/components/ui/card.tsx +92 -0
- package/template/src/components/ui/carousel.tsx +239 -0
- package/template/src/components/ui/chart.tsx +357 -0
- package/template/src/components/ui/checkbox.tsx +32 -0
- package/template/src/components/ui/collapsible.tsx +31 -0
- package/template/src/components/ui/command.tsx +182 -0
- package/template/src/components/ui/context-menu.tsx +252 -0
- package/template/src/components/ui/dialog.tsx +141 -0
- package/template/src/components/ui/drawer.tsx +135 -0
- package/template/src/components/ui/dropdown-menu.tsx +255 -0
- package/template/src/components/ui/empty.tsx +104 -0
- package/template/src/components/ui/field.tsx +246 -0
- package/template/src/components/ui/form.tsx +168 -0
- package/template/src/components/ui/hover-card.tsx +44 -0
- package/template/src/components/ui/input-group.tsx +170 -0
- package/template/src/components/ui/input-otp.tsx +75 -0
- package/template/src/components/ui/input.tsx +21 -0
- package/template/src/components/ui/item.tsx +193 -0
- package/template/src/components/ui/kbd.tsx +28 -0
- package/template/src/components/ui/label.tsx +24 -0
- package/template/src/components/ui/menubar.tsx +274 -0
- package/template/src/components/ui/navigation-menu.tsx +168 -0
- package/template/src/components/ui/pagination.tsx +127 -0
- package/template/src/components/ui/popover.tsx +48 -0
- package/template/src/components/ui/progress.tsx +29 -0
- package/template/src/components/ui/radio-group.tsx +45 -0
- package/template/src/components/ui/resizable.tsx +54 -0
- package/template/src/components/ui/scroll-area.tsx +58 -0
- package/template/src/components/ui/select.tsx +188 -0
- package/template/src/components/ui/separator.tsx +28 -0
- package/template/src/components/ui/sheet.tsx +137 -0
- package/template/src/components/ui/sidebar.tsx +726 -0
- package/template/src/components/ui/skeleton.tsx +13 -0
- package/template/src/components/ui/slider.tsx +63 -0
- package/template/src/components/ui/sonner.tsx +38 -0
- package/template/src/components/ui/spinner.tsx +16 -0
- package/template/src/components/ui/switch.tsx +31 -0
- package/template/src/components/ui/table.tsx +114 -0
- package/template/src/components/ui/tabs.tsx +66 -0
- package/template/src/components/ui/textarea.tsx +18 -0
- package/template/src/components/ui/toggle-group.tsx +81 -0
- package/template/src/components/ui/toggle.tsx +45 -0
- package/template/src/components/ui/tooltip.tsx +61 -0
- package/template/src/constants/constants.json +58 -0
- package/template/src/hooks/use-is-mobile.ts +21 -0
- package/template/src/hooks/use-page-title.ts +49 -0
- package/template/src/hooks/use-theme.ts +57 -0
- package/template/src/index.css +128 -0
- package/template/src/lang/en/about.json +4 -0
- package/template/src/lang/en/contact.json +39 -0
- package/template/src/lang/en/cookies.json +4 -0
- package/template/src/lang/en/footer.json +12 -0
- package/template/src/lang/en/forgotPassword.json +37 -0
- package/template/src/lang/en/header.json +10 -0
- package/template/src/lang/en/hero.json +8 -0
- package/template/src/lang/en/index.json +30 -0
- package/template/src/lang/en/login.json +18 -0
- package/template/src/lang/en/notfound.json +7 -0
- package/template/src/lang/en/privacy.json +4 -0
- package/template/src/lang/en/register.json +25 -0
- package/template/src/lang/en/terms.json +4 -0
- package/template/src/lang/index.ts +86 -0
- package/template/src/lang/tr/about.json +4 -0
- package/template/src/lang/tr/contact.json +39 -0
- package/template/src/lang/tr/cookies.json +4 -0
- package/template/src/lang/tr/footer.json +12 -0
- package/template/src/lang/tr/forgotPassword.json +37 -0
- package/template/src/lang/tr/header.json +10 -0
- package/template/src/lang/tr/hero.json +8 -0
- package/template/src/lang/tr/index.json +30 -0
- package/template/src/lang/tr/login.json +18 -0
- package/template/src/lang/tr/notfound.json +7 -0
- package/template/src/lang/tr/privacy.json +4 -0
- package/template/src/lang/tr/register.json +25 -0
- package/template/src/lang/tr/terms.json +4 -0
- package/template/src/lib/api.ts +237 -0
- package/template/src/lib/storage.ts +109 -0
- package/template/src/lib/utils.ts +15 -0
- package/template/src/main.tsx +13 -0
- package/template/src/modules/api/USAGE.md +515 -0
- package/template/src/modules/api/customer-client.ts +20 -0
- package/template/src/modules/api/get-error-message.ts +18 -0
- package/template/src/modules/api/validation/en.json +29 -0
- package/template/src/modules/api/validation/tr.json +29 -0
- package/template/src/modules/auth/USAGE.md +248 -0
- package/template/src/modules/auth/auth-header-menu.tsx +123 -0
- package/template/src/modules/auth/auth-store.ts +57 -0
- package/template/src/modules/auth/forgot-password-page.tsx +371 -0
- package/template/src/modules/auth/login-page.tsx +183 -0
- package/template/src/modules/auth/register-page.tsx +252 -0
- package/template/src/modules/auth/use-auth.ts +273 -0
- package/template/src/modules/db/adapters/IDataAdapter.ts +26 -0
- package/template/src/modules/db/adapters/SqliteAdapter.ts +364 -0
- package/template/src/modules/db/adapters/index.ts +2 -0
- package/template/src/modules/db/config.ts +59 -0
- package/template/src/modules/db/core/DataManager.ts +125 -0
- package/template/src/modules/db/core/types.ts +101 -0
- package/template/src/modules/db/index.ts +42 -0
- package/template/src/modules/db/react/QueryProvider.tsx +16 -0
- package/template/src/modules/db/react/index.ts +23 -0
- package/template/src/modules/db/react/queryClient.ts +64 -0
- package/template/src/modules/db/react/useRepository.ts +400 -0
- package/template/src/modules/db/utils/parsers.ts +96 -0
- package/template/src/pages/Index.tsx +108 -0
- package/template/src/pages/NotFound.tsx +35 -0
- package/template/src/router.tsx +14 -0
- package/template/src/types/index.ts +0 -0
- package/template/src/vite-env.d.ts +1 -0
- package/template/tsconfig.app.json +32 -0
- package/template/tsconfig.json +17 -0
- package/template/tsconfig.node.json +26 -0
- package/template/vite.config.ts +74 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pricing-section",
|
|
3
|
+
"type": "registry:component",
|
|
4
|
+
"title": "Pricing Section",
|
|
5
|
+
"description": "Three-tier pricing section with feature comparison. Features Starter, Professional (highlighted as popular), and Enterprise tiers. Each card shows price, description, feature list with checkmarks, and CTA button. Includes money-back guarantee text.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"card",
|
|
8
|
+
"badge"
|
|
9
|
+
],
|
|
10
|
+
"usage": "import { PricingSection } from '@/modules/pricing-section';\n\n<PricingSection />\n\n- 3-tier pricing cards\n- Popular tier highlighted\n- Feature checkmarks\n- Customize prices via lang files",
|
|
11
|
+
"files": [
|
|
12
|
+
{
|
|
13
|
+
"path": "pricing-section/index.ts",
|
|
14
|
+
"type": "registry:index",
|
|
15
|
+
"target": "$modules$/pricing-section/index.ts",
|
|
16
|
+
"content": "export * from './pricing-section';\r\n"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"path": "pricing-section/pricing-section.tsx",
|
|
20
|
+
"type": "registry:component",
|
|
21
|
+
"target": "$modules$/pricing-section/pricing-section.tsx",
|
|
22
|
+
"content": "import { Link } from \"react-router\";\r\nimport { ArrowRight, Check } from \"lucide-react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport {\r\n Card,\r\n CardContent,\r\n CardFooter,\r\n CardHeader,\r\n CardTitle,\r\n} from \"@/components/ui/card\";\r\nimport { Badge } from \"@/components/ui/badge\";\r\n\r\ninterface PricingSectionProps {\r\n className?: string;\r\n}\r\n\r\nexport function PricingSection({ className }: PricingSectionProps) {\r\n const { t } = useTranslation(\"pricing-section\");\r\n\r\n const tiers = [\r\n {\r\n id: \"starter\",\r\n name: t(\"starterName\", \"Starter\"),\r\n description: t(\r\n \"starterDesc\",\r\n \"Perfect for individuals and small projects\"\r\n ),\r\n price: t(\"starterPrice\", \"$9\"),\r\n frequency: t(\"perMonth\", \"/month\"),\r\n features: [\r\n t(\"starterFeature1\", \"Up to 3 projects\"),\r\n t(\"starterFeature2\", \"Basic analytics\"),\r\n t(\"starterFeature3\", \"Email support\"),\r\n t(\"starterFeature4\", \"1GB storage\"),\r\n ],\r\n popular: false,\r\n },\r\n {\r\n id: \"professional\",\r\n name: t(\"proName\", \"Professional\"),\r\n description: t(\"proDesc\", \"Best for growing businesses and teams\"),\r\n price: t(\"proPrice\", \"$29\"),\r\n frequency: t(\"perMonth\", \"/month\"),\r\n features: [\r\n t(\"proFeature1\", \"Unlimited projects\"),\r\n t(\"proFeature2\", \"Advanced analytics\"),\r\n t(\"proFeature3\", \"Priority support\"),\r\n t(\"proFeature4\", \"10GB storage\"),\r\n t(\"proFeature5\", \"API access\"),\r\n ],\r\n popular: true,\r\n },\r\n {\r\n id: \"enterprise\",\r\n name: t(\"enterpriseName\", \"Enterprise\"),\r\n description: t(\r\n \"enterpriseDesc\",\r\n \"For large organizations with custom needs\"\r\n ),\r\n price: t(\"enterprisePrice\", \"$99\"),\r\n frequency: t(\"perMonth\", \"/month\"),\r\n features: [\r\n t(\"enterpriseFeature1\", \"Everything in Pro\"),\r\n t(\"enterpriseFeature2\", \"Unlimited storage\"),\r\n t(\"enterpriseFeature3\", \"24/7 phone support\"),\r\n t(\"enterpriseFeature4\", \"Custom integrations\"),\r\n t(\"enterpriseFeature5\", \"Dedicated manager\"),\r\n t(\"enterpriseFeature6\", \"SLA guarantee\"),\r\n ],\r\n popular: false,\r\n },\r\n ];\r\n\r\n return (\r\n <section className={cn(\"py-16 md:py-24\", className)}>\r\n <div className=\"container mx-auto px-4\">\r\n <div className=\"text-center mb-12\">\r\n <p className=\"text-sm font-medium text-primary mb-2\">\r\n {t(\"label\", \"Pricing\")}\r\n </p>\r\n <h2 className=\"text-3xl font-bold md:text-4xl lg:text-5xl mb-4\">\r\n {t(\"title\", \"Simple, transparent pricing\")}\r\n </h2>\r\n <p className=\"text-muted-foreground max-w-2xl mx-auto\">\r\n {t(\r\n \"subtitle\",\r\n \"Choose the plan that's right for you. All plans include a 14-day free trial.\"\r\n )}\r\n </p>\r\n </div>\r\n\r\n <div className=\"grid md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto\">\r\n {tiers.map((tier) => (\r\n <Card\r\n key={tier.id}\r\n className={cn(\r\n \"relative overflow-hidden\",\r\n tier.popular && \"border-primary shadow-lg scale-105\"\r\n )}\r\n >\r\n {tier.popular && (\r\n <div className=\"absolute top-0 right-0\">\r\n <Badge className=\"rounded-full\">\r\n {t(\"popular\", \"Most Popular\")}\r\n </Badge>\r\n </div>\r\n )}\r\n\r\n <CardHeader>\r\n <CardTitle className=\"text-xl font-bold\">{tier.name}</CardTitle>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {tier.description}\r\n </p>\r\n </CardHeader>\r\n\r\n <CardContent>\r\n <div className=\"flex items-baseline gap-1 mb-6\">\r\n <span className=\"text-4xl font-bold\">{tier.price}</span>\r\n <span className=\"text-muted-foreground text-sm\">\r\n {tier.frequency}\r\n </span>\r\n </div>\r\n\r\n <ul className=\"space-y-3\">\r\n {tier.features.map((feature, index) => (\r\n <li key={index} className=\"flex items-center gap-2\">\r\n <Check className=\"h-4 w-4 text-primary flex-shrink-0\" />\r\n <span className=\"text-sm text-muted-foreground\">\r\n {feature}\r\n </span>\r\n </li>\r\n ))}\r\n </ul>\r\n </CardContent>\r\n\r\n <CardFooter>\r\n <Button\r\n asChild\r\n className=\"w-full\"\r\n variant={tier.popular ? \"default\" : \"outline\"}\r\n >\r\n <Link to=\"/register\">\r\n {t(\"cta\", \"Get Started\")}\r\n <ArrowRight className=\"ml-2 h-4 w-4\" />\r\n </Link>\r\n </Button>\r\n </CardFooter>\r\n </Card>\r\n ))}\r\n </div>\r\n\r\n <p className=\"text-center text-sm text-muted-foreground mt-8\">\r\n {t(\"guarantee\", \"30-day money-back guarantee. No questions asked.\")}\r\n </p>\r\n </div>\r\n </section>\r\n );\r\n}\r\n"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"path": "pricing-section/lang/en.json",
|
|
26
|
+
"type": "registry:lang",
|
|
27
|
+
"target": "$modules$/pricing-section/lang/en.json",
|
|
28
|
+
"content": "{\r\n \"label\": \"Pricing\",\r\n \"title\": \"AI will customize this pricing title based on your site needs\",\r\n \"subtitle\": \"This subtitle will be replaced with pricing information relevant to your service offerings.\",\r\n \"perMonth\": \"/month\",\r\n \"popular\": \"Most Popular\",\r\n \"cta\": \"Get Started\",\r\n \"guarantee\": \"Lorem ipsum dolor sit amet. AI will customize guarantee text based on your policies.\",\r\n \"starterName\": \"Starter\",\r\n \"starterDesc\": \"AI will customize this plan description based on your pricing tiers\",\r\n \"starterPrice\": \"$9\",\r\n \"starterFeature1\": \"Replace with your starter plan feature\",\r\n \"starterFeature2\": \"This feature will be customized by AI\",\r\n \"starterFeature3\": \"Placeholder feature text\",\r\n \"starterFeature4\": \"AI will replace this with relevant feature\",\r\n \"proName\": \"Pro\",\r\n \"proDesc\": \"This description will be customized based on your mid-tier offerings\",\r\n \"proPrice\": \"$29\",\r\n \"proFeature1\": \"Customize this plan feature\",\r\n \"proFeature2\": \"AI will generate appropriate feature description\",\r\n \"proFeature3\": \"Placeholder text for plan features\",\r\n \"proFeature4\": \"Replace with actual plan feature\",\r\n \"proFeature5\": \"AI will customize this feature based on your plan\",\r\n \"enterpriseName\": \"Advanced\",\r\n \"enterpriseDesc\": \"AI will customize this for your advanced offering\",\r\n \"enterprisePrice\": \"$99\",\r\n \"enterpriseFeature1\": \"Advanced plan feature placeholder\",\r\n \"enterpriseFeature2\": \"This will be replaced by AI with advanced plan features\",\r\n \"enterpriseFeature3\": \"Customize advanced plan feature description\",\r\n \"enterpriseFeature4\": \"AI will generate appropriate advanced plan feature\",\r\n \"enterpriseFeature5\": \"Placeholder for advanced plan feature\",\r\n \"enterpriseFeature6\": \"Replace with an actual advanced plan feature\"\r\n}\r\n"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"path": "pricing-section/lang/tr.json",
|
|
32
|
+
"type": "registry:lang",
|
|
33
|
+
"target": "$modules$/pricing-section/lang/tr.json",
|
|
34
|
+
"content": "{\r\n \"label\": \"Fiyatlandırma\",\r\n \"title\": \"AI bu fiyatlandırma başlığını site ihtiyaçlarınıza göre özelleştirecektir\",\r\n \"subtitle\": \"Bu alt başlık hizmet tekliflerinizle ilgili fiyatlandırma bilgileriyle değiştirilecektir.\",\r\n \"perMonth\": \"/ay\",\r\n \"popular\": \"En Popüler\",\r\n \"cta\": \"Başlayın\",\r\n \"guarantee\": \"Lorem ipsum dolor sit amet. AI garanti metnini politikalarınıza göre özelleştirecektir.\",\r\n \"starterName\": \"Başlangıç\",\r\n \"starterDesc\": \"AI bu plan açıklamasını fiyatlandırma kademelerinize göre özelleştirecektir\",\r\n \"starterPrice\": \"99₺\",\r\n \"starterFeature1\": \"Başlangıç planı özelliğinizle değiştirin\",\r\n \"starterFeature2\": \"Bu özellik AI tarafından özelleştirilecektir\",\r\n \"starterFeature3\": \"Placeholder özellik metni\",\r\n \"starterFeature4\": \"AI bunu ilgili özellikle değiştirecektir\",\r\n \"proName\": \"Pro\",\r\n \"proDesc\": \"Bu açıklama orta seviye tekliflerinize göre özelleştirilecektir\",\r\n \"proPrice\": \"299₺\",\r\n \"proFeature1\": \"Bu plan özelliğini özelleştirin\",\r\n \"proFeature2\": \"AI uygun özellik açıklaması üretecektir\",\r\n \"proFeature3\": \"Plan özellikleri için placeholder metin\",\r\n \"proFeature4\": \"Gerçek plan özelliğiyle değiştirin\",\r\n \"proFeature5\": \"AI bu özelliği planınıza göre özelleştirecektir\",\r\n \"enterpriseName\": \"Gelişmiş\",\r\n \"enterpriseDesc\": \"AI bunu gelişmiş teklifiniz için özelleştirecektir\",\r\n \"enterprisePrice\": \"999₺\",\r\n \"enterpriseFeature1\": \"Gelişmiş plan özelliği placeholder\",\r\n \"enterpriseFeature2\": \"Bu AI tarafından gelişmiş plan özellikleriyle değiştirilecektir\",\r\n \"enterpriseFeature3\": \"Gelişmiş plan özelliği açıklamasını özelleştirin\",\r\n \"enterpriseFeature4\": \"AI uygun gelişmiş plan özelliği üretecektir\",\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
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"exports": {
|
|
38
|
+
"types": [],
|
|
39
|
+
"variables": [
|
|
40
|
+
"PricingSection"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "privacy-page",
|
|
3
|
+
"type": "registry:page",
|
|
4
|
+
"title": "Privacy Policy Page",
|
|
5
|
+
"description": "GDPR/CCPA-friendly privacy policy page with table of contents, collapsible sections, and legal text formatting. Covers data collection, usage, cookies, third-party services, user rights, and contact information. Last updated date and version tracking included. Professionally structured for compliance.",
|
|
6
|
+
"registryDependencies": [],
|
|
7
|
+
"usage": "import { PrivacyPage } from '@/modules/privacy-page';\n\n<Route path=\"/privacy\" element={<PrivacyPage />} />\n\n• GDPR/CCPA compliant structure\n• Sections: data collection, cookies, rights, contact\n• Edit content in lang/en.json",
|
|
8
|
+
"route": {
|
|
9
|
+
"path": "/privacy",
|
|
10
|
+
"componentName": "PrivacyPage"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
{
|
|
14
|
+
"path": "privacy-page/index.ts",
|
|
15
|
+
"type": "registry:index",
|
|
16
|
+
"target": "$modules$/privacy-page/index.ts",
|
|
17
|
+
"content": "export * from './privacy-page';\r\nexport { PrivacyPage as default } from './privacy-page';\r\n"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"path": "privacy-page/privacy-page.tsx",
|
|
21
|
+
"type": "registry:page",
|
|
22
|
+
"target": "$modules$/privacy-page/privacy-page.tsx",
|
|
23
|
+
"content": "import { useTranslation } from \"react-i18next\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { Layout } from \"@/components/Layout\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Shield, Eye, Lock, UserCheck, Mail, FileText } from \"lucide-react\";\nimport { FadeIn, StaggerContainer, StaggerItem } from \"@/modules/animations\";\n\nexport function PrivacyPage() {\n const { t } = useTranslation(\"privacy-page\");\n usePageTitle({ title: t(\"title\") });\n\n const sections = [\n { icon: Eye, titleKey: \"collectionTitle\", contentKey: \"collectionContent\" },\n { icon: FileText, titleKey: \"usageTitle\", contentKey: \"usageContent\" },\n { icon: Lock, titleKey: \"securityTitle\", contentKey: \"securityContent\" },\n { icon: UserCheck, titleKey: \"rightsTitle\", contentKey: \"rightsContent\" },\n { icon: Shield, titleKey: \"cookiesTitle\", contentKey: \"cookiesContent\" },\n { icon: Mail, titleKey: \"contactTitle\", contentKey: \"contactContent\" },\n ];\n\n return (\n <Layout>\n <div className=\"min-h-screen bg-muted/30 py-12\">\n <div className=\"container mx-auto px-4\">\n {/* Header */}\n <FadeIn className=\"text-center mb-12\">\n <div className=\"w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-4\">\n <Shield className=\"w-8 h-8 text-primary\" />\n </div>\n <h1 className=\"text-4xl font-bold text-foreground mb-4\">\n {t(\"title\")}\n </h1>\n <p className=\"text-muted-foreground\">\n {t(\"lastUpdated\")}: {t(\"updateDate\")}\n </p>\n </FadeIn>\n\n {/* Introduction */}\n <FadeIn delay={0.1} className=\"max-w-4xl mx-auto mb-12\">\n <Card>\n <CardContent className=\"p-8\">\n <p className=\"text-muted-foreground leading-relaxed\">\n {t(\"introduction\")}\n </p>\n </CardContent>\n </Card>\n </FadeIn>\n\n {/* Sections */}\n <StaggerContainer className=\"max-w-4xl mx-auto space-y-6\">\n {sections.map(({ icon: Icon, titleKey, contentKey }, index) => (\n <StaggerItem key={titleKey}>\n <Card>\n <CardContent className=\"p-8\">\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center flex-shrink-0\">\n <Icon className=\"w-5 h-5 text-primary\" />\n </div>\n <div className=\"flex-1\">\n <h2 className=\"text-xl font-semibold mb-3\">\n {index + 1}. {t(titleKey)}\n </h2>\n <p className=\"text-muted-foreground\">{t(contentKey)}</p>\n </div>\n </div>\n </CardContent>\n </Card>\n </StaggerItem>\n ))}\n </StaggerContainer>\n\n {/* Footer Note */}\n <FadeIn className=\"max-w-4xl mx-auto mt-12 text-center\">\n <p className=\"text-sm text-muted-foreground\">{t(\"footerNote\")}</p>\n </FadeIn>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default PrivacyPage;\n"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"path": "privacy-page/lang/en.json",
|
|
27
|
+
"type": "registry:lang",
|
|
28
|
+
"target": "$modules$/privacy-page/lang/en.json",
|
|
29
|
+
"content": "{\r\n \"title\": \"Privacy Policy\",\r\n \"lastUpdated\": \"Last Updated\",\r\n \"updateDate\": \"January 2026\",\r\n \"introduction\": \"We are committed to protecting your privacy and ensuring the security of your personal information. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you visit our website or use our services.\",\r\n \"collectionTitle\": \"Information We Collect\",\r\n \"collectionContent\": \"We collect information that you provide directly to us, including personal identification information such as name, email address, and phone number. We also collect account credentials, payment information, communications with us, and device/usage data automatically when you use our services.\",\r\n \"usageTitle\": \"How We Use Your Information\",\r\n \"usageContent\": \"We use the information we collect to provide, maintain, and improve our services. This includes processing transactions, sending promotional communications with your consent, responding to your requests, monitoring usage trends, and detecting fraudulent activities.\",\r\n \"securityTitle\": \"Data Security\",\r\n \"securityContent\": \"We implement appropriate technical and organizational security measures to protect your personal information. This includes SSL/TLS encryption for data transmission, secure data storage with access controls, regular security assessments, and employee training on data protection.\",\r\n \"rightsTitle\": \"Your Rights\",\r\n \"rightsContent\": \"You have certain rights regarding your personal information subject to local data protection laws. These include the right to access and receive a copy of your data, rectify inaccurate information, request deletion, object to processing, and data portability to another service provider.\",\r\n \"cookiesTitle\": \"Cookies and Tracking\",\r\n \"cookiesContent\": \"We use cookies and similar tracking technologies to collect information about your browsing activities. This includes essential cookies for functionality, analytics cookies to understand user behavior, and marketing cookies for personalized advertising. You can manage preferences in your browser settings.\",\r\n \"contactTitle\": \"Contact Us\",\r\n \"contactContent\": \"If you have any questions about this Privacy Policy or our data practices, please contact us through our website contact form, by email, or through the contact information provided on our site.\",\r\n \"footerNote\": \"This privacy policy is subject to change. We will notify you of any material changes by posting the new policy on this page.\"\r\n}\r\n"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"path": "privacy-page/lang/tr.json",
|
|
33
|
+
"type": "registry:lang",
|
|
34
|
+
"target": "$modules$/privacy-page/lang/tr.json",
|
|
35
|
+
"content": "{\r\n \"title\": \"Gizlilik Politikası\",\r\n \"lastUpdated\": \"Son Güncelleme\",\r\n \"updateDate\": \"Ocak 2026\",\r\n \"introduction\": \"Gizliliğinizi korumaya ve kişisel bilgilerinizin güvenliğini sağlamaya kararlıyız. Bu Gizlilik Politikası, web sitemizi ziyaret ettiğinizde veya hizmetlerimizi kullandığınızda bilgilerinizi nasıl topladığımızı, kullandığımızı, açıkladığımızı ve koruduğumuzu açıklamaktadır.\",\r\n \"collectionTitle\": \"Topladığımız Bilgiler\",\r\n \"collectionContent\": \"Bize doğrudan sağladığınız bilgileri topluyoruz; bunlar arasında ad, e-posta adresi ve telefon numarası gibi kişisel kimlik bilgileri bulunmaktadır. Ayrıca hesap bilgilerini, ödeme bilgilerini, bizimle olan iletişimlerinizi ve hizmetlerimizi kullandığınızda otomatik olarak cihaz/kullanım verilerini topluyoruz.\",\r\n \"usageTitle\": \"Bilgilerinizi Nasıl Kullanıyoruz\",\r\n \"usageContent\": \"Topladığımız bilgileri hizmetlerimizi sağlamak, sürdürmek ve iyileştirmek için kullanıyoruz. Bu, işlemleri işlemeyi, izninizle promosyon iletişimleri göndermeyi, taleplerinize yanıt vermeyi, kullanım eğilimlerini izlemeyi ve dolandırıcılık faaliyetlerini tespit etmeyi içerir.\",\r\n \"securityTitle\": \"Veri Güvenliği\",\r\n \"securityContent\": \"Kişisel bilgilerinizi korumak için uygun teknik ve organizasyonel güvenlik önlemleri uyguluyoruz. Bu, veri iletimi için SSL/TLS şifreleme, erişim kontrolleri ile güvenli veri depolama, düzenli güvenlik değerlendirmeleri ve veri koruma konusunda çalışan eğitimini içerir.\",\r\n \"rightsTitle\": \"Haklarınız\",\r\n \"rightsContent\": \"Yerel veri koruma yasalarına tabi olarak kişisel bilgilerinizle ilgili belirli haklara sahipsiniz. Bunlar arasında verilerinize erişme ve kopyasını alma, yanlış bilgileri düzeltme, silme talep etme, işlemeye itiraz etme ve başka bir hizmet sağlayıcısına veri taşınabilirliği hakları bulunmaktadır.\",\r\n \"cookiesTitle\": \"Çerezler ve İzleme\",\r\n \"cookiesContent\": \"Tarama faaliyetleriniz hakkında bilgi toplamak için çerezler ve benzer izleme teknolojileri kullanıyoruz. Bu, işlevsellik için gerekli çerezleri, kullanıcı davranışını anlamak için analitik çerezleri ve kişiselleştirilmiş reklamcılık için pazarlama çerezlerini içerir. Tercihlerinizi tarayıcı ayarlarınızdan yönetebilirsiniz.\",\r\n \"contactTitle\": \"Bize Ulaşın\",\r\n \"contactContent\": \"Bu Gizlilik Politikası veya veri uygulamalarımız hakkında sorularınız varsa, lütfen web sitemizdeki iletişim formu, e-posta veya sitemizde sağlanan iletişim bilgileri aracılığıyla bizimle iletişime geçin.\",\r\n \"footerNote\": \"Bu gizlilik politikası değişikliğe tabidir. Herhangi bir önemli değişikliği bu sayfada yeni politikayı yayınlayarak size bildireceğiz.\"\r\n}\r\n"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"exports": {
|
|
39
|
+
"types": [],
|
|
40
|
+
"variables": [
|
|
41
|
+
"PrivacyPage",
|
|
42
|
+
"default"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "product-card-detailed",
|
|
3
|
+
"type": "registry:component",
|
|
4
|
+
"title": "Product Card Detailed",
|
|
5
|
+
"description": "Detailed product card with wishlist button, hover scale effect, price with optional discount, description, and Add to Cart/Buy Now action buttons. Perfect for product grids with more information.",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"lucide-react"
|
|
8
|
+
],
|
|
9
|
+
"registryDependencies": [
|
|
10
|
+
"button"
|
|
11
|
+
],
|
|
12
|
+
"usage": "import { ProductCardDetailed } from '@/modules/product-card-detailed';\n\n<ProductCardDetailed\n href=\"/products/1\"\n image=\"/images/product.jpg\"\n title=\"Wireless Headphones\"\n price={49.99}\n originalPrice={80}\n description=\"Product description here...\"\n onAddToCart={() => {}}\n onBuyNow={() => {}}\n/>",
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "product-card-detailed/index.ts",
|
|
16
|
+
"type": "registry:index",
|
|
17
|
+
"target": "$modules$/product-card-detailed/index.ts",
|
|
18
|
+
"content": "export * from './product-card-detailed';\r\n"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"path": "product-card-detailed/product-card-detailed.tsx",
|
|
22
|
+
"type": "registry:component",
|
|
23
|
+
"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 href: string;\r\n image: string;\r\n title: string;\r\n price: number;\r\n originalPrice?: number;\r\n currency?: string;\r\n description?: string;\r\n onAddToCart?: () => void;\r\n onBuyNow?: () => void;\r\n onWishlist?: () => void;\r\n className?: string;\r\n}\r\n\r\nexport function ProductCardDetailed({\r\n href,\r\n image,\r\n title,\r\n price,\r\n originalPrice,\r\n currency = \"$\",\r\n description,\r\n onAddToCart,\r\n onBuyNow,\r\n onWishlist,\r\n className,\r\n}: ProductCardDetailedProps) {\r\n const { t } = useTranslation(\"product-card-detailed\");\r\n\r\n return (\r\n <div className={cn(\"group relative block overflow-hidden h-full flex flex-col\", className)}>\r\n <div className=\"relative\">\r\n <button\r\n onClick={onWishlist}\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 className=\"size-4\" />\r\n </button>\r\n\r\n <Link to={href}>\r\n <img\r\n src={image}\r\n alt={title}\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 {currency}{price.toFixed(2)}\r\n {originalPrice && (\r\n <span className=\"text-muted-foreground line-through ml-2\">\r\n {currency}{originalPrice.toFixed(2)}\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={href} className=\"hover:underline\">\r\n {title}\r\n </Link>\r\n </h3>\r\n\r\n <div className=\"mt-1.5 min-h-[4.5rem]\">\r\n {description && (\r\n <p className=\"line-clamp-3 text-muted-foreground text-sm\">\r\n {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={onBuyNow}\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={onAddToCart}\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
|
+
{
|
|
27
|
+
"path": "product-card-detailed/lang/en.json",
|
|
28
|
+
"type": "registry:lang",
|
|
29
|
+
"target": "$modules$/product-card-detailed/lang/en.json",
|
|
30
|
+
"content": "{\r\n \"wishlist\": \"Wishlist\",\r\n \"addToCart\": \"Add to Cart\",\r\n \"buyNow\": \"Buy Now\"\r\n}\r\n"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"path": "product-card-detailed/lang/tr.json",
|
|
34
|
+
"type": "registry:lang",
|
|
35
|
+
"target": "$modules$/product-card-detailed/lang/tr.json",
|
|
36
|
+
"content": "{\r\n \"wishlist\": \"Favorilere Ekle\",\r\n \"addToCart\": \"Sepete Ekle\",\r\n \"buyNow\": \"Hemen Al\"\r\n}\r\n"
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"exports": {
|
|
40
|
+
"types": [],
|
|
41
|
+
"variables": [
|
|
42
|
+
"ProductCardDetailed"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "product-card-hover",
|
|
3
|
+
"type": "registry:component",
|
|
4
|
+
"title": "Product Card Hover",
|
|
5
|
+
"description": "Product card with image swap on hover effect. Shows default image and reveals alternate image on mouse hover with smooth opacity transition. Displays product name, price, and optional color count.",
|
|
6
|
+
"registryDependencies": [],
|
|
7
|
+
"usage": "import { ProductCardHover } from '@/modules/product-card-hover';\n\n<ProductCardHover\n href=\"/products/1\"\n image=\"/images/product-1.jpg\"\n hoverImage=\"/images/product-1-alt.jpg\"\n title=\"Limited Edition Sports Trainer\"\n price={189.99}\n colorCount={6}\n/>",
|
|
8
|
+
"files": [
|
|
9
|
+
{
|
|
10
|
+
"path": "product-card-hover/index.ts",
|
|
11
|
+
"type": "registry:index",
|
|
12
|
+
"target": "$modules$/product-card-hover/index.ts",
|
|
13
|
+
"content": "export * from './product-card-hover';\r\n"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"path": "product-card-hover/product-card-hover.tsx",
|
|
17
|
+
"type": "registry:component",
|
|
18
|
+
"target": "$modules$/product-card-hover/product-card-hover.tsx",
|
|
19
|
+
"content": "import { Link } from \"react-router\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { useTranslation } from \"react-i18next\";\r\n\r\ninterface ProductCardHoverProps {\r\n href: string;\r\n image: string;\r\n hoverImage: string;\r\n title: string;\r\n price: number;\r\n currency?: string;\r\n colorCount?: number;\r\n className?: string;\r\n}\r\n\r\nexport function ProductCardHover({\r\n href,\r\n image,\r\n hoverImage,\r\n title,\r\n price,\r\n currency = \"$\",\r\n colorCount,\r\n className,\r\n}: ProductCardHoverProps) {\r\n const { t } = useTranslation(\"product-card-hover\");\r\n\r\n return (\r\n <Link to={href} className={cn(\"group block overflow-hidden\", className)}>\r\n <div className=\"relative h-[350px] sm:h-[450px]\">\r\n <img\r\n src={image}\r\n alt={title}\r\n className=\"absolute inset-0 h-full w-full object-cover opacity-100 group-hover:opacity-0 transition-opacity duration-300\"\r\n />\r\n <img\r\n src={hoverImage}\r\n alt={title}\r\n className=\"absolute inset-0 h-full w-full object-cover opacity-0 group-hover:opacity-100 transition-opacity duration-300\"\r\n />\r\n </div>\r\n\r\n <div className=\"relative bg-background pt-3\">\r\n <h3 className=\"text-sm group-hover:underline group-hover:underline-offset-4\">\r\n {title}\r\n </h3>\r\n\r\n <div className=\"mt-1.5 flex items-center justify-between\">\r\n <p className=\"tracking-wide\">\r\n {currency}{price.toFixed(2)}\r\n </p>\r\n\r\n {colorCount && (\r\n <p className=\"text-xs tracking-wide uppercase text-muted-foreground\">\r\n {colorCount} {t(\"colors\", \"Colors\")}\r\n </p>\r\n )}\r\n </div>\r\n </div>\r\n </Link>\r\n );\r\n}\r\n"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "product-card-hover/lang/en.json",
|
|
23
|
+
"type": "registry:lang",
|
|
24
|
+
"target": "$modules$/product-card-hover/lang/en.json",
|
|
25
|
+
"content": "{\r\n \"colors\": \"Colors\"\r\n}\r\n"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "product-card-hover/lang/tr.json",
|
|
29
|
+
"type": "registry:lang",
|
|
30
|
+
"target": "$modules$/product-card-hover/lang/tr.json",
|
|
31
|
+
"content": "{\r\n \"colors\": \"Renk\"\r\n}\r\n"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"exports": {
|
|
35
|
+
"types": [],
|
|
36
|
+
"variables": [
|
|
37
|
+
"ProductCardHover"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "product-card",
|
|
3
|
+
"type": "registry:component",
|
|
4
|
+
"title": "Product Card",
|
|
5
|
+
"description": "Versatile e-commerce product card with 4 variants: 'grid' (standard), 'list' (horizontal), 'featured' (large with badge), 'compact' (minimal). Includes add-to-cart button and favorite toggle. Shows price, sale badge, and rating.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"ecommerce-core"
|
|
8
|
+
],
|
|
9
|
+
"usage": "import { ProductCard } from '@/modules/product-card';\n\n<ProductCard product={product} variant=\"grid\" />\n\n• Props: product (Product from ecommerce-core), variant (grid|list|featured|compact)\n• Uses useCart() and useFavorites() from ecommerce-core (Zustand)\n• Features: add to cart, toggle favorite, sale badge, rating display",
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "product-card/index.ts",
|
|
13
|
+
"type": "registry:index",
|
|
14
|
+
"target": "$modules$/product-card/index.ts",
|
|
15
|
+
"content": "export * from './product-card';\r\n"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"path": "product-card/product-card.tsx",
|
|
19
|
+
"type": "registry:component",
|
|
20
|
+
"target": "$modules$/product-card/product-card.tsx",
|
|
21
|
+
"content": "import React from \"react\";\nimport { Link } from \"react-router\";\nimport { Heart, ShoppingCart } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\nimport { useCart, useFavorites, formatPrice } from \"@/modules/ecommerce-core\";\nimport { useTranslation } from \"react-i18next\";\nimport constants from \"@/constants/constants.json\";\nimport { cn } from \"@/lib/utils\";\n\ninterface ProductCardProps {\n product: Product;\n variant?: \"grid\" | \"list\" | \"featured\" | \"compact\";\n className?: string;\n}\n\nexport const ProductCard: React.FC<ProductCardProps> = ({\n product,\n variant = \"grid\",\n className,\n}) => {\n const { addItem } = useCart();\n const { addToFavorites, removeFromFavorites, isFavorite } = useFavorites();\n const { t } = useTranslation(\"product-card\");\n\n const currentPrice = product.on_sale\n ? product.sale_price || product.price\n : product.price;\n const isProductFavorite = isFavorite(product.id);\n const isOutOfStock = product.stock <= 0;\n\n const handleAddToCart = (e: React.MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (isOutOfStock) {\n return; // Don't add to cart if out of stock\n }\n\n addItem(product);\n };\n\n const handleToggleFavorite = (e: React.MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (isProductFavorite) {\n removeFromFavorites(product.id);\n } else {\n addToFavorites(product);\n }\n };\n\n if (variant === \"list\") {\n return (\n <Card\n className={cn(\n \"overflow-hidden p-0 hover:shadow-lg transition-all duration-200\",\n className\n )}\n >\n <Link\n to={`/products/${product.slug}`}\n className=\"flex flex-col sm:flex-row\"\n >\n <div className=\"w-full sm:w-48 md:w-56 h-48 sm:h-56 flex-shrink-0 relative\">\n <img\n src={product.images[0] || \"/images/placeholder.png\"}\n alt={product.name}\n className=\"w-full h-full object-cover\"\n />\n {/* Badges on image */}\n <div className=\"absolute top-3 left-3 flex flex-wrap gap-1.5 max-w-[calc(100%-1.5rem)]\">\n {isOutOfStock && (\n <Badge\n variant=\"secondary\"\n className=\"text-xs font-semibold shadow-md bg-muted text-muted-foreground border-0\"\n >\n {t(\"outOfStock\", \"Out of Stock\")}\n </Badge>\n )}\n {!isOutOfStock && product.on_sale && (\n <Badge\n variant=\"destructive\"\n className=\"text-xs font-semibold shadow-md bg-red-600 hover:bg-red-700 text-white border-0\"\n >\n {t(\"sale\", \"Sale\")}\n </Badge>\n )}\n {!isOutOfStock && product.is_new && (\n <Badge\n variant=\"secondary\"\n className=\"text-xs font-semibold shadow-md bg-blue-600 hover:bg-blue-700 text-white border-0\"\n >\n {t(\"new\", \"New\")}\n </Badge>\n )}\n {!isOutOfStock && product.featured && (\n <Badge\n variant=\"default\"\n className=\"text-xs font-semibold shadow-md bg-green-600 hover:bg-green-700 text-white border-0\"\n >\n {t(\"featured\", \"Featured\")}\n </Badge>\n )}\n </div>\n </div>\n <CardContent className=\"flex-1 p-4 sm:p-6\">\n <div className=\"flex flex-col sm:flex-row sm:justify-between sm:items-start h-full gap-4\">\n <div className=\"flex-1 flex flex-col justify-between\">\n <div>\n <h3 className=\"text-lg sm:text-xl font-semibold text-foreground mb-2 sm:mb-3 line-clamp-2 leading-normal\">\n {product.name}\n </h3>\n <p className=\"text-sm sm:text-base text-muted-foreground mb-3 sm:mb-4 line-clamp-2 sm:line-clamp-3 leading-relaxed\">\n {product.description}\n </p>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"flex flex-col gap-0.5\">\n {product.on_sale && (\n <span className=\"text-sm sm:text-base text-muted-foreground line-through\">\n {formatPrice(product.price, constants.site.currency)}\n </span>\n )}\n <span className=\"text-lg sm:text-xl font-bold text-foreground\">\n {formatPrice(currentPrice, constants.site.currency)}\n </span>\n {product.on_sale && (\n <span className=\"text-xs sm:text-sm text-green-600 dark:text-green-400 font-medium\">\n {t(\"save\", \"Save\")}{\" \"}\n {formatPrice(\n product.price - currentPrice,\n constants.site.currency\n )}\n </span>\n )}\n </div>\n </div>\n </div>\n <div className=\"flex sm:flex-col gap-3 sm:ml-6 justify-end sm:justify-start\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={handleToggleFavorite}\n className={cn(\n \"w-10 h-10 sm:w-12 sm:h-12 p-0 shadow-sm hover:shadow-md transition-all duration-200\",\n isProductFavorite\n ? \"text-red-600 dark:text-red-400 border-red-300 dark:border-red-700 bg-red-50 dark:bg-red-950 hover:bg-red-100 dark:hover:bg-red-900 hover:border-red-400\"\n : \"text-muted-foreground border-border bg-card hover:bg-muted hover:text-red-500 hover:border-red-300\"\n )}\n >\n <Heart\n className={cn(\n \"w-4 h-4 sm:w-5 sm:h-5\",\n isProductFavorite && \"fill-current\"\n )}\n />\n </Button>\n <Button\n size=\"sm\"\n onClick={handleAddToCart}\n disabled={isOutOfStock}\n className={cn(\n \"w-10 h-10 sm:w-12 sm:h-12 p-0 shadow-sm hover:shadow-md transition-all duration-200\",\n isOutOfStock && \"opacity-50 cursor-not-allowed\"\n )}\n >\n <ShoppingCart className=\"w-4 h-4 sm:w-5 sm:h-5\" />\n </Button>\n </div>\n </div>\n </CardContent>\n </Link>\n </Card>\n );\n }\n\n // Grid, Featured, and Compact variants\n return (\n <Card\n className={cn(\n \"group overflow-hidden border-0 p-0 shadow-lg hover:shadow-2xl hover:-translate-y-2 transition-all duration-500 bg-card rounded-2xl h-full flex flex-col\",\n variant === \"compact\" && \"max-w-xs\",\n className\n )}\n >\n <Link to={`/products/${product.slug}`} className=\"flex flex-col h-full\">\n <div className=\"relative\">\n <div\n className={cn(\n \"w-full bg-muted overflow-hidden\",\n variant === \"featured\"\n ? \"aspect-square\"\n : variant === \"compact\"\n ? \"aspect-square\"\n : \"aspect-[4/3]\"\n )}\n >\n <img\n src={product.images[0] || \"/images/placeholder.png\"}\n alt={product.name}\n className=\"absolute inset-0 w-full h-full object-cover group-hover:scale-105 transition-transform duration-300\"\n />\n </div>\n\n {/* Badges */}\n <div className=\"absolute top-3 left-3 flex flex-col gap-1.5\">\n {isOutOfStock && (\n <Badge\n variant=\"secondary\"\n className=\"text-xs font-semibold shadow-lg bg-muted text-muted-foreground border-0\"\n >\n {t(\"outOfStock\", \"Out of Stock\")}\n </Badge>\n )}\n {!isOutOfStock && product.on_sale && (\n <Badge\n variant=\"destructive\"\n className=\"text-xs font-semibold shadow-lg bg-red-600 hover:bg-red-700 text-white border-0\"\n >\n {t(\"sale\", \"Sale\")}\n </Badge>\n )}\n {!isOutOfStock && product.is_new && (\n <Badge\n variant=\"secondary\"\n className=\"text-xs font-semibold shadow-lg bg-blue-600 hover:bg-blue-700 text-white border-0\"\n >\n {t(\"new\", \"New\")}\n </Badge>\n )}\n {!isOutOfStock && product.featured && (\n <Badge\n variant=\"default\"\n className=\"text-xs font-semibold shadow-lg bg-green-600 hover:bg-green-700 text-white border-0\"\n >\n {t(\"featured\", \"Featured\")}\n </Badge>\n )}\n </div>\n\n {/* Favorite button */}\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={handleToggleFavorite}\n className={cn(\n \"absolute top-3 right-3 w-10 h-10 p-0 shadow-lg backdrop-blur-sm border-2 transition-all duration-200\",\n isProductFavorite\n ? \"text-red-600 dark:text-red-400 border-red-300 dark:border-red-700 bg-card/95 hover:bg-red-50 dark:hover:bg-red-950 hover:border-red-400\"\n : \"text-muted-foreground border-border bg-card/90 hover:bg-card hover:text-red-500 hover:border-red-300\"\n )}\n >\n <Heart\n className={cn(\"w-5 h-5\", isProductFavorite && \"fill-current\")}\n />\n </Button>\n </div>\n\n <CardContent className=\"p-6 flex flex-col flex-1\">\n <h3\n className={cn(\n \"font-semibold text-foreground mb-2 line-clamp-2 leading-normal\",\n variant === \"compact\" ? \"text-sm\" : \"text-base\"\n )}\n >\n {product.name}\n </h3>\n\n {variant !== \"compact\" && (\n <p className=\"text-sm text-muted-foreground mb-4 line-clamp-2 leading-relaxed\">\n {product.description}\n </p>\n )}\n\n <div className=\"flex items-end justify-between gap-3 mt-auto\">\n <div className=\"flex flex-col gap-0.5\">\n {product.on_sale && (\n <span className=\"text-sm text-muted-foreground line-through\">\n {formatPrice(product.price, constants.site.currency)}\n </span>\n )}\n <span\n className={cn(\n \"font-bold text-foreground\",\n variant === \"compact\" ? \"text-base\" : \"text-lg\"\n )}\n >\n {formatPrice(currentPrice, constants.site.currency)}\n </span>\n {product.on_sale && (\n <span className=\"text-xs text-green-600 dark:text-green-400 font-medium\">\n {t(\"save\", \"Save\")}{\" \"}\n {formatPrice(\n product.price - currentPrice,\n constants.site.currency\n )}\n </span>\n )}\n </div>\n\n <Button\n size={variant === \"compact\" ? \"sm\" : \"default\"}\n onClick={handleAddToCart}\n disabled={isOutOfStock}\n className={cn(\n \"flex-shrink-0 shadow-sm hover:shadow-md transition-all duration-200\",\n variant === \"compact\"\n ? \"h-9 w-9 p-0\"\n : \"h-10 px-4 text-sm whitespace-nowrap\",\n isOutOfStock && \"opacity-50 cursor-not-allowed\"\n )}\n >\n <ShoppingCart\n className={cn(\n variant === \"compact\" ? \"w-4 h-4\" : \"w-4 h-4 mr-2\"\n )}\n />\n {variant !== \"compact\" &&\n (isOutOfStock\n ? t(\"outOfStock\", \"Out of Stock\")\n : t(\"addToCart\", \"Add to Cart\"))}\n </Button>\n </div>\n </CardContent>\n </Link>\n </Card>\n );\n};\n"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"path": "product-card/lang/en.json",
|
|
25
|
+
"type": "registry:lang",
|
|
26
|
+
"target": "$modules$/product-card/lang/en.json",
|
|
27
|
+
"content": "{\r\n \"outOfStock\": \"Out of Stock\",\r\n \"sale\": \"Sale\",\r\n \"new\": \"New\",\r\n \"featured\": \"Featured\",\r\n \"addToCart\": \"Add to Cart\",\r\n \"addToCartShort\": \"Add\",\r\n \"save\": \"Save\"\r\n}\r\n"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"path": "product-card/lang/tr.json",
|
|
31
|
+
"type": "registry:lang",
|
|
32
|
+
"target": "$modules$/product-card/lang/tr.json",
|
|
33
|
+
"content": "{\r\n \"outOfStock\": \"Stokta Yok\",\r\n \"sale\": \"İndirim\",\r\n \"new\": \"Yeni\",\r\n \"featured\": \"Öne Çıkan\",\r\n \"addToCart\": \"Sepete Ekle\",\r\n \"addToCartShort\": \"Ekle\",\r\n \"save\": \"Tasarruf\"\r\n}\r\n"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"exports": {
|
|
37
|
+
"types": [],
|
|
38
|
+
"variables": [
|
|
39
|
+
"ProductCard"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "product-detail-block",
|
|
3
|
+
"type": "registry:block",
|
|
4
|
+
"title": "Product Detail Block",
|
|
5
|
+
"description": "Product detail view with image gallery (lightbox zoom), variant selector, quantity input, specifications tabs, reviews section, and add to cart button. Mobile responsive with sticky add-to-cart bar.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"ecommerce-core"
|
|
8
|
+
],
|
|
9
|
+
"usage": "import { ProductDetailBlock } from '@/modules/product-detail-block';\n\n<ProductDetailBlock product={product} />\n\n• Uses useCart() from ecommerce-core (Zustand)\n• Sections: gallery, info, variants, specs, reviews\n• Props: product (Product type from ecommerce-core)",
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "product-detail-block/index.ts",
|
|
13
|
+
"type": "registry:index",
|
|
14
|
+
"target": "$modules$/product-detail-block/index.ts",
|
|
15
|
+
"content": "export * from './product-detail-block';\r\n"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"path": "product-detail-block/product-detail-block.tsx",
|
|
19
|
+
"type": "registry:block",
|
|
20
|
+
"target": "$modules$/product-detail-block/product-detail-block.tsx",
|
|
21
|
+
"content": "import { useState } from \"react\";\nimport {\n Star,\n Heart,\n Share2,\n Truck,\n RotateCcw,\n Shield,\n Plus,\n Minus,\n ChevronLeft,\n ChevronRight,\n X,\n ZoomIn,\n} from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport { Dialog, DialogContent, DialogClose } from \"@/components/ui/dialog\";\nimport { useCart, formatPrice } from \"@/modules/ecommerce-core\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\nimport { useTranslation } from \"react-i18next\";\nimport constants from \"@/constants/constants.json\";\n\ninterface ProductDetailBlockProps {\n product: Product;\n}\n\nexport function ProductDetailBlock({ product }: ProductDetailBlockProps) {\n const { t } = useTranslation(\"product-detail-block\");\n const { addItem } = useCart();\n\n const [selectedImageIndex, setSelectedImageIndex] = useState(0);\n const [quantity, setQuantity] = useState(1);\n const [selectedVariant, setSelectedVariant] = useState<string>(\"\");\n const [isAdding, setIsAdding] = useState(false);\n const [isImageModalOpen, setIsImageModalOpen] = useState(false);\n const [zoomLevel, setZoomLevel] = useState(1);\n const [position, setPosition] = useState({ x: 0, y: 0 });\n const [isDragging, setIsDragging] = useState(false);\n const [dragStart, setDragStart] = useState({ x: 0, y: 0 });\n\n const handleAddToCart = async () => {\n if (product) {\n setIsAdding(true);\n // Add multiple items by calling addItem quantity times\n for (let i = 0; i < quantity; i++) {\n addItem(product);\n }\n\n // Show success feedback\n setTimeout(() => {\n setIsAdding(false);\n }, 1000);\n }\n };\n\n const handleQuantityChange = (change: number) => {\n const newQuantity = quantity + change;\n if (newQuantity >= 1 && newQuantity <= product.stock) {\n setQuantity(newQuantity);\n }\n };\n\n const features = [\n {\n icon: Truck,\n title: t(\"freeShipping\", \"Free Shipping\"),\n description: t(\"freeShippingDesc\", \"On orders over 50\"),\n },\n {\n icon: RotateCcw,\n title: t(\"easyReturns\", \"Easy Returns\"),\n description: t(\"easyReturnsDesc\", \"30-day return policy\"),\n },\n {\n icon: Shield,\n title: t(\"secureCheckout\", \"Secure Checkout\"),\n description: t(\"secureCheckoutDesc\", \"SSL encrypted payment\"),\n },\n ];\n\n return (\n <>\n <div className=\"grid lg:grid-cols-2 gap-12\">\n {/* Product Images */}\n <div className=\"space-y-4\">\n {/* Main Image */}\n <div className=\"aspect-square relative overflow-hidden rounded-lg bg-muted group\">\n <img\n src={product.images[selectedImageIndex] || \"/images/placeholder.png\"}\n alt={product.name}\n className=\"w-full h-full object-cover group-hover:scale-105 transition-transform duration-300 cursor-zoom-in\"\n onClick={() => setIsImageModalOpen(true)}\n />\n\n {/* Zoom Indicator */}\n <div className=\"absolute bottom-4 right-4 bg-black/50 text-white p-2 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity\">\n <ZoomIn className=\"w-5 h-5\" />\n </div>\n\n {/* Navigation Arrows */}\n {product.images.length > 1 && (\n <>\n <Button\n variant=\"secondary\"\n size=\"icon\"\n className=\"absolute left-4 top-1/2 transform -translate-y-1/2\"\n onClick={() =>\n setSelectedImageIndex(\n selectedImageIndex === 0\n ? product.images.length - 1\n : selectedImageIndex - 1\n )\n }\n >\n <ChevronLeft className=\"h-4 w-4\" />\n </Button>\n <Button\n variant=\"secondary\"\n size=\"icon\"\n className=\"absolute right-4 top-1/2 transform -translate-y-1/2\"\n onClick={() =>\n setSelectedImageIndex(\n selectedImageIndex === product.images.length - 1\n ? 0\n : selectedImageIndex + 1\n )\n }\n >\n <ChevronRight className=\"h-4 w-4\" />\n </Button>\n </>\n )}\n\n {/* Badges */}\n <div className=\"absolute top-4 left-4 flex flex-col gap-2\">\n {product.on_sale && (\n <Badge variant=\"destructive\">{t(\"sale\", \"Sale\")}</Badge>\n )}\n {product.is_new && (\n <Badge variant=\"secondary\">{t(\"new\", \"New\")}</Badge>\n )}\n </div>\n </div>\n\n {/* Thumbnail Images */}\n {product.images.length > 1 && (\n <div className=\"flex gap-3 overflow-x-auto\">\n {product.images.map((image, index) => (\n <button\n key={index}\n className={`flex-shrink-0 w-20 h-20 rounded-lg overflow-hidden border-2 transition-colors ${\n selectedImageIndex === index\n ? \"border-primary\"\n : \"border-transparent hover:border-muted-foreground\"\n }`}\n onClick={() => setSelectedImageIndex(index)}\n >\n <img\n src={image}\n alt={`${product.name} ${index + 1}`}\n className=\"w-full h-full object-cover\"\n />\n </button>\n ))}\n </div>\n )}\n </div>\n\n {/* Product Info */}\n <div className=\"space-y-6\">\n {/* Header */}\n <div>\n <div className=\"flex items-center gap-2 mb-2\">\n <Badge variant=\"outline\">\n {product.category_name ||\n product.categories?.[0]?.name ||\n product.category}\n </Badge>\n {product.featured && (\n <Badge variant=\"secondary\">{t(\"featured\", \"Featured\")}</Badge>\n )}\n </div>\n <h1 className=\"text-3xl font-bold mb-4\">{product.name}</h1>\n\n {/* Rating */}\n <div className=\"flex items-center gap-4 mb-4\">\n <div className=\"flex items-center gap-1\">\n {[...Array(5)].map((_, i) => (\n <Star\n key={i}\n className={`h-4 w-4 ${\n i < Math.floor(product.rating)\n ? \"fill-current text-yellow-400\"\n : \"text-muted-foreground\"\n }`}\n />\n ))}\n <span className=\"text-sm font-medium ml-1\">\n {product.rating}\n </span>\n </div>\n </div>\n\n {/* Price */}\n <div className=\"flex items-center gap-3 mb-6\">\n <span className=\"text-3xl font-bold\">\n {formatPrice(\n product.on_sale && product.sale_price\n ? product.sale_price\n : product.price,\n constants.site.currency\n )}\n </span>\n {product.on_sale && product.sale_price && (\n <span className=\"text-xl text-muted-foreground line-through\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </span>\n )}\n </div>\n </div>\n\n {/* Description */}\n <div>\n <p className=\"text-muted-foreground leading-relaxed\">\n {product.description}\n </p>\n </div>\n\n {/* Variants */}\n {product.variants && product.variants.length > 0 && (\n <div>\n <label className=\"text-sm font-medium mb-2 block\">\n {t(\"size\", \"Size\")}\n </label>\n <Select\n value={selectedVariant}\n onValueChange={setSelectedVariant}\n >\n <SelectTrigger className=\"w-full\">\n <SelectValue placeholder={t(\"selectSize\", \"Select size\")} />\n </SelectTrigger>\n <SelectContent>\n {product.variants.map((variant: any) => (\n <SelectItem\n key={variant.id}\n value={variant.id}\n disabled={variant.stockQuantity === 0}\n >\n {variant.value}{\" \"}\n {variant.stockQuantity === 0 &&\n `(${t(\"outOfStock\", \"Out of Stock\")})`}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )}\n\n {/* Quantity */}\n <div>\n <label className=\"text-sm font-medium mb-2 block\">\n {t(\"quantity\", \"Quantity\")}\n </label>\n <div className=\"flex items-center gap-3\">\n <div className=\"flex items-center border rounded-lg\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => handleQuantityChange(-1)}\n disabled={quantity <= 1}\n >\n <Minus className=\"h-4 w-4\" />\n </Button>\n <span className=\"w-12 text-center font-medium\">{quantity}</span>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => handleQuantityChange(1)}\n disabled={quantity >= product.stock}\n >\n <Plus className=\"h-4 w-4\" />\n </Button>\n </div>\n <span className=\"text-sm text-muted-foreground\">\n {product.stock} {t(\"available\", \"available\")}\n </span>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"space-y-3\">\n <div className=\"flex gap-3\">\n <Button\n size=\"lg\"\n className=\"flex-1\"\n disabled={product.stock <= 0 || isAdding}\n variant=\"default\"\n onClick={handleAddToCart}\n >\n {isAdding ? (\n <>\n <div className=\"w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2\" />\n {t(\"adding\", \"Adding...\")}\n </>\n ) : (\n t(\"addToCart\", \"Add to Cart\")\n )}\n </Button>\n <Button variant=\"outline\" size=\"lg\">\n <Heart className=\"h-4 w-4\" />\n </Button>\n <Button variant=\"outline\" size=\"lg\">\n <Share2 className=\"h-4 w-4\" />\n </Button>\n </div>\n\n {product.stock > 0 ? (\n <p className=\"text-sm text-green-600 dark:text-green-400 font-medium\">\n ✓ {t(\"inStockReady\", \"In stock and ready to ship\")} (\n {product.stock} {t(\"available\", \"available\")})\n </p>\n ) : (\n <p className=\"text-sm text-red-600 dark:text-red-400 font-medium\">\n ✗ {t(\"currentlyOutOfStock\", \"Currently out of stock\")}\n </p>\n )}\n </div>\n\n {/* Features */}\n <div className=\"grid grid-cols-3 gap-4 pt-6 border-t\">\n {features.map((feature, index) => {\n const Icon = feature.icon;\n return (\n <div key={index} className=\"text-center\">\n <Icon className=\"h-6 w-6 mx-auto mb-2 text-primary\" />\n <h4 className=\"text-sm font-medium\">{feature.title}</h4>\n <p className=\"text-xs text-muted-foreground\">\n {feature.description}\n </p>\n </div>\n );\n })}\n </div>\n </div>\n </div>\n\n {/* Product Details Tabs */}\n <Tabs defaultValue=\"description\" className=\"mt-16\">\n <TabsList className=\"grid w-full grid-cols-2\">\n <TabsTrigger value=\"description\">\n {t(\"description\", \"Description\")}\n </TabsTrigger>\n <TabsTrigger value=\"specifications\">\n {t(\"specifications\", \"Specifications\")}\n </TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"description\" className=\"mt-6\">\n <Card>\n <CardContent className=\"pt-6\">\n <div className=\"prose max-w-none\">\n <p className=\"text-muted-foreground leading-relaxed\">\n {product.description}\n </p>\n <Separator className=\"my-6\" />\n <h3 className=\"text-lg font-semibold mb-3\">\n {t(\"keyFeatures\", \"Key Features\")}\n </h3>\n <ul className=\"space-y-2\">\n {product.tags.map((tag, index) => (\n <li key={index} className=\"flex items-center gap-2\">\n <div className=\"w-1.5 h-1.5 bg-primary rounded-full\"></div>\n <span className=\"capitalize\">{tag}</span>\n </li>\n ))}\n </ul>\n </div>\n </CardContent>\n </Card>\n </TabsContent>\n\n <TabsContent value=\"specifications\" className=\"mt-6\">\n <Card>\n <CardContent className=\"pt-6\">\n {product.specifications ? (\n <div className=\"grid gap-4\">\n {Object.entries(product.specifications).map(\n ([key, value]) => (\n <div\n key={key}\n className=\"bg-muted/30 rounded-lg p-4 flex justify-between items-center\"\n >\n <span className=\"font-medium text-foreground capitalize\">\n {key\n .replace(/_/g, \" \")\n .replace(/([A-Z])/g, \" $1\")\n .trim()}\n </span>\n <span className=\"text-muted-foreground font-mono bg-background px-3 py-1 rounded-md border\">\n {value}\n </span>\n </div>\n )\n )}\n </div>\n ) : (\n <p className=\"text-muted-foreground\">\n {t(\"noSpecifications\", \"No specifications available.\")}\n </p>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n </Tabs>\n\n {/* Image Zoom Modal */}\n <Dialog\n open={isImageModalOpen}\n onOpenChange={(open) => {\n setIsImageModalOpen(open);\n if (!open) {\n setZoomLevel(1);\n setPosition({ x: 0, y: 0 });\n }\n }}\n >\n <DialogContent className=\"max-w-[98vw] w-[98vw] h-[98vh] p-0 overflow-hidden bg-black/95\">\n <DialogClose className=\"absolute top-4 right-4 z-50 rounded-full bg-black/60 hover:bg-black/80 p-2 transition-all border border-white/10 backdrop-blur-md\">\n <X className=\"h-6 w-6 text-white\" />\n </DialogClose>\n\n <div className=\"relative w-full h-full flex items-center justify-center\">\n {/* Image Navigation */}\n {product.images.length > 1 && (\n <>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"absolute left-4 z-40 bg-black/60 hover:bg-black/80 text-white border border-white/10 backdrop-blur-md\"\n onClick={() => {\n setSelectedImageIndex(\n selectedImageIndex === 0\n ? product.images.length - 1\n : selectedImageIndex - 1\n );\n setZoomLevel(1);\n setPosition({ x: 0, y: 0 });\n }}\n >\n <ChevronLeft className=\"h-8 w-8\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"absolute right-4 z-40 bg-black/60 hover:bg-black/80 text-white border border-white/10 backdrop-blur-md\"\n onClick={() => {\n setSelectedImageIndex(\n selectedImageIndex === product.images.length - 1\n ? 0\n : selectedImageIndex + 1\n );\n setZoomLevel(1);\n setPosition({ x: 0, y: 0 });\n }}\n >\n <ChevronRight className=\"h-8 w-8\" />\n </Button>\n </>\n )}\n\n {/* Zoom Controls */}\n <div className=\"absolute bottom-4 left-1/2 transform -translate-x-1/2 z-40 flex items-center gap-2 bg-black/60 backdrop-blur-md rounded-full px-4 py-3 border border-white/10\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-white hover:bg-white/20 h-8 w-8 p-0 rounded-full\"\n onClick={() => {\n const newZoom = Math.max(1, zoomLevel - 0.3);\n setZoomLevel(newZoom);\n if (newZoom === 1) setPosition({ x: 0, y: 0 });\n }}\n disabled={zoomLevel <= 1}\n >\n <Minus className=\"h-4 w-4\" />\n </Button>\n <span className=\"text-white text-sm font-semibold min-w-[60px] text-center\">\n {Math.round(zoomLevel * 100)}%\n </span>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-white hover:bg-white/20 h-8 w-8 p-0 rounded-full\"\n onClick={() => setZoomLevel(Math.min(4, zoomLevel + 0.3))}\n disabled={zoomLevel >= 4}\n >\n <Plus className=\"h-4 w-4\" />\n </Button>\n <div className=\"w-px h-6 bg-white/20 mx-2\" />\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-white hover:bg-white/20 text-xs px-3 h-8 rounded-full\"\n onClick={() => {\n setZoomLevel(1);\n setPosition({ x: 0, y: 0 });\n }}\n disabled={zoomLevel === 1}\n >\n Reset\n </Button>\n </div>\n\n {/* Zoomable Image */}\n <div\n className=\"relative w-full h-full flex items-center justify-center overflow-hidden select-none\"\n style={{\n cursor:\n zoomLevel > 1\n ? isDragging\n ? \"grabbing\"\n : \"grab\"\n : \"zoom-in\",\n }}\n onWheel={(e) => {\n e.preventDefault();\n const delta = e.deltaY > 0 ? -0.15 : 0.15;\n const newZoom = Math.max(1, Math.min(4, zoomLevel + delta));\n setZoomLevel(newZoom);\n if (newZoom === 1) setPosition({ x: 0, y: 0 });\n }}\n onMouseDown={(e) => {\n if (zoomLevel > 1) {\n setIsDragging(true);\n setDragStart({\n x: e.clientX - position.x,\n y: e.clientY - position.y,\n });\n } else {\n // Double click to zoom\n if (e.detail === 2) {\n setZoomLevel(2.5);\n }\n }\n }}\n onMouseMove={(e) => {\n if (isDragging && zoomLevel > 1) {\n setPosition({\n x: e.clientX - dragStart.x,\n y: e.clientY - dragStart.y,\n });\n }\n }}\n onMouseUp={() => setIsDragging(false)}\n onMouseLeave={() => setIsDragging(false)}\n onClick={(e) => {\n if (zoomLevel === 1 && e.detail === 1) {\n // Single click to zoom in\n setZoomLevel(2.5);\n }\n }}\n >\n <div\n className=\"transition-transform duration-300 ease-out\"\n style={{\n transform: `scale(${zoomLevel}) translate(${\n position.x / zoomLevel\n }px, ${position.y / zoomLevel}px)`,\n transformOrigin: \"center center\",\n }}\n >\n <img\n src={product.images[selectedImageIndex] || \"/images/placeholder.png\"}\n alt={product.name}\n className=\"max-w-[90vw] max-h-[90vh] object-contain pointer-events-none\"\n draggable={false}\n />\n </div>\n </div>\n\n {/* Image Counter & Info */}\n <div className=\"absolute top-4 left-1/2 transform -translate-x-1/2 z-40 bg-black/60 backdrop-blur-md rounded-full px-4 py-2 border border-white/10\">\n {product.images.length > 1 ? (\n <span className=\"text-white text-sm font-semibold\">\n {selectedImageIndex + 1} / {product.images.length}\n </span>\n ) : (\n <span className=\"text-white text-xs opacity-70\">\n {t(\"zoomHint\", \"Scroll to zoom • Click & drag to pan\")}\n </span>\n )}\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </>\n );\n}\n"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"path": "product-detail-block/lang/en.json",
|
|
25
|
+
"type": "registry:lang",
|
|
26
|
+
"target": "$modules$/product-detail-block/lang/en.json",
|
|
27
|
+
"content": "{\n \"freeShipping\": \"Free Shipping\",\n \"freeShippingDesc\": \"On orders over 50\",\n \"easyReturns\": \"Easy Returns\",\n \"easyReturnsDesc\": \"30-day return policy\",\n \"secureCheckout\": \"Secure Checkout\",\n \"secureCheckoutDesc\": \"SSL encrypted payment\",\n \"size\": \"Size\",\n \"selectSize\": \"Select size\",\n \"outOfStock\": \"Out of Stock\",\n \"available\": \"available\",\n \"inStockReady\": \"In stock and ready to ship\",\n \"currentlyOutOfStock\": \"Currently out of stock\",\n \"description\": \"Description\",\n \"specifications\": \"Specifications\",\n \"keyFeatures\": \"Key Features\",\n \"noSpecifications\": \"No specifications available.\",\n \"sale\": \"Sale\",\n \"new\": \"New\",\n \"featured\": \"Featured\",\n \"quantity\": \"Quantity\",\n \"addToCart\": \"Add to Cart\",\n \"adding\": \"Adding...\",\n \"zoomHint\": \"Scroll to zoom • Click & drag to pan\"\n}\n"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"path": "product-detail-block/lang/tr.json",
|
|
31
|
+
"type": "registry:lang",
|
|
32
|
+
"target": "$modules$/product-detail-block/lang/tr.json",
|
|
33
|
+
"content": "{\n \"freeShipping\": \"Ücretsiz Kargo\",\n \"freeShippingDesc\": \"50 üzeri siparişlerde\",\n \"easyReturns\": \"Kolay İade\",\n \"easyReturnsDesc\": \"30 günlük iade garantisi\",\n \"secureCheckout\": \"Güvenli Ödeme\",\n \"secureCheckoutDesc\": \"SSL şifreli ödeme\",\n \"size\": \"Beden\",\n \"selectSize\": \"Beden seçin\",\n \"outOfStock\": \"Stokta Yok\",\n \"available\": \"mevcut\",\n \"inStockReady\": \"Stokta ve gönderime hazır\",\n \"currentlyOutOfStock\": \"Şu anda stokta yok\",\n \"description\": \"Açıklama\",\n \"specifications\": \"Özellikler\",\n \"keyFeatures\": \"Temel Özellikler\",\n \"noSpecifications\": \"Teknik özellik bilgisi yok.\",\n \"sale\": \"İndirim\",\n \"new\": \"Yeni\",\n \"featured\": \"Öne Çıkan\",\n \"quantity\": \"Adet\",\n \"addToCart\": \"Sepete Ekle\",\n \"adding\": \"Ekleniyor...\",\n \"zoomHint\": \"Yakınlaştırmak için kaydır • Kaydırmak için tıkla ve sürükle\"\n}\n"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"exports": {
|
|
37
|
+
"types": [],
|
|
38
|
+
"variables": [
|
|
39
|
+
"ProductDetailBlock"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "product-detail-section",
|
|
3
|
+
"type": "registry:component",
|
|
4
|
+
"title": "Product Detail Section",
|
|
5
|
+
"description": "Product detail UI section with image, title, brand, price, description, color/size selectors, rating stars, social share buttons, and add to cart/wishlist actions. Fully customizable via props.",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"lucide-react"
|
|
8
|
+
],
|
|
9
|
+
"registryDependencies": [
|
|
10
|
+
"button",
|
|
11
|
+
"select"
|
|
12
|
+
],
|
|
13
|
+
"usage": "import { ProductDetailSection } from '@/modules/product-detail-section';\n\n<ProductDetailSection\n image=\"/images/product.jpg\"\n title=\"Product Name\"\n brand=\"Brand\"\n price={58}\n description=\"Product description...\"\n colors={[{ name: 'Red', value: '#ef4444' }]}\n sizes={['SM', 'M', 'L', 'XL']}\n rating={4}\n reviewCount={12}\n onAddToCart={() => {}}\n/>",
|
|
14
|
+
"files": [
|
|
15
|
+
{
|
|
16
|
+
"path": "product-detail-section/index.ts",
|
|
17
|
+
"type": "registry:index",
|
|
18
|
+
"target": "$modules$/product-detail-section/index.ts",
|
|
19
|
+
"content": "export * from './product-detail-section';\r\n"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "product-detail-section/product-detail-section.tsx",
|
|
23
|
+
"type": "registry:component",
|
|
24
|
+
"target": "$modules$/product-detail-section/product-detail-section.tsx",
|
|
25
|
+
"content": "import { useState } from \"react\";\r\nimport { Star, Heart, Facebook, Twitter, MessageCircle } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from \"@/components/ui/select\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { useTranslation } from \"react-i18next\";\r\n\r\ninterface ProductColor {\r\n name: string;\r\n value: string;\r\n}\r\n\r\ninterface ProductDetailSectionProps {\r\n image: string;\r\n title: string;\r\n brand?: string;\r\n price: number;\r\n currency?: string;\r\n description: string;\r\n colors?: ProductColor[];\r\n sizes?: string[];\r\n rating?: number;\r\n reviewCount?: number;\r\n onAddToCart?: () => void;\r\n onAddToWishlist?: () => void;\r\n className?: string;\r\n}\r\n\r\nexport function ProductDetailSection({\r\n image,\r\n title,\r\n brand,\r\n price,\r\n currency = \"$\",\r\n description,\r\n colors = [],\r\n sizes = [],\r\n rating = 0,\r\n reviewCount = 0,\r\n onAddToCart,\r\n onAddToWishlist,\r\n className,\r\n}: ProductDetailSectionProps) {\r\n const { t } = useTranslation(\"product-detail-section\");\r\n const [selectedColor, setSelectedColor] = useState<string>(colors[0]?.name || \"\");\r\n const [selectedSize, setSelectedSize] = useState<string>(sizes[0] || \"\");\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)\r\n ? \"fill-primary text-primary\"\r\n : \"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={title}\r\n className=\"lg:w-1/2 w-full lg:h-auto h-64 object-cover object-center rounded\"\r\n src={image}\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 {brand && (\r\n <h2 className=\"text-sm text-muted-foreground tracking-widest uppercase\">\r\n {brand}\r\n </h2>\r\n )}\r\n <h1 className=\"text-3xl font-medium mb-1\">{title}</h1>\r\n\r\n <div className=\"flex mb-4\">\r\n <span className=\"flex items-center\">\r\n {renderStars(rating)}\r\n <span className=\"text-muted-foreground ml-3\">\r\n {reviewCount} {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 href=\"#\" className=\"text-muted-foreground hover:text-foreground\">\r\n <Facebook className=\"w-5 h-5\" />\r\n </a>\r\n <a href=\"#\" className=\"text-muted-foreground hover:text-foreground\">\r\n <Twitter className=\"w-5 h-5\" />\r\n </a>\r\n <a href=\"#\" className=\"text-muted-foreground hover:text-foreground\">\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\">{description}</p>\r\n\r\n <div className=\"flex mt-6 items-center pb-5 border-b-2 border-border mb-5\">\r\n {colors.length > 0 && (\r\n <div className=\"flex items-center\">\r\n <span className=\"mr-3\">{t(\"color\", \"Color\")}</span>\r\n {colors.map((color) => (\r\n <button\r\n key={color.name}\r\n onClick={() => setSelectedColor(color.name)}\r\n className={cn(\r\n \"border-2 rounded-full w-6 h-6 focus:outline-none ml-1\",\r\n selectedColor === color.name\r\n ? \"border-primary\"\r\n : \"border-border\"\r\n )}\r\n style={{ backgroundColor: color.value }}\r\n title={color.name}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n\r\n {sizes.length > 0 && (\r\n <div className=\"flex ml-6 items-center\">\r\n <span className=\"mr-3\">{t(\"size\", \"Size\")}</span>\r\n <Select value={selectedSize} onValueChange={setSelectedSize}>\r\n <SelectTrigger className=\"w-20\">\r\n <SelectValue />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {sizes.map((size) => (\r\n <SelectItem key={size} value={size}>\r\n {size}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n )}\r\n </div>\r\n\r\n <div className=\"flex items-center\">\r\n <span className=\"font-medium text-2xl\">\r\n {currency}{price.toFixed(2)}\r\n </span>\r\n <Button onClick={onAddToCart} className=\"ml-auto\">\r\n {t(\"addToCart\", \"Add to Cart\")}\r\n </Button>\r\n <Button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n onClick={onAddToWishlist}\r\n className=\"rounded-full ml-4\"\r\n >\r\n <Heart className=\"w-5 h-5\" />\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
|
+
{
|
|
28
|
+
"path": "product-detail-section/lang/en.json",
|
|
29
|
+
"type": "registry:lang",
|
|
30
|
+
"target": "$modules$/product-detail-section/lang/en.json",
|
|
31
|
+
"content": "{\r\n \"reviews\": \"Reviews\",\r\n \"color\": \"Color\",\r\n \"size\": \"Size\",\r\n \"addToCart\": \"Add to Cart\"\r\n}\r\n"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"path": "product-detail-section/lang/tr.json",
|
|
35
|
+
"type": "registry:lang",
|
|
36
|
+
"target": "$modules$/product-detail-section/lang/tr.json",
|
|
37
|
+
"content": "{\r\n \"reviews\": \"Değerlendirme\",\r\n \"color\": \"Renk\",\r\n \"size\": \"Beden\",\r\n \"addToCart\": \"Sepete Ekle\"\r\n}\r\n"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"exports": {
|
|
41
|
+
"types": [],
|
|
42
|
+
"variables": [
|
|
43
|
+
"ProductDetailSection"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "products-page",
|
|
3
|
+
"type": "registry:page",
|
|
4
|
+
"title": "Products Page",
|
|
5
|
+
"description": "Full-featured product listing page with sidebar filters (category, price range, rating), sorting options (price, name, rating, newest), view toggle (grid/list), pagination, and search integration. Uses ProductCard component for display. Includes loading skeletons, empty state handling, and responsive design with collapsible mobile filters.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"ecommerce-core",
|
|
8
|
+
"product-card"
|
|
9
|
+
],
|
|
10
|
+
"route": {
|
|
11
|
+
"path": "/products",
|
|
12
|
+
"componentName": "ProductsPage"
|
|
13
|
+
},
|
|
14
|
+
"usage": "import ProductsPage from '@/modules/products-page';\n\n<Route path=\"/products\" element={<ProductsPage />} />\n\n• Installed at: src/modules/products-page/\n• Add link: <Link to=\"/products\">Browse Products</Link>\n• Supports filters, sorting, grid/list view, pagination\n• Uses useProducts hook for data fetching",
|
|
15
|
+
"files": [
|
|
16
|
+
{
|
|
17
|
+
"path": "products-page/index.ts",
|
|
18
|
+
"type": "registry:index",
|
|
19
|
+
"target": "$modules$/products-page/index.ts",
|
|
20
|
+
"content": "export * from './products-page';\r\nexport { ProductsPage as default } from './products-page';\r\n"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": "products-page/products-page.tsx",
|
|
24
|
+
"type": "registry:page",
|
|
25
|
+
"target": "$modules$/products-page/products-page.tsx",
|
|
26
|
+
"content": "import { useState, useEffect, useRef, useCallback } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { useTranslation } from \"react-i18next\";\nimport { Filter, Grid, List } from \"lucide-react\";\nimport { Layout } from \"@/components/Layout\";\nimport { Button } from \"@/components/ui/button\";\nimport { FadeIn } from \"@/modules/animations\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { ProductCard } from \"@/modules/product-card/product-card\";\nimport { useProducts, useCategories } from \"@/modules/ecommerce-core\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\n\nexport function ProductsPage() {\n const { t } = useTranslation(\"products-page\");\n const { products, loading: productsLoading } = useProducts();\n const { categories, loading: categoriesLoading } = useCategories();\n const loading = productsLoading || categoriesLoading;\n\n const [searchParams, setSearchParams] = useSearchParams();\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n const [sortBy, setSortBy] = useState(\"featured\");\n const [selectedCategories, setSelectedCategories] = useState<string[]>([]);\n const [selectedFeatures, setSelectedFeatures] = useState<string[]>([]);\n const [filteredProducts, setFilteredProducts] = useState<Product[]>([]);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const minPriceRef = useRef<HTMLInputElement>(null);\n const maxPriceRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n const query = searchParams.get(\"search\") || \"\";\n const categorySlug = searchParams.get(\"category\");\n setSearchQuery(query);\n if (categorySlug) {\n setSelectedCategories([categorySlug]);\n }\n }, [searchParams]);\n\n useEffect(() => {\n const minPrice = parseFloat(searchParams.get(\"minPrice\") || \"0\") || 0;\n const maxPrice =\n parseFloat(searchParams.get(\"maxPrice\") || \"999999\") || 999999;\n\n let filtered = products.filter((product) => {\n const currentPrice =\n product.on_sale && product.sale_price\n ? product.sale_price\n : product.price;\n return currentPrice >= minPrice && currentPrice <= maxPrice;\n });\n\n if (selectedCategories.length > 0) {\n filtered = filtered.filter((product) => {\n return selectedCategories.some((selectedCategory) => {\n if (product.category === selectedCategory) return true;\n return product.categories?.some(\n (cat) => cat.slug === selectedCategory\n );\n });\n });\n }\n\n if (selectedFeatures.length > 0) {\n filtered = filtered.filter((product) => {\n return selectedFeatures.every((feature) => {\n switch (feature) {\n case \"on_sale\":\n return product.on_sale;\n case \"is_new\":\n return product.is_new;\n case \"featured\":\n return product.featured;\n case \"in_stock\":\n return product.stock > 0;\n default:\n return true;\n }\n });\n });\n }\n\n // Apply sorting\n const sorted = [...filtered].sort((a, b) => {\n switch (sortBy) {\n case \"price-low\":\n return (\n (a.on_sale ? a.sale_price || a.price : a.price) -\n (b.on_sale ? b.sale_price || b.price : b.price)\n );\n case \"price-high\":\n return (\n (b.on_sale ? b.sale_price || b.price : b.price) -\n (a.on_sale ? a.sale_price || a.price : a.price)\n );\n case \"newest\":\n return (\n new Date(b.created_at || 0).getTime() -\n new Date(a.created_at || 0).getTime()\n );\n case \"featured\":\n default:\n return (b.featured ? 1 : 0) - (a.featured ? 1 : 0);\n }\n });\n\n setFilteredProducts(sorted);\n }, [products, searchParams, selectedFeatures, selectedCategories, sortBy]);\n\n const handlePriceFilter = useCallback(() => {\n const minPrice = minPriceRef.current?.value || \"\";\n const maxPrice = maxPriceRef.current?.value || \"\";\n const params = new URLSearchParams(searchParams);\n if (minPrice) params.set(\"minPrice\", minPrice);\n else params.delete(\"minPrice\");\n if (maxPrice) params.set(\"maxPrice\", maxPrice);\n else params.delete(\"maxPrice\");\n setSearchParams(params);\n }, [searchParams, setSearchParams]);\n\n const handleCategoryChange = useCallback(\n (category: string, checked: boolean) => {\n if (checked) {\n setSelectedCategories((prev) => [...prev, category]);\n } else {\n setSelectedCategories((prev) => prev.filter((c) => c !== category));\n }\n },\n []\n );\n\n const handleFeatureChange = useCallback(\n (feature: string, checked: boolean) => {\n if (checked) {\n setSelectedFeatures((prev) => [...prev, feature]);\n } else {\n setSelectedFeatures((prev) => prev.filter((f) => f !== feature));\n }\n },\n []\n );\n\n const sortOptions = [\n { value: \"featured\", label: t(\"featured\", \"Featured\") },\n { value: \"price-low\", label: t(\"sortPriceLow\", \"Price: Low to High\") },\n { value: \"price-high\", label: t(\"sortPriceHigh\", \"Price: High to Low\") },\n { value: \"newest\", label: t(\"sortNewest\", \"Newest\") },\n ];\n\n const FilterSidebar = () => (\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"categories\", \"Categories\")}\n </h3>\n <div className=\"space-y-3\">\n {categories.map((category) => (\n <div\n key={category.id}\n className=\"flex items-center space-x-3 p-2 rounded-lg hover:bg-muted/50 transition-colors\"\n >\n <Checkbox\n id={`category-${category.id}`}\n checked={selectedCategories.includes(category.slug)}\n onCheckedChange={(checked) =>\n handleCategoryChange(category.slug, checked as boolean)\n }\n className=\"data-[state=checked]:bg-primary data-[state=checked]:border-primary\"\n />\n <label\n htmlFor={`category-${category.id}`}\n className=\"text-sm font-medium leading-none cursor-pointer flex-1\"\n >\n {category.name}\n </label>\n </div>\n ))}\n </div>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"priceRange\", \"Price Range\")}\n </h3>\n <div className=\"space-y-3 p-3 bg-muted/30 rounded-lg\">\n <div className=\"grid grid-cols-2 gap-3\">\n <input\n ref={minPriceRef}\n type=\"number\"\n placeholder={t(\"minPrice\", \"Min\")}\n defaultValue={searchParams.get(\"minPrice\") || \"\"}\n onKeyDown={(e) => e.key === \"Enter\" && handlePriceFilter()}\n className=\"w-full px-3 py-2 border border-input rounded-lg text-sm bg-background\"\n />\n <input\n ref={maxPriceRef}\n type=\"number\"\n placeholder={t(\"maxPrice\", \"Max\")}\n defaultValue={searchParams.get(\"maxPrice\") || \"\"}\n onKeyDown={(e) => e.key === \"Enter\" && handlePriceFilter()}\n className=\"w-full px-3 py-2 border border-input rounded-lg text-sm bg-background\"\n />\n </div>\n </div>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"features\", \"Features\")}\n </h3>\n <div className=\"space-y-3\">\n {[\n { key: \"on_sale\", label: t(\"onSale\", \"On Sale\") },\n { key: \"is_new\", label: t(\"newArrivals\", \"New Arrivals\") },\n { key: \"featured\", label: t(\"featuredLabel\", \"Featured\") },\n { key: \"in_stock\", label: t(\"inStock\", \"In Stock\") },\n ].map((feature) => (\n <div\n key={feature.key}\n className=\"flex items-center space-x-3 p-2 rounded-lg hover:bg-muted/50 transition-colors\"\n >\n <Checkbox\n id={feature.key}\n checked={selectedFeatures.includes(feature.key)}\n onCheckedChange={(checked) =>\n handleFeatureChange(feature.key, checked as boolean)\n }\n className=\"data-[state=checked]:bg-primary data-[state=checked]:border-primary\"\n />\n <label\n htmlFor={feature.key}\n className=\"text-sm font-medium leading-none cursor-pointer flex-1\"\n >\n {feature.label}\n </label>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n\n return (\n <Layout>\n <div className=\"container mx-auto px-4 py-8\">\n <FadeIn className=\"mb-8\">\n <div className=\"flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6\">\n <div className=\"space-y-1\">\n <h1 className=\"text-2xl lg:text-3xl font-bold\">\n {searchQuery\n ? t(\"searchResultsFor\", `Search Results for \"${searchQuery}\"`)\n : t(\"allProducts\", \"All Products\")}\n </h1>\n <p className=\"text-sm lg:text-base text-muted-foreground\">\n {t(\"showing\", \"Showing\")} {filteredProducts.length}{\" \"}\n {t(\"of\", \"of\")} {products.length} {t(\"products\", \"products\")}\n </p>\n </div>\n {searchQuery && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n setSearchQuery(\"\");\n setSearchParams({});\n }}\n className=\"w-fit\"\n >\n {t(\"clearSearch\", \"Clear Search\")}\n </Button>\n )}\n </div>\n\n <div className=\"flex flex-col sm:flex-row gap-3 items-stretch sm:items-center justify-between\">\n <Sheet>\n <SheetTrigger asChild>\n <Button\n variant=\"outline\"\n className=\"lg:hidden w-full sm:w-auto\"\n >\n <Filter className=\"h-4 w-4 mr-2\" />\n {t(\"filters\", \"Filters\")}\n </Button>\n </SheetTrigger>\n <SheetContent side=\"left\" className=\"w-[300px]\">\n <SheetHeader>\n <SheetTitle>{t(\"filters\", \"Filters\")}</SheetTitle>\n <SheetDescription>\n {t(\"refineSearch\", \"Refine your product search\")}\n </SheetDescription>\n </SheetHeader>\n <div className=\"mt-6\">\n <FilterSidebar />\n </div>\n </SheetContent>\n </Sheet>\n\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-3\">\n <Select value={sortBy} onValueChange={setSortBy}>\n <SelectTrigger className=\"w-full sm:w-[160px]\">\n <SelectValue placeholder={t(\"sortBy\", \"Sort by\")} />\n </SelectTrigger>\n <SelectContent>\n {sortOptions.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <div className=\"flex border rounded-lg p-1 w-full sm:w-auto\">\n <Button\n variant={viewMode === \"grid\" ? \"default\" : \"ghost\"}\n size=\"sm\"\n onClick={() => setViewMode(\"grid\")}\n className=\"flex-1 sm:flex-none\"\n >\n <Grid className=\"h-4 w-4\" />\n </Button>\n <Button\n variant={viewMode === \"list\" ? \"default\" : \"ghost\"}\n size=\"sm\"\n onClick={() => setViewMode(\"list\")}\n className=\"flex-1 sm:flex-none\"\n >\n <List className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </div>\n </FadeIn>\n\n <div className=\"flex gap-8\">\n <aside className=\"hidden lg:block w-64 flex-shrink-0\">\n <div className=\"sticky top-24\">\n <FilterSidebar />\n </div>\n </aside>\n\n <div className=\"flex-1\">\n {loading ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8\">\n {[...Array(6)].map((_, i) => (\n <div\n key={i}\n className=\"animate-pulse bg-card rounded-lg shadow-md overflow-hidden\"\n >\n <div className=\"aspect-square bg-muted mb-4\"></div>\n <div className=\"p-4\">\n <div className=\"h-4 bg-muted rounded w-3/4 mb-2\"></div>\n <div className=\"h-3 bg-muted rounded w-1/2 mb-3\"></div>\n <div className=\"h-4 bg-muted rounded w-1/3\"></div>\n </div>\n </div>\n ))}\n </div>\n ) : viewMode === \"grid\" ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8\">\n {filteredProducts.map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n variant=\"grid\"\n />\n ))}\n </div>\n ) : (\n <div className=\"space-y-6\">\n {filteredProducts.map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n variant=\"list\"\n />\n ))}\n </div>\n )}\n\n {!loading && filteredProducts.length === 0 && (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground\">\n {t(\n \"noProductsFound\",\n \"No products found matching your criteria.\"\n )}\n </p>\n </div>\n )}\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"path": "products-page/lang/en.json",
|
|
30
|
+
"type": "registry:lang",
|
|
31
|
+
"target": "$modules$/products-page/lang/en.json",
|
|
32
|
+
"content": "{\r\n \"title\": \"Products\",\r\n \"allProducts\": \"All Products\",\r\n \"searchResultsFor\": \"Search Results\",\r\n \"showing\": \"Showing\",\r\n \"of\": \"of\",\r\n \"products\": \"products\",\r\n \"clearSearch\": \"Clear Search\",\r\n \"filters\": \"Filters\",\r\n \"refineSearch\": \"Refine your product search\",\r\n \"categories\": \"Categories\",\r\n \"priceRange\": \"Price Range\",\r\n \"minPrice\": \"Min\",\r\n \"maxPrice\": \"Max\",\r\n \"features\": \"Features\",\r\n \"onSale\": \"On Sale\",\r\n \"newArrivals\": \"New Arrivals\",\r\n \"featuredLabel\": \"Featured\",\r\n \"inStock\": \"In Stock\",\r\n \"sortBy\": \"Sort by\",\r\n \"featured\": \"Featured\",\r\n \"sortPriceLow\": \"Price: Low to High\",\r\n \"sortPriceHigh\": \"Price: High to Low\",\r\n \"sortNewest\": \"Newest\",\r\n \"noProductsFound\": \"No products found matching your criteria.\"\r\n}\r\n"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"path": "products-page/lang/tr.json",
|
|
36
|
+
"type": "registry:lang",
|
|
37
|
+
"target": "$modules$/products-page/lang/tr.json",
|
|
38
|
+
"content": "{\r\n \"title\": \"Ürünler\",\r\n \"allProducts\": \"Tüm Ürünler\",\r\n \"searchResultsFor\": \"Arama Sonuçları\",\r\n \"showing\": \"Gösterilen\",\r\n \"of\": \"/\",\r\n \"products\": \"ürün\",\r\n \"clearSearch\": \"Aramayı Temizle\",\r\n \"filters\": \"Filtreler\",\r\n \"refineSearch\": \"Ürün aramanızı daraltın\",\r\n \"categories\": \"Kategoriler\",\r\n \"priceRange\": \"Fiyat Aralığı\",\r\n \"minPrice\": \"Min\",\r\n \"maxPrice\": \"Max\",\r\n \"features\": \"Özellikler\",\r\n \"onSale\": \"İndirimde\",\r\n \"newArrivals\": \"Yeni Gelenler\",\r\n \"featuredLabel\": \"Öne Çıkan\",\r\n \"inStock\": \"Stokta\",\r\n \"sortBy\": \"Sırala\",\r\n \"featured\": \"Öne Çıkan\",\r\n \"sortPriceLow\": \"Fiyat: Düşükten Yükseğe\",\r\n \"sortPriceHigh\": \"Fiyat: Yüksekten Düşüğe\",\r\n \"sortNewest\": \"En Yeni\",\r\n \"noProductsFound\": \"Kriterlerinize uygun ürün bulunamadı.\"\r\n}\r\n"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"exports": {
|
|
42
|
+
"types": [],
|
|
43
|
+
"variables": [
|
|
44
|
+
"ProductsPage",
|
|
45
|
+
"default"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|