@promakeai/cli 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,7 +27,7 @@
27
27
  "path": "my-orders-page/my-orders-page.tsx",
28
28
  "type": "registry:page",
29
29
  "target": "$modules$/my-orders-page/my-orders-page.tsx",
30
- "content": "import { useEffect, useState } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport {\r\n Package,\r\n Loader2,\r\n ShoppingBag,\r\n ArrowLeft,\r\n Calendar,\r\n CreditCard,\r\n Truck,\r\n CheckCircle2,\r\n Clock,\r\n XCircle,\r\n} from \"lucide-react\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Card, CardContent } from \"@/components/ui/card\";\r\nimport { Badge } from \"@/components/ui/badge\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { customerClient } from \"@/modules/api\";\r\n\r\ninterface OrderItem {\r\n name: string;\r\n description?: string;\r\n quantity: number;\r\n amount: number;\r\n img?: string;\r\n}\r\n\r\ninterface Order {\r\n id: string;\r\n created_at: string;\r\n status: string;\r\n payment_status: string;\r\n total_amount: number;\r\n currency: string;\r\n product_data?: OrderItem[];\r\n}\r\n\r\ntype PageStatus = \"loading\" | \"success\" | \"error\" | \"unauthorized\";\r\n\r\nexport function MyOrdersPage() {\r\n const { t } = useTranslation(\"my-orders-page\");\r\n usePageTitle({ title: t(\"title\", \"My Orders\") });\r\n\r\n const { isAuthenticated, token } = useAuth();\r\n\r\n const [status, setStatus] = useState<PageStatus>(\"loading\");\r\n const [orders, setOrders] = useState<Order[]>([]);\r\n\r\n useEffect(() => {\r\n if (!isAuthenticated || !token) {\r\n setStatus(\"unauthorized\");\r\n return;\r\n }\r\n\r\n const fetchOrders = async () => {\r\n try {\r\n const response = (await (customerClient as any).orders.getMyOrders()) as any;\r\n // Handle both 'orders' and 'transactions' keys from API\r\n const ordersList = response.orders || response.transactions || [];\r\n setOrders(ordersList);\r\n setStatus(\"success\");\r\n } catch (error: any) {\r\n console.error(\"Error fetching orders:\", error);\r\n\r\n if (error?.response?.status === 401) {\r\n setStatus(\"unauthorized\");\r\n } else {\r\n setStatus(\"error\");\r\n toast.error(t(\"loadError\", \"Failed to load orders. Please try again.\"));\r\n }\r\n }\r\n };\r\n\r\n fetchOrders();\r\n }, [isAuthenticated, token, t]);\r\n\r\n const getStatusIcon = (orderStatus: string) => {\r\n const statusLower = orderStatus.toLowerCase();\r\n if ([\"paid\", \"completed\", \"tamamlandı\"].includes(statusLower)) {\r\n return <CheckCircle2 className=\"w-4 h-4\" />;\r\n }\r\n if ([\"processing\", \"hazırlanıyor\"].includes(statusLower)) {\r\n return <Clock className=\"w-4 h-4\" />;\r\n }\r\n if ([\"shipped\", \"kargoda\"].includes(statusLower)) {\r\n return <Truck className=\"w-4 h-4\" />;\r\n }\r\n if ([\"cancelled\", \"iptal\", \"failed\"].includes(statusLower)) {\r\n return <XCircle className=\"w-4 h-4\" />;\r\n }\r\n return <Package className=\"w-4 h-4\" />;\r\n };\r\n\r\n const getStatusBadgeVariant = (\r\n orderStatus: string\r\n ): \"default\" | \"secondary\" | \"destructive\" | \"outline\" => {\r\n const statusLower = orderStatus.toLowerCase();\r\n if ([\"paid\", \"completed\", \"tamamlandı\"].includes(statusLower)) {\r\n return \"default\";\r\n }\r\n if ([\"processing\", \"hazırlanıyor\"].includes(statusLower)) {\r\n return \"secondary\";\r\n }\r\n if ([\"shipped\", \"kargoda\"].includes(statusLower)) {\r\n return \"outline\";\r\n }\r\n if ([\"cancelled\", \"iptal\", \"failed\"].includes(statusLower)) {\r\n return \"destructive\";\r\n }\r\n return \"secondary\";\r\n };\r\n\r\n const getPaymentStatusBadge = (\r\n paymentStatus: string\r\n ): \"default\" | \"secondary\" | \"destructive\" => {\r\n if (paymentStatus === \"paid\") return \"default\";\r\n if (paymentStatus === \"unpaid\" || paymentStatus === \"pending\")\r\n return \"secondary\";\r\n return \"destructive\";\r\n };\r\n\r\n const formatDate = (dateString: string) => {\r\n try {\r\n return new Date(dateString).toLocaleDateString(undefined, {\r\n year: \"numeric\",\r\n month: \"long\",\r\n day: \"numeric\",\r\n hour: \"2-digit\",\r\n minute: \"2-digit\",\r\n });\r\n } catch {\r\n return dateString;\r\n }\r\n };\r\n\r\n const formatCurrency = (amount: number, currency: string) => {\r\n return new Intl.NumberFormat(\"en-US\", {\r\n style: \"currency\",\r\n currency: currency.toUpperCase(),\r\n }).format(amount / 100);\r\n };\r\n\r\n // Unauthorized\r\n if (status === \"unauthorized\") {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-muted-foreground mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"loginRequired\", \"Login Required\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"loginRequiredDescription\",\r\n \"Please login to view your orders.\"\r\n )}\r\n </p>\r\n <div className=\"flex flex-col gap-3\">\r\n <Button asChild>\r\n <Link to=\"/login\">{t(\"login\", \"Login\")}</Link>\r\n </Button>\r\n <Button variant=\"outline\" asChild>\r\n <Link to=\"/register\">\r\n {t(\"createAccount\", \"Create Account\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Loading\r\n if (status === \"loading\") {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Loader2 className=\"w-16 h-16 text-primary mx-auto mb-4 animate-spin\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"loadingOrders\", \"Loading Orders\")}\r\n </h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\"pleaseWait\", \"Please wait...\")}\r\n </p>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Error\r\n if (status === \"error\") {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-red-500 mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"errorTitle\", \"Something went wrong\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"errorDescription\",\r\n \"We couldn't load your orders. Please try again.\"\r\n )}\r\n </p>\r\n <Button onClick={() => window.location.reload()}>\r\n {t(\"tryAgain\", \"Try Again\")}\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // No orders\r\n if (orders.length === 0) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-muted-foreground mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"noOrders\", \"No Orders Yet\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"noOrdersDescription\",\r\n \"You haven't placed any orders yet. Start shopping to see your orders here.\"\r\n )}\r\n </p>\r\n <Button asChild>\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"startShopping\", \"Start Shopping\")}\r\n </Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Success - Has orders\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n {/* Header */}\r\n <div className=\"flex items-center gap-4 mb-8\">\r\n <Button variant=\"ghost\" size=\"icon\" asChild>\r\n <Link to=\"/\">\r\n <ArrowLeft className=\"h-4 w-4\" />\r\n </Link>\r\n </Button>\r\n <div>\r\n <h1 className=\"text-3xl font-bold\">{t(\"title\", \"My Orders\")}</h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\"orderCount\", \"{{count}} order(s)\", { count: orders.length })}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n {/* Orders List */}\r\n <div className=\"space-y-6\">\r\n {orders.map((order) => (\r\n <Card key={order.id} className=\"overflow-hidden\">\r\n {/* Order Header */}\r\n <div className=\"bg-muted/50 px-6 py-4 border-b\">\r\n <div className=\"flex flex-col md:flex-row md:items-center md:justify-between gap-4\">\r\n <div className=\"flex flex-wrap items-center gap-x-6 gap-y-2 text-sm\">\r\n <div>\r\n <span className=\"text-muted-foreground\">\r\n {t(\"orderNumber\", \"Order\")}:\r\n </span>{\" \"}\r\n <span className=\"font-mono font-medium\">\r\n #{order.id.slice(0, 8)}\r\n </span>\r\n </div>\r\n <div className=\"flex items-center gap-1.5 text-muted-foreground\">\r\n <Calendar className=\"w-4 h-4\" />\r\n <span>{formatDate(order.created_at)}</span>\r\n </div>\r\n <div className=\"flex items-center gap-1.5 text-muted-foreground\">\r\n <CreditCard className=\"w-4 h-4\" />\r\n <Badge\r\n variant={getPaymentStatusBadge(order.payment_status)}\r\n className=\"text-xs\"\r\n >\r\n {order.payment_status === \"paid\"\r\n ? t(\"paid\", \"Paid\")\r\n : t(\"unpaid\", \"Unpaid\")}\r\n </Badge>\r\n </div>\r\n </div>\r\n <div className=\"flex items-center gap-3\">\r\n <Badge\r\n variant={getStatusBadgeVariant(order.status)}\r\n className=\"flex items-center gap-1.5\"\r\n >\r\n {getStatusIcon(order.status)}\r\n <span className=\"capitalize\">{order.status}</span>\r\n </Badge>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Order Items */}\r\n <CardContent className=\"p-6\">\r\n <div className=\"space-y-4\">\r\n {order.product_data?.map((item, index) => (\r\n <div\r\n key={index}\r\n className=\"flex gap-4 pb-4 last:pb-0 last:border-0 border-b border-border/50\"\r\n >\r\n {/* Product Image */}\r\n <div className=\"flex-shrink-0\">\r\n <img\r\n src={item.img || \"/images/placeholder.png\"}\r\n alt={item.name}\r\n className=\"w-20 h-20 md:w-24 md:h-24 object-cover rounded-lg border\"\r\n onError={(e) => {\r\n (e.target as HTMLImageElement).src =\r\n \"/images/placeholder.png\";\r\n }}\r\n />\r\n </div>\r\n\r\n {/* Product Details */}\r\n <div className=\"flex-1 min-w-0\">\r\n <h3 className=\"font-semibold text-base md:text-lg truncate\">\r\n {item.name}\r\n </h3>\r\n {item.description && (\r\n <p className=\"text-sm text-muted-foreground line-clamp-2 mt-1\">\r\n {item.description}\r\n </p>\r\n )}\r\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 mt-2 text-sm\">\r\n <span className=\"text-muted-foreground\">\r\n {t(\"quantity\", \"Qty\")}:{\" \"}\r\n <span className=\"font-medium text-foreground\">\r\n {item.quantity}\r\n </span>\r\n </span>\r\n <span className=\"font-semibold text-foreground\">\r\n {formatCurrency(item.amount, order.currency)}\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n\r\n {/* Order Total */}\r\n <div className=\"flex items-center justify-between mt-6 pt-4 border-t\">\r\n <span className=\"text-muted-foreground\">\r\n {t(\"total\", \"Total\")}\r\n </span>\r\n <span className=\"text-xl font-bold\">\r\n {formatCurrency(order.total_amount, order.currency)}\r\n </span>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n ))}\r\n </div>\r\n\r\n {/* Continue Shopping */}\r\n <div className=\"mt-8 text-center\">\r\n <Button variant=\"outline\" asChild>\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default MyOrdersPage;\r\n"
30
+ "content": "import { useEffect, useState } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport {\r\n Package,\r\n Loader2,\r\n ShoppingBag,\r\n ArrowLeft,\r\n Calendar,\r\n CreditCard,\r\n Truck,\r\n CheckCircle2,\r\n Clock,\r\n XCircle,\r\n} from \"lucide-react\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Card, CardContent } from \"@/components/ui/card\";\r\nimport { Badge } from \"@/components/ui/badge\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { customerClient } from \"@/modules/api\";\r\n\r\ninterface OrderItem {\r\n name: string;\r\n description?: string;\r\n quantity: number;\r\n amount: number;\r\n img?: string;\r\n}\r\n\r\ninterface Order {\r\n id: string;\r\n created_at: string;\r\n status: string;\r\n payment_status: string;\r\n total_amount: number;\r\n currency: string;\r\n product_data?: OrderItem[];\r\n}\r\n\r\ntype PageStatus = \"loading\" | \"success\" | \"error\" | \"unauthorized\";\r\n\r\nexport function MyOrdersPage() {\r\n const { t } = useTranslation(\"my-orders-page\");\r\n usePageTitle({ title: t(\"title\", \"My Orders\") });\r\n\r\n const { isAuthenticated, token } = useAuth();\r\n\r\n const [status, setStatus] = useState<PageStatus>(\"loading\");\r\n const [orders, setOrders] = useState<Order[]>([]);\r\n\r\n useEffect(() => {\r\n if (!isAuthenticated || !token) {\r\n setStatus(\"unauthorized\");\r\n return;\r\n }\r\n\r\n const fetchOrders = async () => {\r\n try {\r\n const response = await customerClient.orders.getMyOrders();\r\n const ordersList = response.orders;\r\n setOrders(ordersList);\r\n setStatus(\"success\");\r\n } catch (error: any) {\r\n console.error(\"Error fetching orders:\", error);\r\n\r\n if (error?.response?.status === 401) {\r\n setStatus(\"unauthorized\");\r\n } else {\r\n setStatus(\"error\");\r\n toast.error(t(\"loadError\", \"Failed to load orders. Please try again.\"));\r\n }\r\n }\r\n };\r\n\r\n fetchOrders();\r\n }, [isAuthenticated, token, t]);\r\n\r\n const getStatusIcon = (orderStatus: string) => {\r\n const statusLower = orderStatus.toLowerCase();\r\n if ([\"paid\", \"completed\", \"tamamlandı\"].includes(statusLower)) {\r\n return <CheckCircle2 className=\"w-4 h-4\" />;\r\n }\r\n if ([\"processing\", \"hazırlanıyor\"].includes(statusLower)) {\r\n return <Clock className=\"w-4 h-4\" />;\r\n }\r\n if ([\"shipped\", \"kargoda\"].includes(statusLower)) {\r\n return <Truck className=\"w-4 h-4\" />;\r\n }\r\n if ([\"cancelled\", \"iptal\", \"failed\"].includes(statusLower)) {\r\n return <XCircle className=\"w-4 h-4\" />;\r\n }\r\n return <Package className=\"w-4 h-4\" />;\r\n };\r\n\r\n const getStatusBadgeVariant = (\r\n orderStatus: string\r\n ): \"default\" | \"secondary\" | \"destructive\" | \"outline\" => {\r\n const statusLower = orderStatus.toLowerCase();\r\n if ([\"paid\", \"completed\", \"tamamlandı\"].includes(statusLower)) {\r\n return \"default\";\r\n }\r\n if ([\"processing\", \"hazırlanıyor\"].includes(statusLower)) {\r\n return \"secondary\";\r\n }\r\n if ([\"shipped\", \"kargoda\"].includes(statusLower)) {\r\n return \"outline\";\r\n }\r\n if ([\"cancelled\", \"iptal\", \"failed\"].includes(statusLower)) {\r\n return \"destructive\";\r\n }\r\n return \"secondary\";\r\n };\r\n\r\n const getPaymentStatusBadge = (\r\n paymentStatus: string\r\n ): \"default\" | \"secondary\" | \"destructive\" => {\r\n if (paymentStatus === \"paid\") return \"default\";\r\n if (paymentStatus === \"unpaid\" || paymentStatus === \"pending\")\r\n return \"secondary\";\r\n return \"destructive\";\r\n };\r\n\r\n const formatDate = (dateString: string) => {\r\n try {\r\n return new Date(dateString).toLocaleDateString(undefined, {\r\n year: \"numeric\",\r\n month: \"long\",\r\n day: \"numeric\",\r\n hour: \"2-digit\",\r\n minute: \"2-digit\",\r\n });\r\n } catch {\r\n return dateString;\r\n }\r\n };\r\n\r\n const formatCurrency = (amount: number, currency: string) => {\r\n return new Intl.NumberFormat(\"en-US\", {\r\n style: \"currency\",\r\n currency: currency.toUpperCase(),\r\n }).format(amount / 100);\r\n };\r\n\r\n // Unauthorized\r\n if (status === \"unauthorized\") {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-muted-foreground mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"loginRequired\", \"Login Required\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"loginRequiredDescription\",\r\n \"Please login to view your orders.\"\r\n )}\r\n </p>\r\n <div className=\"flex flex-col gap-3\">\r\n <Button asChild>\r\n <Link to=\"/login\">{t(\"login\", \"Login\")}</Link>\r\n </Button>\r\n <Button variant=\"outline\" asChild>\r\n <Link to=\"/register\">\r\n {t(\"createAccount\", \"Create Account\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Loading\r\n if (status === \"loading\") {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Loader2 className=\"w-16 h-16 text-primary mx-auto mb-4 animate-spin\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"loadingOrders\", \"Loading Orders\")}\r\n </h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\"pleaseWait\", \"Please wait...\")}\r\n </p>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Error\r\n if (status === \"error\") {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-red-500 mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"errorTitle\", \"Something went wrong\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"errorDescription\",\r\n \"We couldn't load your orders. Please try again.\"\r\n )}\r\n </p>\r\n <Button onClick={() => window.location.reload()}>\r\n {t(\"tryAgain\", \"Try Again\")}\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // No orders\r\n if (orders.length === 0) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-muted-foreground mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"noOrders\", \"No Orders Yet\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"noOrdersDescription\",\r\n \"You haven't placed any orders yet. Start shopping to see your orders here.\"\r\n )}\r\n </p>\r\n <Button asChild>\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"startShopping\", \"Start Shopping\")}\r\n </Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Success - Has orders\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n {/* Header */}\r\n <div className=\"flex items-center gap-4 mb-8\">\r\n <Button variant=\"ghost\" size=\"icon\" asChild>\r\n <Link to=\"/\">\r\n <ArrowLeft className=\"h-4 w-4\" />\r\n </Link>\r\n </Button>\r\n <div>\r\n <h1 className=\"text-3xl font-bold\">{t(\"title\", \"My Orders\")}</h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\"orderCount\", \"{{count}} order(s)\", { count: orders.length })}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n {/* Orders List */}\r\n <div className=\"space-y-6\">\r\n {orders.map((order) => (\r\n <Card key={order.id} className=\"overflow-hidden\">\r\n {/* Order Header */}\r\n <div className=\"bg-muted/50 px-6 py-4 border-b\">\r\n <div className=\"flex flex-col md:flex-row md:items-center md:justify-between gap-4\">\r\n <div className=\"flex flex-wrap items-center gap-x-6 gap-y-2 text-sm\">\r\n <div>\r\n <span className=\"text-muted-foreground\">\r\n {t(\"orderNumber\", \"Order\")}:\r\n </span>{\" \"}\r\n <span className=\"font-mono font-medium\">\r\n #{order.id.slice(0, 8)}\r\n </span>\r\n </div>\r\n <div className=\"flex items-center gap-1.5 text-muted-foreground\">\r\n <Calendar className=\"w-4 h-4\" />\r\n <span>{formatDate(order.created_at)}</span>\r\n </div>\r\n <div className=\"flex items-center gap-1.5 text-muted-foreground\">\r\n <CreditCard className=\"w-4 h-4\" />\r\n <Badge\r\n variant={getPaymentStatusBadge(order.payment_status)}\r\n className=\"text-xs\"\r\n >\r\n {order.payment_status === \"paid\"\r\n ? t(\"paid\", \"Paid\")\r\n : t(\"unpaid\", \"Unpaid\")}\r\n </Badge>\r\n </div>\r\n </div>\r\n <div className=\"flex items-center gap-3\">\r\n <Badge\r\n variant={getStatusBadgeVariant(order.status)}\r\n className=\"flex items-center gap-1.5\"\r\n >\r\n {getStatusIcon(order.status)}\r\n <span className=\"capitalize\">{order.status}</span>\r\n </Badge>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Order Items */}\r\n <CardContent className=\"p-6\">\r\n <div className=\"space-y-4\">\r\n {order.product_data?.map((item, index) => (\r\n <div\r\n key={index}\r\n className=\"flex gap-4 pb-4 last:pb-0 last:border-0 border-b border-border/50\"\r\n >\r\n {/* Product Image */}\r\n <div className=\"flex-shrink-0\">\r\n <img\r\n src={item.img || \"/images/placeholder.png\"}\r\n alt={item.name}\r\n className=\"w-20 h-20 md:w-24 md:h-24 object-cover rounded-lg border\"\r\n onError={(e) => {\r\n (e.target as HTMLImageElement).src =\r\n \"/images/placeholder.png\";\r\n }}\r\n />\r\n </div>\r\n\r\n {/* Product Details */}\r\n <div className=\"flex-1 min-w-0\">\r\n <h3 className=\"font-semibold text-base md:text-lg truncate\">\r\n {item.name}\r\n </h3>\r\n {item.description && (\r\n <p className=\"text-sm text-muted-foreground line-clamp-2 mt-1\">\r\n {item.description}\r\n </p>\r\n )}\r\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 mt-2 text-sm\">\r\n <span className=\"text-muted-foreground\">\r\n {t(\"quantity\", \"Qty\")}:{\" \"}\r\n <span className=\"font-medium text-foreground\">\r\n {item.quantity}\r\n </span>\r\n </span>\r\n <span className=\"font-semibold text-foreground\">\r\n {formatCurrency(item.amount, order.currency)}\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n\r\n {/* Order Total */}\r\n <div className=\"flex items-center justify-between mt-6 pt-4 border-t\">\r\n <span className=\"text-muted-foreground\">\r\n {t(\"total\", \"Total\")}\r\n </span>\r\n <span className=\"text-xl font-bold\">\r\n {formatCurrency(order.total_amount, order.currency)}\r\n </span>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n ))}\r\n </div>\r\n\r\n {/* Continue Shopping */}\r\n <div className=\"mt-8 text-center\">\r\n <Button variant=\"outline\" asChild>\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default MyOrdersPage;\r\n"
31
31
  },
