@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": "cart-drawer",
|
|
3
|
+
"type": "registry:component",
|
|
4
|
+
"title": "Cart Drawer",
|
|
5
|
+
"description": "Shopping cart drawer that slides in from the right. Displays cart items with images, prices, quantities, remove buttons, subtotal calculation, checkout button, and continue shopping link. Uses shadcn Sheet component.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"sheet",
|
|
8
|
+
"button"
|
|
9
|
+
],
|
|
10
|
+
"usage": "import { CartDrawer } from '@/modules/cart-drawer';\n\nconst [open, setOpen] = useState(false);\n\n<CartDrawer\n open={open}\n onOpenChange={setOpen}\n items={cartItems}\n onRemove={(id) => removeFromCart(id)}\n checkoutHref=\"/checkout\"\n/>",
|
|
11
|
+
"files": [
|
|
12
|
+
{
|
|
13
|
+
"path": "cart-drawer/index.ts",
|
|
14
|
+
"type": "registry:index",
|
|
15
|
+
"target": "$modules$/cart-drawer/index.ts",
|
|
16
|
+
"content": "export * from './cart-drawer';\r\n"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"path": "cart-drawer/cart-drawer.tsx",
|
|
20
|
+
"type": "registry:component",
|
|
21
|
+
"target": "$modules$/cart-drawer/cart-drawer.tsx",
|
|
22
|
+
"content": "import { Link } from \"react-router\";\r\nimport { ShoppingCart } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport {\r\n Sheet,\r\n SheetContent,\r\n SheetHeader,\r\n SheetTitle,\r\n SheetTrigger,\r\n} from \"@/components/ui/sheet\";\r\nimport { useTranslation } from \"react-i18next\";\r\n\r\ninterface CartItem {\r\n id: string | number;\r\n name: string;\r\n href: string;\r\n color?: string;\r\n price: number;\r\n quantity: number;\r\n image: string;\r\n imageAlt?: string;\r\n}\r\n\r\ninterface CartDrawerProps {\r\n items: CartItem[];\r\n currency?: string;\r\n onRemove?: (id: string | number) => void;\r\n checkoutHref?: string;\r\n open?: boolean;\r\n onOpenChange?: (open: boolean) => void;\r\n}\r\n\r\nexport function CartDrawer({\r\n items,\r\n currency = \"$\",\r\n onRemove,\r\n checkoutHref = \"/checkout\",\r\n open,\r\n onOpenChange,\r\n}: CartDrawerProps) {\r\n const { t } = useTranslation(\"cart-drawer\");\r\n\r\n const subtotal = items.reduce(\r\n (sum, item) => sum + item.price * item.quantity,\r\n 0\r\n );\r\n\r\n return (\r\n <Sheet open={open} onOpenChange={onOpenChange}>\r\n <SheetTrigger asChild>\r\n <Button variant=\"ghost\" size=\"icon\" className=\"relative\">\r\n <ShoppingCart className=\"h-5 w-5\" />\r\n {items.length > 0 && (\r\n <span className=\"absolute -top-1 -right-1 h-5 w-5 rounded-full bg-primary text-primary-foreground text-xs flex items-center justify-center\">\r\n {items.length}\r\n </span>\r\n )}\r\n </Button>\r\n </SheetTrigger>\r\n <SheetContent className=\"w-full sm:max-w-md flex flex-col px-6\">\r\n <SheetHeader>\r\n <SheetTitle>{t(\"title\", \"Shopping cart\")}</SheetTitle>\r\n </SheetHeader>\r\n\r\n <div className=\"flex-1 overflow-y-auto mt-8\">\r\n {items.length === 0 ? (\r\n <p className=\"text-center text-muted-foreground py-8\">\r\n {t(\"empty\", \"Your cart is empty\")}\r\n </p>\r\n ) : (\r\n <ul className=\"-my-6 divide-y divide-border\">\r\n {items.map((item) => (\r\n <li key={item.id} className=\"flex py-6\">\r\n <div className=\"size-24 shrink-0 overflow-hidden rounded-md border border-border\">\r\n <img\r\n alt={item.imageAlt || item.name}\r\n src={item.image}\r\n className=\"size-full object-cover\"\r\n />\r\n </div>\r\n\r\n <div className=\"ml-4 flex flex-1 flex-col\">\r\n <div>\r\n <div className=\"flex justify-between text-base font-medium\">\r\n <h3>\r\n <Link to={item.href}>{item.name}</Link>\r\n </h3>\r\n <p className=\"ml-4\">\r\n {currency}{item.price.toFixed(2)}\r\n </p>\r\n </div>\r\n {item.color && (\r\n <p className=\"mt-1 text-sm text-muted-foreground\">\r\n {item.color}\r\n </p>\r\n )}\r\n </div>\r\n <div className=\"flex flex-1 items-end justify-between text-sm\">\r\n <p className=\"text-muted-foreground\">\r\n {t(\"qty\", \"Qty\")} {item.quantity}\r\n </p>\r\n\r\n <button\r\n type=\"button\"\r\n onClick={() => onRemove?.(item.id)}\r\n className=\"font-medium text-primary hover:text-primary/80\"\r\n >\r\n {t(\"remove\", \"Remove\")}\r\n </button>\r\n </div>\r\n </div>\r\n </li>\r\n ))}\r\n </ul>\r\n )}\r\n </div>\r\n\r\n <div className=\"border-t border-border pt-6 mt-6\">\r\n <div className=\"flex justify-between text-base font-medium\">\r\n <p>{t(\"subtotal\", \"Subtotal\")}</p>\r\n <p>{currency}{subtotal.toFixed(2)}</p>\r\n </div>\r\n <p className=\"mt-0.5 text-sm text-muted-foreground\">\r\n {t(\"shippingNote\", \"Shipping and taxes calculated at checkout.\")}\r\n </p>\r\n <div className=\"mt-6\">\r\n <Button asChild className=\"w-full\">\r\n <Link to={checkoutHref}>{t(\"checkout\", \"Checkout\")}</Link>\r\n </Button>\r\n </div>\r\n <div className=\"mt-6 flex justify-center text-center text-sm text-muted-foreground\">\r\n <p>\r\n {t(\"or\", \"or\")}{\" \"}\r\n <button\r\n type=\"button\"\r\n onClick={() => onOpenChange?.(false)}\r\n className=\"font-medium text-primary hover:text-primary/80\"\r\n >\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n <span aria-hidden=\"true\"> →</span>\r\n </button>\r\n </p>\r\n </div>\r\n </div>\r\n </SheetContent>\r\n </Sheet>\r\n );\r\n}\r\n"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"path": "cart-drawer/lang/en.json",
|
|
26
|
+
"type": "registry:lang",
|
|
27
|
+
"target": "$modules$/cart-drawer/lang/en.json",
|
|
28
|
+
"content": "{\r\n \"title\": \"Shopping cart\",\r\n \"empty\": \"Your cart is empty\",\r\n \"qty\": \"Qty\",\r\n \"remove\": \"Remove\",\r\n \"subtotal\": \"Subtotal\",\r\n \"shippingNote\": \"Shipping and taxes calculated at checkout.\",\r\n \"checkout\": \"Checkout\",\r\n \"or\": \"or\",\r\n \"continueShopping\": \"Continue Shopping\"\r\n}\r\n"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"path": "cart-drawer/lang/tr.json",
|
|
32
|
+
"type": "registry:lang",
|
|
33
|
+
"target": "$modules$/cart-drawer/lang/tr.json",
|
|
34
|
+
"content": "{\r\n \"title\": \"Sepet\",\r\n \"empty\": \"Sepetiniz boş\",\r\n \"qty\": \"Adet\",\r\n \"remove\": \"Kaldır\",\r\n \"subtotal\": \"Ara Toplam\",\r\n \"shippingNote\": \"Kargo ve vergiler ödeme sırasında hesaplanır.\",\r\n \"checkout\": \"Ödemeye Geç\",\r\n \"or\": \"veya\",\r\n \"continueShopping\": \"Alışverişe Devam Et\"\r\n}\r\n"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"exports": {
|
|
38
|
+
"types": [],
|
|
39
|
+
"variables": [
|
|
40
|
+
"CartDrawer"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cart-page",
|
|
3
|
+
"type": "registry:page",
|
|
4
|
+
"title": "Cart Page",
|
|
5
|
+
"description": "Shopping cart page with item list showing product image, name, price, and quantity controls (+/- buttons). Features order summary sidebar with subtotal, shipping estimate, tax calculation, and total. Includes empty cart state with CTA, remove item confirmation, quantity validation, and proceed to checkout button. Responsive layout with mobile-optimized summary.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"ecommerce-core"
|
|
8
|
+
],
|
|
9
|
+
"route": {
|
|
10
|
+
"path": "/cart",
|
|
11
|
+
"componentName": "CartPage"
|
|
12
|
+
},
|
|
13
|
+
"usage": "import CartPage from '@/modules/cart-page';\n\n<Route path=\"/cart\" element={<CartPage />} />\n\n• Uses useCart() from ecommerce-core (Zustand)\n• Features: item list, quantity controls, order summary\n• Empty cart state with shop CTA",
|
|
14
|
+
"files": [
|
|
15
|
+
{
|
|
16
|
+
"path": "cart-page/index.ts",
|
|
17
|
+
"type": "registry:index",
|
|
18
|
+
"target": "$modules$/cart-page/index.ts",
|
|
19
|
+
"content": "export * from './cart-page';\r\nexport { CartPage as default } from './cart-page';\r\n"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "cart-page/cart-page.tsx",
|
|
23
|
+
"type": "registry:page",
|
|
24
|
+
"target": "$modules$/cart-page/cart-page.tsx",
|
|
25
|
+
"content": "import { Link } from \"react-router\";\nimport { Trash2, Plus, Minus, ArrowLeft, ShoppingBag } from \"lucide-react\";\nimport { Layout } from \"@/components/Layout\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Input } from \"@/components/ui/input\";\nimport { useTranslation } from \"react-i18next\";\nimport { useCart, formatPrice } from \"@/modules/ecommerce-core\";\nimport constants from \"@/constants/constants.json\";\nimport { FadeIn } from \"@/modules/animations\";\n\nexport function CartPage() {\n const { t } = useTranslation(\"cart-page\");\n const { state, removeItem, updateQuantity } = useCart();\n const { items, total } = state;\n\n const currency = constants.site.currency || \"USD\";\n const shipping = 0;\n const tax = 0;\n const freeShippingThreshold = 100;\n\n const getProductPrice = (product: { price: number; sale_price?: number; on_sale?: boolean }) => {\n return product.on_sale && product.sale_price ? product.sale_price : product.price;\n };\n\n const handleQuantityChange = (productId: number | string, newQuantity: number) => {\n if (newQuantity <= 0) {\n removeItem(productId);\n } else {\n updateQuantity(productId, newQuantity);\n }\n };\n\n const handleQuantityInputChange = (productId: number | string, value: string) => {\n const quantity = parseInt(value) || 1;\n handleQuantityChange(productId, quantity);\n };\n\n const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);\n const finalTotal = total + shipping + tax;\n\n if (items.length === 0) {\n return (\n <Layout>\n <div className=\"container mx-auto px-4 py-8\">\n <div className=\"max-w-2xl mx-auto text-center\">\n <div className=\"mb-8\">\n <ShoppingBag className=\"h-24 w-24 mx-auto text-muted-foreground mb-4\" />\n <h1 className=\"text-3xl font-bold mb-4\">\n {t(\"empty\", \"Your Cart is Empty\")}\n </h1>\n <p className=\"text-muted-foreground mb-8\">\n {t(\"emptyDescription\", \"Looks like you haven't added any items to your cart yet.\")}\n </p>\n <Button asChild size=\"lg\">\n <Link to=\"/products\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n {t(\"continueShopping\", \"Continue Shopping\")}\n </Link>\n </Button>\n </div>\n </div>\n </div>\n </Layout>\n );\n }\n\n return (\n <Layout>\n <div className=\"container mx-auto px-4 py-8\">\n <FadeIn className=\"flex items-center gap-4 mb-8\">\n <Button variant=\"ghost\" size=\"icon\" asChild>\n <Link to=\"/products\">\n <ArrowLeft className=\"h-4 w-4\" />\n </Link>\n </Button>\n <div>\n <h1 className=\"text-3xl font-bold\">{t(\"title\", \"Shopping Cart\")}</h1>\n <p className=\"text-muted-foreground\">\n {itemCount} {t(\"itemsInCart\", \"items in your cart\")}\n </p>\n </div>\n </FadeIn>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-8\">\n <div className=\"lg:col-span-2 space-y-4\">\n {items.map((item) => (\n <Card key={item.id}>\n <CardContent className=\"p-6\">\n <div className=\"flex gap-4\">\n <div className=\"w-24 h-24 flex-shrink-0\">\n <img\n src={item.product.images[0] || \"/images/placeholder.png\"}\n alt={item.product.name}\n className=\"w-full h-full object-cover rounded-lg\"\n />\n </div>\n\n <div className=\"flex-1 space-y-2\">\n <div className=\"flex items-start justify-between\">\n <div>\n <h3 className=\"font-semibold\">{item.product.name}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {item.product.category_name ||\n item.product.categories?.[0]?.name ||\n item.product.category}\n </p>\n </div>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => removeItem(item.product.id)}\n className=\"text-destructive hover:text-destructive\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </div>\n\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Button\n variant=\"outline\"\n size=\"icon\"\n className=\"h-8 w-8\"\n onClick={() => handleQuantityChange(item.product.id, item.quantity - 1)}\n >\n <Minus className=\"h-3 w-3\" />\n </Button>\n <Input\n type=\"number\"\n value={item.quantity}\n onChange={(e) => handleQuantityInputChange(item.product.id, e.target.value)}\n className=\"w-16 text-center\"\n min=\"1\"\n />\n <Button\n variant=\"outline\"\n size=\"icon\"\n className=\"h-8 w-8\"\n onClick={() => handleQuantityChange(item.product.id, item.quantity + 1)}\n >\n <Plus className=\"h-3 w-3\" />\n </Button>\n </div>\n\n <div className=\"text-right\">\n <p className=\"font-semibold\">\n {formatPrice(getProductPrice(item.product) * item.quantity, currency)}\n </p>\n {item.quantity > 1 && (\n <p className=\"text-sm text-muted-foreground\">\n {formatPrice(getProductPrice(item.product), currency)} {t(\"each\", \"each\")}\n </p>\n )}\n </div>\n </div>\n </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>{t(\"orderSummary\", \"Order Summary\")}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex justify-between\">\n <span>\n {t(\"subtotal\", \"Subtotal\")} ({itemCount} {t(\"items\", \"items\")})\n </span>\n <span>{formatPrice(total, currency)}</span>\n </div>\n\n <div className=\"flex justify-between\">\n <span>{t(\"shipping\", \"Shipping\")}</span>\n <span>\n {shipping === 0 ? t(\"free\", \"Free\") : formatPrice(shipping, currency)}\n </span>\n </div>\n\n <div className=\"flex justify-between\">\n <span>{t(\"tax\", \"Tax\")}</span>\n <span>{formatPrice(tax, currency)}</span>\n </div>\n\n <Separator />\n\n <div className=\"flex justify-between text-lg font-semibold\">\n <span>{t(\"total\", \"Total\")}</span>\n <span>{formatPrice(finalTotal, currency)}</span>\n </div>\n\n {shipping > 0 && freeShippingThreshold && freeShippingThreshold > total && (\n <div className=\"text-sm text-muted-foreground bg-muted/50 p-3 rounded-lg\">\n {t(\"freeShippingMessage\", \"Add {{amount}} more for free shipping!\").replace(\n \"{{amount}}\",\n formatPrice(freeShippingThreshold - total, currency)\n )}\n </div>\n )}\n\n <Button asChild className=\"w-full\" size=\"lg\">\n <Link to=\"/checkout\">{t(\"proceedToCheckout\", \"Proceed to Checkout\")}</Link>\n </Button>\n\n <Button variant=\"outline\" asChild className=\"w-full\">\n <Link to=\"/products\">{t(\"continueShopping\", \"Continue Shopping\")}</Link>\n </Button>\n </CardContent>\n </Card>\n\n <Card>\n <CardContent className=\"p-4\">\n <div className=\"text-center space-y-2\">\n <div className=\"text-sm text-muted-foreground\">\n {t(\"secureCheckout\", \"Secure Checkout\")}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {t(\"secureCheckoutDescription\", \"Your payment information is encrypted and secure\")}\n </p>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "cart-page/lang/en.json",
|
|
29
|
+
"type": "registry:lang",
|
|
30
|
+
"target": "$modules$/cart-page/lang/en.json",
|
|
31
|
+
"content": "{\r\n \"title\": \"Shopping Cart\",\r\n \"empty\": \"Your Cart is Empty\",\r\n \"emptyDescription\": \"Looks like you haven't added any items to your cart yet.\",\r\n \"addToCart\": \"Add to Cart\",\r\n \"removeItem\": \"Remove Item\",\r\n \"quantity\": \"Quantity\",\r\n \"subtotal\": \"Subtotal\",\r\n \"shipping\": \"Shipping\",\r\n \"tax\": \"Tax\",\r\n \"total\": \"Total\",\r\n \"proceedToCheckout\": \"Proceed to Checkout\",\r\n \"secureCheckout\": \"Secure Checkout\",\r\n \"orderSummary\": \"Order Summary\",\r\n \"itemsInCart\": \"items in your cart\",\r\n \"items\": \"items\",\r\n \"variant\": \"Variant\",\r\n \"each\": \"each\",\r\n \"freeShippingMessage\": \"Add {{amount}} more for free shipping!\",\r\n \"continueShopping\": \"Continue Shopping\",\r\n \"secureCheckoutDescription\": \"Your payment information is encrypted and secure\",\r\n \"free\": \"Free\"\r\n}\r\n"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"path": "cart-page/lang/tr.json",
|
|
35
|
+
"type": "registry:lang",
|
|
36
|
+
"target": "$modules$/cart-page/lang/tr.json",
|
|
37
|
+
"content": "{\r\n \"title\": \"Alışveriş Sepeti\",\r\n \"empty\": \"Sepetiniz Boş\",\r\n \"emptyDescription\": \"Henüz sepetinize hiç ürün eklememişsiniz.\",\r\n \"addToCart\": \"Sepete Ekle\",\r\n \"removeItem\": \"Ürünü Kaldır\",\r\n \"quantity\": \"Adet\",\r\n \"subtotal\": \"Ara Toplam\",\r\n \"shipping\": \"Kargo\",\r\n \"tax\": \"Vergi\",\r\n \"total\": \"Toplam\",\r\n \"proceedToCheckout\": \"Ödemeye Geç\",\r\n \"secureCheckout\": \"Güvenli Ödeme\",\r\n \"orderSummary\": \"Sipariş Özeti\",\r\n \"itemsInCart\": \"sepetinizdeki ürün\",\r\n \"items\": \"ürün\",\r\n \"variant\": \"Varyant\",\r\n \"each\": \"adet\",\r\n \"freeShippingMessage\": \"Ücretsiz kargo için {{amount}} daha ekleyin!\",\r\n \"continueShopping\": \"Alışverişe Devam Et\",\r\n \"secureCheckoutDescription\": \"Ödeme bilgileriniz şifreli ve güvenlidir\",\r\n \"free\": \"Ücretsiz\"\r\n}\r\n"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"exports": {
|
|
41
|
+
"types": [],
|
|
42
|
+
"variables": [
|
|
43
|
+
"CartPage",
|
|
44
|
+
"default"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "category-section",
|
|
3
|
+
"type": "registry:component",
|
|
4
|
+
"title": "Category Section",
|
|
5
|
+
"description": "E-commerce category showcase grid with image cards. Each card has category image, name overlay, and hover zoom effect. Supports 4-6 categories in responsive grid (2 cols mobile, 3 cols tablet, 4+ cols desktop). Click navigates to filtered products page. Great for homepage category browsing.",
|
|
6
|
+
"registryDependencies": [],
|
|
7
|
+
"usage": "import { CategorySection } from '@/modules/category-section';\n\n<CategorySection />\n\n• Installed at: src/modules/category-section/\n• Customize content: src/modules/category-section/lang/*.json\n• Categories link to: /products?category={slug}",
|
|
8
|
+
"files": [
|
|
9
|
+
{
|
|
10
|
+
"path": "category-section/index.ts",
|
|
11
|
+
"type": "registry:index",
|
|
12
|
+
"target": "$modules$/category-section/index.ts",
|
|
13
|
+
"content": "export * from './category-section';\r\n"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"path": "category-section/category-section.tsx",
|
|
17
|
+
"type": "registry:component",
|
|
18
|
+
"target": "$modules$/category-section/category-section.tsx",
|
|
19
|
+
"content": "import { Link } from \"react-router\";\nimport { ArrowRight } from \"lucide-react\";\nimport { Card } from \"@/components/ui/card\";\nimport { useTranslation } from \"react-i18next\";\n\nexport interface CategoryItem {\n id: string | number;\n slug: string;\n name: string;\n description?: string;\n image: string;\n}\n\nexport interface CategorySectionProps {\n categories: CategoryItem[];\n loading?: boolean;\n}\n\nexport function CategorySection({\n categories,\n loading = false,\n}: CategorySectionProps) {\n const { t } = useTranslation(\"category-section\");\n\n return (\n <section className=\"py-8 sm:py-12 md:py-16 lg:py-20 bg-gradient-to-b from-background to-muted/20 border-t border-border/20\">\n <div className=\"container mx-auto px-3 sm:px-4 lg:px-8\">\n <div className=\"text-center mb-6 sm:mb-8 md:mb-12 lg:mb-16 px-2\">\n <h2 className=\"text-xl sm:text-2xl md:text-3xl lg:text-4xl xl:text-5xl font-bold mb-2 sm:mb-3 md:mb-4 bg-gradient-to-r from-primary to-primary/80 bg-clip-text text-transparent leading-normal pb-1\">\n {t(\"title\", \"Shop by Category\")}\n </h2>\n <div className=\"w-12 sm:w-16 h-1 bg-gradient-to-r from-primary/50 to-primary/20 mx-auto mb-3 sm:mb-4 md:mb-6 rounded-full\"></div>\n <p className=\"text-xs sm:text-sm md:text-base lg:text-lg text-muted-foreground max-w-3xl mx-auto leading-relaxed\">\n {t(\"subtitle\", \"Discover our carefully curated collections\")}\n </p>\n </div>\n\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6 lg:gap-8\">\n {loading\n ? [...Array(4)].map((_, i) => (\n <div key={i} className=\"animate-pulse\">\n <div className=\"aspect-[3/2] bg-muted rounded-xl mb-4\"></div>\n <div className=\"h-5 bg-muted rounded w-3/4 mb-2\"></div>\n <div className=\"h-4 bg-muted rounded w-1/2\"></div>\n </div>\n ))\n : categories.map((category) => (\n <Link\n key={category.id}\n to={`/products?category=${category.slug}`}\n className=\"group block\"\n >\n <Card className=\"overflow-hidden border-0 p-0 shadow-lg hover:shadow-2xl transition-all duration-500 group-hover:-translate-y-2 rounded-2xl\">\n <div className=\"aspect-[4/3] relative overflow-hidden\">\n <img\n src={category.image}\n alt={category.name}\n className=\"absolute inset-0 w-full h-full object-cover group-hover:scale-110 transition-transform duration-700\"\n />\n <div className=\"absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent group-hover:from-black/70 transition-all duration-300\"></div>\n\n <div className=\"absolute bottom-0 left-0 right-0 p-4 sm:p-6\">\n <h3 className=\"text-lg sm:text-xl font-bold text-white mb-1 sm:mb-2 group-hover:text-primary-foreground transition-colors\">\n {category.name}\n </h3>\n {category.description && (\n <p className=\"text-xs sm:text-sm text-white/90 line-clamp-2 group-hover:text-white transition-colors\">\n {category.description}\n </p>\n )}\n\n <div className=\"flex items-center mt-2 sm:mt-3 text-white/80 group-hover:text-white transition-all duration-300 transform group-hover:translate-x-1\">\n <span className=\"text-xs sm:text-sm font-medium mr-2\">\n {t(\"explore\", \"Explore\")}\n </span>\n <ArrowRight className=\"w-3 h-3 sm:w-4 sm:h-4\" />\n </div>\n </div>\n </div>\n </Card>\n </Link>\n ))}\n </div>\n </div>\n </section>\n );\n}\n"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "category-section/lang/en.json",
|
|
23
|
+
"type": "registry:lang",
|
|
24
|
+
"target": "$modules$/category-section/lang/en.json",
|
|
25
|
+
"content": "{\r\n \"title\": \"Shop by Category\",\r\n \"subtitle\": \"Discover our carefully curated collections\",\r\n \"explore\": \"Explore\"\r\n}\r\n"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "category-section/lang/tr.json",
|
|
29
|
+
"type": "registry:lang",
|
|
30
|
+
"target": "$modules$/category-section/lang/tr.json",
|
|
31
|
+
"content": "{\r\n \"title\": \"Kategorilere Göz Atın\",\r\n \"subtitle\": \"Özenle seçilmiş koleksiyonlarımızı keşfedin\",\r\n \"explore\": \"Keşfet\"\r\n}\r\n"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"exports": {
|
|
35
|
+
"types": [
|
|
36
|
+
"CategoryItem",
|
|
37
|
+
"CategorySectionProps"
|
|
38
|
+
],
|
|
39
|
+
"variables": [
|
|
40
|
+
"CategorySection"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "checkout-page",
|
|
3
|
+
"type": "registry:page",
|
|
4
|
+
"title": "Checkout Page",
|
|
5
|
+
"description": "Multi-step checkout page with customer information form (name, email, phone, address), shipping method selection, payment method options (credit card, PayPal, bank transfer), and order review. Features form validation, order summary sidebar, shipping cost calculation, and place order button. Includes progress indicator for checkout steps.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"ecommerce-core"
|
|
8
|
+
],
|
|
9
|
+
"route": {
|
|
10
|
+
"path": "/checkout",
|
|
11
|
+
"componentName": "CheckoutPage"
|
|
12
|
+
},
|
|
13
|
+
"usage": "import CheckoutPage from '@/modules/checkout-page';\n\n<Route path=\"/checkout\" element={<CheckoutPage />} />\n\n• Uses useCart() from ecommerce-core (Zustand)\n• Steps: customer info → shipping → payment → review\n• Form validation with react-hook-form\n• Connect payment provider (Stripe, PayPal, etc.)",
|
|
14
|
+
"files": [
|
|
15
|
+
{
|
|
16
|
+
"path": "checkout-page/index.ts",
|
|
17
|
+
"type": "registry:index",
|
|
18
|
+
"target": "$modules$/checkout-page/index.ts",
|
|
19
|
+
"content": "export * from './checkout-page';\r\nexport { CheckoutPage as default } from './checkout-page';\r\n"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "checkout-page/checkout-page.tsx",
|
|
23
|
+
"type": "registry:page",
|
|
24
|
+
"target": "$modules$/checkout-page/checkout-page.tsx",
|
|
25
|
+
"content": "import { useState } from \"react\";\nimport { Link, useNavigate } from \"react-router\";\nimport { ArrowLeft, CreditCard, Banknote, Truck, Check } from \"lucide-react\";\nimport { Layout } from \"@/components/Layout\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { RadioGroup, RadioGroupItem } from \"@/components/ui/radio-group\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport { useTranslation } from \"react-i18next\";\nimport { useCart, formatPrice } from \"@/modules/ecommerce-core\";\nimport constants from \"@/constants/constants.json\";\nimport { FadeIn } from \"@/modules/animations\";\n\ninterface Country {\n value: string;\n label: string;\n}\n\ntype PaymentMethod = \"card\" | \"cash\" | \"transfer\";\n\ninterface CheckoutFormData {\n firstName: string;\n lastName: string;\n email: string;\n phone: string;\n address: string;\n city: string;\n postalCode: string;\n country: string;\n notes: string;\n}\n\nconst DEFAULT_COUNTRIES: Country[] = [\n { value: \"US\", label: \"United States\" },\n { value: \"GB\", label: \"United Kingdom\" },\n { value: \"CA\", label: \"Canada\" },\n { value: \"AU\", label: \"Australia\" },\n { value: \"DE\", label: \"Germany\" },\n { value: \"FR\", label: \"France\" },\n { value: \"TR\", label: \"Turkey\" },\n { value: \"JP\", label: \"Japan\" },\n];\n\nexport function CheckoutPage() {\n const { t } = useTranslation(\"checkout-page\");\n const navigate = useNavigate();\n const { state, clearCart } = useCart();\n const { items, total } = state;\n\n const currency = constants.site.currency || \"USD\";\n const shipping = 0;\n const tax = 0;\n const countries = DEFAULT_COUNTRIES;\n\n const getProductPrice = (product: {\n price: number;\n sale_price?: number;\n on_sale?: boolean;\n }) => {\n return product.on_sale && product.sale_price\n ? product.sale_price\n : product.price;\n };\n\n const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>(\"card\");\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [formData, setFormData] = useState<CheckoutFormData>({\n firstName: \"\",\n lastName: \"\",\n email: \"\",\n phone: \"\",\n address: \"\",\n city: \"\",\n postalCode: \"\",\n country: \"\",\n notes: \"\",\n });\n const [agreedToTerms, setAgreedToTerms] = useState(false);\n\n const finalTotal = total + shipping + tax;\n\n const handleInputChange = (\n e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>\n ) => {\n const { name, value } = e.target;\n setFormData((prev) => ({ ...prev, [name]: value }));\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!agreedToTerms) return;\n\n setIsSubmitting(true);\n\n // Simulate order submission\n await new Promise((resolve) => setTimeout(resolve, 1500));\n\n // Clear cart and redirect to success page\n clearCart();\n navigate(\"/\");\n\n setIsSubmitting(false);\n };\n\n const paymentMethods = [\n {\n id: \"card\" as PaymentMethod,\n label: t(\"card\", \"Credit/Debit Card\"),\n description: t(\n \"cardDescription\",\n \"Pay securely with your credit or debit card\"\n ),\n icon: CreditCard,\n iconColor: \"text-blue-600\",\n },\n {\n id: \"transfer\" as PaymentMethod,\n label: t(\"transfer\", \"Bank Transfer\"),\n description: t(\n \"transferDescription\",\n \"Transfer payment to our bank account\"\n ),\n icon: Banknote,\n iconColor: \"text-primary\",\n },\n {\n id: \"cash\" as PaymentMethod,\n label: t(\"cash\", \"Cash on Delivery\"),\n description: t(\n \"cashDescription\",\n \"Pay when your order arrives at your doorstep\"\n ),\n icon: Truck,\n iconColor: \"text-green-600 dark:text-green-400\",\n },\n ];\n\n if (items.length === 0) {\n return (\n <Layout>\n <div className=\"container mx-auto px-4 py-8\">\n <div className=\"max-w-2xl mx-auto text-center\">\n <h1 className=\"text-3xl font-bold mb-4\">\n {t(\"cartEmpty\", \"Your cart is empty\")}\n </h1>\n <p className=\"text-muted-foreground mb-8\">\n {t(\n \"cartEmptyDescription\",\n \"Please add items to your cart before proceeding to checkout.\"\n )}\n </p>\n <Button asChild>\n <Link to=\"/products\">\n {t(\"continueShopping\", \"Continue Shopping\")}\n </Link>\n </Button>\n </div>\n </div>\n </Layout>\n );\n }\n\n return (\n <Layout>\n <div className=\"container mx-auto px-4 py-8\">\n <FadeIn className=\"flex items-center gap-4 mb-8\">\n <Button variant=\"ghost\" size=\"icon\" asChild>\n <Link to=\"/cart\">\n <ArrowLeft className=\"h-4 w-4\" />\n </Link>\n </Button>\n <div>\n <h1 className=\"text-3xl font-bold\">{t(\"title\", \"Checkout\")}</h1>\n <p className=\"text-muted-foreground\">\n {t(\"completeOrder\", \"Complete your order\")}\n </p>\n </div>\n </FadeIn>\n\n <form onSubmit={handleSubmit}>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-8\">\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Contact Information */}\n <FadeIn delay={0.1}>\n <Card>\n <CardHeader>\n <CardTitle>\n {t(\"contactInformation\", \"Contact Information\")}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <Label htmlFor=\"firstName\">\n {t(\"firstName\", \"First Name\")} *\n </Label>\n <Input\n id=\"firstName\"\n name=\"firstName\"\n value={formData.firstName}\n onChange={handleInputChange}\n required\n />\n </div>\n <div>\n <Label htmlFor=\"lastName\">\n {t(\"lastName\", \"Last Name\")} *\n </Label>\n <Input\n id=\"lastName\"\n name=\"lastName\"\n value={formData.lastName}\n onChange={handleInputChange}\n required\n />\n </div>\n </div>\n <div>\n <Label htmlFor=\"email\">\n {t(\"email\", \"Email Address\")} *\n </Label>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n value={formData.email}\n onChange={handleInputChange}\n required\n />\n </div>\n <div>\n <Label htmlFor=\"phone\">\n {t(\"phone\", \"Phone Number\")} *\n </Label>\n <Input\n id=\"phone\"\n name=\"phone\"\n type=\"tel\"\n value={formData.phone}\n onChange={handleInputChange}\n required\n />\n </div>\n </CardContent>\n </Card>\n </FadeIn>\n\n {/* Shipping Address */}\n <FadeIn delay={0.2}>\n <Card>\n <CardHeader>\n <CardTitle>\n {t(\"shippingAddress\", \"Shipping Address\")}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <Label htmlFor=\"address\">{t(\"address\", \"Address\")} *</Label>\n <Textarea\n id=\"address\"\n name=\"address\"\n value={formData.address}\n onChange={handleInputChange}\n placeholder={t(\n \"addressPlaceholder\",\n \"Street address, apartment, suite, etc.\"\n )}\n required\n />\n </div>\n <div>\n <Label htmlFor=\"country\">{t(\"country\", \"Country\")} *</Label>\n <Select\n value={formData.country}\n onValueChange={(value) =>\n setFormData((prev) => ({ ...prev, country: value }))\n }\n required\n >\n <SelectTrigger id=\"country\">\n <SelectValue\n placeholder={t(\"selectCountry\", \"Select a country\")}\n />\n </SelectTrigger>\n <SelectContent>\n {countries.map((country) => (\n <SelectItem key={country.value} value={country.value}>\n {country.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <Label htmlFor=\"city\">{t(\"city\", \"City\")} *</Label>\n <Input\n id=\"city\"\n name=\"city\"\n value={formData.city}\n onChange={handleInputChange}\n required\n />\n </div>\n <div>\n <Label htmlFor=\"postalCode\">\n {t(\"postalCode\", \"Postal Code\")} *\n </Label>\n <Input\n id=\"postalCode\"\n name=\"postalCode\"\n value={formData.postalCode}\n onChange={handleInputChange}\n required\n />\n </div>\n </div>\n </CardContent>\n </Card>\n </FadeIn>\n\n {/* Payment Method */}\n <FadeIn delay={0.3}>\n <Card>\n <CardHeader>\n <CardTitle>{t(\"paymentMethod\", \"Payment Method\")}</CardTitle>\n </CardHeader>\n <CardContent>\n <RadioGroup\n value={paymentMethod}\n onValueChange={(value) =>\n setPaymentMethod(value as PaymentMethod)\n }\n className=\"space-y-4\"\n >\n {paymentMethods.map((method) => (\n <div\n key={method.id}\n className=\"flex items-center space-x-2 p-4 border rounded-lg\"\n >\n <RadioGroupItem value={method.id} id={method.id} />\n <Label\n htmlFor={method.id}\n className=\"flex-1 cursor-pointer\"\n >\n <div className=\"flex items-center gap-3\">\n <method.icon\n className={`h-5 w-5 ${method.iconColor}`}\n />\n <div>\n <div className=\"font-medium\">{method.label}</div>\n <div className=\"text-sm text-muted-foreground\">\n {method.description}\n </div>\n </div>\n </div>\n </Label>\n </div>\n ))}\n </RadioGroup>\n </CardContent>\n </Card>\n </FadeIn>\n\n {/* Order Notes */}\n <FadeIn delay={0.4}>\n <Card>\n <CardHeader>\n <CardTitle>\n {t(\"orderNotesOptional\", \"Order Notes (Optional)\")}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <Textarea\n name=\"notes\"\n value={formData.notes}\n onChange={handleInputChange}\n placeholder={t(\n \"orderNotesPlaceholder\",\n \"Special instructions for your order...\"\n )}\n rows={3}\n />\n </CardContent>\n </Card>\n </FadeIn>\n </div>\n\n {/* Order Summary */}\n <FadeIn delay={0.2} className=\"lg:col-span-1\">\n <Card className=\"sticky top-24\">\n <CardHeader>\n <CardTitle>{t(\"orderSummary\", \"Order Summary\")}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"space-y-3\">\n {items.map((item) => (\n <div key={item.id} className=\"flex gap-3\">\n <img\n src={\n item.product.images?.[0] ||\n \"/images/placeholder.png\"\n }\n alt={item.product.name}\n className=\"w-12 h-12 object-cover rounded\"\n />\n <div className=\"flex-1 space-y-1\">\n <h4 className=\"text-sm font-medium leading-normal\">\n {item.product.name}\n </h4>\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-muted-foreground\">\n {t(\"qty\", \"Qty\")}: {item.quantity}\n </span>\n <span>\n {formatPrice(\n getProductPrice(item.product) * item.quantity,\n currency\n )}\n </span>\n </div>\n </div>\n </div>\n ))}\n </div>\n\n <Separator />\n\n <div className=\"space-y-2\">\n <div className=\"flex justify-between\">\n <span>{t(\"subtotal\", \"Subtotal\")}</span>\n <span>{formatPrice(total, currency)}</span>\n </div>\n <div className=\"flex justify-between\">\n <span>{t(\"shipping\", \"Shipping\")}</span>\n <span>\n {shipping === 0\n ? t(\"free\", \"Free\")\n : formatPrice(shipping, currency)}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span>{t(\"tax\", \"Tax\")}</span>\n <span>{formatPrice(tax, currency)}</span>\n </div>\n </div>\n\n <Separator />\n\n <div className=\"flex justify-between text-lg font-semibold\">\n <span>{t(\"total\", \"Total\")}</span>\n <span>{formatPrice(finalTotal, currency)}</span>\n </div>\n\n <div className=\"flex items-start space-x-2\">\n <Checkbox\n id=\"terms\"\n checked={agreedToTerms}\n onCheckedChange={(checked) =>\n setAgreedToTerms(checked as boolean)\n }\n />\n <Label htmlFor=\"terms\" className=\"text-sm leading-relaxed\">\n {t(\"agreeToTermsTextBefore\", \"I agree to the\")}{\" \"}\n <Link\n to=\"/terms\"\n className=\"text-primary hover:underline\"\n >\n {t(\"termsOfService\", \"Terms of Service\")}\n </Link>{\" \"}\n {t(\"and\", \"and\")}{\" \"}\n <Link\n to=\"/privacy\"\n className=\"text-primary hover:underline\"\n >\n {t(\"privacyPolicy\", \"Privacy Policy\")}\n </Link>\n </Label>\n </div>\n\n <Button\n type=\"submit\"\n className=\"w-full\"\n size=\"lg\"\n disabled={!agreedToTerms || isSubmitting}\n >\n {isSubmitting ? (\n <>\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\n {t(\"processing\", \"Processing...\")}\n </>\n ) : (\n <>\n <Check className=\"w-4 h-4 mr-2\" />\n {paymentMethod === \"card\"\n ? t(\"proceedToPayment\", \"Proceed to Payment\")\n : t(\"placeOrder\", \"Place Order\")}\n </>\n )}\n </Button>\n </CardContent>\n </Card>\n </FadeIn>\n </div>\n </form>\n </div>\n </Layout>\n );\n}\n"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "checkout-page/lang/en.json",
|
|
29
|
+
"type": "registry:lang",
|
|
30
|
+
"target": "$modules$/checkout-page/lang/en.json",
|
|
31
|
+
"content": "{\r\n \"title\": \"Checkout\",\r\n \"completeOrder\": \"Complete your order\",\r\n \"contactInformation\": \"Contact Information\",\r\n \"shippingAddress\": \"Shipping Address\",\r\n \"paymentMethod\": \"Payment Method\",\r\n \"orderSummary\": \"Order Summary\",\r\n \"placeOrder\": \"Place Order\",\r\n \"processing\": \"Processing...\",\r\n \"firstName\": \"First Name\",\r\n \"lastName\": \"Last Name\",\r\n \"email\": \"Email Address\",\r\n \"phone\": \"Phone Number\",\r\n \"address\": \"Address\",\r\n \"city\": \"City\",\r\n \"postalCode\": \"Postal Code\",\r\n \"country\": \"Country\",\r\n \"selectCountry\": \"Select a country\",\r\n \"qty\": \"Qty\",\r\n \"subtotal\": \"Subtotal\",\r\n \"shipping\": \"Shipping\",\r\n \"free\": \"Free\",\r\n \"tax\": \"Tax\",\r\n \"total\": \"Total\",\r\n \"and\": \"and\",\r\n \"termsOfService\": \"Terms of Service\",\r\n \"privacyPolicy\": \"Privacy Policy\",\r\n \"addressPlaceholder\": \"Street address, apartment, suite, etc.\",\r\n \"orderNotesOptional\": \"Order Notes (Optional)\",\r\n \"orderNotesPlaceholder\": \"Special instructions for your order...\",\r\n \"agreeToTermsTextBefore\": \"I agree to the\",\r\n \"agreeToTermsError\": \"Please agree to the terms and conditions\",\r\n \"card\": \"Credit/Debit Card\",\r\n \"transfer\": \"Bank Transfer\",\r\n \"cash\": \"Cash on Delivery\",\r\n \"cardDescription\": \"Pay securely with your credit or debit card\",\r\n \"transferDescription\": \"Transfer payment to our bank account\",\r\n \"cashDescription\": \"Pay when your order arrives at your doorstep\",\r\n \"proceedToPayment\": \"Proceed to Payment\",\r\n \"cartEmpty\": \"Your cart is empty\",\r\n \"cartEmptyDescription\": \"Please add items to your cart before proceeding to checkout.\",\r\n \"continueShopping\": \"Continue Shopping\"\r\n}\r\n"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"path": "checkout-page/lang/tr.json",
|
|
35
|
+
"type": "registry:lang",
|
|
36
|
+
"target": "$modules$/checkout-page/lang/tr.json",
|
|
37
|
+
"content": "{\r\n \"title\": \"Ödeme\",\r\n \"completeOrder\": \"Siparişinizi tamamlayın\",\r\n \"contactInformation\": \"İletişim Bilgileri\",\r\n \"shippingAddress\": \"Teslimat Adresi\",\r\n \"paymentMethod\": \"Ödeme Yöntemi\",\r\n \"orderSummary\": \"Sipariş Özeti\",\r\n \"placeOrder\": \"Siparişi Tamamla\",\r\n \"processing\": \"İşleniyor...\",\r\n \"firstName\": \"Ad\",\r\n \"lastName\": \"Soyad\",\r\n \"email\": \"E-posta Adresi\",\r\n \"phone\": \"Telefon Numarası\",\r\n \"address\": \"Adres\",\r\n \"city\": \"Şehir\",\r\n \"postalCode\": \"Posta Kodu\",\r\n \"country\": \"Ülke\",\r\n \"selectCountry\": \"Ülke seçin\",\r\n \"qty\": \"Adet\",\r\n \"subtotal\": \"Ara Toplam\",\r\n \"shipping\": \"Kargo\",\r\n \"free\": \"Ücretsiz\",\r\n \"tax\": \"Vergi\",\r\n \"total\": \"Toplam\",\r\n \"and\": \"ve\",\r\n \"termsOfService\": \"Hizmet Koşulları\",\r\n \"privacyPolicy\": \"Gizlilik Politikası\",\r\n \"addressPlaceholder\": \"Sokak adresi, daire, suit, vb.\",\r\n \"orderNotesOptional\": \"Sipariş Notları (Opsiyonel)\",\r\n \"orderNotesPlaceholder\": \"Siparişiniz için özel talimatlar...\",\r\n \"agreeToTermsTextBefore\": \"Kabul ediyorum:\",\r\n \"agreeToTermsError\": \"Lütfen hizmet koşullarını kabul edin\",\r\n \"card\": \"Kredi/Banka Kartı\",\r\n \"transfer\": \"Banka Havalesi\",\r\n \"cash\": \"Kapıda Ödeme\",\r\n \"cardDescription\": \"Kredi veya banka kartınızla güvenli ödeme\",\r\n \"transferDescription\": \"Banka hesabımıza ödeme transferi\",\r\n \"cashDescription\": \"Siparişiniz kapınıza geldiğinde ödeme\",\r\n \"proceedToPayment\": \"Ödemeye Geç\",\r\n \"cartEmpty\": \"Sepetiniz boş\",\r\n \"cartEmptyDescription\": \"Ödemeye geçmeden önce lütfen sepetinize ürün ekleyin.\",\r\n \"continueShopping\": \"Alışverişe Devam Et\"\r\n}\r\n"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"exports": {
|
|
41
|
+
"types": [],
|
|
42
|
+
"variables": [
|
|
43
|
+
"CheckoutPage",
|
|
44
|
+
"default"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "contact-info-grid",
|
|
3
|
+
"type": "registry:component",
|
|
4
|
+
"title": "Contact Info Grid",
|
|
5
|
+
"description": "4-card grid displaying contact information (email, office address, phone, live chat). Each card has an icon, label, description, and clickable value. Uses constants.json for contact details. Perfect as a standalone section or complement to contact forms.",
|
|
6
|
+
"registryDependencies": [],
|
|
7
|
+
"usage": "import { ContactInfoGrid } from '@/modules/contact-info-grid';\n\n<ContactInfoGrid />\n\n- 4-card responsive grid\n- Email, Office, Phone, Chat\n- Uses constants.json values\n- Hover effects on cards",
|
|
8
|
+
"files": [
|
|
9
|
+
{
|
|
10
|
+
"path": "contact-info-grid/index.ts",
|
|
11
|
+
"type": "registry:index",
|
|
12
|
+
"target": "$modules$/contact-info-grid/index.ts",
|
|
13
|
+
"content": "export * from './contact-info-grid';\r\n"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"path": "contact-info-grid/contact-info-grid.tsx",
|
|
17
|
+
"type": "registry:component",
|
|
18
|
+
"target": "$modules$/contact-info-grid/contact-info-grid.tsx",
|
|
19
|
+
"content": "import { Mail, MapPin, MessageCircle, Phone } from \"lucide-react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface ContactInfoGridProps {\r\n className?: string;\r\n}\r\n\r\nexport function ContactInfoGrid({ className }: ContactInfoGridProps) {\r\n const { t } = useTranslation(\"contact-info-grid\");\r\n\r\n const contactItems = [\r\n {\r\n icon: Mail,\r\n label: t(\"emailLabel\", \"Email\"),\r\n description: t(\"emailDesc\", \"We respond to all emails within 24 hours.\"),\r\n value: constants.email || \"hello@example.com\",\r\n href: `mailto:${constants.email || \"hello@example.com\"}`,\r\n },\r\n {\r\n icon: MapPin,\r\n label: t(\"officeLabel\", \"Office\"),\r\n description: t(\"officeDesc\", \"Drop by our office for a chat.\"),\r\n value: `${constants.address?.line1 || \"\"}, ${constants.address?.city || \"\"}`,\r\n href: \"#\",\r\n },\r\n {\r\n icon: Phone,\r\n label: t(\"phoneLabel\", \"Phone\"),\r\n description: t(\"phoneDesc\", \"We're available Mon-Fri, 9am-5pm.\"),\r\n value: constants.phone || \"+1 234 567 890\",\r\n href: `tel:${constants.phone || \"+1234567890\"}`,\r\n },\r\n {\r\n icon: MessageCircle,\r\n label: t(\"chatLabel\", \"Live Chat\"),\r\n description: t(\"chatDesc\", \"Get instant help from our support team.\"),\r\n value: t(\"startChat\", \"Start Chat\"),\r\n href: \"#\",\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=\"mb-12\">\r\n <h2 className=\"text-3xl font-bold md:text-4xl mb-4\">\r\n {t(\"title\", \"Get in Touch\")}\r\n </h2>\r\n <p className=\"text-lg text-muted-foreground max-w-xl\">\r\n {t(\"subtitle\", \"Have questions? We'd love to hear from you. Choose your preferred way to reach us.\")}\r\n </p>\r\n </div>\r\n\r\n <div className=\"grid sm:grid-cols-2 lg:grid-cols-4 gap-6\">\r\n {contactItems.map((item, index) => (\r\n <div\r\n key={index}\r\n className=\"rounded-xl bg-muted/50 p-6 hover:bg-muted transition-colors\"\r\n >\r\n <span className=\"mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-primary/10\">\r\n <item.icon className=\"h-6 w-6 text-primary\" />\r\n </span>\r\n <h3 className=\"mb-2 text-lg font-semibold\">{item.label}</h3>\r\n <p className=\"mb-3 text-sm text-muted-foreground\">\r\n {item.description}\r\n </p>\r\n <a\r\n href={item.href}\r\n className=\"text-sm font-semibold text-primary hover:underline\"\r\n >\r\n {item.value}\r\n </a>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n </section>\r\n );\r\n}\r\n"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "contact-info-grid/lang/en.json",
|
|
23
|
+
"type": "registry:lang",
|
|
24
|
+
"target": "$modules$/contact-info-grid/lang/en.json",
|
|
25
|
+
"content": "{\r\n \"title\": \"Get in Touch\",\r\n \"subtitle\": \"AI will customize this contact subtitle based on your site. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"emailLabel\": \"Email\",\r\n \"emailDesc\": \"We respond to all emails within 24 hours.\",\r\n \"officeLabel\": \"Office\",\r\n \"officeDesc\": \"Drop by our office for a chat.\",\r\n \"phoneLabel\": \"Phone\",\r\n \"phoneDesc\": \"We're available Mon-Fri, 9am-5pm.\",\r\n \"chatLabel\": \"Live Chat\",\r\n \"chatDesc\": \"Get instant help from our support team.\",\r\n \"startChat\": \"Start Chat\"\r\n}\r\n"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "contact-info-grid/lang/tr.json",
|
|
29
|
+
"type": "registry:lang",
|
|
30
|
+
"target": "$modules$/contact-info-grid/lang/tr.json",
|
|
31
|
+
"content": "{\r\n \"title\": \"İletişime Geçin\",\r\n \"subtitle\": \"AI bu iletişim alt başlığını sitenize göre özelleştirecektir. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"emailLabel\": \"E-posta\",\r\n \"emailDesc\": \"Tüm e-postalara 24 saat içinde yanıt veriyoruz.\",\r\n \"officeLabel\": \"Ofis\",\r\n \"officeDesc\": \"Sohbet etmek için ofisimize uğrayın.\",\r\n \"phoneLabel\": \"Telefon\",\r\n \"phoneDesc\": \"Pazartesi-Cuma, 9:00-17:00 arası müsaitiz.\",\r\n \"chatLabel\": \"Canlı Sohbet\",\r\n \"chatDesc\": \"Destek ekibimizden anında yardım alın.\",\r\n \"startChat\": \"Sohbet Başlat\"\r\n}\r\n"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"exports": {
|
|
35
|
+
"types": [],
|
|
36
|
+
"variables": [
|
|
37
|
+
"ContactInfoGrid"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "contact-page-centered",
|
|
3
|
+
"type": "registry:page",
|
|
4
|
+
"title": "Contact Page Centered",
|
|
5
|
+
"description": "Centered contact page layout with contact info cards above the form. Features 3 info cards (email, phone, address), centered header, and a simple name/email/message form. Clean, minimal design perfect for landing pages.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"card",
|
|
8
|
+
"input",
|
|
9
|
+
"textarea",
|
|
10
|
+
"label"
|
|
11
|
+
],
|
|
12
|
+
"usage": "import { ContactPageCentered } from '@/modules/contact-page-centered';\n\n<ContactPageCentered />\n\n- 3 contact info cards\n- Centered form layout\n- Name, email, message fields\n- Success/error states",
|
|
13
|
+
"route": {
|
|
14
|
+
"path": "/contact",
|
|
15
|
+
"componentName": "ContactPageCentered"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
{
|
|
19
|
+
"path": "contact-page-centered/index.ts",
|
|
20
|
+
"type": "registry:index",
|
|
21
|
+
"target": "$modules$/contact-page-centered/index.ts",
|
|
22
|
+
"content": "export * from './contact-page-centered';\r\nexport { ContactPageCentered as default } from './contact-page-centered';\r\n"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"path": "contact-page-centered/contact-page-centered.tsx",
|
|
26
|
+
"type": "registry:component",
|
|
27
|
+
"target": "$modules$/contact-page-centered/contact-page-centered.tsx",
|
|
28
|
+
"content": "import React, { useState } from \"react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { Mail, Phone, MapPin } from \"lucide-react\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { useApiService } from \"@/lib/api\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Textarea } from \"@/components/ui/textarea\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Card, CardContent } from \"@/components/ui/card\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface ContactPageCenteredProps {\r\n className?: string;\r\n}\r\n\r\nexport function ContactPageCentered({ className }: ContactPageCenteredProps) {\r\n const { t } = useTranslation(\"contact-page-centered\");\r\n const apiService = useApiService();\r\n\r\n const [formData, setFormData] = useState({\r\n name: \"\",\r\n email: \"\",\r\n message: \"\",\r\n });\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [submitStatus, setSubmitStatus] = useState<\"idle\" | \"success\" | \"error\">(\"idle\");\r\n\r\n const contactCards = [\r\n {\r\n icon: Mail,\r\n title: t(\"emailTitle\", \"Email\"),\r\n value: constants.email || \"hello@example.com\",\r\n href: `mailto:${constants.email || \"hello@example.com\"}`,\r\n },\r\n {\r\n icon: Phone,\r\n title: t(\"phoneTitle\", \"Phone\"),\r\n value: constants.phone || \"+1 234 567 890\",\r\n href: `tel:${constants.phone || \"+1234567890\"}`,\r\n },\r\n {\r\n icon: MapPin,\r\n title: t(\"addressTitle\", \"Address\"),\r\n value: constants.address?.city || \"New York, USA\",\r\n href: \"#\",\r\n },\r\n ];\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setIsSubmitting(true);\r\n setSubmitStatus(\"idle\");\r\n\r\n try {\r\n await apiService.submitForm(\r\n formData,\r\n {\r\n email_subject1: \"Thank you for contacting us\",\r\n email_subject2: \"New Contact Form Submission\",\r\n fields: [\r\n { name: \"name\", required: true },\r\n { name: \"email\", required: true },\r\n { name: \"message\", required: true },\r\n ],\r\n },\r\n constants.site.defaultLanguage\r\n );\r\n\r\n setSubmitStatus(\"success\");\r\n setFormData({ name: \"\", email: \"\", message: \"\" });\r\n setTimeout(() => setSubmitStatus(\"idle\"), 5000);\r\n } catch {\r\n setSubmitStatus(\"error\");\r\n setTimeout(() => setSubmitStatus(\"idle\"), 5000);\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\r\n setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));\r\n };\r\n\r\n return (\r\n <Layout>\r\n <div className={cn(\"min-h-screen bg-muted/30 py-16 md:py-24\", className)}>\r\n <div className=\"container mx-auto px-4 max-w-4xl\">\r\n {/* Header */}\r\n <div className=\"text-center mb-12\">\r\n <h1 className=\"text-4xl font-bold mb-4\">{t(\"title\", \"Contact Us\")}</h1>\r\n <p className=\"text-lg text-muted-foreground max-w-2xl mx-auto\">\r\n {t(\"subtitle\", \"We'd love to hear from you. Send us a message and we'll respond as soon as possible.\")}\r\n </p>\r\n </div>\r\n\r\n {/* Contact Cards */}\r\n <div className=\"grid sm:grid-cols-3 gap-4 mb-12\">\r\n {contactCards.map((card, index) => (\r\n <Card key={index} className=\"text-center\">\r\n <CardContent className=\"pt-6\">\r\n <div className=\"mx-auto w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center mb-4\">\r\n <card.icon className=\"h-6 w-6 text-primary\" />\r\n </div>\r\n <h3 className=\"font-semibold mb-1\">{card.title}</h3>\r\n <a\r\n href={card.href}\r\n className=\"text-sm text-muted-foreground hover:text-primary transition-colors\"\r\n >\r\n {card.value}\r\n </a>\r\n </CardContent>\r\n </Card>\r\n ))}\r\n </div>\r\n\r\n {/* Form */}\r\n <Card>\r\n <CardContent className=\"pt-6\">\r\n <form onSubmit={handleSubmit} className=\"space-y-6\">\r\n <div className=\"grid sm:grid-cols-2 gap-4\">\r\n <div>\r\n <Label htmlFor=\"name\">{t(\"nameLabel\", \"Name\")} *</Label>\r\n <Input\r\n id=\"name\"\r\n name=\"name\"\r\n value={formData.name}\r\n onChange={handleChange}\r\n placeholder={t(\"namePlaceholder\", \"Your name\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </div>\r\n <div>\r\n <Label htmlFor=\"email\">{t(\"emailLabel\", \"Email\")} *</Label>\r\n <Input\r\n id=\"email\"\r\n name=\"email\"\r\n type=\"email\"\r\n value={formData.email}\r\n onChange={handleChange}\r\n placeholder={t(\"emailPlaceholder\", \"your@email.com\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"message\">{t(\"messageLabel\", \"Message\")} *</Label>\r\n <Textarea\r\n id=\"message\"\r\n name=\"message\"\r\n value={formData.message}\r\n onChange={handleChange}\r\n placeholder={t(\"messagePlaceholder\", \"How can we help you?\")}\r\n required\r\n rows={6}\r\n className=\"mt-1 resize-none\"\r\n />\r\n </div>\r\n\r\n {submitStatus === \"success\" && (\r\n <div className=\"p-4 bg-green-500/10 border border-green-500/30 rounded-lg\">\r\n <p className=\"text-green-600 dark:text-green-400 text-sm font-medium\">\r\n {t(\"success\", \"Message sent successfully! We'll get back to you soon.\")}\r\n </p>\r\n </div>\r\n )}\r\n\r\n {submitStatus === \"error\" && (\r\n <div className=\"p-4 bg-destructive/10 border border-destructive/30 rounded-lg\">\r\n <p className=\"text-destructive text-sm font-medium\">\r\n {t(\"error\", \"Something went wrong. Please try again.\")}\r\n </p>\r\n </div>\r\n )}\r\n\r\n <Button type=\"submit\" size=\"lg\" className=\"w-full\" disabled={isSubmitting}>\r\n {isSubmitting ? t(\"sending\", \"Sending...\") : t(\"submit\", \"Send Message\")}\r\n </Button>\r\n </form>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"path": "contact-page-centered/lang/en.json",
|
|
32
|
+
"type": "registry:lang",
|
|
33
|
+
"target": "$modules$/contact-page-centered/lang/en.json",
|
|
34
|
+
"content": "{\r\n \"title\": \"Contact Us\",\r\n \"subtitle\": \"AI will customize this contact subtitle. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor.\",\r\n \"emailTitle\": \"Email\",\r\n \"phoneTitle\": \"Phone\",\r\n \"addressTitle\": \"Address\",\r\n \"nameLabel\": \"Name\",\r\n \"namePlaceholder\": \"Your name\",\r\n \"emailLabel\": \"Email\",\r\n \"emailPlaceholder\": \"your@email.com\",\r\n \"messageLabel\": \"Message\",\r\n \"messagePlaceholder\": \"How can we help you?\",\r\n \"submit\": \"Send Message\",\r\n \"sending\": \"Sending...\",\r\n \"success\": \"Message sent successfully! We'll get back to you soon.\",\r\n \"error\": \"Something went wrong. Please try again.\"\r\n}\r\n"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"path": "contact-page-centered/lang/tr.json",
|
|
38
|
+
"type": "registry:lang",
|
|
39
|
+
"target": "$modules$/contact-page-centered/lang/tr.json",
|
|
40
|
+
"content": "{\r\n \"title\": \"İletişim\",\r\n \"subtitle\": \"AI bu iletişim alt başlığını özelleştirecektir. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor.\",\r\n \"emailTitle\": \"E-posta\",\r\n \"phoneTitle\": \"Telefon\",\r\n \"addressTitle\": \"Adres\",\r\n \"nameLabel\": \"İsim\",\r\n \"namePlaceholder\": \"Adınız\",\r\n \"emailLabel\": \"E-posta\",\r\n \"emailPlaceholder\": \"email@adresiniz.com\",\r\n \"messageLabel\": \"Mesaj\",\r\n \"messagePlaceholder\": \"Size nasıl yardımcı olabiliriz?\",\r\n \"submit\": \"Mesaj Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"success\": \"Mesajınız başarıyla gönderildi! En kısa sürede size döneceğiz.\",\r\n \"error\": \"Bir şeyler yanlış gitti. Lütfen tekrar deneyin.\"\r\n}\r\n"
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"exports": {
|
|
44
|
+
"types": [],
|
|
45
|
+
"variables": [
|
|
46
|
+
"ContactPageCentered",
|
|
47
|
+
"default"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "contact-page-map-overlay",
|
|
3
|
+
"type": "registry:page",
|
|
4
|
+
"title": "Contact Page (Map Overlay)",
|
|
5
|
+
"description": "Stunning full-screen Google Map background with glassmorphism contact form and info cards overlaid. Modern, eye-catching design with backdrop blur effects.",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"lucide-react"
|
|
8
|
+
],
|
|
9
|
+
"registryDependencies": [
|
|
10
|
+
"button",
|
|
11
|
+
"input",
|
|
12
|
+
"label",
|
|
13
|
+
"textarea",
|
|
14
|
+
"card",
|
|
15
|
+
"google-map"
|
|
16
|
+
],
|
|
17
|
+
"usage": "import { ContactPageMapOverlay } from '@/modules/contact-page-map-overlay';\n\n<ContactPageMapOverlay />\n\n- Full-screen map background\n- Glassmorphism form card\n- Contact info overlay\n- Social media links\n- Open in Maps link",
|
|
18
|
+
"route": {
|
|
19
|
+
"path": "/contact",
|
|
20
|
+
"componentName": "ContactPageMapOverlay"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
{
|
|
24
|
+
"path": "contact-page-map-overlay/index.ts",
|
|
25
|
+
"type": "registry:index",
|
|
26
|
+
"target": "$modules$/contact-page-map-overlay/index.ts",
|
|
27
|
+
"content": "export * from \"./contact-page-map-overlay\";\n"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"path": "contact-page-map-overlay/contact-page-map-overlay.tsx",
|
|
31
|
+
"type": "registry:component",
|
|
32
|
+
"target": "$modules$/contact-page-map-overlay/contact-page-map-overlay.tsx",
|
|
33
|
+
"content": "import React, { useState, useMemo, useEffect } from \"react\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { useTranslation } from \"react-i18next\";\nimport { Layout } from \"@/components/Layout\";\nimport { useApiService } from \"@/lib/api\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { Label } from \"@/components/ui/label\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport {\n Mail,\n Phone,\n MapPin,\n Facebook,\n Twitter,\n Instagram,\n Linkedin,\n Send,\n ExternalLink,\n} from \"lucide-react\";\nimport constants from \"@/constants/constants.json\";\nimport { GoogleMap } from \"@/modules/google-map\";\n\nconst socialIcons: Record<string, React.ElementType> = {\n facebook: Facebook,\n twitter: Twitter,\n instagram: Instagram,\n linkedin: Linkedin,\n};\n\nexport function ContactPageMapOverlay() {\n const { t } = useTranslation(\"contact-page-map-overlay\");\n usePageTitle({ title: t(\"title\") });\n\n const apiService = useApiService();\n\n const socialLinks = useMemo(() => {\n const socialMedia = constants.socialMedia as\n | Record<string, string>\n | undefined;\n if (!socialMedia) return [];\n return Object.entries(socialMedia)\n .filter(([platform, url]) => url && socialIcons[platform])\n .map(([platform, url]) => ({\n platform,\n url,\n Icon: socialIcons[platform],\n }));\n }, []);\n\n const [formData, setFormData] = useState({\n name: \"\",\n email: \"\",\n message: \"\",\n });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [submitStatus, setSubmitStatus] = useState<\n \"idle\" | \"success\" | \"error\"\n >(\"idle\");\n\n // Auto-reset status after 5 seconds with proper cleanup\n useEffect(() => {\n if (submitStatus === \"idle\") return;\n const timer = setTimeout(() => setSubmitStatus(\"idle\"), 5000);\n return () => clearTimeout(timer);\n }, [submitStatus]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setIsSubmitting(true);\n setSubmitStatus(\"idle\");\n\n try {\n const currentLanguage = constants.site.defaultLanguage;\n\n await apiService.submitForm(\n formData,\n {\n email_subject1: \"Thank you for contacting us\",\n email_subject2: \"New Contact Form Submission\",\n fields: [\n { name: \"name\", required: true },\n { name: \"email\", required: true },\n { name: \"message\", required: true },\n ],\n },\n currentLanguage\n );\n\n setSubmitStatus(\"success\");\n setFormData({\n name: \"\",\n email: \"\",\n message: \"\",\n });\n } catch (error: unknown) {\n console.error(\"Form submission failed:\", error);\n setSubmitStatus(\"error\");\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const handleChange = (\n e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>\n ) => {\n setFormData((prev) => ({\n ...prev,\n [e.target.name]: e.target.value,\n }));\n };\n\n // Default coordinates (can be customized via constants)\n const mapLatitude = (constants as any).location?.latitude || 41.0082;\n const mapLongitude = (constants as any).location?.longitude || 28.9784;\n\n return (\n <Layout>\n <div className=\"relative min-h-screen\">\n {/* Full-screen Map Background */}\n <div className=\"absolute inset-0\">\n <GoogleMap\n latitude={mapLatitude}\n longitude={mapLongitude}\n zoom={14}\n height=\"100%\"\n className=\"rounded-none border-0 h-full\"\n title={t(\"mapTitle\")}\n />\n {/* Dark overlay for better readability */}\n <div className=\"absolute inset-0 bg-black/30 pointer-events-none\" />\n </div>\n\n {/* Content Overlay */}\n <div className=\"relative z-10 min-h-screen flex items-center py-12 px-4\">\n <div className=\"container mx-auto max-w-6xl\">\n <div className=\"grid lg:grid-cols-5 gap-8 items-center\">\n {/* Form Card - Glassmorphism */}\n <Card className=\"lg:col-span-3 backdrop-blur-xl bg-background/85 dark:bg-background/90 border-white/20 shadow-2xl\">\n <CardHeader>\n <CardTitle className=\"text-2xl lg:text-3xl\">\n {t(\"title\")}\n </CardTitle>\n <p className=\"text-muted-foreground mt-2\">\n {t(\"description\")}\n </p>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <Label htmlFor=\"name\" className=\"text-sm font-medium\">\n {t(\"fullName\")} *\n </Label>\n <Input\n id=\"name\"\n name=\"name\"\n type=\"text\"\n value={formData.name}\n onChange={handleChange}\n placeholder={t(\"fullNamePlaceholder\")}\n required\n className=\"mt-1.5 bg-background/50\"\n />\n </div>\n <div>\n <Label htmlFor=\"email\" className=\"text-sm font-medium\">\n {t(\"emailAddress\")} *\n </Label>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n value={formData.email}\n onChange={handleChange}\n placeholder={t(\"emailPlaceholder\")}\n required\n className=\"mt-1.5 bg-background/50\"\n />\n </div>\n </div>\n\n <div>\n <Label htmlFor=\"message\" className=\"text-sm font-medium\">\n {t(\"message\")} *\n </Label>\n <Textarea\n id=\"message\"\n name=\"message\"\n value={formData.message}\n onChange={handleChange}\n placeholder={t(\"messagePlaceholder\")}\n required\n rows={4}\n className=\"mt-1.5 resize-none bg-background/50\"\n />\n </div>\n\n {submitStatus === \"success\" && (\n <div className=\"p-3 bg-green-500/10 border border-green-500/30 rounded-lg\">\n <p className=\"text-green-600 dark:text-green-400 text-sm\">\n {t(\"success\")}\n </p>\n </div>\n )}\n\n {submitStatus === \"error\" && (\n <div className=\"p-3 bg-destructive/10 border border-destructive/30 rounded-lg\">\n <p className=\"text-destructive text-sm\">{t(\"error\")}</p>\n </div>\n )}\n\n <Button\n type=\"submit\"\n size=\"lg\"\n className=\"w-full\"\n disabled={isSubmitting}\n >\n {isSubmitting ? (\n <>\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\n {t(\"sending\")}\n </>\n ) : (\n <>\n <Send className=\"w-4 h-4 mr-2\" />\n {t(\"submit\")}\n </>\n )}\n </Button>\n </form>\n </CardContent>\n </Card>\n\n {/* Contact Info Card - Glassmorphism */}\n <Card className=\"lg:col-span-2 backdrop-blur-xl bg-background/85 dark:bg-background/90 border-white/20 shadow-2xl\">\n <CardHeader>\n <CardTitle>{t(\"contactInfo\")}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0\">\n <Mail className=\"w-5 h-5 text-primary\" />\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">{t(\"email\")}</p>\n <a\n href={`mailto:${constants.email}`}\n className=\"font-medium hover:text-primary transition-colors\"\n >\n {constants.email}\n </a>\n </div>\n </div>\n\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0\">\n <Phone className=\"w-5 h-5 text-primary\" />\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">{t(\"phone\")}</p>\n <a\n href={`tel:${constants.phone}`}\n className=\"font-medium hover:text-primary transition-colors\"\n >\n {constants.phone}\n </a>\n </div>\n </div>\n\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0\">\n <MapPin className=\"w-5 h-5 text-primary\" />\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">{t(\"address\")}</p>\n <p className=\"font-medium\">\n {constants.address.line1}\n <br />\n {constants.address.city}, {constants.address.state} {constants.address.postalCode}\n </p>\n </div>\n </div>\n\n {/* Open in Maps Link */}\n <a\n href={`https://www.google.com/maps?q=${mapLatitude},${mapLongitude}`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-2 text-sm text-primary hover:underline mt-2\"\n >\n <ExternalLink className=\"w-4 h-4\" />\n {t(\"openInMaps\")}\n </a>\n\n {/* Social Links */}\n {socialLinks.length > 0 && (\n <div className=\"pt-4 border-t\">\n <p className=\"text-sm text-muted-foreground mb-3\">\n {t(\"followUs\")}\n </p>\n <div className=\"flex gap-2\">\n {socialLinks.map(({ platform, url, Icon }) => (\n <a\n key={platform}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"h-10 w-10 flex items-center justify-center rounded-full border text-muted-foreground hover:text-primary hover:border-primary transition-colors\"\n >\n <Icon className=\"h-4 w-4\" />\n </a>\n ))}\n </div>\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"path": "contact-page-map-overlay/lang/en.json",
|
|
37
|
+
"type": "registry:lang",
|
|
38
|
+
"target": "$modules$/contact-page-map-overlay/lang/en.json",
|
|
39
|
+
"content": "{\n \"title\": \"Contact Us\",\n \"description\": \"We'd love to hear from you. Send us a message and we'll respond as soon as possible.\",\n \"contactInfo\": \"Contact Information\",\n \"email\": \"Email\",\n \"phone\": \"Phone\",\n \"address\": \"Address\",\n \"fullName\": \"Full Name\",\n \"fullNamePlaceholder\": \"Your name\",\n \"emailAddress\": \"Email Address\",\n \"emailPlaceholder\": \"your@email.com\",\n \"message\": \"Message\",\n \"messagePlaceholder\": \"How can we help you?\",\n \"submit\": \"Send Message\",\n \"sending\": \"Sending...\",\n \"success\": \"Message sent successfully! We'll get back to you soon.\",\n \"error\": \"Something went wrong. Please try again.\",\n \"followUs\": \"Follow us\",\n \"mapTitle\": \"Our Location\",\n \"openInMaps\": \"Open in Google Maps\"\n}\n"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"path": "contact-page-map-overlay/lang/tr.json",
|
|
43
|
+
"type": "registry:lang",
|
|
44
|
+
"target": "$modules$/contact-page-map-overlay/lang/tr.json",
|
|
45
|
+
"content": "{\n \"title\": \"Bize Ulaşın\",\n \"description\": \"Sizden haber almak isteriz. Bize mesaj gönderin, en kısa sürede yanıtlayacağız.\",\n \"contactInfo\": \"İletişim Bilgileri\",\n \"email\": \"E-posta\",\n \"phone\": \"Telefon\",\n \"address\": \"Adres\",\n \"fullName\": \"Ad Soyad\",\n \"fullNamePlaceholder\": \"Adınız\",\n \"emailAddress\": \"E-posta Adresi\",\n \"emailPlaceholder\": \"email@adresiniz.com\",\n \"message\": \"Mesaj\",\n \"messagePlaceholder\": \"Size nasıl yardımcı olabiliriz?\",\n \"submit\": \"Mesaj Gönder\",\n \"sending\": \"Gönderiliyor...\",\n \"success\": \"Mesaj başarıyla gönderildi! En kısa sürede yanıtlayacağız.\",\n \"error\": \"Bir hata oluştu. Lütfen tekrar deneyin.\",\n \"followUs\": \"Bizi takip edin\",\n \"mapTitle\": \"Konumumuz\",\n \"openInMaps\": \"Google Maps'te Aç\"\n}\n"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"exports": {
|
|
49
|
+
"types": [],
|
|
50
|
+
"variables": [
|
|
51
|
+
"ContactPageMapOverlay"
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "contact-page-map-split",
|
|
3
|
+
"type": "registry:page",
|
|
4
|
+
"title": "Contact Page (Split with Map)",
|
|
5
|
+
"description": "Modern 50/50 split layout contact page with contact form on the left and full-height Google Map on the right. Includes email, phone, address info cards and social media links.",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"lucide-react"
|
|
8
|
+
],
|
|
9
|
+
"registryDependencies": [
|
|
10
|
+
"button",
|
|
11
|
+
"input",
|
|
12
|
+
"label",
|
|
13
|
+
"textarea",
|
|
14
|
+
"google-map",
|
|
15
|
+
"animations"
|
|
16
|
+
],
|
|
17
|
+
"usage": "import { ContactPageMapSplit } from '@/modules/contact-page-map-split';\n\n<ContactPageMapSplit />\n\n- 50/50 split layout\n- Contact form with validation\n- Google Map integration\n- Social media links\n- Responsive design",
|
|
18
|
+
"route": {
|
|
19
|
+
"path": "/contact",
|
|
20
|
+
"componentName": "ContactPageMapSplit"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
{
|
|
24
|
+
"path": "contact-page-map-split/index.ts",
|
|
25
|
+
"type": "registry:index",
|
|
26
|
+
"target": "$modules$/contact-page-map-split/index.ts",
|
|
27
|
+
"content": "export * from \"./contact-page-map-split\";\n"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"path": "contact-page-map-split/contact-page-map-split.tsx",
|
|
31
|
+
"type": "registry:component",
|
|
32
|
+
"target": "$modules$/contact-page-map-split/contact-page-map-split.tsx",
|
|
33
|
+
"content": "import React, { useState, useMemo, useEffect } from \"react\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { useTranslation } from \"react-i18next\";\nimport { Layout } from \"@/components/Layout\";\nimport { useApiService } from \"@/lib/api\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n Mail,\n Phone,\n MapPin,\n Facebook,\n Twitter,\n Instagram,\n Linkedin,\n Send,\n} from \"lucide-react\";\nimport constants from \"@/constants/constants.json\";\nimport { GoogleMap } from \"@/modules/google-map\";\n\nconst socialIcons: Record<string, React.ElementType> = {\n facebook: Facebook,\n twitter: Twitter,\n instagram: Instagram,\n linkedin: Linkedin,\n};\n\nexport function ContactPageMapSplit() {\n const { t } = useTranslation(\"contact-page-map-split\");\n usePageTitle({ title: t(\"title\") });\n\n const apiService = useApiService();\n\n const socialLinks = useMemo(() => {\n const socialMedia = constants.socialMedia as\n | Record<string, string>\n | undefined;\n if (!socialMedia) return [];\n return Object.entries(socialMedia)\n .filter(([platform, url]) => url && socialIcons[platform])\n .map(([platform, url]) => ({\n platform,\n url,\n Icon: socialIcons[platform],\n }));\n }, []);\n\n const [formData, setFormData] = useState({\n name: \"\",\n email: \"\",\n phone: \"\",\n message: \"\",\n });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [submitStatus, setSubmitStatus] = useState<\n \"idle\" | \"success\" | \"error\"\n >(\"idle\");\n\n // Auto-reset status after 5 seconds with proper cleanup\n useEffect(() => {\n if (submitStatus === \"idle\") return;\n const timer = setTimeout(() => setSubmitStatus(\"idle\"), 5000);\n return () => clearTimeout(timer);\n }, [submitStatus]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setIsSubmitting(true);\n setSubmitStatus(\"idle\");\n\n try {\n const currentLanguage = constants.site.defaultLanguage;\n\n await apiService.submitForm(\n formData,\n {\n email_subject1: \"Thank you for contacting us\",\n email_subject2: \"New Contact Form Submission\",\n fields: [\n { name: \"name\", required: true },\n { name: \"email\", required: true },\n { name: \"phone\", required: false },\n { name: \"message\", required: true },\n ],\n },\n currentLanguage\n );\n\n setSubmitStatus(\"success\");\n setFormData({\n name: \"\",\n email: \"\",\n phone: \"\",\n message: \"\",\n });\n } catch (error: unknown) {\n console.error(\"Form submission failed:\", error);\n setSubmitStatus(\"error\");\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const handleChange = (\n e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>\n ) => {\n setFormData((prev) => ({\n ...prev,\n [e.target.name]: e.target.value,\n }));\n };\n\n // Default coordinates (can be customized via constants)\n const mapLatitude = (constants as any).location?.latitude || 41.0082;\n const mapLongitude = (constants as any).location?.longitude || 28.9784;\n\n return (\n <Layout>\n <div className=\"min-h-screen flex flex-col lg:flex-row\">\n {/* Left Side - Form & Info */}\n <div className=\"w-full lg:w-1/2 bg-background py-12 lg:py-16 px-6 lg:px-12 flex flex-col justify-center\">\n <div className=\"max-w-lg mx-auto w-full\">\n {/* Header */}\n <div className=\"mb-10\">\n <h1 className=\"text-3xl lg:text-4xl font-bold text-foreground mb-3\">\n {t(\"title\")}\n </h1>\n <p className=\"text-muted-foreground\">\n {t(\"description\")}\n </p>\n </div>\n\n {/* Contact Info Cards */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4 mb-10\">\n <div className=\"flex items-center gap-3 p-4 rounded-xl bg-muted/50\">\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center\">\n <Mail className=\"w-5 h-5 text-primary\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-xs text-muted-foreground\">{t(\"email\")}</p>\n <p className=\"text-sm font-medium truncate\">{constants.email}</p>\n </div>\n </div>\n\n <div className=\"flex items-center gap-3 p-4 rounded-xl bg-muted/50\">\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center\">\n <Phone className=\"w-5 h-5 text-primary\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-xs text-muted-foreground\">{t(\"phone\")}</p>\n <p className=\"text-sm font-medium truncate\">{constants.phone}</p>\n </div>\n </div>\n\n <div className=\"flex items-center gap-3 p-4 rounded-xl bg-muted/50 sm:col-span-2\">\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0\">\n <MapPin className=\"w-5 h-5 text-primary\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-xs text-muted-foreground\">{t(\"address\")}</p>\n <p className=\"text-sm font-medium\">\n {constants.address.line1}, {constants.address.city}\n </p>\n </div>\n </div>\n </div>\n\n {/* Contact Form */}\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <Label htmlFor=\"name\" className=\"text-sm font-medium\">\n {t(\"fullName\")} *\n </Label>\n <Input\n id=\"name\"\n name=\"name\"\n type=\"text\"\n value={formData.name}\n onChange={handleChange}\n placeholder={t(\"fullNamePlaceholder\")}\n required\n className=\"mt-1.5\"\n />\n </div>\n <div>\n <Label htmlFor=\"email\" className=\"text-sm font-medium\">\n {t(\"emailAddress\")} *\n </Label>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n value={formData.email}\n onChange={handleChange}\n placeholder={t(\"emailPlaceholder\")}\n required\n className=\"mt-1.5\"\n />\n </div>\n </div>\n\n <div>\n <Label htmlFor=\"phone\" className=\"text-sm font-medium\">\n {t(\"phoneNumber\")}\n </Label>\n <Input\n id=\"phone\"\n name=\"phone\"\n type=\"tel\"\n value={formData.phone}\n onChange={handleChange}\n placeholder={t(\"phonePlaceholder\")}\n className=\"mt-1.5\"\n />\n </div>\n\n <div>\n <Label htmlFor=\"message\" className=\"text-sm font-medium\">\n {t(\"message\")} *\n </Label>\n <Textarea\n id=\"message\"\n name=\"message\"\n value={formData.message}\n onChange={handleChange}\n placeholder={t(\"messagePlaceholder\")}\n required\n rows={4}\n className=\"mt-1.5 resize-none\"\n />\n </div>\n\n {submitStatus === \"success\" && (\n <div className=\"p-3 bg-green-500/10 border border-green-500/30 rounded-lg\">\n <p className=\"text-green-600 dark:text-green-400 text-sm\">\n {t(\"success\")}\n </p>\n </div>\n )}\n\n {submitStatus === \"error\" && (\n <div className=\"p-3 bg-destructive/10 border border-destructive/30 rounded-lg\">\n <p className=\"text-destructive text-sm\">{t(\"error\")}</p>\n </div>\n )}\n\n <Button\n type=\"submit\"\n size=\"lg\"\n className=\"w-full\"\n disabled={isSubmitting}\n >\n {isSubmitting ? (\n <>\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\n {t(\"sending\")}\n </>\n ) : (\n <>\n <Send className=\"w-4 h-4 mr-2\" />\n {t(\"submit\")}\n </>\n )}\n </Button>\n </form>\n\n {/* Social Links */}\n {socialLinks.length > 0 && (\n <div className=\"mt-10 pt-6 border-t\">\n <p className=\"text-sm text-muted-foreground mb-3\">\n {t(\"followUs\")}\n </p>\n <div className=\"flex gap-2\">\n {socialLinks.map(({ platform, url, Icon }) => (\n <a\n key={platform}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"h-10 w-10 flex items-center justify-center rounded-full border text-muted-foreground hover:text-primary hover:border-primary transition-colors\"\n >\n <Icon className=\"h-4 w-4\" />\n </a>\n ))}\n </div>\n </div>\n )}\n </div>\n </div>\n\n {/* Right Side - Map */}\n <div className=\"w-full lg:w-1/2 h-[400px] lg:h-auto lg:min-h-screen relative\">\n <GoogleMap\n latitude={mapLatitude}\n longitude={mapLongitude}\n zoom={14}\n height=\"100%\"\n className=\"rounded-none border-0 h-full\"\n title={t(\"mapTitle\")}\n />\n </div>\n </div>\n </Layout>\n );\n}\n"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"path": "contact-page-map-split/lang/en.json",
|
|
37
|
+
"type": "registry:lang",
|
|
38
|
+
"target": "$modules$/contact-page-map-split/lang/en.json",
|
|
39
|
+
"content": "{\n \"title\": \"Get in Touch\",\n \"description\": \"Have a question or want to work together? Fill out the form below and we'll get back to you as soon as possible.\",\n \"email\": \"Email\",\n \"phone\": \"Phone\",\n \"address\": \"Address\",\n \"fullName\": \"Full Name\",\n \"fullNamePlaceholder\": \"Your name\",\n \"emailAddress\": \"Email Address\",\n \"emailPlaceholder\": \"your@email.com\",\n \"phoneNumber\": \"Phone Number\",\n \"phonePlaceholder\": \"+1 (555) 000-0000\",\n \"message\": \"Message\",\n \"messagePlaceholder\": \"How can we help you?\",\n \"submit\": \"Send Message\",\n \"sending\": \"Sending...\",\n \"success\": \"Your message has been sent successfully! We'll get back to you soon.\",\n \"error\": \"Something went wrong. Please try again later.\",\n \"followUs\": \"Follow us on social media\",\n \"mapTitle\": \"Our Location\"\n}\n"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"path": "contact-page-map-split/lang/tr.json",
|
|
43
|
+
"type": "registry:lang",
|
|
44
|
+
"target": "$modules$/contact-page-map-split/lang/tr.json",
|
|
45
|
+
"content": "{\n \"title\": \"Bize Ulaşın\",\n \"description\": \"Sorunuz mu var veya birlikte çalışmak mı istiyorsunuz? Aşağıdaki formu doldurun, en kısa sürede size döneceğiz.\",\n \"email\": \"E-posta\",\n \"phone\": \"Telefon\",\n \"address\": \"Adres\",\n \"fullName\": \"Ad Soyad\",\n \"fullNamePlaceholder\": \"Adınız\",\n \"emailAddress\": \"E-posta Adresi\",\n \"emailPlaceholder\": \"email@adresiniz.com\",\n \"phoneNumber\": \"Telefon Numarası\",\n \"phonePlaceholder\": \"+90 (555) 000 00 00\",\n \"message\": \"Mesaj\",\n \"messagePlaceholder\": \"Size nasıl yardımcı olabiliriz?\",\n \"submit\": \"Mesaj Gönder\",\n \"sending\": \"Gönderiliyor...\",\n \"success\": \"Mesajınız başarıyla gönderildi! En kısa sürede size döneceğiz.\",\n \"error\": \"Bir hata oluştu. Lütfen daha sonra tekrar deneyin.\",\n \"followUs\": \"Sosyal medyada bizi takip edin\",\n \"mapTitle\": \"Konumumuz\"\n}\n"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"exports": {
|
|
49
|
+
"types": [],
|
|
50
|
+
"variables": [
|
|
51
|
+
"ContactPageMapSplit"
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
}
|