32
32
  {
33
33
  "path": "my-orders-page/lang/en.json",
@@ -24,7 +24,7 @@
24
24
  "path": "order-confirmation-page/order-confirmation-page.tsx",
25
25
  "type": "registry:page",
26
26
  "target": "$modules$/order-confirmation-page/order-confirmation-page.tsx",
27
- "content": "import { useEffect, useState } from \"react\";\r\nimport { Link, useParams, useSearchParams } from \"react-router\";\r\nimport {\r\n CheckCircle,\r\n Package,\r\n Truck,\r\n CreditCard,\r\n ArrowLeft,\r\n Clock,\r\n AlertCircle,\r\n Loader2,\r\n ShoppingBag,\r\n} from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\r\nimport { Badge } from \"@/components/ui/badge\";\r\nimport { Separator } from \"@/components/ui/separator\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { formatPrice } from \"@/modules/ecommerce-core\";\r\nimport { customerClient } from \"@/modules/api\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface CheckoutData {\r\n items: any[];\r\n total: number;\r\n customerInfo: {\r\n firstName: string;\r\n lastName: string;\r\n email: string;\r\n phone: string;\r\n address: string;\r\n city: string;\r\n postalCode: string;\r\n country: string;\r\n notes?: string;\r\n };\r\n paymentMethod: string;\r\n paymentProvider: string;\r\n}\r\n\r\nexport function OrderConfirmationPage() {\r\n const { t } = useTranslation(\"order-confirmation-page\");\r\n usePageTitle({ title: t(\"title\", \"Order Confirmation\") });\r\n\r\n const { orderId } = useParams<{ orderId: string }>();\r\n const [searchParams] = useSearchParams();\r\n const sessionId = searchParams.get(\"session_id\");\r\n\r\n const currency = (constants as any).site?.currency || \"USD\";\r\n\r\n const [checkoutData, setCheckoutData] = useState<CheckoutData | null>(null);\r\n const [paymentStatus, setPaymentStatus] = useState<string | null>(null);\r\n const [isCheckingStatus, setIsCheckingStatus] = useState(false);\r\n const [statusError, setStatusError] = useState<string | null>(null);\r\n\r\n // Check if this is a Stripe payment\r\n const isStripePayment = orderId?.startsWith(\"stripe-\") || !!sessionId;\r\n\r\n useEffect(() => {\r\n window.scrollTo(0, 0);\r\n\r\n // Load checkout data from localStorage\r\n const savedData = localStorage.getItem(\"pending_checkout\");\r\n if (savedData) {\r\n try {\r\n setCheckoutData(JSON.parse(savedData));\r\n // Clear after loading\r\n localStorage.removeItem(\"pending_checkout\");\r\n } catch (e) {\r\n console.error(\"Failed to parse checkout data:\", e);\r\n }\r\n }\r\n\r\n // If Stripe payment, check status\r\n if (isStripePayment && (sessionId || orderId)) {\r\n checkStripePaymentStatus();\r\n }\r\n }, []);\r\n\r\n const checkStripePaymentStatus = async () => {\r\n const sid = sessionId || orderId?.replace(\"stripe-\", \"\");\r\n if (!sid) return;\r\n\r\n setIsCheckingStatus(true);\r\n setStatusError(null);\r\n\r\n try {\r\n const response = await (customerClient as any).payment.status({ sessionId: sid });\r\n setPaymentStatus(response.status);\r\n } catch (error: any) {\r\n console.error(\"Payment status check error:\", error);\r\n setStatusError(error.message || \"An error occurred\");\r\n } finally {\r\n setIsCheckingStatus(false);\r\n }\r\n };\r\n\r\n // No checkout data found\r\n if (!checkoutData) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-muted-foreground mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"orderNotFound\", \"Order Not Found\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"orderNotFoundDescription\",\r\n \"We couldn't find the order details. Please check your email for confirmation.\"\r\n )}\r\n </p>\r\n <Button asChild>\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n </Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n const { items, total, customerInfo, paymentMethod, paymentProvider } = checkoutData;\r\n\r\n const getPaymentMethodDisplay = () => {\r\n if (paymentProvider === \"cash_on_delivery\" || paymentMethod === \"cash\") {\r\n return {\r\n icon: <Truck className=\"w-4 h-4 text-green-600\" />,\r\n label: t(\"cashOnDelivery\", \"Cash on Delivery\"),\r\n };\r\n }\r\n if (paymentProvider === \"bank_transfer\" || paymentMethod === \"transfer\") {\r\n return {\r\n icon: <CreditCard className=\"w-4 h-4 text-primary\" />,\r\n label: t(\"bankTransfer\", \"Bank Transfer\"),\r\n };\r\n }\r\n return {\r\n icon: <CreditCard className=\"w-4 h-4 text-blue-600\" />,\r\n label: t(\"creditCard\", \"Credit Card\"),\r\n };\r\n };\r\n\r\n const paymentDisplay = getPaymentMethodDisplay();\r\n\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <div className=\"max-w-4xl mx-auto\">\r\n {/* Success Header */}\r\n <div className=\"text-center mb-8\">\r\n <div className=\"w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center mx-auto mb-4\">\r\n <CheckCircle className=\"w-8 h-8 text-green-600 dark:text-green-400\" />\r\n </div>\r\n <h1 className=\"text-3xl font-bold text-green-600 dark:text-green-400 mb-2\">\r\n {t(\"title\", \"Order Confirmed!\")}\r\n </h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\r\n \"thankYou\",\r\n \"Thank you for your order! We've received your order and will process it shortly.\"\r\n )}\r\n </p>\r\n </div>\r\n\r\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-8\">\r\n {/* Order Details */}\r\n <div className=\"lg:col-span-2 space-y-6\">\r\n {/* Order Info */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"flex items-center gap-2\">\r\n <Package className=\"w-5 h-5\" />\r\n {t(\"orderInformation\", \"Order Information\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent className=\"space-y-4\">\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n {orderId && (\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"orderId\", \"Order ID\")}\r\n </p>\r\n <p className=\"font-semibold font-mono\">#{orderId.slice(0, 12)}</p>\r\n </div>\r\n )}\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"orderDate\", \"Order Date\")}\r\n </p>\r\n <p className=\"font-semibold\">\r\n {new Date().toLocaleDateString()}\r\n </p>\r\n </div>\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"totalAmount\", \"Total Amount\")}\r\n </p>\r\n <p className=\"font-semibold text-lg\">\r\n {formatPrice(total, currency)}\r\n </p>\r\n </div>\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"paymentMethod\", \"Payment Method\")}\r\n </p>\r\n <div className=\"flex items-center gap-2\">\r\n {paymentDisplay.icon}\r\n <span className=\"font-semibold\">{paymentDisplay.label}</span>\r\n </div>\r\n </div>\r\n {isStripePayment && (\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"paymentStatus\", \"Payment Status\")}\r\n </p>\r\n <div className=\"flex items-center gap-2\">\r\n {isCheckingStatus ? (\r\n <div className=\"flex items-center gap-2 text-muted-foreground\">\r\n <Loader2 className=\"w-4 h-4 animate-spin\" />\r\n <span className=\"text-sm\">{t(\"checking\", \"Checking...\")}</span>\r\n </div>\r\n ) : statusError ? (\r\n <Badge variant=\"destructive\" className=\"flex items-center gap-1\">\r\n <AlertCircle className=\"w-3 h-3\" />\r\n {t(\"statusError\", \"Error\")}\r\n </Badge>\r\n ) : paymentStatus === \"succeeded\" ? (\r\n <Badge className=\"bg-green-100 text-green-800 border-green-200 flex items-center gap-1\">\r\n <CheckCircle className=\"w-3 h-3\" />\r\n {t(\"paymentSucceeded\", \"Paid\")}\r\n </Badge>\r\n ) : paymentStatus === \"processing\" ? (\r\n <Badge className=\"bg-blue-100 text-blue-800 border-blue-200 flex items-center gap-1\">\r\n <Loader2 className=\"w-3 h-3 animate-spin\" />\r\n {t(\"processing\", \"Processing\")}\r\n </Badge>\r\n ) : paymentStatus === \"requires_payment_method\" ? (\r\n <Badge className=\"bg-yellow-100 text-yellow-800 border-yellow-200 flex items-center gap-1\">\r\n <AlertCircle className=\"w-3 h-3\" />\r\n {t(\"requiresPayment\", \"Requires Payment\")}\r\n </Badge>\r\n ) : paymentStatus ? (\r\n <Badge variant=\"secondary\" className=\"flex items-center gap-1\">\r\n <Clock className=\"w-3 h-3\" />\r\n {paymentStatus}\r\n </Badge>\r\n ) : null}\r\n </div>\r\n </div>\r\n )}\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"orderStatus\", \"Order Status\")}\r\n </p>\r\n <Badge className=\"bg-yellow-100 text-yellow-800 border-yellow-200\">\r\n <Clock className=\"w-3 h-3 mr-1\" />\r\n {t(\"pending\", \"Pending\")}\r\n </Badge>\r\n </div>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n\r\n {/* Delivery Info */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"deliveryInformation\", \"Delivery Information\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"space-y-2\">\r\n <p className=\"font-semibold\">\r\n {customerInfo.firstName} {customerInfo.lastName}\r\n </p>\r\n <p className=\"text-sm text-muted-foreground\">{customerInfo.email}</p>\r\n <p className=\"text-sm text-muted-foreground\">{customerInfo.phone}</p>\r\n <div className=\"mt-3\">\r\n <p className=\"text-sm font-medium text-muted-foreground\">\r\n {t(\"address\", \"Address\")}:\r\n </p>\r\n <p className=\"text-sm\">{customerInfo.address}</p>\r\n <p className=\"text-sm\">\r\n {customerInfo.city} {customerInfo.postalCode}\r\n </p>\r\n </div>\r\n {customerInfo.notes && (\r\n <div className=\"mt-3\">\r\n <p className=\"text-sm font-medium text-muted-foreground\">\r\n {t(\"notes\", \"Notes\")}:\r\n </p>\r\n <p className=\"text-sm\">{customerInfo.notes}</p>\r\n </div>\r\n )}\r\n </div>\r\n </CardContent>\r\n </Card>\r\n\r\n {/* Order Items */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"orderItems\", \"Order Items\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"space-y-4\">\r\n {items.map((item: any, index: number) => (\r\n <div key={item.id || index} className=\"flex gap-4\">\r\n <img\r\n src={item.product?.images?.[0] || \"/images/placeholder.png\"}\r\n alt={item.product?.name || \"Product\"}\r\n className=\"w-16 h-16 object-cover rounded border\"\r\n />\r\n <div className=\"flex-1\">\r\n <h4 className=\"font-medium\">{item.product?.name}</h4>\r\n <div className=\"flex justify-between items-center mt-2\">\r\n <span className=\"text-sm\">\r\n {t(\"qty\", \"Qty\")}: {item.quantity}\r\n </span>\r\n <span className=\"font-semibold\">\r\n {formatPrice(\r\n (item.product?.on_sale\r\n ? item.product?.sale_price\r\n : item.product?.price) * item.quantity,\r\n currency\r\n )}\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n\r\n <Separator className=\"my-4\" />\r\n\r\n <div className=\"flex justify-between font-semibold text-lg\">\r\n <span>{t(\"total\", \"Total\")}</span>\r\n <span>{formatPrice(total, currency)}</span>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n\r\n {/* Sidebar */}\r\n <div className=\"space-y-6\">\r\n {/* Bank Transfer Instructions */}\r\n {(paymentProvider === \"bank_transfer\" || paymentMethod === \"transfer\") && (\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"text-lg\">\r\n {t(\"paymentInstructions\", \"Payment Instructions\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"p-4 bg-primary/10 border border-primary/20 rounded-lg\">\r\n <p className=\"text-sm font-medium text-primary mb-2\">\r\n {t(\"bankTransferDetails\", \"Bank Transfer Details\")}\r\n </p>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\r\n \"bankDetailsEmail\",\r\n \"Bank details will be sent via email. Please complete the transfer within 48 hours.\"\r\n )}\r\n </p>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n )}\r\n\r\n {/* Cash on Delivery Info */}\r\n {(paymentProvider === \"cash_on_delivery\" || paymentMethod === \"cash\") && (\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"text-lg\">\r\n {t(\"deliveryPayment\", \"Payment on Delivery\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg\">\r\n <p className=\"text-sm font-medium text-green-800 dark:text-green-200 mb-2\">\r\n {t(\"cashOnDeliveryInfo\", \"Cash on Delivery\")}\r\n </p>\r\n <p className=\"text-sm text-green-700 dark:text-green-300\">\r\n {t(\r\n \"codInstructions\",\r\n \"Pay when your order arrives. Our delivery team will contact you before delivery.\"\r\n )}\r\n </p>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n )}\r\n\r\n {/* Actions */}\r\n <Card>\r\n <CardContent className=\"pt-6 space-y-4\">\r\n <Button asChild className=\"w-full\">\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n </Link>\r\n </Button>\r\n\r\n <Button variant=\"outline\" asChild className=\"w-full\">\r\n <Link to=\"/\">\r\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\r\n {t(\"backToHome\", \"Back to Home\")}\r\n </Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default OrderConfirmationPage;\r\n"
27
+ "content": "import { useEffect, useState } from \"react\";\r\nimport { Link, useParams, useSearchParams } from \"react-router\";\r\nimport {\r\n CheckCircle,\r\n Package,\r\n Truck,\r\n CreditCard,\r\n ArrowLeft,\r\n Clock,\r\n AlertCircle,\r\n Loader2,\r\n ShoppingBag,\r\n} from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\r\nimport { Badge } from \"@/components/ui/badge\";\r\nimport { Separator } from \"@/components/ui/separator\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { formatPrice } from \"@/modules/ecommerce-core\";\r\nimport { customerClient } from \"@/modules/api\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface CheckoutData {\r\n items: any[];\r\n total: number;\r\n customerInfo: {\r\n firstName: string;\r\n lastName: string;\r\n email: string;\r\n phone: string;\r\n address: string;\r\n city: string;\r\n postalCode: string;\r\n country: string;\r\n notes?: string;\r\n };\r\n paymentMethod: string;\r\n paymentProvider: string;\r\n}\r\n\r\nexport function OrderConfirmationPage() {\r\n const { t } = useTranslation(\"order-confirmation-page\");\r\n usePageTitle({ title: t(\"title\", \"Order Confirmation\") });\r\n\r\n const { orderId } = useParams<{ orderId: string }>();\r\n const [searchParams] = useSearchParams();\r\n const sessionId = searchParams.get(\"session_id\");\r\n\r\n const currency = (constants as any).site?.currency || \"USD\";\r\n\r\n const [checkoutData, setCheckoutData] = useState<CheckoutData | null>(null);\r\n const [paymentStatus, setPaymentStatus] = useState<string | null>(null);\r\n const [isCheckingStatus, setIsCheckingStatus] = useState(false);\r\n const [statusError, setStatusError] = useState<string | null>(null);\r\n\r\n // Check if this is a Stripe payment\r\n const isStripePayment = orderId?.startsWith(\"stripe-\") || !!sessionId;\r\n\r\n useEffect(() => {\r\n window.scrollTo(0, 0);\r\n\r\n // Load checkout data from localStorage\r\n const savedData = localStorage.getItem(\"pending_checkout\");\r\n if (savedData) {\r\n try {\r\n setCheckoutData(JSON.parse(savedData));\r\n // Clear after loading\r\n localStorage.removeItem(\"pending_checkout\");\r\n } catch (e) {\r\n console.error(\"Failed to parse checkout data:\", e);\r\n }\r\n }\r\n\r\n // If Stripe payment, check status\r\n if (isStripePayment && (sessionId || orderId)) {\r\n checkStripePaymentStatus();\r\n }\r\n }, []);\r\n\r\n const checkStripePaymentStatus = async () => {\r\n const sid = sessionId || orderId?.replace(\"stripe-\", \"\");\r\n if (!sid) return;\r\n\r\n setIsCheckingStatus(true);\r\n setStatusError(null);\r\n\r\n try {\r\n const response = await customerClient.orders.getBySessionId({ sessionId: sid });\r\n setPaymentStatus(response.payment_status === \"paid\" ? \"succeeded\" : response.payment_status);\r\n } catch (error: any) {\r\n console.error(\"Payment status check error:\", error);\r\n setStatusError(error.message || \"An error occurred\");\r\n } finally {\r\n setIsCheckingStatus(false);\r\n }\r\n };\r\n\r\n // No checkout data found\r\n if (!checkoutData) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-muted-foreground mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"orderNotFound\", \"Order Not Found\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"orderNotFoundDescription\",\r\n \"We couldn't find the order details. Please check your email for confirmation.\"\r\n )}\r\n </p>\r\n <Button asChild>\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n </Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n const { items, total, customerInfo, paymentMethod, paymentProvider } = checkoutData;\r\n\r\n const getPaymentMethodDisplay = () => {\r\n if (paymentProvider === \"cash_on_delivery\" || paymentMethod === \"cash\") {\r\n return {\r\n icon: <Truck className=\"w-4 h-4 text-green-600\" />,\r\n label: t(\"cashOnDelivery\", \"Cash on Delivery\"),\r\n };\r\n }\r\n if (paymentProvider === \"bank_transfer\" || paymentMethod === \"transfer\") {\r\n return {\r\n icon: <CreditCard className=\"w-4 h-4 text-primary\" />,\r\n label: t(\"bankTransfer\", \"Bank Transfer\"),\r\n };\r\n }\r\n return {\r\n icon: <CreditCard className=\"w-4 h-4 text-blue-600\" />,\r\n label: t(\"creditCard\", \"Credit Card\"),\r\n };\r\n };\r\n\r\n const paymentDisplay = getPaymentMethodDisplay();\r\n\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <div className=\"max-w-4xl mx-auto\">\r\n {/* Success Header */}\r\n <div className=\"text-center mb-8\">\r\n <div className=\"w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center mx-auto mb-4\">\r\n <CheckCircle className=\"w-8 h-8 text-green-600 dark:text-green-400\" />\r\n </div>\r\n <h1 className=\"text-3xl font-bold text-green-600 dark:text-green-400 mb-2\">\r\n {t(\"title\", \"Order Confirmed!\")}\r\n </h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\r\n \"thankYou\",\r\n \"Thank you for your order! We've received your order and will process it shortly.\"\r\n )}\r\n </p>\r\n </div>\r\n\r\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-8\">\r\n {/* Order Details */}\r\n <div className=\"lg:col-span-2 space-y-6\">\r\n {/* Order Info */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"flex items-center gap-2\">\r\n <Package className=\"w-5 h-5\" />\r\n {t(\"orderInformation\", \"Order Information\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent className=\"space-y-4\">\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n {orderId && (\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"orderId\", \"Order ID\")}\r\n </p>\r\n <p className=\"font-semibold font-mono\">#{orderId.slice(0, 12)}</p>\r\n </div>\r\n )}\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"orderDate\", \"Order Date\")}\r\n </p>\r\n <p className=\"font-semibold\">\r\n {new Date().toLocaleDateString()}\r\n </p>\r\n </div>\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"totalAmount\", \"Total Amount\")}\r\n </p>\r\n <p className=\"font-semibold text-lg\">\r\n {formatPrice(total, currency)}\r\n </p>\r\n </div>\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"paymentMethod\", \"Payment Method\")}\r\n </p>\r\n <div className=\"flex items-center gap-2\">\r\n {paymentDisplay.icon}\r\n <span className=\"font-semibold\">{paymentDisplay.label}</span>\r\n </div>\r\n </div>\r\n {isStripePayment && (\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"paymentStatus\", \"Payment Status\")}\r\n </p>\r\n <div className=\"flex items-center gap-2\">\r\n {isCheckingStatus ? (\r\n <div className=\"flex items-center gap-2 text-muted-foreground\">\r\n <Loader2 className=\"w-4 h-4 animate-spin\" />\r\n <span className=\"text-sm\">{t(\"checking\", \"Checking...\")}</span>\r\n </div>\r\n ) : statusError ? (\r\n <Badge variant=\"destructive\" className=\"flex items-center gap-1\">\r\n <AlertCircle className=\"w-3 h-3\" />\r\n {t(\"statusError\", \"Error\")}\r\n </Badge>\r\n ) : paymentStatus === \"succeeded\" ? (\r\n <Badge className=\"bg-green-100 text-green-800 border-green-200 flex items-center gap-1\">\r\n <CheckCircle className=\"w-3 h-3\" />\r\n {t(\"paymentSucceeded\", \"Paid\")}\r\n </Badge>\r\n ) : paymentStatus === \"processing\" ? (\r\n <Badge className=\"bg-blue-100 text-blue-800 border-blue-200 flex items-center gap-1\">\r\n <Loader2 className=\"w-3 h-3 animate-spin\" />\r\n {t(\"processing\", \"Processing\")}\r\n </Badge>\r\n ) : paymentStatus === \"requires_payment_method\" ? (\r\n <Badge className=\"bg-yellow-100 text-yellow-800 border-yellow-200 flex items-center gap-1\">\r\n <AlertCircle className=\"w-3 h-3\" />\r\n {t(\"requiresPayment\", \"Requires Payment\")}\r\n </Badge>\r\n ) : paymentStatus ? (\r\n <Badge variant=\"secondary\" className=\"flex items-center gap-1\">\r\n <Clock className=\"w-3 h-3\" />\r\n {paymentStatus}\r\n </Badge>\r\n ) : null}\r\n </div>\r\n </div>\r\n )}\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"orderStatus\", \"Order Status\")}\r\n </p>\r\n <Badge className=\"bg-yellow-100 text-yellow-800 border-yellow-200\">\r\n <Clock className=\"w-3 h-3 mr-1\" />\r\n {t(\"pending\", \"Pending\")}\r\n </Badge>\r\n </div>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n\r\n {/* Delivery Info */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"deliveryInformation\", \"Delivery Information\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"space-y-2\">\r\n <p className=\"font-semibold\">\r\n {customerInfo.firstName} {customerInfo.lastName}\r\n </p>\r\n <p className=\"text-sm text-muted-foreground\">{customerInfo.email}</p>\r\n <p className=\"text-sm text-muted-foreground\">{customerInfo.phone}</p>\r\n <div className=\"mt-3\">\r\n <p className=\"text-sm font-medium text-muted-foreground\">\r\n {t(\"address\", \"Address\")}:\r\n </p>\r\n <p className=\"text-sm\">{customerInfo.address}</p>\r\n <p className=\"text-sm\">\r\n {customerInfo.city} {customerInfo.postalCode}\r\n </p>\r\n </div>\r\n {customerInfo.notes && (\r\n <div className=\"mt-3\">\r\n <p className=\"text-sm font-medium text-muted-foreground\">\r\n {t(\"notes\", \"Notes\")}:\r\n </p>\r\n <p className=\"text-sm\">{customerInfo.notes}</p>\r\n </div>\r\n )}\r\n </div>\r\n </CardContent>\r\n </Card>\r\n\r\n {/* Order Items */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"orderItems\", \"Order Items\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"space-y-4\">\r\n {items.map((item: any, index: number) => (\r\n <div key={item.id || index} className=\"flex gap-4\">\r\n <img\r\n src={item.product?.images?.[0] || \"/images/placeholder.png\"}\r\n alt={item.product?.name || \"Product\"}\r\n className=\"w-16 h-16 object-cover rounded border\"\r\n />\r\n <div className=\"flex-1\">\r\n <h4 className=\"font-medium\">{item.product?.name}</h4>\r\n <div className=\"flex justify-between items-center mt-2\">\r\n <span className=\"text-sm\">\r\n {t(\"qty\", \"Qty\")}: {item.quantity}\r\n </span>\r\n <span className=\"font-semibold\">\r\n {formatPrice(\r\n (item.product?.on_sale\r\n ? item.product?.sale_price\r\n : item.product?.price) * item.quantity,\r\n currency\r\n )}\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n\r\n <Separator className=\"my-4\" />\r\n\r\n <div className=\"flex justify-between font-semibold text-lg\">\r\n <span>{t(\"total\", \"Total\")}</span>\r\n <span>{formatPrice(total, currency)}</span>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n\r\n {/* Sidebar */}\r\n <div className=\"space-y-6\">\r\n {/* Bank Transfer Instructions */}\r\n {(paymentProvider === \"bank_transfer\" || paymentMethod === \"transfer\") && (\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"text-lg\">\r\n {t(\"paymentInstructions\", \"Payment Instructions\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"p-4 bg-primary/10 border border-primary/20 rounded-lg\">\r\n <p className=\"text-sm font-medium text-primary mb-2\">\r\n {t(\"bankTransferDetails\", \"Bank Transfer Details\")}\r\n </p>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\r\n \"bankDetailsEmail\",\r\n \"Bank details will be sent via email. Please complete the transfer within 48 hours.\"\r\n )}\r\n </p>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n )}\r\n\r\n {/* Cash on Delivery Info */}\r\n {(paymentProvider === \"cash_on_delivery\" || paymentMethod === \"cash\") && (\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"text-lg\">\r\n {t(\"deliveryPayment\", \"Payment on Delivery\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <div className=\"p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg\">\r\n <p className=\"text-sm font-medium text-green-800 dark:text-green-200 mb-2\">\r\n {t(\"cashOnDeliveryInfo\", \"Cash on Delivery\")}\r\n </p>\r\n <p className=\"text-sm text-green-700 dark:text-green-300\">\r\n {t(\r\n \"codInstructions\",\r\n \"Pay when your order arrives. Our delivery team will contact you before delivery.\"\r\n )}\r\n </p>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n )}\r\n\r\n {/* Actions */}\r\n <Card>\r\n <CardContent className=\"pt-6 space-y-4\">\r\n <Button asChild className=\"w-full\">\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n </Link>\r\n </Button>\r\n\r\n <Button variant=\"outline\" asChild className=\"w-full\">\r\n <Link to=\"/\">\r\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\r\n {t(\"backToHome\", \"Back to Home\")}\r\n </Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default OrderConfirmationPage;\r\n"
28
28
  },
29
29
  {
30
30
  "path": "order-confirmation-page/lang/en.json",
@@ -18,7 +18,7 @@
18
18
  "path": "post-card/post-card.tsx",
19
19
  "type": "registry:component",
20
20
  "target": "$modules$/post-card/post-card.tsx",
21
- "content": "import { Link } from \"react-router\";\nimport { Calendar, Eye, Clock, ArrowRight } from \"lucide-react\";\nimport {\n Card,\n CardContent,\n CardFooter,\n CardHeader,\n} from \"@/components/ui/card\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { useTranslation } from \"react-i18next\";\nimport type { Post } from \"@/modules/blog-core/types\";\n\ninterface CardWrapperProps {\n children: React.ReactNode;\n className?: string;\n}\n\nfunction CardWrapper({ children, className = \"\" }: CardWrapperProps) {\n return (\n <Card\n className={`group overflow-hidden border-0 p-0 shadow-sm hover:shadow-lg transition-all duration-300 h-full ${className}`}\n >\n {children}\n </Card>\n );\n}\n\ninterface PostCardProps {\n post: Post;\n layout?: \"grid\" | \"list\";\n showExcerpt?: boolean;\n className?: string;\n}\n\nexport function PostCard({\n post,\n layout = \"grid\",\n showExcerpt = true,\n className = \"\",\n}: PostCardProps) {\n const { t } = useTranslation(\"post-card\");\n\n const formatDate = (dateString: string) => {\n return new Date(dateString).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n };\n\n if (layout === \"list\") {\n return (\n <CardWrapper className={className}>\n <div className=\"flex flex-col md:flex-row\">\n {post.featured_image && (\n <div className=\"md:w-1/3 flex-shrink-0\">\n <Link to={`/blog/${post.slug}`}>\n <img\n src={post.featured_image}\n alt={post.title}\n className=\"w-full h-48 md:h-full object-cover\"\n onError={(e) => {\n e.currentTarget.src = \"/images/placeholder.png\";\n }}\n />\n </Link>\n </div>\n )}\n\n <div className=\"flex-1 flex flex-col\">\n <CardHeader className=\"pt-6 pb-3\">\n {post.categories && post.categories.length > 0 && (\n <div className=\"flex flex-wrap gap-1 mb-2\">\n {post.categories.slice(0, 2).map((category) => (\n <Badge\n key={category.slug}\n variant=\"secondary\"\n className=\"text-xs\"\n >\n <Link to={`/blog?categories=${category.slug}`}>\n {category.name}\n </Link>\n </Badge>\n ))}\n </div>\n )}\n\n <h3 className=\"text-xl font-semibold line-clamp-2 group-hover:text-primary transition-colors\">\n <Link to={`/blog/${post.slug}`}>{post.title}</Link>\n </h3>\n </CardHeader>\n\n <CardContent className=\"flex-1 pb-3\">\n {showExcerpt && post.excerpt && (\n <p className=\"text-muted-foreground text-sm line-clamp-3 mb-4\">\n {post.excerpt}\n </p>\n )}\n\n <div className=\"flex flex-wrap items-center gap-4 text-xs text-muted-foreground\">\n <div className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n <span>{formatDate(post.published_at)}</span>\n </div>\n\n {post.read_time > 0 && (\n <div className=\"flex items-center gap-1\">\n <Clock className=\"h-3 w-3\" />\n <span>\n {post.read_time} {t(\"minRead\", \"min\")}\n </span>\n </div>\n )}\n\n {post.view_count > 0 && (\n <div className=\"flex items-center gap-1\">\n <Eye className=\"h-3 w-3\" />\n <span>{post.view_count.toLocaleString()}</span>\n </div>\n )}\n </div>\n </CardContent>\n\n <CardFooter className=\"pt-0 pb-6\">\n <Button variant=\"ghost\" size=\"sm\" asChild>\n <Link to={`/blog/${post.slug}`}>\n {t(\"readMore\", \"Read More\")}\n <ArrowRight className=\"h-3 w-3 ml-1\" />\n </Link>\n </Button>\n </CardFooter>\n </div>\n </div>\n </CardWrapper>\n );\n }\n\n return (\n <CardWrapper className={className}>\n <div className=\"flex flex-col h-full\">\n {post.featured_image && (\n <div className=\"aspect-video overflow-hidden\">\n <Link to={`/blog/${post.slug}`}>\n <img\n src={post.featured_image}\n alt={post.title}\n className=\"w-full h-full object-cover group-hover:scale-105 transition-transform duration-300\"\n onError={(e) => {\n e.currentTarget.src = \"/images/placeholder.png\";\n }}\n />\n </Link>\n </div>\n )}\n\n <div className=\"flex flex-col flex-1\">\n <CardHeader className=\"pt-6 pb-3\">\n {post.categories && post.categories.length > 0 && (\n <div className=\"flex flex-wrap gap-1 mb-2\">\n {post.categories.slice(0, 2).map((category) => (\n <Badge\n key={category.slug}\n variant=\"secondary\"\n className=\"text-xs\"\n >\n <Link to={`/blog?categories=${category.slug}`}>\n {category.name}\n </Link>\n </Badge>\n ))}\n </div>\n )}\n\n <h3 className=\"text-lg font-semibold line-clamp-2 group-hover:text-primary transition-colors\">\n <Link to={`/blog/${post.slug}`}>{post.title}</Link>\n </h3>\n </CardHeader>\n\n <CardContent className=\"flex-1 pb-3\">\n {showExcerpt && post.excerpt && (\n <p className=\"text-muted-foreground text-sm line-clamp-3 mb-4\">\n {post.excerpt}\n </p>\n )}\n\n <div className=\"flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n <div className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n <span>{formatDate(post.published_at)}</span>\n </div>\n\n {post.read_time > 0 && (\n <div className=\"flex items-center gap-1\">\n <Clock className=\"h-3 w-3\" />\n <span>\n {post.read_time} {t(\"minRead\", \"min\")}\n </span>\n </div>\n )}\n </div>\n </CardContent>\n\n <CardFooter className=\"pt-0 pb-6 mt-auto\">\n <div className=\"flex items-center justify-between w-full\">\n <Button variant=\"ghost\" size=\"sm\" asChild>\n <Link to={`/blog/${post.slug}`}>\n {t(\"readMore\", \"Read More\")}\n <ArrowRight className=\"h-3 w-3 ml-1\" />\n </Link>\n </Button>\n\n {post.view_count > 0 && (\n <div className=\"flex items-center gap-1 text-xs text-muted-foreground\">\n <Eye className=\"h-3 w-3\" />\n <span>{post.view_count.toLocaleString()}</span>\n </div>\n )}\n </div>\n </CardFooter>\n </div>\n </div>\n </CardWrapper>\n );\n}\n"
21
+ "content": "import { Link } from \"react-router\";\nimport { Calendar, Eye, Clock, ArrowRight } from \"lucide-react\";\nimport {\n Card,\n CardContent,\n CardFooter,\n CardHeader,\n} from \"@/components/ui/card\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { useTranslation } from \"react-i18next\";\nimport type { Post } from \"@/modules/blog-core/types\";\n\ninterface CardWrapperProps {\n children: React.ReactNode;\n className?: string;\n}\n\nfunction CardWrapper({ children, className = \"\" }: CardWrapperProps) {\n return (\n <Card\n className={`group overflow-hidden border-0 p-0 shadow-sm hover:shadow-lg transition-all duration-300 h-full ${className}`}\n >\n {children}\n </Card>\n );\n}\n\ninterface PostCardProps {\n post: Post;\n layout?: \"grid\" | \"list\";\n showExcerpt?: boolean;\n className?: string;\n}\n\nexport function PostCard({\n post,\n layout = \"grid\",\n showExcerpt = true,\n className = \"\",\n}: PostCardProps) {\n const { t } = useTranslation(\"post-card\");\n\n const formatDate = (dateString: string) => {\n return new Date(dateString).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n };\n\n if (layout === \"list\") {\n return (\n <CardWrapper className={className}>\n <div className=\"flex flex-col md:flex-row\">\n {post.featured_image && (\n <div className=\"md:w-1/3 flex-shrink-0\">\n <Link to={`/blog/${post.slug}`}>\n <img\n src={post.featured_image}\n alt={post.title}\n className=\"w-full h-48 md:h-full object-cover\"\n onError={(e) => {\n e.currentTarget.src = \"/images/placeholder.png\";\n }}\n />\n </Link>\n </div>\n )}\n\n <div className=\"flex-1 flex flex-col\">\n <CardHeader className=\"pt-6 pb-3\">\n {post.categories && post.categories.length > 0 && (\n <div className=\"flex flex-wrap gap-1 mb-2\">\n {post.categories.slice(0, 2).map((category) => (\n <div key={category.slug} className=\"contents\" data-db-table=\"blog_categories\" data-db-id={category.slug}>\n <Badge\n variant=\"secondary\"\n className=\"text-xs\"\n >\n <Link to={`/blog?categories=${category.slug}`}>\n {category.name}\n </Link>\n </Badge>\n </div>\n ))}\n </div>\n )}\n\n <h3 className=\"text-xl font-semibold line-clamp-2 group-hover:text-primary transition-colors\">\n <Link to={`/blog/${post.slug}`}>{post.title}</Link>\n </h3>\n </CardHeader>\n\n <CardContent className=\"flex-1 pb-3\">\n {showExcerpt && post.excerpt && (\n <p className=\"text-muted-foreground text-sm line-clamp-3 mb-4\">\n {post.excerpt}\n </p>\n )}\n\n <div className=\"flex flex-wrap items-center gap-4 text-xs text-muted-foreground\">\n <div className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n <span>{formatDate(post.published_at)}</span>\n </div>\n\n {post.read_time > 0 && (\n <div className=\"flex items-center gap-1\">\n <Clock className=\"h-3 w-3\" />\n <span>\n {post.read_time} {t(\"minRead\", \"min\")}\n </span>\n </div>\n )}\n\n {post.view_count > 0 && (\n <div className=\"flex items-center gap-1\">\n <Eye className=\"h-3 w-3\" />\n <span>{post.view_count.toLocaleString()}</span>\n </div>\n )}\n </div>\n </CardContent>\n\n <CardFooter className=\"pt-0 pb-6\">\n <Button variant=\"ghost\" size=\"sm\" asChild>\n <Link to={`/blog/${post.slug}`}>\n {t(\"readMore\", \"Read More\")}\n <ArrowRight className=\"h-3 w-3 ml-1\" />\n </Link>\n </Button>\n </CardFooter>\n </div>\n </div>\n </CardWrapper>\n );\n }\n\n return (\n <CardWrapper className={className}>\n <div className=\"flex flex-col h-full\">\n {post.featured_image && (\n <div className=\"aspect-video overflow-hidden\">\n <Link to={`/blog/${post.slug}`}>\n <img\n src={post.featured_image}\n alt={post.title}\n className=\"w-full h-full object-cover group-hover:scale-105 transition-transform duration-300\"\n onError={(e) => {\n e.currentTarget.src = \"/images/placeholder.png\";\n }}\n />\n </Link>\n </div>\n )}\n\n <div className=\"flex flex-col flex-1\">\n <CardHeader className=\"pt-6 pb-3\">\n {post.categories && post.categories.length > 0 && (\n <div className=\"flex flex-wrap gap-1 mb-2\">\n {post.categories.slice(0, 2).map((category) => (\n <Badge\n key={category.slug}\n variant=\"secondary\"\n className=\"text-xs\"\n >\n <Link to={`/blog?categories=${category.slug}`}>\n {category.name}\n </Link>\n </Badge>\n ))}\n </div>\n )}\n\n <h3 className=\"text-lg font-semibold line-clamp-2 group-hover:text-primary transition-colors\">\n <Link to={`/blog/${post.slug}`}>{post.title}</Link>\n </h3>\n </CardHeader>\n\n <CardContent className=\"flex-1 pb-3\">\n {showExcerpt && post.excerpt && (\n <p className=\"text-muted-foreground text-sm line-clamp-3 mb-4\">\n {post.excerpt}\n </p>\n )}\n\n <div className=\"flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n <div className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n <span>{formatDate(post.published_at)}</span>\n </div>\n\n {post.read_time > 0 && (\n <div className=\"flex items-center gap-1\">\n <Clock className=\"h-3 w-3\" />\n <span>\n {post.read_time} {t(\"minRead\", \"min\")}\n </span>\n </div>\n )}\n </div>\n </CardContent>\n\n <CardFooter className=\"pt-0 pb-6 mt-auto\">\n <div className=\"flex items-center justify-between w-full\">\n <Button variant=\"ghost\" size=\"sm\" asChild>\n <Link to={`/blog/${post.slug}`}>\n {t(\"readMore\", \"Read More\")}\n <ArrowRight className=\"h-3 w-3 ml-1\" />\n </Link>\n </Button>\n\n {post.view_count > 0 && (\n <div className=\"flex items-center gap-1 text-xs text-muted-foreground\">\n <Eye className=\"h-3 w-3\" />\n <span>{post.view_count.toLocaleString()}</span>\n </div>\n )}\n </div>\n </CardFooter>\n </div>\n </div>\n </CardWrapper>\n );\n}\n"
22
22
  },
23
23
  {
24
24
  "path": "post-card/lang/en.json",
@@ -24,7 +24,7 @@
24
24
  "path": "products-page/products-page.tsx",
25
25
  "type": "registry:page",
26
26
  "target": "$modules$/products-page/products-page.tsx",
27
- "content": "import { useState, useRef, useCallback, useMemo } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { useTranslation } from \"react-i18next\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { Filter, Grid, List } from \"lucide-react\";\nimport { Layout } from \"@/components/Layout\";\nimport { Button } from \"@/components/ui/button\";\nimport { FadeIn } from \"@/modules/animations\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { ProductCard } from \"@/modules/product-card/product-card\";\nimport { useProducts, useCategories } from \"@/modules/ecommerce-core\";\nimport type { Product, Category } from \"@/modules/ecommerce-core/types\";\n\ninterface FilterSidebarProps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n t: any;\n categories: Category[];\n selectedCategories: string[];\n handleCategoryChange: (category: string, checked: boolean) => void;\n selectedFeatures: string[];\n handleFeatureChange: (feature: string, checked: boolean) => void;\n minPriceRef: React.RefObject<HTMLInputElement | null>;\n maxPriceRef: React.RefObject<HTMLInputElement | null>;\n searchParams: URLSearchParams;\n handlePriceFilter: () => void;\n}\n\nfunction FilterSidebar({\n t,\n categories,\n selectedCategories,\n handleCategoryChange,\n selectedFeatures,\n handleFeatureChange,\n minPriceRef,\n maxPriceRef,\n searchParams,\n handlePriceFilter,\n}: FilterSidebarProps) {\n return (\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"categories\", \"Categories\")}\n </h3>\n <div className=\"space-y-3\">\n {categories.map((category) => (\n <div\n key={category.id}\n className=\"flex items-center space-x-3 p-2 rounded-lg hover:bg-muted/50 transition-colors\"\n >\n <Checkbox\n id={`category-${category.id}`}\n checked={selectedCategories.includes(category.slug)}\n onCheckedChange={(checked) =>\n handleCategoryChange(category.slug, checked as boolean)\n }\n className=\"data-[state=checked]:bg-primary data-[state=checked]:border-primary\"\n />\n <label\n htmlFor={`category-${category.id}`}\n className=\"text-sm font-medium leading-none cursor-pointer flex-1\"\n >\n {category.name}\n </label>\n </div>\n ))}\n </div>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"priceRange\", \"Price Range\")}\n </h3>\n <div className=\"space-y-3 p-3 bg-muted/30 rounded-lg\">\n <div className=\"grid grid-cols-2 gap-3\">\n <input\n ref={minPriceRef}\n type=\"number\"\n placeholder={t(\"minPrice\", \"Min\")}\n defaultValue={searchParams.get(\"minPrice\") || \"\"}\n onKeyDown={(e) => e.key === \"Enter\" && handlePriceFilter()}\n className=\"w-full px-3 py-2 border border-input rounded-lg text-sm bg-background\"\n />\n <input\n ref={maxPriceRef}\n type=\"number\"\n placeholder={t(\"maxPrice\", \"Max\")}\n defaultValue={searchParams.get(\"maxPrice\") || \"\"}\n onKeyDown={(e) => e.key === \"Enter\" && handlePriceFilter()}\n className=\"w-full px-3 py-2 border border-input rounded-lg text-sm bg-background\"\n />\n </div>\n </div>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"features\", \"Features\")}\n </h3>\n <div className=\"space-y-3\">\n {[\n { key: \"on_sale\", label: t(\"onSale\", \"On Sale\") },\n { key: \"is_new\", label: t(\"newArrivals\", \"New Arrivals\") },\n { key: \"featured\", label: t(\"featuredLabel\", \"Featured\") },\n { key: \"in_stock\", label: t(\"inStock\", \"In Stock\") },\n ].map((feature) => (\n <div\n key={feature.key}\n className=\"flex items-center space-x-3 p-2 rounded-lg hover:bg-muted/50 transition-colors\"\n >\n <Checkbox\n id={feature.key}\n checked={selectedFeatures.includes(feature.key)}\n onCheckedChange={(checked) =>\n handleFeatureChange(feature.key, checked as boolean)\n }\n className=\"data-[state=checked]:bg-primary data-[state=checked]:border-primary\"\n />\n <label\n htmlFor={feature.key}\n className=\"text-sm font-medium leading-none cursor-pointer flex-1\"\n >\n {feature.label}\n </label>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\nexport function ProductsPage() {\n const { t } = useTranslation(\"products-page\");\n usePageTitle({ title: t(\"pageTitle\", \"Products\") });\n const { products, loading: productsLoading } = useProducts();\n const { categories, loading: categoriesLoading } = useCategories();\n const loading = productsLoading || categoriesLoading;\n\n const [searchParams, setSearchParams] = useSearchParams();\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n const [sortBy, setSortBy] = useState(\"featured\");\n const [selectedCategories, setSelectedCategories] = useState<string[]>(() => {\n const categorySlug = searchParams.get(\"category\");\n return categorySlug ? [categorySlug] : [];\n });\n const [selectedFeatures, setSelectedFeatures] = useState<string[]>([]);\n const searchQuery = searchParams.get(\"search\") || \"\";\n const minPriceRef = useRef<HTMLInputElement>(null);\n const maxPriceRef = useRef<HTMLInputElement>(null);\n\n const filteredProducts = useMemo(() => {\n const minPrice = parseFloat(searchParams.get(\"minPrice\") || \"0\") || 0;\n const maxPrice =\n parseFloat(searchParams.get(\"maxPrice\") || \"999999\") || 999999;\n\n let filtered = products.filter((product) => {\n const currentPrice =\n product.on_sale && product.sale_price\n ? product.sale_price\n : product.price;\n return currentPrice >= minPrice && currentPrice <= maxPrice;\n });\n\n if (selectedCategories.length > 0) {\n filtered = filtered.filter((product) => {\n return selectedCategories.some((selectedCategory) => {\n if (product.category === selectedCategory) return true;\n return product.categories?.some(\n (cat) => cat.slug === selectedCategory\n );\n });\n });\n }\n\n if (selectedFeatures.length > 0) {\n filtered = filtered.filter((product) => {\n return selectedFeatures.every((feature) => {\n switch (feature) {\n case \"on_sale\":\n return product.on_sale;\n case \"is_new\":\n return product.is_new;\n case \"featured\":\n return product.featured;\n case \"in_stock\":\n return product.stock > 0;\n default:\n return true;\n }\n });\n });\n }\n\n // Apply sorting\n return [...filtered].sort((a, b) => {\n switch (sortBy) {\n case \"price-low\":\n return (\n (a.on_sale ? a.sale_price || a.price : a.price) -\n (b.on_sale ? b.sale_price || b.price : b.price)\n );\n case \"price-high\":\n return (\n (b.on_sale ? b.sale_price || b.price : b.price) -\n (a.on_sale ? a.sale_price || a.price : a.price)\n );\n case \"newest\":\n return (\n new Date(b.created_at || 0).getTime() -\n new Date(a.created_at || 0).getTime()\n );\n case \"featured\":\n default:\n return (b.featured ? 1 : 0) - (a.featured ? 1 : 0);\n }\n });\n }, [products, searchParams, selectedFeatures, selectedCategories, sortBy]);\n\n const handlePriceFilter = useCallback(() => {\n const minPrice = minPriceRef.current?.value || \"\";\n const maxPrice = maxPriceRef.current?.value || \"\";\n const params = new URLSearchParams(searchParams);\n if (minPrice) params.set(\"minPrice\", minPrice);\n else params.delete(\"minPrice\");\n if (maxPrice) params.set(\"maxPrice\", maxPrice);\n else params.delete(\"maxPrice\");\n setSearchParams(params);\n }, [searchParams, setSearchParams]);\n\n const handleCategoryChange = useCallback(\n (category: string, checked: boolean) => {\n if (checked) {\n setSelectedCategories((prev) => [...prev, category]);\n } else {\n setSelectedCategories((prev) => prev.filter((c) => c !== category));\n }\n },\n []\n );\n\n const handleFeatureChange = useCallback(\n (feature: string, checked: boolean) => {\n if (checked) {\n setSelectedFeatures((prev) => [...prev, feature]);\n } else {\n setSelectedFeatures((prev) => prev.filter((f) => f !== feature));\n }\n },\n []\n );\n\n const sortOptions = [\n { value: \"featured\", label: t(\"featured\", \"Featured\") },\n { value: \"price-low\", label: t(\"sortPriceLow\", \"Price: Low to High\") },\n { value: \"price-high\", label: t(\"sortPriceHigh\", \"Price: High to Low\") },\n { value: \"newest\", label: t(\"sortNewest\", \"Newest\") },\n ];\n\n const filterSidebarProps: FilterSidebarProps = {\n t,\n categories,\n selectedCategories,\n handleCategoryChange,\n selectedFeatures,\n handleFeatureChange,\n minPriceRef,\n maxPriceRef,\n searchParams,\n handlePriceFilter,\n };\n\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <FadeIn className=\"mb-8\">\n <div className=\"flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6\">\n <div className=\"space-y-1\">\n <h1 className=\"text-2xl lg:text-3xl font-bold\">\n {searchQuery\n ? t(\"searchResultsFor\", `Search Results for \"${searchQuery}\"`)\n : t(\"allProducts\", \"All Products\")}\n </h1>\n <p className=\"text-sm lg:text-base text-muted-foreground\">\n {t(\"showing\", \"Showing\")} {filteredProducts.length}{\" \"}\n {t(\"of\", \"of\")} {products.length} {t(\"products\", \"products\")}\n </p>\n </div>\n {searchQuery && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setSearchParams({})}\n className=\"w-fit\"\n >\n {t(\"clearSearch\", \"Clear Search\")}\n </Button>\n )}\n </div>\n\n <div className=\"flex flex-col sm:flex-row gap-3 items-stretch sm:items-center justify-between\">\n <Sheet>\n <SheetTrigger asChild>\n <Button\n variant=\"outline\"\n className=\"lg:hidden w-full sm:w-auto\"\n >\n <Filter className=\"h-4 w-4 mr-2\" />\n {t(\"filters\", \"Filters\")}\n </Button>\n </SheetTrigger>\n <SheetContent side=\"left\" className=\"w-[300px]\">\n <SheetHeader>\n <SheetTitle>{t(\"filters\", \"Filters\")}</SheetTitle>\n <SheetDescription>\n {t(\"refineSearch\", \"Refine your product search\")}\n </SheetDescription>\n </SheetHeader>\n <div className=\"mt-6\">\n <FilterSidebar {...filterSidebarProps} />\n </div>\n </SheetContent>\n </Sheet>\n\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-3\">\n <Select value={sortBy} onValueChange={setSortBy}>\n <SelectTrigger className=\"w-full sm:w-[160px]\">\n <SelectValue placeholder={t(\"sortBy\", \"Sort by\")} />\n </SelectTrigger>\n <SelectContent>\n {sortOptions.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <div className=\"flex border rounded-lg p-1 w-full sm:w-auto\">\n <Button\n variant={viewMode === \"grid\" ? \"default\" : \"ghost\"}\n size=\"sm\"\n onClick={() => setViewMode(\"grid\")}\n className=\"flex-1 sm:flex-none\"\n >\n <Grid className=\"h-4 w-4\" />\n </Button>\n <Button\n variant={viewMode === \"list\" ? \"default\" : \"ghost\"}\n size=\"sm\"\n onClick={() => setViewMode(\"list\")}\n className=\"flex-1 sm:flex-none\"\n >\n <List className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </div>\n </FadeIn>\n\n <div className=\"flex gap-8\">\n <aside className=\"hidden lg:block w-64 flex-shrink-0\">\n <div className=\"sticky top-24\">\n <FilterSidebar {...filterSidebarProps} />\n </div>\n </aside>\n\n <div className=\"flex-1\">\n {loading ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8\">\n {[...Array(6)].map((_, i) => (\n <div\n key={i}\n className=\"animate-pulse bg-card rounded-lg shadow-md overflow-hidden\"\n >\n <div className=\"aspect-square bg-muted mb-4\"></div>\n <div className=\"p-4\">\n <div className=\"h-4 bg-muted rounded w-3/4 mb-2\"></div>\n <div className=\"h-3 bg-muted rounded w-1/2 mb-3\"></div>\n <div className=\"h-4 bg-muted rounded w-1/3\"></div>\n </div>\n </div>\n ))}\n </div>\n ) : viewMode === \"grid\" ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8\">\n {filteredProducts.map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n variant=\"grid\"\n />\n ))}\n </div>\n ) : (\n <div className=\"space-y-6\">\n {filteredProducts.map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n variant=\"list\"\n />\n ))}\n </div>\n )}\n\n {!loading && filteredProducts.length === 0 && (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground\">\n {t(\n \"noProductsFound\",\n \"No products found matching your criteria.\"\n )}\n </p>\n </div>\n )}\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default ProductsPage;\n"
27
+ "content": "import { useState, useRef, useCallback, useMemo } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { useTranslation } from \"react-i18next\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { Filter, Grid, List } from \"lucide-react\";\nimport { Layout } from \"@/components/Layout\";\nimport { Button } from \"@/components/ui/button\";\nimport { FadeIn } from \"@/modules/animations\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { ProductCard } from \"@/modules/product-card/product-card\";\nimport { useProducts, useCategories } from \"@/modules/ecommerce-core\";\nimport type { Product, Category } from \"@/modules/ecommerce-core/types\";\n\ninterface FilterSidebarProps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n t: any;\n categories: Category[];\n selectedCategories: string[];\n handleCategoryChange: (category: string, checked: boolean) => void;\n selectedFeatures: string[];\n handleFeatureChange: (feature: string, checked: boolean) => void;\n minPriceRef: React.RefObject<HTMLInputElement | null>;\n maxPriceRef: React.RefObject<HTMLInputElement | null>;\n searchParams: URLSearchParams;\n handlePriceFilter: () => void;\n}\n\nfunction FilterSidebar({\n t,\n categories,\n selectedCategories,\n handleCategoryChange,\n selectedFeatures,\n handleFeatureChange,\n minPriceRef,\n maxPriceRef,\n searchParams,\n handlePriceFilter,\n}: FilterSidebarProps) {\n return (\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"categories\", \"Categories\")}\n </h3>\n <div className=\"space-y-3\">\n {categories.map((category) => (\n <div\n key={category.id}\n className=\"flex items-center space-x-3 p-2 rounded-lg hover:bg-muted/50 transition-colors\"\n data-db-table=\"product_categories\"\n data-db-id={category.id}\n >\n <Checkbox\n id={`category-${category.id}`}\n checked={selectedCategories.includes(category.slug)}\n onCheckedChange={(checked) =>\n handleCategoryChange(category.slug, checked as boolean)\n }\n className=\"data-[state=checked]:bg-primary data-[state=checked]:border-primary\"\n />\n <label\n htmlFor={`category-${category.id}`}\n className=\"text-sm font-medium leading-none cursor-pointer flex-1\"\n >\n {category.name}\n </label>\n </div>\n ))}\n </div>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"priceRange\", \"Price Range\")}\n </h3>\n <div className=\"space-y-3 p-3 bg-muted/30 rounded-lg\">\n <div className=\"grid grid-cols-2 gap-3\">\n <input\n ref={minPriceRef}\n type=\"number\"\n placeholder={t(\"minPrice\", \"Min\")}\n defaultValue={searchParams.get(\"minPrice\") || \"\"}\n onKeyDown={(e) => e.key === \"Enter\" && handlePriceFilter()}\n className=\"w-full px-3 py-2 border border-input rounded-lg text-sm bg-background\"\n />\n <input\n ref={maxPriceRef}\n type=\"number\"\n placeholder={t(\"maxPrice\", \"Max\")}\n defaultValue={searchParams.get(\"maxPrice\") || \"\"}\n onKeyDown={(e) => e.key === \"Enter\" && handlePriceFilter()}\n className=\"w-full px-3 py-2 border border-input rounded-lg text-sm bg-background\"\n />\n </div>\n </div>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"features\", \"Features\")}\n </h3>\n <div className=\"space-y-3\">\n {[\n { key: \"on_sale\", label: t(\"onSale\", \"On Sale\") },\n { key: \"is_new\", label: t(\"newArrivals\", \"New Arrivals\") },\n { key: \"featured\", label: t(\"featuredLabel\", \"Featured\") },\n { key: \"in_stock\", label: t(\"inStock\", \"In Stock\") },\n ].map((feature) => (\n <div\n key={feature.key}\n className=\"flex items-center space-x-3 p-2 rounded-lg hover:bg-muted/50 transition-colors\"\n >\n <Checkbox\n id={feature.key}\n checked={selectedFeatures.includes(feature.key)}\n onCheckedChange={(checked) =>\n handleFeatureChange(feature.key, checked as boolean)\n }\n className=\"data-[state=checked]:bg-primary data-[state=checked]:border-primary\"\n />\n <label\n htmlFor={feature.key}\n className=\"text-sm font-medium leading-none cursor-pointer flex-1\"\n >\n {feature.label}\n </label>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\nexport function ProductsPage() {\n const { t } = useTranslation(\"products-page\");\n usePageTitle({ title: t(\"pageTitle\", \"Products\") });\n const { products, loading: productsLoading } = useProducts();\n const { categories, loading: categoriesLoading } = useCategories();\n const loading = productsLoading || categoriesLoading;\n\n const [searchParams, setSearchParams] = useSearchParams();\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n const [sortBy, setSortBy] = useState(\"featured\");\n const [selectedCategories, setSelectedCategories] = useState<string[]>(() => {\n const categorySlug = searchParams.get(\"category\");\n return categorySlug ? [categorySlug] : [];\n });\n const [selectedFeatures, setSelectedFeatures] = useState<string[]>([]);\n const searchQuery = searchParams.get(\"search\") || \"\";\n const minPriceRef = useRef<HTMLInputElement>(null);\n const maxPriceRef = useRef<HTMLInputElement>(null);\n\n const filteredProducts = useMemo(() => {\n const minPrice = parseFloat(searchParams.get(\"minPrice\") || \"0\") || 0;\n const maxPrice =\n parseFloat(searchParams.get(\"maxPrice\") || \"999999\") || 999999;\n\n let filtered = products.filter((product) => {\n const currentPrice =\n product.on_sale && product.sale_price\n ? product.sale_price\n : product.price;\n return currentPrice >= minPrice && currentPrice <= maxPrice;\n });\n\n if (selectedCategories.length > 0) {\n filtered = filtered.filter((product) => {\n return selectedCategories.some((selectedCategory) => {\n if (product.category === selectedCategory) return true;\n return product.categories?.some(\n (cat) => cat.slug === selectedCategory\n );\n });\n });\n }\n\n if (selectedFeatures.length > 0) {\n filtered = filtered.filter((product) => {\n return selectedFeatures.every((feature) => {\n switch (feature) {\n case \"on_sale\":\n return product.on_sale;\n case \"is_new\":\n return product.is_new;\n case \"featured\":\n return product.featured;\n case \"in_stock\":\n return product.stock > 0;\n default:\n return true;\n }\n });\n });\n }\n\n // Apply sorting\n return [...filtered].sort((a, b) => {\n switch (sortBy) {\n case \"price-low\":\n return (\n (a.on_sale ? a.sale_price || a.price : a.price) -\n (b.on_sale ? b.sale_price || b.price : b.price)\n );\n case \"price-high\":\n return (\n (b.on_sale ? b.sale_price || b.price : b.price) -\n (a.on_sale ? a.sale_price || a.price : a.price)\n );\n case \"newest\":\n return (\n new Date(b.created_at || 0).getTime() -\n new Date(a.created_at || 0).getTime()\n );\n case \"featured\":\n default:\n return (b.featured ? 1 : 0) - (a.featured ? 1 : 0);\n }\n });\n }, [products, searchParams, selectedFeatures, selectedCategories, sortBy]);\n\n const handlePriceFilter = useCallback(() => {\n const minPrice = minPriceRef.current?.value || \"\";\n const maxPrice = maxPriceRef.current?.value || \"\";\n const params = new URLSearchParams(searchParams);\n if (minPrice) params.set(\"minPrice\", minPrice);\n else params.delete(\"minPrice\");\n if (maxPrice) params.set(\"maxPrice\", maxPrice);\n else params.delete(\"maxPrice\");\n setSearchParams(params);\n }, [searchParams, setSearchParams]);\n\n const handleCategoryChange = useCallback(\n (category: string, checked: boolean) => {\n if (checked) {\n setSelectedCategories((prev) => [...prev, category]);\n } else {\n setSelectedCategories((prev) => prev.filter((c) => c !== category));\n }\n },\n []\n );\n\n const handleFeatureChange = useCallback(\n (feature: string, checked: boolean) => {\n if (checked) {\n setSelectedFeatures((prev) => [...prev, feature]);\n } else {\n setSelectedFeatures((prev) => prev.filter((f) => f !== feature));\n }\n },\n []\n );\n\n const sortOptions = [\n { value: \"featured\", label: t(\"featured\", \"Featured\") },\n { value: \"price-low\", label: t(\"sortPriceLow\", \"Price: Low to High\") },\n { value: \"price-high\", label: t(\"sortPriceHigh\", \"Price: High to Low\") },\n { value: \"newest\", label: t(\"sortNewest\", \"Newest\") },\n ];\n\n const filterSidebarProps: FilterSidebarProps = {\n t,\n categories,\n selectedCategories,\n handleCategoryChange,\n selectedFeatures,\n handleFeatureChange,\n minPriceRef,\n maxPriceRef,\n searchParams,\n handlePriceFilter,\n };\n\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <FadeIn className=\"mb-8\">\n <div className=\"flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6\">\n <div className=\"space-y-1\">\n <h1 className=\"text-2xl lg:text-3xl font-bold\">\n {searchQuery\n ? t(\"searchResultsFor\", `Search Results for \"${searchQuery}\"`)\n : t(\"allProducts\", \"All Products\")}\n </h1>\n <p className=\"text-sm lg:text-base text-muted-foreground\">\n {t(\"showing\", \"Showing\")} {filteredProducts.length}{\" \"}\n {t(\"of\", \"of\")} {products.length} {t(\"products\", \"products\")}\n </p>\n </div>\n {searchQuery && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setSearchParams({})}\n className=\"w-fit\"\n >\n {t(\"clearSearch\", \"Clear Search\")}\n </Button>\n )}\n </div>\n\n <div className=\"flex flex-col sm:flex-row gap-3 items-stretch sm:items-center justify-between\">\n <Sheet>\n <SheetTrigger asChild>\n <Button\n variant=\"outline\"\n className=\"lg:hidden w-full sm:w-auto\"\n >\n <Filter className=\"h-4 w-4 mr-2\" />\n {t(\"filters\", \"Filters\")}\n </Button>\n </SheetTrigger>\n <SheetContent side=\"left\" className=\"w-[300px]\">\n <SheetHeader>\n <SheetTitle>{t(\"filters\", \"Filters\")}</SheetTitle>\n <SheetDescription>\n {t(\"refineSearch\", \"Refine your product search\")}\n </SheetDescription>\n </SheetHeader>\n <div className=\"mt-6\">\n <FilterSidebar {...filterSidebarProps} />\n </div>\n </SheetContent>\n </Sheet>\n\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-3\">\n <Select value={sortBy} onValueChange={setSortBy}>\n <SelectTrigger className=\"w-full sm:w-[160px]\">\n <SelectValue placeholder={t(\"sortBy\", \"Sort by\")} />\n </SelectTrigger>\n <SelectContent>\n {sortOptions.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <div className=\"flex border rounded-lg p-1 w-full sm:w-auto\">\n <Button\n variant={viewMode === \"grid\" ? \"default\" : \"ghost\"}\n size=\"sm\"\n onClick={() => setViewMode(\"grid\")}\n className=\"flex-1 sm:flex-none\"\n >\n <Grid className=\"h-4 w-4\" />\n </Button>\n <Button\n variant={viewMode === \"list\" ? \"default\" : \"ghost\"}\n size=\"sm\"\n onClick={() => setViewMode(\"list\")}\n className=\"flex-1 sm:flex-none\"\n >\n <List className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </div>\n </FadeIn>\n\n <div className=\"flex gap-8\">\n <aside className=\"hidden lg:block w-64 flex-shrink-0\">\n <div className=\"sticky top-24\">\n <FilterSidebar {...filterSidebarProps} />\n </div>\n </aside>\n\n <div className=\"flex-1\">\n {loading ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8\">\n {[...Array(6)].map((_, i) => (\n <div\n key={i}\n className=\"animate-pulse bg-card rounded-lg shadow-md overflow-hidden\"\n >\n <div className=\"aspect-square bg-muted mb-4\"></div>\n <div className=\"p-4\">\n <div className=\"h-4 bg-muted rounded w-3/4 mb-2\"></div>\n <div className=\"h-3 bg-muted rounded w-1/2 mb-3\"></div>\n <div className=\"h-4 bg-muted rounded w-1/3\"></div>\n </div>\n </div>\n ))}\n </div>\n ) : viewMode === \"grid\" ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8\">\n {filteredProducts.map((product) => (\n <div key={product.id} className=\"contents\" data-db-table=\"products\" data-db-id={product.id}>\n <ProductCard\n product={product}\n variant=\"grid\"\n />\n </div>\n ))}\n </div>\n ) : (\n <div className=\"space-y-6\">\n {filteredProducts.map((product) => (\n <div key={product.id} className=\"contents\" data-db-table=\"products\" data-db-id={product.id}>\n <ProductCard\n product={product}\n variant=\"list\"\n />\n </div>\n ))}\n </div>\n )}\n\n {!loading && filteredProducts.length === 0 && (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground\">\n {t(\n \"noProductsFound\",\n \"No products found matching your criteria.\"\n )}\n </p>\n </div>\n )}\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default ProductsPage;\n"
28
28
  },
29
29
  {
30
30
  "path": "products-page/lang/en.json",
@@ -19,7 +19,7 @@
19
19
  "path": "related-posts-block/related-posts-block.tsx",
20
20
  "type": "registry:block",
21
21
  "target": "$modules$/related-posts-block/related-posts-block.tsx",
22
- "content": "import { PostCard } from \"@/modules/post-card/post-card\";\nimport { useTranslation } from \"react-i18next\";\nimport type { Post } from \"@/modules/blog-core/types\";\n\ninterface RelatedPostsBlockProps {\n posts: Post[];\n title?: string;\n}\n\nexport function RelatedPostsBlock({ posts, title }: RelatedPostsBlockProps) {\n const { t } = useTranslation(\"related-posts-block\");\n\n if (posts.length === 0) {\n return null;\n }\n\n return (\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-12\">\n <div className=\"max-w-6xl mx-auto\">\n <h2 className=\"text-3xl font-bold mb-8\">\n {title || t(\"title\", \"Related Posts\")}\n </h2>\n <div className=\"grid gap-6 md:grid-cols-2 lg:grid-cols-3\">\n {posts.map((post) => (\n <PostCard key={post.id} post={post} layout=\"grid\" />\n ))}\n </div>\n </div>\n </div>\n );\n}\n"
22
+ "content": "import { PostCard } from \"@/modules/post-card/post-card\";\nimport { useTranslation } from \"react-i18next\";\nimport type { Post } from \"@/modules/blog-core/types\";\n\ninterface RelatedPostsBlockProps {\n posts: Post[];\n title?: string;\n}\n\nexport function RelatedPostsBlock({ posts, title }: RelatedPostsBlockProps) {\n const { t } = useTranslation(\"related-posts-block\");\n\n if (posts.length === 0) {\n return null;\n }\n\n return (\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-12\">\n <div className=\"max-w-6xl mx-auto\">\n <h2 className=\"text-3xl font-bold mb-8\">\n {title || t(\"title\", \"Related Posts\")}\n </h2>\n <div className=\"grid gap-6 md:grid-cols-2 lg:grid-cols-3\">\n {posts.map((post) => (\n <div key={post.id} className=\"contents\" data-db-table=\"posts\" data-db-id={post.id}>\n <PostCard post={post} layout=\"grid\" />\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n"
23
23
  },
24
24
  {
25
25
  "path": "related-posts-block/lang/en.json",
@@ -19,7 +19,7 @@
19
19
  "path": "related-products-block/related-products-block.tsx",
20
20
  "type": "registry:block",
21
21
  "target": "$modules$/related-products-block/related-products-block.tsx",
22
- "content": "import { Link } from \"react-router\";\nimport { Star } from \"lucide-react\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { useTranslation } from \"react-i18next\";\nimport constants from \"@/constants/constants.json\";\nimport { formatPrice } from \"@/modules/ecommerce-core/format-price\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\n\ninterface RelatedProductsBlockProps {\n products: Product[];\n title?: string;\n}\n\nexport function RelatedProductsBlock({\n products,\n title,\n}: RelatedProductsBlockProps) {\n const { t } = useTranslation(\"related-products-block\");\n\n if (products.length === 0) {\n return null;\n }\n\n return (\n <div>\n <h2 className=\"text-2xl font-bold mb-6\">\n {title || t(\"title\", \"Related Products\")}\n </h2>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6\">\n {products.map((product) => (\n <Card\n key={product.id}\n className=\"group overflow-hidden border-0 p-0 shadow-sm hover:shadow-lg transition-all duration-300\"\n >\n <Link to={`/products/${product.slug}`}>\n <div className=\"relative aspect-square overflow-hidden cursor-pointer\">\n <img\n src={product.images[0] || \"/images/placeholder.png\"}\n alt={product.name}\n className=\"w-full h-full object-cover group-hover:scale-105 transition-transform duration-300\"\n />\n </div>\n </Link>\n <CardContent className=\"p-4\">\n <Link to={`/products/${product.slug}`}>\n <h3 className=\"font-semibold hover:text-primary transition-colors line-clamp-1\">\n {product.name}\n </h3>\n </Link>\n <div className=\"flex items-center justify-between mt-2\">\n <span className=\"font-semibold\">\n {formatPrice(product.price, constants.site.currency)}\n </span>\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 fill-current text-yellow-400\" />\n <span className=\"text-xs\">{product.rating}</span>\n </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n </div>\n );\n}\n"
22
+ "content": "import { Link } from \"react-router\";\nimport { Star } from \"lucide-react\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { useTranslation } from \"react-i18next\";\nimport constants from \"@/constants/constants.json\";\nimport { formatPrice } from \"@/modules/ecommerce-core/format-price\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\n\ninterface RelatedProductsBlockProps {\n products: Product[];\n title?: string;\n}\n\nexport function RelatedProductsBlock({\n products,\n title,\n}: RelatedProductsBlockProps) {\n const { t } = useTranslation(\"related-products-block\");\n\n if (products.length === 0) {\n return null;\n }\n\n return (\n <div>\n <h2 className=\"text-2xl font-bold mb-6\">\n {title || t(\"title\", \"Related Products\")}\n </h2>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6\">\n {products.map((product) => (\n <div key={product.id} className=\"contents\" data-db-table=\"products\" data-db-id={product.id}>\n <Card\n className=\"group overflow-hidden border-0 p-0 shadow-sm hover:shadow-lg transition-all duration-300\"\n >\n <Link to={`/products/${product.slug}`}>\n <div className=\"relative aspect-square overflow-hidden cursor-pointer\">\n <img\n src={product.images[0] || \"/images/placeholder.png\"}\n alt={product.name}\n className=\"w-full h-full object-cover group-hover:scale-105 transition-transform duration-300\"\n />\n </div>\n </Link>\n <CardContent className=\"p-4\">\n <Link to={`/products/${product.slug}`}>\n <h3 className=\"font-semibold hover:text-primary transition-colors line-clamp-1\">\n {product.name}\n </h3>\n </Link>\n <div className=\"flex items-center justify-between mt-2\">\n <span className=\"font-semibold\">\n {formatPrice(product.price, constants.site.currency)}\n </span>\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 fill-current text-yellow-400\" />\n <span className=\"text-xs\">{product.rating}</span>\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n ))}\n </div>\n </div>\n );\n}\n"
23
23
  },
24
24
  {
25
25
  "path": "related-products-block/lang/en.json",
@@ -2,14 +2,12 @@
2
2
  "name": "reset-password-page",
3
3
  "type": "registry:page",
4
4
  "title": "Reset Password Page",
5
- "description": "Split-screen password reset page with form on the left and full-height image on the right. Features new password input with confirmation, validates reset code from URL. Clean minimal design with API integration and responsive layout.",
5
+ "description": "Centered card password reset page with Layout wrapper. Validates reset code and username from URL parameters (?code=&username=). Features new password input with confirmation and show/hide toggle. Uses useAuth hook from auth-core for password reset.",
6
6
  "registryDependencies": [
7
- "button",
8
- "input",
9
- "auth",
7
+ "auth-core",
10
8
  "api"
11
9
  ],
12
- "usage": "import ResetPasswordPage from '@/modules/reset-password-page';\n\n<ResetPasswordPage\n image=\"/images/reset-bg.jpg\"\n/>\n\n• Installed at: src/modules/reset-password-page/\n• Customize text: src/modules/reset-password-page/lang/*.json\n• Uses customerClient.auth.resetPassword() for API\n• Expects ?code= URL parameter from email link\n• Add to your router as a page component",
10
+ "usage": "import ResetPasswordPage from '@/modules/reset-password-page';\n\n<ResetPasswordPage />\n\n• Installed at: src/modules/reset-password-page/\n• Customize text: src/modules/reset-password-page/lang/*.json\n• Uses useAuth() hook from auth-core:\n const { resetPassword } = useAuth();\n await resetPassword(username, code, newPassword);\n• Expects ?code= and ?username= URL parameters from email link\n• Add to your router as a page component",
13
11
  "route": {
14
12
  "path": "/reset-password",
15
13
  "componentName": "ResetPasswordPage"
@@ -18,22 +16,33 @@
18
16
  {
19
17
  "path": "reset-password-page/index.ts",
20
18
  "type": "registry:index",
21
- "target": "$modules$/reset-password-page/index.ts"
19
+ "target": "$modules$/reset-password-page/index.ts",
20
+ "content": "export * from './reset-password-page';\r\nexport { default } from './reset-password-page';\r\n"
22
21
  },
23
22
  {
24
23
  "path": "reset-password-page/reset-password-page.tsx",
25
24
  "type": "registry:page",
26
- "target": "$modules$/reset-password-page/reset-password-page.tsx"
25
+ "target": "$modules$/reset-password-page/reset-password-page.tsx",
26
+ "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useSearchParams } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { getErrorMessage } from \"@/modules/api\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport {\r\n Card,\r\n CardContent,\r\n CardHeader,\r\n CardTitle,\r\n CardDescription,\r\n} from \"@/components/ui/card\";\r\nimport { KeyRound, ArrowLeft, Eye, EyeOff, CheckCircle2 } from \"lucide-react\";\r\n\r\nexport function ResetPasswordPage() {\r\n const { t } = useTranslation(\"reset-password-page\");\r\n usePageTitle({ title: t(\"title\", \"Reset Password\") });\r\n const navigate = useNavigate();\r\n const [searchParams] = useSearchParams();\r\n const { resetPassword } = useAuth();\r\n\r\n const code = searchParams.get(\"code\") || \"\";\r\n const username =\r\n searchParams.get(\"username\") || searchParams.get(\"email\") || \"\";\r\n\r\n const [password, setPassword] = useState(\"\");\r\n const [confirmPassword, setConfirmPassword] = useState(\"\");\r\n const [showPassword, setShowPassword] = useState(false);\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const [isSuccess, setIsSuccess] = useState(false);\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n\r\n if (password !== confirmPassword) {\r\n setError(t(\"passwordMismatch\", \"Passwords do not match\"));\r\n return;\r\n }\r\n\r\n if (!code || !username) {\r\n setError(\r\n t(\r\n \"invalidLink\",\r\n \"Invalid or expired reset link. Please request a new one.\",\r\n ),\r\n );\r\n return;\r\n }\r\n\r\n setIsLoading(true);\r\n\r\n try {\r\n await resetPassword(username, code, password);\r\n setIsSuccess(true);\r\n toast.success(\r\n t(\"passwordResetSuccess\", \"Password reset successfully!\"),\r\n );\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\r\n \"resetPasswordError\",\r\n \"Failed to reset password. Please try again.\",\r\n ),\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n // Success state\r\n if (isSuccess) {\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen bg-muted/30 py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"max-w-md mx-auto\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8 text-center\">\r\n <CheckCircle2 className=\"w-16 h-16 text-green-500 mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"passwordReset\", \"Password Reset!\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"passwordResetDescription\",\r\n \"Your password has been reset successfully. You can now log in with your new password.\",\r\n )}\r\n </p>\r\n <Button asChild className=\"w-full\">\r\n <Link to=\"/login\">\r\n {t(\"goToLogin\", \"Go to Login\")}\r\n </Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Invalid link state\r\n if (!code || !username) {\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen bg-muted/30 py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"max-w-md mx-auto\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8 text-center\">\r\n <h1 className=\"text-2xl font-bold mb-2 text-red-600\">\r\n {t(\"invalidLinkTitle\", \"Invalid Reset Link\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"invalidLinkDescription\",\r\n \"This password reset link is invalid or has expired. Please request a new one.\",\r\n )}\r\n </p>\r\n <Button\r\n onClick={() => navigate(\"/forgot-password\")}\r\n className=\"w-full mb-3\"\r\n >\r\n {t(\"requestNewLink\", \"Request New Link\")}\r\n </Button>\r\n <Link\r\n to=\"/login\"\r\n className=\"inline-flex items-center justify-center gap-1 text-sm text-muted-foreground hover:text-primary\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to Login\")}\r\n </Link>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen bg-muted/30 py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"text-center mb-12\">\r\n <h1 className=\"text-4xl font-bold text-foreground mb-4\">\r\n {t(\"title\", \"Reset Password\")}\r\n </h1>\r\n <div className=\"w-16 h-1 bg-primary mx-auto mb-6\"></div>\r\n <p className=\"text-lg text-muted-foreground max-w-xl mx-auto\">\r\n {t(\"subtitle\", \"Enter your new password below\")}\r\n </p>\r\n </div>\r\n\r\n <div className=\"max-w-md mx-auto\">\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"flex items-center gap-2\">\r\n <KeyRound className=\"w-5 h-5 text-primary\" />\r\n {t(\"resetPassword\", \"Reset Password\")}\r\n </CardTitle>\r\n <CardDescription>\r\n {t(\"subtitle\", \"Enter your new password below\")}\r\n </CardDescription>\r\n </CardHeader>\r\n <CardContent>\r\n <form onSubmit={handleSubmit} className=\"space-y-6\">\r\n <div>\r\n <Label htmlFor=\"password\">\r\n {t(\"newPassword\", \"New Password\")} *\r\n </Label>\r\n <div className=\"relative\">\r\n <Input\r\n id=\"password\"\r\n type={showPassword ? \"text\" : \"password\"}\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n placeholder={t(\r\n \"newPasswordPlaceholder\",\r\n \"Enter new password\",\r\n )}\r\n required\r\n className=\"mt-1 pr-10\"\r\n autoComplete=\"new-password\"\r\n minLength={8}\r\n disabled={isLoading}\r\n />\r\n <button\r\n type=\"button\"\r\n onClick={() => setShowPassword(!showPassword)}\r\n className=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\r\n >\r\n {showPassword ? (\r\n <EyeOff className=\"w-4 h-4\" />\r\n ) : (\r\n <Eye className=\"w-4 h-4\" />\r\n )}\r\n </button>\r\n </div>\r\n <p className=\"text-xs text-muted-foreground mt-1\">\r\n {t(\r\n \"passwordRequirements\",\r\n \"At least 8 characters, 1 letter and 1 number\",\r\n )}\r\n </p>\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"confirmPassword\">\r\n {t(\"confirmPassword\", \"Confirm Password\")} *\r\n </Label>\r\n <Input\r\n id=\"confirmPassword\"\r\n type={showPassword ? \"text\" : \"password\"}\r\n value={confirmPassword}\r\n onChange={(e) => setConfirmPassword(e.target.value)}\r\n placeholder={t(\r\n \"confirmPasswordPlaceholder\",\r\n \"Confirm new password\",\r\n )}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"new-password\"\r\n minLength={8}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-4 bg-red-50 border border-red-200 rounded-lg\">\r\n <p className=\"text-red-800 text-sm font-medium\">\r\n {error}\r\n </p>\r\n </div>\r\n )}\r\n\r\n <Button\r\n type=\"submit\"\r\n size=\"lg\"\r\n className=\"w-full\"\r\n disabled={isLoading}\r\n >\r\n {isLoading ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"resetting\", \"Resetting...\")}\r\n </>\r\n ) : (\r\n t(\"resetPassword\", \"Reset Password\")\r\n )}\r\n </Button>\r\n\r\n <div className=\"text-center\">\r\n <Link\r\n to=\"/login\"\r\n className=\"text-sm text-muted-foreground hover:text-primary inline-flex items-center gap-1\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to Login\")}\r\n </Link>\r\n </div>\r\n </form>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default ResetPasswordPage;\r\n"
27
27
  },
28
28
  {
29
29
  "path": "reset-password-page/lang/en.json",
30
30
  "type": "registry:lang",
31
- "target": "$modules$/reset-password-page/lang/en.json"
31
+ "target": "$modules$/reset-password-page/lang/en.json",
32
+ "content": "{\r\n \"title\": \"Reset Password\",\r\n \"subtitle\": \"Enter your new password below\",\r\n \"newPassword\": \"New Password\",\r\n \"newPasswordPlaceholder\": \"Enter new password\",\r\n \"confirmPassword\": \"Confirm Password\",\r\n \"confirmPasswordPlaceholder\": \"Confirm new password\",\r\n \"resetPassword\": \"Reset Password\",\r\n \"resetting\": \"Resetting...\",\r\n \"passwordRequirements\": \"At least 8 characters, 1 letter and 1 number\",\r\n \"passwordMismatch\": \"Passwords do not match\",\r\n \"invalidLink\": \"Invalid or expired reset link. Please request a new one.\",\r\n \"passwordResetSuccess\": \"Password reset successfully!\",\r\n \"resetPasswordError\": \"Failed to reset password. Please try again.\",\r\n \"passwordReset\": \"Password Reset!\",\r\n \"passwordResetDescription\": \"Your password has been reset successfully. You can now log in with your new password.\",\r\n \"goToLogin\": \"Go to Login\",\r\n \"invalidLinkTitle\": \"Invalid Reset Link\",\r\n \"invalidLinkDescription\": \"This password reset link is invalid or has expired. Please request a new one.\",\r\n \"requestNewLink\": \"Request New Link\",\r\n \"backToLogin\": \"Back to Login\"\r\n}\r\n"
32
33
  },
33
34
  {
34
35
  "path": "reset-password-page/lang/tr.json",
35
36
  "type": "registry:lang",
36
- "target": "$modules$/reset-password-page/lang/tr.json"
37
+ "target": "$modules$/reset-password-page/lang/tr.json",
38
+ "content": "{\r\n \"title\": \"Şifre Sıfırla\",\r\n \"subtitle\": \"Yeni şifrenizi aşağıya girin\",\r\n \"newPassword\": \"Yeni Şifre\",\r\n \"newPasswordPlaceholder\": \"Yeni şifre girin\",\r\n \"confirmPassword\": \"Şifreyi Onayla\",\r\n \"confirmPasswordPlaceholder\": \"Yeni şifreyi onaylayın\",\r\n \"resetPassword\": \"Şifreyi Sıfırla\",\r\n \"resetting\": \"Sıfırlanıyor...\",\r\n \"passwordRequirements\": \"En az 8 karakter, 1 harf ve 1 rakam\",\r\n \"passwordMismatch\": \"Şifreler eşleşmiyor\",\r\n \"invalidLink\": \"Geçersiz veya süresi dolmuş sıfırlama bağlantısı. Lütfen yeni bir tane isteyin.\",\r\n \"passwordResetSuccess\": \"Şifre başarıyla sıfırlandı!\",\r\n \"resetPasswordError\": \"Şifre sıfırlanamadı. Lütfen tekrar deneyin.\",\r\n \"passwordReset\": \"Şifre Sıfırlandı!\",\r\n \"passwordResetDescription\": \"Şifreniz başarıyla sıfırlandı. Artık yeni şifrenizle giriş yapabilirsiniz.\",\r\n \"goToLogin\": \"Girişe Git\",\r\n \"invalidLinkTitle\": \"Geçersiz Sıfırlama Bağlantısı\",\r\n \"invalidLinkDescription\": \"Bu şifre sıfırlama bağlantısı geçersiz veya süresi dolmuş. Lütfen yeni bir tane isteyin.\",\r\n \"requestNewLink\": \"Yeni Bağlantı İste\",\r\n \"backToLogin\": \"Girişe Dön\"\r\n}\r\n"
37
39
  }
38
- ]
40
+ ],
41
+ "exports": {
42
+ "types": [],
43
+ "variables": [
44
+ "ResetPasswordPage",
45
+ "default"
46
+ ]
47
+ }
39
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promakeai/cli",
3
- "version": "0.3.8",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "promake": "dist/index.js"
package/template/bun.lock CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "new",
6
6
  "dependencies": {
7
7
  "@hookform/resolvers": "^5.2.2",
8
- "@promakeai/customer-backend-client": "^1.0.8",
8
+ "@promakeai/customer-backend-client": "^1.1.0",
9
9
  "@promakeai/inspector": "^1.5.1",
10
10
  "@radix-ui/react-accordion": "^1.2.12",
11
11
  "@radix-ui/react-alert-dialog": "^1.1.15",
@@ -228,7 +228,7 @@
228
228
 
229
229
  "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
230
230
 
231
- "@promakeai/customer-backend-client": ["@promakeai/customer-backend-client@1.0.8", "", { "dependencies": { "axios": "^1.7.0", "zod": "^4.1.13" }, "peerDependencies": { "@tanstack/react-query": ">=5.0.0", "react": ">=18.0.0" }, "optionalPeers": ["@tanstack/react-query", "react"] }, "sha512-doPnoJQsa6mpOjNyUQocSk6qglHRSSSm84ZS5m8A1Y2+ZE+eM9wzDdBZsauYSfRGe2bYhWu5Y3k2pv8q4QoC1A=="],
231
+ "@promakeai/customer-backend-client": ["@promakeai/customer-backend-client@1.1.0", "", { "dependencies": { "axios": "^1.7.0", "zod": "^4.1.13" }, "peerDependencies": { "@tanstack/react-query": ">=5.0.0", "react": ">=18.0.0" }, "optionalPeers": ["@tanstack/react-query", "react"] }, "sha512-Ql56YtwRQyJ9toFyfSkkn0deAdlVodNdK7d2xd9nVVAS7C2F8fxIuJ3xCtWtdLfXjponHhG2qdy9ILq8V4fpbA=="],
232
232
 
233
233
  "@promakeai/inspector": ["@promakeai/inspector@1.5.1", "", { "dependencies": { "@promakeai/inspector-types": "1.5.1", "clsx": "^2.1.1", "lodash": "^4.17.21", "lucide-react": "^0.554.0", "react-colorful": "^5.6.1", "vite-plugin-component-debugger": "^2.2.0", "zustand": "^5.0.8" }, "peerDependencies": { "react": ">=18.0.0", "vite": ">=5.0.0" }, "optionalPeers": ["vite"] }, "sha512-sKclusU4D9xsTJZZ629cFFq2GXyD2l/aOKQdQ7qq4eNfYW85nmoS+D4aQ9M88LcNngO+uFmLCUBnpZHwamYS/A=="],
234
234
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@promakeai/template",
3
3
  "private": true,
4
- "version": "0.0.1",
4
+ "version": "0.1.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -13,7 +13,7 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "@hookform/resolvers": "^5.2.2",
16
- "@promakeai/customer-backend-client": "^1.0.8",
16
+ "@promakeai/customer-backend-client": "^1.1.0",
17
17
  "@promakeai/inspector": "^1.5.1",
18
18
  "@radix-ui/react-accordion": "^1.2.12",
19
19
  "@radix-ui/react-alert-dialog": "^1.1.15",