This commit is contained in:
shafi54 2026-05-10 19:44:26 +05:30
parent 396eba7c1b
commit 4d660e945b
81 changed files with 3732 additions and 1413 deletions

View file

@ -1,6 +1,6 @@
import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { a as Trash2 } from "../_libs/lucide-react.mjs"; import { a as Trash2 } from "../_libs/lucide-react.mjs";
import { a as MyText, i as MyButton, l as Quantifier, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, l as Quantifier, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs";
import { a as useUpdateCartItem, i as useRemoveFromCart, r as useGetCart } from "./cart-query-hooks-Bz8ID9jY.mjs"; import { a as useUpdateCartItem, i as useRemoveFromCart, r as useGetCart } from "./cart-query-hooks-Bz8ID9jY.mjs";
import { t as useAllProducts } from "./prominent-api-hooks-CNVDntUD.mjs"; import { t as useAllProducts } from "./prominent-api-hooks-CNVDntUD.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
@ -23,13 +23,13 @@ function CartPage() {
const product = productsById[item.productId]; const product = productsById[item.productId];
if (product) total += product.price * item.quantity; if (product) total += product.price * item.quantity;
}); });
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-4 text-xl", className: "mb-4 text-xl",
children: "Your Cart" children: "Your Cart"
}), cartItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { }), cartItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex flex-col items-center gap-4 py-20", className: "flex flex-col items-center gap-4 py-20",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-gray-500", className: "text-gray-500",
children: "Your cart is empty" children: "Your cart is empty"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyButton, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyButton, {
@ -52,13 +52,13 @@ function CartPage() {
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex-1", className: "flex-1",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "text-sm", className: "text-sm",
numberOfLines: 1, numberOfLines: 1,
children: product.name children: product.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-brand-600 text-sm font-bold", className: "text-brand-600 text-sm font-bold",
children: ["₹", price] children: ["₹", price]
}), }),
@ -82,10 +82,10 @@ function CartPage() {
className: "fixed bottom-0 left-0 right-0 border-t border-gray-200 bg-white p-4 shadow-lg", className: "fixed bottom-0 left-0 right-0 border-t border-gray-200 bg-white p-4 shadow-lg",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-3 flex items-center justify-between", className: "mb-3 flex items-center justify-between",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
children: "Total" children: "Total"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "text-lg text-brand-600", className: "text-lg text-brand-600",
children: ["₹", total] children: ["₹", total]

View file

@ -1,6 +1,6 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime, p as useQueryClient } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime, p as useQueryClient } from "../_libs/react+tanstack__react-query.mjs";
import { a as MyText, i as MyButton, r as LoadingDialog, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, r as LoadingDialog, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs";
import { r as useGetCart, t as clearLocalCart } from "./cart-query-hooks-Bz8ID9jY.mjs"; import { r as useGetCart, t as clearLocalCart } from "./cart-query-hooks-Bz8ID9jY.mjs";
import { n as trpc } from "./trpc-client-CQOIB5UU.mjs"; import { n as trpc } from "./trpc-client-CQOIB5UU.mjs";
import { t as useAllProducts } from "./prominent-api-hooks-CNVDntUD.mjs"; import { t as useAllProducts } from "./prominent-api-hooks-CNVDntUD.mjs";
@ -59,14 +59,14 @@ function CheckoutPage() {
}); });
}; };
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-4 text-xl", className: "mb-4 text-xl",
children: "Checkout" children: "Checkout"
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-6", className: "mb-6",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-2", className: "mb-2",
children: "Delivery Address" children: "Delivery Address"
@ -74,11 +74,11 @@ function CheckoutPage() {
onClick: () => setSelectedAddressId(addr.id), onClick: () => setSelectedAddressId(addr.id),
className: `mb-2 rounded-xl border p-3 ${selectedAddressId === addr.id ? "border-brand-500 bg-brand-50" : "border-gray-200"}`, className: `mb-2 rounded-xl border p-3 ${selectedAddressId === addr.id ? "border-brand-500 bg-brand-50" : "border-gray-200"}`,
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
children: addr.name children: addr.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm text-gray-600", className: "text-sm text-gray-600",
children: [ children: [
addr.addressLine1, addr.addressLine1,
@ -86,7 +86,7 @@ function CheckoutPage() {
addr.city addr.city
] ]
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-sm text-gray-500", className: "text-sm text-gray-500",
children: addr.phone children: addr.phone
}) })
@ -96,7 +96,7 @@ function CheckoutPage() {
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-6", className: "mb-6",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-2", className: "mb-2",
children: "Order Summary" children: "Order Summary"
@ -106,7 +106,7 @@ function CheckoutPage() {
if (!product) return null; if (!product) return null;
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex items-center justify-between py-2", className: "flex items-center justify-between py-2",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm", className: "text-sm",
numberOfLines: 1, numberOfLines: 1,
children: [ children: [
@ -114,7 +114,7 @@ function CheckoutPage() {
" x", " x",
item.quantity item.quantity
] ]
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm font-bold", className: "text-sm font-bold",
children: ["₹", product.price * item.quantity] children: ["₹", product.price * item.quantity]
})] })]
@ -124,10 +124,10 @@ function CheckoutPage() {
className: "mt-2 border-t border-gray-200 pt-2", className: "mt-2 border-t border-gray-200 pt-2",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex items-center justify-between", className: "flex items-center justify-between",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
children: "Total" children: "Total"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "text-brand-600", className: "text-brand-600",
children: ["₹", total] children: ["₹", total]

View file

@ -1,7 +1,7 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { l as ShoppingCart, t as Zap } from "../_libs/lucide-react.mjs"; import { l as ShoppingCart, t as Zap } from "../_libs/lucide-react.mjs";
import { a as MyText, i as MyButton, l as Quantifier, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, l as Quantifier, t as AppContainer } from "./src-u_N1opJl.mjs";
import { n as useAddToCart } from "./cart-query-hooks-Bz8ID9jY.mjs"; import { n as useAddToCart } from "./cart-query-hooks-Bz8ID9jY.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
import { t as create } from "../_libs/zustand.mjs"; import { t as create } from "../_libs/zustand.mjs";
@ -35,7 +35,7 @@ function FlashDeliveryPage() {
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-4 flex items-center gap-2", className: "mb-4 flex items-center gap-2",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Zap, { className: "h-6 w-6 text-yellow-500" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Zap, { className: "h-6 w-6 text-yellow-500" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "text-xl", className: "text-xl",
children: "1 Hr Delivery" children: "1 Hr Delivery"
@ -43,7 +43,7 @@ function FlashDeliveryPage() {
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
className: "mb-4 rounded-xl bg-yellow-50 p-3", className: "mb-4 rounded-xl bg-yellow-50 p-3",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-sm text-yellow-800", className: "text-sm text-yellow-800",
children: "Get these products delivered within 1 hour! Only available for select items." children: "Get these products delivered within 1 hour! Only available for select items."
}) })
@ -64,13 +64,13 @@ function FlashDeliveryPage() {
className: "h-full w-full object-cover" className: "h-full w-full object-cover"
}) })
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "text-sm", className: "text-sm",
numberOfLines: 2, numberOfLines: 2,
children: product.name children: product.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "text-brand-600", className: "text-brand-600",
children: ["₹", price] children: ["₹", price]
@ -98,7 +98,7 @@ function FlashDeliveryPage() {
}), }),
flashProducts.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { flashProducts.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
className: "py-20 text-center", className: "py-20 text-center",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-gray-500", className: "text-gray-500",
children: "No flash delivery products available" children: "No flash delivery products available"
}) })

View file

@ -1,6 +1,6 @@
import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { a as Trash2, t as Zap } from "../_libs/lucide-react.mjs"; import { a as Trash2, t as Zap } from "../_libs/lucide-react.mjs";
import { a as MyText, i as MyButton, l as Quantifier, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, l as Quantifier, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs";
import { a as useUpdateCartItem, i as useRemoveFromCart, r as useGetCart } from "./cart-query-hooks-Bz8ID9jY.mjs"; import { a as useUpdateCartItem, i as useRemoveFromCart, r as useGetCart } from "./cart-query-hooks-Bz8ID9jY.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
import { t as useCentralProductStore } from "./central-product-store-TS-vQ8-V.mjs"; import { t as useCentralProductStore } from "./central-product-store-TS-vQ8-V.mjs";
@ -20,14 +20,14 @@ function FlashCartPage() {
}); });
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-4 flex items-center gap-2", className: "mb-4 flex items-center gap-2",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Zap, { className: "h-5 w-5 text-yellow-500" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Zap, { className: "h-5 w-5 text-yellow-500" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "text-xl", className: "text-xl",
children: "Flash Cart" children: "Flash Cart"
})] })]
}), cartItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { }), cartItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex flex-col items-center gap-4 py-20", className: "flex flex-col items-center gap-4 py-20",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-gray-500", className: "text-gray-500",
children: "Your flash cart is empty" children: "Your flash cart is empty"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyButton, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyButton, {
@ -50,13 +50,13 @@ function FlashCartPage() {
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex-1", className: "flex-1",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "text-sm", className: "text-sm",
numberOfLines: 1, numberOfLines: 1,
children: product.name children: product.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-brand-600 text-sm font-bold", className: "text-brand-600 text-sm font-bold",
children: ["₹", price] children: ["₹", price]
}), }),
@ -80,10 +80,10 @@ function FlashCartPage() {
className: "fixed bottom-0 left-0 right-0 border-t border-gray-200 bg-white p-4 shadow-lg", className: "fixed bottom-0 left-0 right-0 border-t border-gray-200 bg-white p-4 shadow-lg",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-3 flex items-center justify-between", className: "mb-3 flex items-center justify-between",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
children: "Total" children: "Total"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "text-lg text-brand-600", className: "text-lg text-brand-600",
children: ["₹", total] children: ["₹", total]

View file

@ -1,6 +1,6 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime, p as useQueryClient } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime, p as useQueryClient } from "../_libs/react+tanstack__react-query.mjs";
import { a as MyText, i as MyButton, r as LoadingDialog, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, r as LoadingDialog, t as AppContainer } from "./src-u_N1opJl.mjs";
import { r as useGetCart, t as clearLocalCart } from "./cart-query-hooks-Bz8ID9jY.mjs"; import { r as useGetCart, t as clearLocalCart } from "./cart-query-hooks-Bz8ID9jY.mjs";
import { n as trpc } from "./trpc-client-CQOIB5UU.mjs"; import { n as trpc } from "./trpc-client-CQOIB5UU.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
@ -54,14 +54,14 @@ function FlashCheckoutPage() {
}); });
}; };
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-4 text-xl", className: "mb-4 text-xl",
children: "Flash Checkout" children: "Flash Checkout"
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-6", className: "mb-6",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-2", className: "mb-2",
children: "Delivery Address" children: "Delivery Address"
@ -69,11 +69,11 @@ function FlashCheckoutPage() {
onClick: () => setSelectedAddressId(addr.id), onClick: () => setSelectedAddressId(addr.id),
className: `mb-2 w-full rounded-xl border p-3 text-left ${selectedAddressId === addr.id ? "border-brand-500 bg-brand-50" : "border-gray-200"}`, className: `mb-2 w-full rounded-xl border p-3 text-left ${selectedAddressId === addr.id ? "border-brand-500 bg-brand-50" : "border-gray-200"}`,
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
children: addr.name children: addr.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm text-gray-600", className: "text-sm text-gray-600",
children: [ children: [
addr.addressLine1, addr.addressLine1,
@ -81,7 +81,7 @@ function FlashCheckoutPage() {
addr.city addr.city
] ]
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-sm text-gray-500", className: "text-sm text-gray-500",
children: addr.phone children: addr.phone
}) })
@ -91,7 +91,7 @@ function FlashCheckoutPage() {
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-6", className: "mb-6",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-2", className: "mb-2",
children: "Order Summary" children: "Order Summary"
@ -101,7 +101,7 @@ function FlashCheckoutPage() {
if (!product) return null; if (!product) return null;
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex items-center justify-between py-2", className: "flex items-center justify-between py-2",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm", className: "text-sm",
numberOfLines: 1, numberOfLines: 1,
children: [ children: [
@ -109,7 +109,7 @@ function FlashCheckoutPage() {
" x", " x",
item.quantity item.quantity
] ]
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm font-bold", className: "text-sm font-bold",
children: ["₹", (product.discountedPrice ?? product.price) * item.quantity] children: ["₹", (product.discountedPrice ?? product.price) * item.quantity]
})] })]
@ -119,10 +119,10 @@ function FlashCheckoutPage() {
className: "mt-2 border-t border-gray-200 pt-2", className: "mt-2 border-t border-gray-200 pt-2",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex items-center justify-between", className: "flex items-center justify-between",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
children: "Total" children: "Total"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "text-brand-600", className: "text-brand-600",
children: ["₹", total] children: ["₹", total]

View file

@ -1,6 +1,6 @@
import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { t as Zap } from "../_libs/lucide-react.mjs"; import { t as Zap } from "../_libs/lucide-react.mjs";
import { a as MyText, i as MyButton } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton } from "./src-u_N1opJl.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
import { t as Route } from "./flash.order-success-Bs-Lyb2u.mjs"; import { t as Route } from "./flash.order-success-Bs-Lyb2u.mjs";
//#region node_modules/.nitro/vite/services/ssr/assets/flash.order-success-C9tkVfq7.js //#region node_modules/.nitro/vite/services/ssr/assets/flash.order-success-C9tkVfq7.js
@ -15,16 +15,16 @@ function FlashOrderSuccessPage() {
className: "mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-yellow-100", className: "mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-yellow-100",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Zap, { className: "h-10 w-10 text-yellow-600" }) children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Zap, { className: "h-10 w-10 text-yellow-600" })
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-2 text-2xl text-gray-900", className: "mb-2 text-2xl text-gray-900",
children: "1 Hr Order Placed!" children: "1 Hr Order Placed!"
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "mb-1 text-gray-600", className: "mb-1 text-gray-600",
children: ["Order ID: #", orderId] children: ["Order ID: #", orderId]
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "mb-8 text-gray-600", className: "mb-8 text-gray-600",
children: ["Total: ₹", totalAmount] children: ["Total: ₹", totalAmount]
}), }),

View file

@ -1,7 +1,7 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { l as ShoppingCart, t as Zap } from "../_libs/lucide-react.mjs"; import { l as ShoppingCart, t as Zap } from "../_libs/lucide-react.mjs";
import { a as MyText, i as MyButton, l as Quantifier, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, l as Quantifier, t as AppContainer } from "./src-u_N1opJl.mjs";
import { n as useAddToCart } from "./cart-query-hooks-Bz8ID9jY.mjs"; import { n as useAddToCart } from "./cart-query-hooks-Bz8ID9jY.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
import { t as useCentralProductStore } from "./central-product-store-TS-vQ8-V.mjs"; import { t as useCentralProductStore } from "./central-product-store-TS-vQ8-V.mjs";
@ -24,13 +24,13 @@ function FlashProductDetailPage() {
storeId: product.storeId storeId: product.storeId
}, { onSuccess: () => navigate({ to: "/flash/cart" }) }); }, { onSuccess: () => navigate({ to: "/flash/cart" }) });
}; };
if (!product) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: "Product not found" }) }); if (!product) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, { children: "Product not found" }) });
const price = product.discountedPrice ?? product.price; const price = product.discountedPrice ?? product.price;
const imageUrl = product.images?.[0]; const imageUrl = product.images?.[0];
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-4 flex items-center gap-2", className: "mb-4 flex items-center gap-2",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Zap, { className: "h-5 w-5 text-yellow-500" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Zap, { className: "h-5 w-5 text-yellow-500" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-sm font-semibold text-yellow-600", className: "text-sm font-semibold text-yellow-600",
children: "1 Hr Delivery" children: "1 Hr Delivery"
})] })]
@ -43,22 +43,22 @@ function FlashProductDetailPage() {
className: "h-full w-full object-cover" className: "h-full w-full object-cover"
}) })
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-1 text-xl", className: "mb-1 text-xl",
children: product.name children: product.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "mb-4 text-sm text-gray-500", className: "mb-4 text-sm text-gray-500",
children: [product.unitValue, product.unit] children: [product.unitValue, product.unit]
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-4 flex items-baseline gap-2", className: "mb-4 flex items-baseline gap-2",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "text-2xl text-brand-600", className: "text-2xl text-brand-600",
children: ["₹", price] children: ["₹", price]
}), product.discountedPrice && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), product.discountedPrice && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm text-gray-400 line-through", className: "text-sm text-gray-400 line-through",
children: ["₹", product.price] children: ["₹", product.price]
})] })]

View file

@ -1,7 +1,7 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { i as Truck, l as ShoppingCart, n as X, t as Zap } from "../_libs/lucide-react.mjs"; import { i as Truck, l as ShoppingCart, n as X, t as Zap } from "../_libs/lucide-react.mjs";
import { a as MyText, l as Quantifier, n as BottomDialog, s as MyTouchableOpacity, u as SearchBar } from "./src-u_N1opJl.mjs"; import { a as p, l as Quantifier, n as BottomDialog, s as MyTouchableOpacity, u as SearchBar } from "./src-u_N1opJl.mjs";
import { a as useUpdateCartItem, i as useRemoveFromCart, n as useAddToCart, r as useGetCart } from "./cart-query-hooks-Bz8ID9jY.mjs"; import { a as useUpdateCartItem, i as useRemoveFromCart, n as useAddToCart, r as useGetCart } from "./cart-query-hooks-Bz8ID9jY.mjs";
import { a as useStores, n as useBanners, r as useSlots, t as useAllProducts } from "./prominent-api-hooks-CNVDntUD.mjs"; import { a as useStores, n as useBanners, r as useSlots, t as useAllProducts } from "./prominent-api-hooks-CNVDntUD.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
@ -113,11 +113,11 @@ function AddToCartDialog() {
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex-1", className: "flex-1",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "text-lg", className: "text-lg",
children: "Select Delivery Slot" children: "Select Delivery Slot"
}), product?.name && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), product?.name && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm text-gray-500", className: "text-sm text-gray-500",
children: [ children: [
product.name, product.name,
@ -145,7 +145,7 @@ function AddToCartDialog() {
className: `flex items-start gap-3 rounded-xl border bg-gray-50 p-4 ${selectedSlotId === slot.id ? "border-brand-500" : "border-gray-100"}`, className: `flex items-start gap-3 rounded-xl border bg-gray-50 p-4 ${selectedSlotId === slot.id ? "border-brand-500" : "border-gray-100"}`,
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Truck, { className: "mt-0.5 h-5 w-5 shrink-0 text-blue-500" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Truck, { className: "mt-0.5 h-5 w-5 shrink-0 text-blue-500" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "flex-1 text-sm", className: "flex-1 text-sm",
children: [(0, import_dayjs_min.default)(slot.deliveryTime).format("ddd, DD MMM • "), formatTimeRange(slot.deliveryTime)] children: [(0, import_dayjs_min.default)(slot.deliveryTime).format("ddd, DD MMM • "), formatTimeRange(slot.deliveryTime)]
@ -172,7 +172,7 @@ function AddToCartDialog() {
className: `flex items-center gap-3 rounded-xl border p-4 mb-4 ${selectedFlashDelivery ? "border-pink-500 bg-pink-50" : "border-pink-200 bg-pink-50"}`, className: `flex items-center gap-3 rounded-xl border p-4 mb-4 ${selectedFlashDelivery ? "border-pink-500 bg-pink-50" : "border-pink-200 bg-pink-50"}`,
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Zap, { className: "h-5 w-5 shrink-0 text-pink-500" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Zap, { className: "h-5 w-5 shrink-0 text-pink-500" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "flex-1 text-sm", className: "flex-1 text-sm",
children: "1 hr Delivery" children: "1 hr Delivery"
@ -192,7 +192,7 @@ function AddToCartDialog() {
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-4", className: "mb-4",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-2 text-sm", className: "mb-2 text-sm",
children: "Quantity" children: "Quantity"
@ -277,7 +277,7 @@ function HomePage() {
className: "mb-8", className: "mb-8",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
className: "flex items-center justify-between mb-4", className: "flex items-center justify-between mb-4",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "text-lg md:text-xl", className: "text-lg md:text-xl",
children: "Our Stores" children: "Our Stores"
@ -297,7 +297,7 @@ function HomePage() {
className: "mb-24", className: "mb-24",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
className: "flex items-center justify-between mb-4", className: "flex items-center justify-between mb-4",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "text-lg md:text-xl", className: "text-lg md:text-xl",
children: "All Products" children: "All Products"
@ -408,12 +408,12 @@ function StoreCard({ store, onClick }) {
}) })
}) })
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "text-sm truncate", className: "text-sm truncate",
children: store.name children: store.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-xs text-gray-500", className: "text-xs text-gray-500",
children: [store.productCount || 0, " products"] children: [store.productCount || 0, " products"]
}) })
@ -450,29 +450,29 @@ function ProductCard({ product, onClick, onAddToCart }) {
}) })
}) })
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "text-sm leading-tight line-clamp-2 mb-1", className: "text-sm leading-tight line-clamp-2 mb-1",
children: product.name children: product.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex items-baseline gap-1.5", className: "flex items-baseline gap-1.5",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "text-brand-600 text-sm md:text-base", className: "text-brand-600 text-sm md:text-base",
children: ["₹", product.price] children: ["₹", product.price]
}), hasDiscount && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), hasDiscount && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-xs text-gray-400 line-through", className: "text-xs text-gray-400 line-through",
children: ["₹", product.marketPrice] children: ["₹", product.marketPrice]
})] })]
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-[11px] text-gray-400 mb-2", className: "text-[11px] text-gray-400 mb-2",
children: ["/", product.unit] children: ["/", product.unit]
}), }),
product.nextDeliveryDate && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { product.nextDeliveryDate && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-3 flex items-center gap-1 self-start rounded-lg bg-brand-50 px-2 py-1 border border-brand-100", className: "mb-3 flex items-center gap-1 self-start rounded-lg bg-brand-50 px-2 py-1 border border-brand-100",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Truck, { className: "h-3 w-3 text-brand-600" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Truck, { className: "h-3 w-3 text-brand-600" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-[10px] font-bold text-brand-700", className: "text-[10px] font-bold text-brand-700",
children: (0, import_dayjs_min.default)(product.nextDeliveryDate).format("ddd, DD MMM • h:mm A") children: (0, import_dayjs_min.default)(product.nextDeliveryDate).format("ddd, DD MMM • h:mm A")
})] })]
@ -511,15 +511,15 @@ function FloatingCartBar({ onClick }) {
className: "flex-1", className: "flex-1",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex items-center gap-2", className: "flex items-center gap-2",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "text-sm text-white", className: "text-sm text-white",
children: ["₹", totalCartValue] children: ["₹", totalCartValue]
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-xs text-white/80", className: "text-xs text-white/80",
children: itemCount === 0 ? "No items in cart" : `${itemCount} ${itemCount === 1 ? "item" : "items"}` children: itemCount === 0 ? "No items in cart" : `${itemCount} ${itemCount === 1 ? "item" : "items"}`
})] })]
}), remainingForFreeDelivery > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), remainingForFreeDelivery > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-[10px] font-bold text-white/70", className: "text-[10px] font-bold text-white/70",
children: [ children: [
"₹", "₹",
@ -533,11 +533,11 @@ function FloatingCartBar({ onClick }) {
fill: "currentColor", fill: "currentColor",
viewBox: "0 0 24 24", viewBox: "0 0 24 24",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" }) children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" })
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-[10px] font-bold text-emerald-300", className: "text-[10px] font-bold text-emerald-300",
children: "Free Delivery Unlocked" children: "Free Delivery Unlocked"
})] })]
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-[10px] text-white/50", className: "text-[10px] text-white/50",
children: [ children: [
"Shop for ₹", "Shop for ₹",

View file

@ -1,6 +1,6 @@
import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { a as Trash2 } from "../_libs/lucide-react.mjs"; import { a as Trash2 } from "../_libs/lucide-react.mjs";
import { a as MyText, i as MyButton, l as Quantifier, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, l as Quantifier, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs";
import { a as useUpdateCartItem, i as useRemoveFromCart, r as useGetCart } from "./cart-query-hooks-Bz8ID9jY.mjs"; import { a as useUpdateCartItem, i as useRemoveFromCart, r as useGetCart } from "./cart-query-hooks-Bz8ID9jY.mjs";
import { t as useAllProducts } from "./prominent-api-hooks-CNVDntUD.mjs"; import { t as useAllProducts } from "./prominent-api-hooks-CNVDntUD.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
@ -23,13 +23,13 @@ function CartPage() {
const product = productsById[item.productId]; const product = productsById[item.productId];
if (product) total += product.price * item.quantity; if (product) total += product.price * item.quantity;
}); });
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-4 text-xl", className: "mb-4 text-xl",
children: "Your Cart" children: "Your Cart"
}), cartItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { }), cartItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex flex-col items-center gap-4 py-20", className: "flex flex-col items-center gap-4 py-20",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-gray-500", className: "text-gray-500",
children: "Your cart is empty" children: "Your cart is empty"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyButton, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyButton, {
@ -52,13 +52,13 @@ function CartPage() {
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex-1", className: "flex-1",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "text-sm", className: "text-sm",
numberOfLines: 1, numberOfLines: 1,
children: product.name children: product.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-brand-600 text-sm font-bold", className: "text-brand-600 text-sm font-bold",
children: ["₹", price] children: ["₹", price]
}), }),
@ -82,10 +82,10 @@ function CartPage() {
className: "fixed bottom-0 left-0 right-0 border-t border-gray-200 bg-white p-4 shadow-lg", className: "fixed bottom-0 left-0 right-0 border-t border-gray-200 bg-white p-4 shadow-lg",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-3 flex items-center justify-between", className: "mb-3 flex items-center justify-between",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
children: "Total" children: "Total"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "text-lg text-brand-600", className: "text-lg text-brand-600",
children: ["₹", total] children: ["₹", total]

View file

@ -1,6 +1,6 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime, p as useQueryClient } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime, p as useQueryClient } from "../_libs/react+tanstack__react-query.mjs";
import { a as MyText, i as MyButton, r as LoadingDialog, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, r as LoadingDialog, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs";
import { r as useGetCart, t as clearLocalCart } from "./cart-query-hooks-Bz8ID9jY.mjs"; import { r as useGetCart, t as clearLocalCart } from "./cart-query-hooks-Bz8ID9jY.mjs";
import { n as trpc } from "./trpc-client-CQOIB5UU.mjs"; import { n as trpc } from "./trpc-client-CQOIB5UU.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
@ -54,14 +54,14 @@ function CheckoutPage() {
}); });
}; };
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-4 text-xl", className: "mb-4 text-xl",
children: "Checkout" children: "Checkout"
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-6", className: "mb-6",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-2", className: "mb-2",
children: "Delivery Address" children: "Delivery Address"
@ -69,11 +69,11 @@ function CheckoutPage() {
onClick: () => setSelectedAddressId(addr.id), onClick: () => setSelectedAddressId(addr.id),
className: `mb-2 rounded-xl border p-3 ${selectedAddressId === addr.id ? "border-brand-500 bg-brand-50" : "border-gray-200"}`, className: `mb-2 rounded-xl border p-3 ${selectedAddressId === addr.id ? "border-brand-500 bg-brand-50" : "border-gray-200"}`,
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
children: addr.name children: addr.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm text-gray-600", className: "text-sm text-gray-600",
children: [ children: [
addr.addressLine1, addr.addressLine1,
@ -81,7 +81,7 @@ function CheckoutPage() {
addr.city addr.city
] ]
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-sm text-gray-500", className: "text-sm text-gray-500",
children: addr.phone children: addr.phone
}) })
@ -91,7 +91,7 @@ function CheckoutPage() {
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-6", className: "mb-6",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-2", className: "mb-2",
children: "Order Summary" children: "Order Summary"
@ -101,7 +101,7 @@ function CheckoutPage() {
if (!product) return null; if (!product) return null;
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex items-center justify-between py-2", className: "flex items-center justify-between py-2",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm", className: "text-sm",
numberOfLines: 1, numberOfLines: 1,
children: [ children: [
@ -109,7 +109,7 @@ function CheckoutPage() {
" x", " x",
item.quantity item.quantity
] ]
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm font-bold", className: "text-sm font-bold",
children: ["₹", (product.discountedPrice ?? product.price) * item.quantity] children: ["₹", (product.discountedPrice ?? product.price) * item.quantity]
})] })]
@ -119,10 +119,10 @@ function CheckoutPage() {
className: "mt-2 border-t border-gray-200 pt-2", className: "mt-2 border-t border-gray-200 pt-2",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex items-center justify-between", className: "flex items-center justify-between",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
children: "Total" children: "Total"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "text-brand-600", className: "text-brand-600",
children: ["₹", total] children: ["₹", total]

View file

@ -1,6 +1,6 @@
import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { p as Package } from "../_libs/lucide-react.mjs"; import { p as Package } from "../_libs/lucide-react.mjs";
import { a as MyText, i as MyButton } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton } from "./src-u_N1opJl.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
import { t as Route } from "./home.order-success-ng0baB-e.mjs"; import { t as Route } from "./home.order-success-ng0baB-e.mjs";
//#region node_modules/.nitro/vite/services/ssr/assets/home.order-success-COjzwSkc.js //#region node_modules/.nitro/vite/services/ssr/assets/home.order-success-COjzwSkc.js
@ -15,16 +15,16 @@ function OrderSuccessPage() {
className: "mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-green-100", className: "mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-green-100",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Package, { className: "h-10 w-10 text-green-600" }) children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Package, { className: "h-10 w-10 text-green-600" })
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-2 text-2xl text-gray-900", className: "mb-2 text-2xl text-gray-900",
children: "Order Placed!" children: "Order Placed!"
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "mb-1 text-gray-600", className: "mb-1 text-gray-600",
children: ["Order ID: #", orderId] children: ["Order ID: #", orderId]
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "mb-8 text-gray-600", className: "mb-8 text-gray-600",
children: ["Total: ₹", totalAmount] children: ["Total: ₹", totalAmount]
}), }),

View file

@ -1,7 +1,7 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { c as Star, l as ShoppingCart } from "../_libs/lucide-react.mjs"; import { c as Star, l as ShoppingCart } from "../_libs/lucide-react.mjs";
import { a as MyText, i as MyButton, l as Quantifier, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, l as Quantifier, t as AppContainer } from "./src-u_N1opJl.mjs";
import { n as useAddToCart } from "./cart-query-hooks-Bz8ID9jY.mjs"; import { n as useAddToCart } from "./cart-query-hooks-Bz8ID9jY.mjs";
import { n as trpc } from "./trpc-client-CQOIB5UU.mjs"; import { n as trpc } from "./trpc-client-CQOIB5UU.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
@ -26,7 +26,7 @@ function ProductDetailPage() {
storeId: product.storeId storeId: product.storeId
}, { onSuccess: () => navigate({ to: "/cart" }) }); }, { onSuccess: () => navigate({ to: "/cart" }) });
}; };
if (!product) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: "Product not found" }) }); if (!product) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, { children: "Product not found" }) });
const price = product.discountedPrice ?? product.price; const price = product.discountedPrice ?? product.price;
const imageUrl = product.images?.[0]; const imageUrl = product.images?.[0];
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [
@ -38,27 +38,27 @@ function ProductDetailPage() {
className: "h-full w-full object-cover" className: "h-full w-full object-cover"
}) })
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-1 text-xl", className: "mb-1 text-xl",
children: product.name children: product.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "mb-2 text-sm text-gray-500", className: "mb-2 text-sm text-gray-500",
children: [product.unitValue, product.unit] children: [product.unitValue, product.unit]
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-4 flex items-baseline gap-2", className: "mb-4 flex items-baseline gap-2",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "text-2xl text-brand-600", className: "text-2xl text-brand-600",
children: ["₹", price] children: ["₹", price]
}), product.discountedPrice && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), product.discountedPrice && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm text-gray-400 line-through", className: "text-sm text-gray-400 line-through",
children: ["₹", product.price] children: ["₹", product.price]
})] })]
}), }),
product.description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { product.description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "mb-4 text-gray-600", className: "mb-4 text-gray-600",
children: product.description children: product.description
}), }),
@ -79,7 +79,7 @@ function ProductDetailPage() {
}), }),
reviews?.data && reviews.data.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { reviews?.data && reviews.data.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mt-8", className: "mt-8",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-3 text-lg", className: "mb-3 text-lg",
children: "Reviews" children: "Reviews"
@ -88,7 +88,7 @@ function ProductDetailPage() {
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
className: "mb-1 flex items-center gap-1", className: "mb-1 flex items-center gap-1",
children: Array.from({ length: review.rating || 5 }).map((_, j) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Star, { className: "h-3 w-3 fill-yellow-400 text-yellow-400" }, j)) children: Array.from({ length: review.rating || 5 }).map((_, j) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Star, { className: "h-3 w-3 fill-yellow-400 text-yellow-400" }, j))
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-sm text-gray-600", className: "text-sm text-gray-600",
children: review.comment children: review.comment
})] })]

View file

@ -1,6 +1,6 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { a as MyText, s as MyTouchableOpacity, t as AppContainer, u as SearchBar } from "./src-u_N1opJl.mjs"; import { a as p, s as MyTouchableOpacity, t as AppContainer, u as SearchBar } from "./src-u_N1opJl.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
import { t as useCentralProductStore } from "./central-product-store-TS-vQ8-V.mjs"; import { t as useCentralProductStore } from "./central-product-store-TS-vQ8-V.mjs";
import { t as Route } from "./home.search-C7gKn8CW.mjs"; import { t as Route } from "./home.search-C7gKn8CW.mjs";
@ -56,13 +56,13 @@ function SearchPage() {
className: "h-full w-full object-cover" className: "h-full w-full object-cover"
}) })
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "text-sm", className: "text-sm",
numberOfLines: 2, numberOfLines: 2,
children: product.name children: product.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "mt-1 text-brand-600", className: "mt-1 text-brand-600",
children: ["₹", product.discountedPrice ?? product.price] children: ["₹", product.discountedPrice ?? product.price]

View file

@ -1,6 +1,6 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { a as MyText, i as MyButton, o as MyTextInput, s as MyTouchableOpacity } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, o as pInput, s as MyTouchableOpacity } from "./src-u_N1opJl.mjs";
import { n as trpc } from "./trpc-client-CQOIB5UU.mjs"; import { n as trpc } from "./trpc-client-CQOIB5UU.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
import { n as useAuth } from "./auth-context-DzjwonUC.mjs"; import { n as useAuth } from "./auth-context-DzjwonUC.mjs";
@ -124,12 +124,12 @@ function LoginPage() {
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "w-full max-w-md", className: "w-full max-w-md",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-2 text-center text-4xl text-white", className: "mb-2 text-center text-4xl text-white",
children: "Welcome" children: "Welcome"
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "mb-8 text-center text-lg text-blue-100", className: "mb-8 text-center text-lg text-blue-100",
children: "Sign in to continue your journey" children: "Sign in to continue your journey"
}), }),
@ -141,7 +141,7 @@ function LoginPage() {
step === "mobile" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Controller, { step === "mobile" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Controller, {
control, control,
name: "mobile", name: "mobile",
render: ({ field: { onChange, value } }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTextInput, { render: ({ field: { onChange, value } }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(pInput, {
placeholder: "Enter your mobile number", placeholder: "Enter your mobile number",
value, value,
onChange: (e) => { onChange: (e) => {
@ -154,7 +154,7 @@ function LoginPage() {
step === "otp" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { step === "otp" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-6", className: "mb-6",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-3 text-center text-base text-gray-800", className: "mb-3 text-center text-base text-gray-800",
children: "Enter 4-digit OTP" children: "Enter 4-digit OTP"
@ -194,7 +194,7 @@ function LoginPage() {
"" ""
]); ]);
}, },
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "medium", weight: "medium",
className: "text-gray-500", className: "text-gray-500",
children: "Back" children: "Back"
@ -202,7 +202,7 @@ function LoginPage() {
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTouchableOpacity, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTouchableOpacity, {
onClick: () => sendOtpMutation.mutate({ mobile: selectedMobile }), onClick: () => sendOtpMutation.mutate({ mobile: selectedMobile }),
disabled: !canResend, disabled: !canResend,
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: canResend ? "text-brand-600" : "text-gray-400", className: canResend ? "text-brand-600" : "text-gray-400",
children: canResend ? "Resend OTP" : `Resend in ${resendCountdown}s` children: canResend ? "Resend OTP" : `Resend in ${resendCountdown}s`
@ -214,7 +214,7 @@ function LoginPage() {
step === "password" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Controller, { step === "password" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Controller, {
control, control,
name: "password", name: "password",
render: ({ field: { onChange, value } }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTextInput, { render: ({ field: { onChange, value } }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(pInput, {
placeholder: "Enter your password", placeholder: "Enter your password",
value, value,
onChange: (e) => onChange(e.target.value), onChange: (e) => onChange(e.target.value),
@ -244,7 +244,7 @@ function LoginPage() {
]); ]);
}, },
className: "mt-4 block text-center", className: "mt-4 block text-center",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "text-brand-600", className: "text-brand-600",
children: "Or login with Password" children: "Or login with Password"

View file

@ -1,6 +1,6 @@
import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { b as FileText, g as MapPin, h as MessageSquare, l as ShoppingCart, o as Ticket, p as Package, r as User, v as Info } from "../_libs/lucide-react.mjs"; import { b as FileText, g as MapPin, h as MessageSquare, l as ShoppingCart, o as Ticket, p as Package, r as User, v as Info } from "../_libs/lucide-react.mjs";
import { a as MyText, c as ProfileImage, i as MyButton, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, c as ProfileImage, i as MyButton, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
import { n as useAuth } from "./auth-context-DzjwonUC.mjs"; import { n as useAuth } from "./auth-context-DzjwonUC.mjs";
//#region node_modules/.nitro/vite/services/ssr/assets/me-Dn8Tk_dJ.js //#region node_modules/.nitro/vite/services/ssr/assets/me-Dn8Tk_dJ.js
@ -10,7 +10,7 @@ function MePage() {
const { user, logout } = useAuth(); const { user, logout } = useAuth();
if (!user) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { if (!user) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex flex-col items-center gap-4 py-20", className: "flex flex-col items-center gap-4 py-20",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: "Please sign in" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyButton, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, { children: "Please sign in" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyButton, {
textContent: "Sign In", textContent: "Sign In",
onClick: () => navigate({ to: "/login" }) onClick: () => navigate({ to: "/login" })
})] })]
@ -75,18 +75,18 @@ function MePage() {
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProfileImage, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProfileImage, {
uri: user.profileImage, uri: user.profileImage,
size: 64 size: 64
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "text-lg", className: "text-lg",
children: user.name || "User" children: user.name || "User"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-sm text-gray-500", className: "text-sm text-gray-500",
children: user.mobile children: user.mobile
})] })] })] })]
}), }),
menuItems.map((section) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { menuItems.map((section) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-6", className: "mb-6",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-2 text-sm text-gray-500 uppercase tracking-wide", className: "mb-2 text-sm text-gray-500 uppercase tracking-wide",
children: section.section children: section.section
@ -95,7 +95,7 @@ function MePage() {
children: section.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyTouchableOpacity, { children: section.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyTouchableOpacity, {
onClick: () => navigate({ to: item.to }), onClick: () => navigate({ to: item.to }),
className: "flex w-full items-center gap-3 border-b border-gray-50 px-4 py-3.5 last:border-b-0", className: "flex w-full items-center gap-3 border-b border-gray-50 px-4 py-3.5 last:border-b-0",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(item.icon, { className: "h-5 w-5 text-gray-400" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(item.icon, { className: "h-5 w-5 text-gray-400" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "flex-1 text-left text-sm", className: "flex-1 text-left text-sm",
children: item.label children: item.label
})] })]
@ -109,7 +109,7 @@ function MePage() {
className: "mb-8", className: "mb-8",
textContent: "Logout" textContent: "Logout"
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "mb-8 text-center text-xs text-gray-400", className: "mb-8 text-center text-xs text-gray-400",
children: "Version 1.0.0" children: "Version 1.0.0"
}) })

View file

@ -1,11 +1,11 @@
import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { _ as Leaf, i as Truck, u as Shield, y as Heart } from "../_libs/lucide-react.mjs"; import { _ as Leaf, i as Truck, u as Shield, y as Heart } from "../_libs/lucide-react.mjs";
import { a as MyText, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, t as AppContainer } from "./src-u_N1opJl.mjs";
//#region node_modules/.nitro/vite/services/ssr/assets/me.about-ig0mha9d.js //#region node_modules/.nitro/vite/services/ssr/assets/me.about-ig0mha9d.js
var import_jsx_runtime = require_jsx_runtime(); var import_jsx_runtime = require_jsx_runtime();
function AboutPage() { function AboutPage() {
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-6 text-2xl", className: "mb-6 text-2xl",
children: "About Freshyo" children: "About Freshyo"
@ -37,12 +37,12 @@ function AboutPage() {
className: "rounded-xl border border-gray-100 bg-white p-4 shadow-sm", className: "rounded-xl border border-gray-100 bg-white p-4 shadow-sm",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(mission.icon, { className: "mb-2 h-8 w-8 text-brand-500" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(mission.icon, { className: "mb-2 h-8 w-8 text-brand-500" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-1", className: "mb-1",
children: mission.title children: mission.title
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-sm text-gray-600", className: "text-sm text-gray-600",
children: mission.desc children: mission.desc
}) })
@ -51,11 +51,11 @@ function AboutPage() {
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mt-8", className: "mt-8",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-3 text-lg", className: "mb-3 text-lg",
children: "Sourcing & Quality" children: "Sourcing & Quality"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "mb-6 text-sm leading-relaxed text-gray-600", className: "mb-6 text-sm leading-relaxed text-gray-600",
children: "We partner with trusted local farmers who follow ethical and sustainable farming practices. Every product undergoes rigorous quality checks to ensure you receive only the freshest meat." children: "We partner with trusted local farmers who follow ethical and sustainable farming practices. Every product undergoes rigorous quality checks to ensure you receive only the freshest meat."
})] })]

View file

@ -1,7 +1,7 @@
import "../_runtime.mjs"; import "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { g as MapPin } from "../_libs/lucide-react.mjs"; import { g as MapPin } from "../_libs/lucide-react.mjs";
import { a as MyText, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, t as AppContainer } from "./src-u_N1opJl.mjs";
import { n as trpc } from "./trpc-client-CQOIB5UU.mjs"; import { n as trpc } from "./trpc-client-CQOIB5UU.mjs";
require_react(); require_react();
var import_jsx_runtime = require_jsx_runtime(); var import_jsx_runtime = require_jsx_runtime();
@ -10,13 +10,13 @@ function AddressesPage() {
const { data } = trpc.user.address.getUserAddresses.useQuery(); const { data } = trpc.user.address.getUserAddresses.useQuery();
const deleteMutation = trpc.user.address.deleteAddress.useMutation({ onSuccess: () => utils.user.address.getUserAddresses.invalidate() }); const deleteMutation = trpc.user.address.deleteAddress.useMutation({ onSuccess: () => utils.user.address.getUserAddresses.invalidate() });
const addresses = data?.data || []; const addresses = data?.data || [];
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-4 text-xl", className: "mb-4 text-xl",
children: "My Addresses" children: "My Addresses"
}), addresses.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { }), addresses.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex flex-col items-center gap-4 py-20", className: "flex flex-col items-center gap-4 py-20",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MapPin, { className: "h-12 w-12 text-gray-300" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MapPin, { className: "h-12 w-12 text-gray-300" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-gray-500", className: "text-gray-500",
children: "No addresses saved" children: "No addresses saved"
})] })]
@ -29,15 +29,15 @@ function AddressesPage() {
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex-1", className: "flex-1",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
children: addr.name children: addr.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm text-gray-600", className: "text-sm text-gray-600",
children: [addr.addressLine1, addr.addressLine2 ? `, ${addr.addressLine2}` : ""] children: [addr.addressLine1, addr.addressLine2 ? `, ${addr.addressLine2}` : ""]
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm text-gray-600", className: "text-sm text-gray-600",
children: [ children: [
addr.city, addr.city,
@ -47,7 +47,7 @@ function AddressesPage() {
addr.pincode addr.pincode
] ]
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-sm text-gray-500", className: "text-sm text-gray-500",
children: addr.phone children: addr.phone
}), }),

View file

@ -1,7 +1,7 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { f as Plus, h as MessageSquare } from "../_libs/lucide-react.mjs"; import { f as Plus, h as MessageSquare } from "../_libs/lucide-react.mjs";
import { a as MyText, i as MyButton, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs";
import { n as trpc } from "./trpc-client-CQOIB5UU.mjs"; import { n as trpc } from "./trpc-client-CQOIB5UU.mjs";
//#region node_modules/.nitro/vite/services/ssr/assets/me.complaints-CUIcnKsp.js //#region node_modules/.nitro/vite/services/ssr/assets/me.complaints-CUIcnKsp.js
var import_react = /* @__PURE__ */ __toESM(require_react()); var import_react = /* @__PURE__ */ __toESM(require_react());
@ -24,14 +24,14 @@ function ComplaintsPage() {
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-4 flex items-center justify-between", className: "mb-4 flex items-center justify-between",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "text-xl", className: "text-xl",
children: "Help & Complaints" children: "Help & Complaints"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyTouchableOpacity, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyTouchableOpacity, {
onClick: () => setShowForm(!showForm), onClick: () => setShowForm(!showForm),
className: "flex items-center gap-1 text-brand-600", className: "flex items-center gap-1 text-brand-600",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, { className: "h-4 w-4" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, { className: "h-4 w-4" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-sm", className: "text-sm",
children: "New" children: "New"
})] })]
@ -40,7 +40,7 @@ function ComplaintsPage() {
showForm && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { showForm && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-6 rounded-xl border border-gray-100 bg-white p-4 shadow-sm", className: "mb-6 rounded-xl border border-gray-100 bg-white p-4 shadow-sm",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-3", className: "mb-3",
children: "Raise a Complaint" children: "Raise a Complaint"
@ -61,7 +61,7 @@ function ComplaintsPage() {
}), }),
complaints.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { complaints.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex flex-col items-center gap-4 py-20", className: "flex flex-col items-center gap-4 py-20",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageSquare, { className: "h-12 w-12 text-gray-300" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageSquare, { className: "h-12 w-12 text-gray-300" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-gray-500", className: "text-gray-500",
children: "No complaints yet" children: "No complaints yet"
})] })]
@ -75,18 +75,18 @@ function ComplaintsPage() {
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
className: `rounded-full px-2 py-0.5 text-xs font-medium ${complaint.status === "resolved" ? "bg-green-100 text-green-700" : "bg-yellow-100 text-yellow-700"}`, className: `rounded-full px-2 py-0.5 text-xs font-medium ${complaint.status === "resolved" ? "bg-green-100 text-green-700" : "bg-yellow-100 text-yellow-700"}`,
children: complaint.status || "pending" children: complaint.status || "pending"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-xs text-gray-400", className: "text-xs text-gray-400",
children: complaint.createdAt ? new Date(complaint.createdAt).toLocaleDateString() : "" children: complaint.createdAt ? new Date(complaint.createdAt).toLocaleDateString() : ""
})] })]
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-sm text-gray-700", className: "text-sm text-gray-700",
children: complaint.body children: complaint.body
}), }),
complaint.adminResponse && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { complaint.adminResponse && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
className: "mt-2 rounded-lg bg-blue-50 p-2", className: "mt-2 rounded-lg bg-blue-50 p-2",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-xs text-blue-600", className: "text-xs text-blue-600",
children: ["Response: ", complaint.adminResponse] children: ["Response: ", complaint.adminResponse]
}) })

View file

@ -1,7 +1,7 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { o as Ticket } from "../_libs/lucide-react.mjs"; import { o as Ticket } from "../_libs/lucide-react.mjs";
import { a as MyText, i as MyButton, o as MyTextInput, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, o as pInput, t as AppContainer } from "./src-u_N1opJl.mjs";
import { n as trpc } from "./trpc-client-CQOIB5UU.mjs"; import { n as trpc } from "./trpc-client-CQOIB5UU.mjs";
//#region node_modules/.nitro/vite/services/ssr/assets/me.coupons-CK-hvcuK.js //#region node_modules/.nitro/vite/services/ssr/assets/me.coupons-CK-hvcuK.js
var import_react = /* @__PURE__ */ __toESM(require_react()); var import_react = /* @__PURE__ */ __toESM(require_react());
@ -20,14 +20,14 @@ function CouponsPage() {
} }); } });
}; };
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-4 text-xl", className: "mb-4 text-xl",
children: "My Coupons" children: "My Coupons"
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-6 flex gap-2", className: "mb-6 flex gap-2",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTextInput, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(pInput, {
placeholder: "Enter coupon code", placeholder: "Enter coupon code",
value: code, value: code,
onChange: (e) => setCode(e.target.value), onChange: (e) => setCode(e.target.value),
@ -40,7 +40,7 @@ function CouponsPage() {
}), }),
coupons.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { coupons.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex flex-col items-center gap-4 py-20", className: "flex flex-col items-center gap-4 py-20",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Ticket, { className: "h-12 w-12 text-gray-300" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Ticket, { className: "h-12 w-12 text-gray-300" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-gray-500", className: "text-gray-500",
children: "No coupons yet" children: "No coupons yet"
})] })]
@ -49,16 +49,16 @@ function CouponsPage() {
children: coupons.map((coupon) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: coupons.map((coupon) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "rounded-xl border border-dashed border-brand-200 bg-brand-50 p-4", className: "rounded-xl border border-dashed border-brand-200 bg-brand-50 p-4",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "text-brand-700", className: "text-brand-700",
children: coupon.code children: coupon.code
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-sm text-gray-600", className: "text-sm text-gray-600",
children: coupon.description || `${coupon.discountPercent || 0}% off` children: coupon.description || `${coupon.discountPercent || 0}% off`
}), }),
coupon.expiresAt && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { coupon.expiresAt && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "mt-1 text-xs text-gray-400", className: "mt-1 text-xs text-gray-400",
children: ["Expires: ", new Date(coupon.expiresAt).toLocaleDateString()] children: ["Expires: ", new Date(coupon.expiresAt).toLocaleDateString()]
}) })

View file

@ -1,6 +1,6 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { a as MyText, i as MyButton, o as MyTextInput, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, o as pInput, t as AppContainer } from "./src-u_N1opJl.mjs";
import { n as trpc } from "./trpc-client-CQOIB5UU.mjs"; import { n as trpc } from "./trpc-client-CQOIB5UU.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
import { n as useAuth } from "./auth-context-DzjwonUC.mjs"; import { n as useAuth } from "./auth-context-DzjwonUC.mjs";
@ -24,7 +24,7 @@ function EditProfilePage() {
}); });
}; };
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-4 text-xl", className: "mb-4 text-xl",
children: "Edit Profile" children: "Edit Profile"
@ -33,18 +33,18 @@ function EditProfilePage() {
onSubmit: handleSubmit, onSubmit: handleSubmit,
className: "flex flex-col gap-4", className: "flex flex-col gap-4",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTextInput, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(pInput, {
placeholder: "Name", placeholder: "Name",
value: name, value: name,
onChange: (e) => setName(e.target.value) onChange: (e) => setName(e.target.value)
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTextInput, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(pInput, {
placeholder: "Email", placeholder: "Email",
type: "email", type: "email",
value: email, value: email,
onChange: (e) => setEmail(e.target.value) onChange: (e) => setEmail(e.target.value)
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTextInput, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(pInput, {
placeholder: "Mobile", placeholder: "Mobile",
value: user?.mobile || "", value: user?.mobile || "",
disabled: true, disabled: true,

View file

@ -1,6 +1,6 @@
import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { p as Package, x as ChevronRight } from "../_libs/lucide-react.mjs"; import { p as Package, x as ChevronRight } from "../_libs/lucide-react.mjs";
import { a as MyText, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs";
import { n as trpc } from "./trpc-client-CQOIB5UU.mjs"; import { n as trpc } from "./trpc-client-CQOIB5UU.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
//#region node_modules/.nitro/vite/services/ssr/assets/me.orders-CSqnUtwy.js //#region node_modules/.nitro/vite/services/ssr/assets/me.orders-CSqnUtwy.js
@ -12,13 +12,13 @@ function OrdersPage() {
limit: 20 limit: 20
}); });
const orders = data?.data || []; const orders = data?.data || [];
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-4 text-xl", className: "mb-4 text-xl",
children: "My Orders" children: "My Orders"
}), orders.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { }), orders.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex flex-col items-center gap-4 py-20", className: "flex flex-col items-center gap-4 py-20",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Package, { className: "h-12 w-12 text-gray-300" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Package, { className: "h-12 w-12 text-gray-300" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-gray-500", className: "text-gray-500",
children: "No orders yet" children: "No orders yet"
})] })]
@ -32,11 +32,11 @@ function OrdersPage() {
className: "rounded-xl border border-gray-100 bg-white p-4 shadow-sm", className: "rounded-xl border border-gray-100 bg-white p-4 shadow-sm",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex items-center justify-between", className: "flex items-center justify-between",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "semibold", weight: "semibold",
className: "text-sm", className: "text-sm",
children: ["Order #", order.id] children: ["Order #", order.id]
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-xs text-gray-500", className: "text-xs text-gray-500",
children: order.createdAt ? new Date(order.createdAt).toLocaleDateString() : "" children: order.createdAt ? new Date(order.createdAt).toLocaleDateString() : ""
})] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@ -46,7 +46,7 @@ function OrdersPage() {
children: order.status children: order.status
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: "h-4 w-4 text-gray-400" })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: "h-4 w-4 text-gray-400" })]
})] })]
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "mt-1 text-xs text-gray-400", className: "mt-1 text-xs text-gray-400",
children: ["Total: ₹", order.totalAmount || 0] children: ["Total: ₹", order.totalAmount || 0]
})] })]

View file

@ -1,7 +1,7 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { S as ArrowLeft } from "../_libs/lucide-react.mjs"; import { S as ArrowLeft } from "../_libs/lucide-react.mjs";
import { a as MyText, i as MyButton, r as LoadingDialog, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, r as LoadingDialog, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs";
import { n as trpc } from "./trpc-client-CQOIB5UU.mjs"; import { n as trpc } from "./trpc-client-CQOIB5UU.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
import { t as Route } from "./me.orders._id-9KyXzQNP.mjs"; import { t as Route } from "./me.orders._id-9KyXzQNP.mjs";
@ -19,16 +19,16 @@ function OrderDetailPage() {
const handleCancel = () => { const handleCancel = () => {
cancelMutation.mutate({ orderId }, { onSuccess: () => setShowCancelDialog(false) }); cancelMutation.mutate({ orderId }, { onSuccess: () => setShowCancelDialog(false) });
}; };
if (!order) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: "Loading..." }) }); if (!order) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, { children: "Loading..." }) });
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyTouchableOpacity, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyTouchableOpacity, {
onClick: () => navigate({ to: "/me/orders" }), onClick: () => navigate({ to: "/me/orders" }),
className: "mb-4 flex items-center gap-2", className: "mb-4 flex items-center gap-2",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowLeft, { className: "h-5 w-5" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: "Back to Orders" })] children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowLeft, { className: "h-5 w-5" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, { children: "Back to Orders" })]
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-4", className: "mb-4",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "text-xl", className: "text-xl",
children: ["Order #", order.id] children: ["Order #", order.id]
@ -40,31 +40,31 @@ function OrderDetailPage() {
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-6", className: "mb-6",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-2", className: "mb-2",
children: "Items" children: "Items"
}), }),
(order.items || []).map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { (order.items || []).map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex items-center justify-between border-b border-gray-100 py-2", className: "flex items-center justify-between border-b border-gray-100 py-2",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm", className: "text-sm",
children: [ children: [
item.product?.name || `Product #${item.productId}`, item.product?.name || `Product #${item.productId}`,
" x", " x",
item.quantity item.quantity
] ]
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm font-bold", className: "text-sm font-bold",
children: ["₹", item.price || 0] children: ["₹", item.price || 0]
})] })]
}, i)), }, i)),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "flex items-center justify-between pt-2", className: "flex items-center justify-between pt-2",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
children: "Total" children: "Total"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "text-brand-600", className: "text-brand-600",
children: ["₹", order.totalAmount || 0] children: ["₹", order.totalAmount || 0]
@ -74,18 +74,18 @@ function OrderDetailPage() {
}), }),
order.address && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { order.address && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-6", className: "mb-6",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-2", className: "mb-2",
children: "Delivery Address" children: "Delivery Address"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "rounded-xl border border-gray-100 bg-gray-50 p-3", className: "rounded-xl border border-gray-100 bg-gray-50 p-3",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
children: order.address.name children: order.address.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm text-gray-600", className: "text-sm text-gray-600",
children: [ children: [
order.address.addressLine1, order.address.addressLine1,
@ -93,7 +93,7 @@ function OrderDetailPage() {
order.address.city order.address.city
] ]
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "text-sm text-gray-500", className: "text-sm text-gray-500",
children: order.address.phone children: order.address.phone
}) })
@ -112,12 +112,12 @@ function OrderDetailPage() {
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mx-4 w-full max-w-sm rounded-xl bg-white p-6", className: "mx-4 w-full max-w-sm rounded-xl bg-white p-6",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-2 text-lg", className: "mb-2 text-lg",
children: "Cancel Order?" children: "Cancel Order?"
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "mb-6 text-sm text-gray-600", className: "mb-6 text-sm text-gray-600",
children: "Are you sure you want to cancel this order?" children: "Are you sure you want to cancel this order?"
}), }),

View file

@ -1,57 +1,57 @@
import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { a as MyText, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, t as AppContainer } from "./src-u_N1opJl.mjs";
//#region node_modules/.nitro/vite/services/ssr/assets/me.terms-BY5QWW0t.js //#region node_modules/.nitro/vite/services/ssr/assets/me.terms-BY5QWW0t.js
var import_jsx_runtime = require_jsx_runtime(); var import_jsx_runtime = require_jsx_runtime();
function TermsPage() { function TermsPage() {
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-6 text-2xl", className: "mb-6 text-2xl",
children: "Terms & Conditions" children: "Terms & Conditions"
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "prose prose-sm max-w-none text-gray-600", className: "prose prose-sm max-w-none text-gray-600",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-2 mt-4 text-gray-900", className: "mb-2 mt-4 text-gray-900",
children: "1. Acceptance of Terms" children: "1. Acceptance of Terms"
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "mb-4", className: "mb-4",
children: "By using Freshyo, you agree to these terms. If you do not agree, please do not use our service." children: "By using Freshyo, you agree to these terms. If you do not agree, please do not use our service."
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-2 mt-4 text-gray-900", className: "mb-2 mt-4 text-gray-900",
children: "2. Orders and Payments" children: "2. Orders and Payments"
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "mb-4", className: "mb-4",
children: "All orders are subject to availability. We reserve the right to cancel any order. Payments are collected at the time of delivery (COD)." children: "All orders are subject to availability. We reserve the right to cancel any order. Payments are collected at the time of delivery (COD)."
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-2 mt-4 text-gray-900", className: "mb-2 mt-4 text-gray-900",
children: "3. Delivery Policy" children: "3. Delivery Policy"
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "mb-4", className: "mb-4",
children: "Delivery times are estimates. We strive to deliver within the promised time window but delays may occur due to unforeseen circumstances." children: "Delivery times are estimates. We strive to deliver within the promised time window but delays may occur due to unforeseen circumstances."
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-2 mt-4 text-gray-900", className: "mb-2 mt-4 text-gray-900",
children: "4. Returns and Refunds" children: "4. Returns and Refunds"
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "mb-4", className: "mb-4",
children: "If you are not satisfied with the quality of your order, please contact us within 24 hours of delivery. Refunds will be processed after quality assessment." children: "If you are not satisfied with the quality of your order, please contact us within 24 hours of delivery. Refunds will be processed after quality assessment."
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "mb-2 mt-4 text-gray-900", className: "mb-2 mt-4 text-gray-900",
children: "5. Privacy" children: "5. Privacy"
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "mb-4", className: "mb-4",
children: "We respect your privacy. Your personal information is used only for order processing and delivery purposes." children: "We respect your privacy. Your personal information is used only for order processing and delivery purposes."
}) })

View file

@ -1,6 +1,6 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { a as MyText, i as MyButton, o as MyTextInput } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, o as pInput } from "./src-u_N1opJl.mjs";
import { n as trpc } from "./trpc-client-CQOIB5UU.mjs"; import { n as trpc } from "./trpc-client-CQOIB5UU.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
import { n as useAuth } from "./auth-context-DzjwonUC.mjs"; import { n as useAuth } from "./auth-context-DzjwonUC.mjs";
@ -34,12 +34,12 @@ function RegisterPage() {
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "w-full max-w-md", className: "w-full max-w-md",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-2 text-center text-4xl text-white", className: "mb-2 text-center text-4xl text-white",
children: "Create Account" children: "Create Account"
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "mb-8 text-center text-lg text-blue-100", className: "mb-8 text-center text-lg text-blue-100",
children: "Join Freshyo today" children: "Join Freshyo today"
}), }),
@ -49,19 +49,19 @@ function RegisterPage() {
onSubmit: handleSubmit, onSubmit: handleSubmit,
className: "flex flex-col gap-4", className: "flex flex-col gap-4",
children: [ children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTextInput, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(pInput, {
placeholder: "Full Name", placeholder: "Full Name",
value: name, value: name,
onChange: (e) => setName(e.target.value), onChange: (e) => setName(e.target.value),
required: true required: true
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTextInput, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(pInput, {
placeholder: "Email", placeholder: "Email",
type: "email", type: "email",
value: email, value: email,
onChange: (e) => setEmail(e.target.value) onChange: (e) => setEmail(e.target.value)
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTextInput, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(pInput, {
placeholder: "Mobile Number", placeholder: "Mobile Number",
value: mobile, value: mobile,
onChange: (e) => { onChange: (e) => {
@ -70,7 +70,7 @@ function RegisterPage() {
}, },
required: true required: true
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTextInput, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(pInput, {
placeholder: "Password", placeholder: "Password",
type: "password", type: "password",
value: password, value: password,

View file

@ -15,7 +15,7 @@ var weightClasses = {
semibold: "font-semibold", semibold: "font-semibold",
bold: "font-bold" bold: "font-bold"
}; };
function MyText({ children, weight = "normal", numberOfLines, className, style, ...props }) { function p({ children, weight = "normal", numberOfLines, className, style, ...props }) {
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
className: cn(weightClasses[weight], className), className: cn(weightClasses[weight], className),
style: { style: {
@ -45,7 +45,7 @@ function MyButton({ variant = "blue", fullWidth, textContent, children, classNam
children: textContent || children children: textContent || children
}); });
} }
function MyTextInput({ topLabel, fullWidth = true, shrunkPadding = false, error, multiline, className, style, ...props }) { function pInput({ topLabel, fullWidth = true, shrunkPadding = false, error, multiline, className, style, ...props }) {
const inputClasses = cn("flex w-full rounded-md border border-input bg-background px-3 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", shrunkPadding ? "py-1.5" : "py-2", error && "border-destructive", className); const inputClasses = cn("flex w-full rounded-md border border-input bg-background px-3 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", shrunkPadding ? "py-1.5" : "py-2", error && "border-destructive", className);
multilime; multilime;
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@ -53,7 +53,7 @@ function MyTextInput({ topLabel, fullWidth = true, shrunkPadding = false, error,
...fullWidth ? { width: "100%" } : {}, ...fullWidth ? { width: "100%" } : {},
...style ...style
}, },
children: [topLabel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: [topLabel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "medium", weight: "medium",
className: "mb-1 text-sm text-gray-500", className: "mb-1 text-sm text-gray-500",
children: topLabel children: topLabel
@ -178,7 +178,7 @@ function Quantifier({ value, setValue, step = 1, min = 0, max = 99 }) {
className: "flex h-8 w-8 items-center justify-center text-gray-500 hover:text-gray-700 disabled:opacity-30", className: "flex h-8 w-8 items-center justify-center text-gray-500 hover:text-gray-700 disabled:opacity-30",
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Minus, { className: "h-3.5 w-3.5" }) children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Minus, { className: "h-3.5 w-3.5" })
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "min-w-[32px] text-center text-sm", className: "min-w-[32px] text-center text-sm",
children: value children: value
@ -221,4 +221,4 @@ function AppContainer({ children, className }) {
}); });
} }
//#endregion //#endregion
export { MyText as a, ProfileImage as c, MyButton as i, Quantifier as l, BottomDialog as n, MyTextInput as o, LoadingDialog as r, MyTouchableOpacity as s, AppContainer as t, SearchBar as u }; export { p as a, ProfileImage as c, MyButton as i, Quantifier as l, BottomDialog as n, pInput as o, LoadingDialog as r, MyTouchableOpacity as s, AppContainer as t, SearchBar as u };

View file

@ -1,6 +1,6 @@
import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { s as Store } from "../_libs/lucide-react.mjs"; import { s as Store } from "../_libs/lucide-react.mjs";
import { a as MyText, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs";
import { a as useStores } from "./prominent-api-hooks-CNVDntUD.mjs"; import { a as useStores } from "./prominent-api-hooks-CNVDntUD.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
//#region node_modules/.nitro/vite/services/ssr/assets/stores-CcccRdgP.js //#region node_modules/.nitro/vite/services/ssr/assets/stores-CcccRdgP.js
@ -9,7 +9,7 @@ function StoresPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const { data } = useStores(); const { data } = useStores();
const stores = data?.data || []; const stores = data?.data || [];
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-4 text-xl", className: "mb-4 text-xl",
children: "Our Stores" children: "Our Stores"
@ -30,12 +30,12 @@ function StoresPage() {
className: "h-full w-full object-cover" className: "h-full w-full object-cover"
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Store, { className: "h-10 w-10 text-gray-400" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Store, { className: "h-10 w-10 text-gray-400" })
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "text-sm", className: "text-sm",
children: store.name children: store.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-xs text-gray-500", className: "text-xs text-gray-500",
children: [store.productCount || 0, " products"] children: [store.productCount || 0, " products"]
}) })

View file

@ -1,7 +1,7 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { S as ArrowLeft } from "../_libs/lucide-react.mjs"; import { S as ArrowLeft } from "../_libs/lucide-react.mjs";
import { a as MyText, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs";
import { i as useStoreWithProducts } from "./prominent-api-hooks-CNVDntUD.mjs"; import { i as useStoreWithProducts } from "./prominent-api-hooks-CNVDntUD.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
import { t as Route } from "./stores._storeId-Dh-du4bI.mjs"; import { t as Route } from "./stores._storeId-Dh-du4bI.mjs";
@ -33,7 +33,7 @@ function StoreDetailPage() {
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTouchableOpacity, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTouchableOpacity, {
onClick: () => navigate({ to: "/stores" }), onClick: () => navigate({ to: "/stores" }),
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowLeft, { className: "h-5 w-5" }) children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowLeft, { className: "h-5 w-5" })
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "text-xl", className: "text-xl",
children: store?.name || "Store" children: store?.name || "Store"
@ -67,13 +67,13 @@ function StoreDetailPage() {
className: "h-full w-full object-cover" className: "h-full w-full object-cover"
}) })
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "semibold", weight: "semibold",
className: "text-sm", className: "text-sm",
numberOfLines: 2, numberOfLines: 2,
children: product.name children: product.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "mt-1 text-brand-600", className: "mt-1 text-brand-600",
children: ["₹", product.discountedPrice ?? product.price] children: ["₹", product.discountedPrice ?? product.price]

View file

@ -1,7 +1,7 @@
import { o as __toESM } from "../_runtime.mjs"; import { o as __toESM } from "../_runtime.mjs";
import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs"; import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
import { S as ArrowLeft, l as ShoppingCart } from "../_libs/lucide-react.mjs"; import { S as ArrowLeft, l as ShoppingCart } from "../_libs/lucide-react.mjs";
import { a as MyText, i as MyButton, l as Quantifier, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs"; import { a as p, i as MyButton, l as Quantifier, s as MyTouchableOpacity, t as AppContainer } from "./src-u_N1opJl.mjs";
import { n as useAddToCart } from "./cart-query-hooks-Bz8ID9jY.mjs"; import { n as useAddToCart } from "./cart-query-hooks-Bz8ID9jY.mjs";
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs"; import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
import { t as useCentralProductStore } from "./central-product-store-TS-vQ8-V.mjs"; import { t as useCentralProductStore } from "./central-product-store-TS-vQ8-V.mjs";
@ -24,7 +24,7 @@ function StoreProductDetailPage() {
storeId: product.storeId storeId: product.storeId
}, { onSuccess: () => navigate({ to: "/cart" }) }); }, { onSuccess: () => navigate({ to: "/cart" }) });
}; };
if (!product) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { children: "Product not found" }) }); if (!product) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, { children: "Product not found" }) });
const price = product.discountedPrice ?? product.price; const price = product.discountedPrice ?? product.price;
const imageUrl = product.images?.[0]; const imageUrl = product.images?.[0];
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AppContainer, { children: [
@ -44,27 +44,27 @@ function StoreProductDetailPage() {
className: "h-full w-full object-cover" className: "h-full w-full object-cover"
}) })
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
weight: "bold", weight: "bold",
className: "mb-1 text-xl", className: "mb-1 text-xl",
children: product.name children: product.name
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "mb-2 text-sm text-gray-500", className: "mb-2 text-sm text-gray-500",
children: [product.unitValue, product.unit] children: [product.unitValue, product.unit]
}), }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
className: "mb-4 flex items-baseline gap-2", className: "mb-4 flex items-baseline gap-2",
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
weight: "bold", weight: "bold",
className: "text-2xl text-brand-600", className: "text-2xl text-brand-600",
children: ["₹", price] children: ["₹", price]
}), product.discountedPrice && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MyText, { }), product.discountedPrice && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(p, {
className: "text-sm text-gray-400 line-through", className: "text-sm text-gray-400 line-through",
children: ["₹", product.price] children: ["₹", product.price]
})] })]
}), }),
product.description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyText, { product.description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
className: "mb-4 text-gray-600", className: "mb-4 text-gray-600",
children: product.description children: product.description
}), }),

View file

@ -1,6 +1,6 @@
import React, { useState, useMemo, useEffect } from 'react' import React, { useState, useMemo, useEffect } from 'react'
import { useNavigate } from '@tanstack/react-router' import { useNavigate } from '@tanstack/react-router'
import { BottomDialog, MyText, MyTouchableOpacity, Quantifier } from 'web-components' import { BottomDialog, p, div, Quantifier } from 'web-components'
import { useSlots } from '../hooks/prominent-api-hooks' import { useSlots } from '../hooks/prominent-api-hooks'
import { useAddToCart, useUpdateCartItem, useRemoveFromCart, useGetCart } from '../hooks/cart-query-hooks' import { useAddToCart, useUpdateCartItem, useRemoveFromCart, useGetCart } from '../hooks/cart-query-hooks'
import { useCartStore } from '../lib/stores/cart-store' import { useCartStore } from '../lib/stores/cart-store'
@ -123,13 +123,13 @@ export default function AddToCartDialog() {
<Truck className="h-5 w-5 text-blue-500" /> <Truck className="h-5 w-5 text-blue-500" />
</div> </div>
<div className="flex-1"> <div className="flex-1">
<MyText weight="bold" className="text-lg"> <p className="font-bold text-lg">
Select Delivery Slot Select Delivery Slot
</MyText> </p>
{product?.name && ( {product?.name && (
<MyText className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
{product.name} ({product.productQuantity}{product.unitNotation ? ` ${product.unitNotation}` : ''}) {product.name} ({product.productQuantity}{product.unitNotation ? ` ${product.unitNotation}` : ''})
</MyText> </p>
)} )}
</div> </div>
<button onClick={clearAddedToCartProduct} className="text-gray-400 hover:text-gray-600"> <button onClick={clearAddedToCartProduct} className="text-gray-400 hover:text-gray-600">
@ -139,7 +139,7 @@ export default function AddToCartDialog() {
<div className="max-h-[40vh] space-y-3 overflow-y-auto mb-4"> <div className="max-h-[40vh] space-y-3 overflow-y-auto mb-4">
{availableSlots.map((slot: any) => ( {availableSlots.map((slot: any) => (
<MyTouchableOpacity <div
key={slot.id} key={slot.id}
onClick={() => { onClick={() => {
setSelectedSlotId(slot.id) setSelectedSlotId(slot.id)
@ -150,9 +150,9 @@ export default function AddToCartDialog() {
}`} }`}
> >
<Truck className="mt-0.5 h-5 w-5 shrink-0 text-blue-500" /> <Truck className="mt-0.5 h-5 w-5 shrink-0 text-blue-500" />
<MyText weight="bold" className="flex-1 text-sm"> <p className="font-bold flex-1 text-sm">
{dayjs(slot.deliveryTime).format('ddd, DD MMM • ')}{formatTimeRange(slot.deliveryTime)} {dayjs(slot.deliveryTime).format('ddd, DD MMM • ')}{formatTimeRange(slot.deliveryTime)}
</MyText> </p>
{selectedSlotId === slot.id ? ( {selectedSlotId === slot.id ? (
<svg className="h-6 w-6 shrink-0 text-brand-500" fill="currentColor" viewBox="0 0 24 24"> <svg className="h-6 w-6 shrink-0 text-brand-500" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" /> <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
@ -162,12 +162,12 @@ export default function AddToCartDialog() {
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" /> <path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" />
</svg> </svg>
)} )}
</MyTouchableOpacity> </div>
))} ))}
</div> </div>
{showFlashOption && ( {showFlashOption && (
<MyTouchableOpacity <div
onClick={() => { onClick={() => {
setSelectedFlashDelivery(true) setSelectedFlashDelivery(true)
setSelectedSlotId(null) setSelectedSlotId(null)
@ -177,9 +177,9 @@ export default function AddToCartDialog() {
}`} }`}
> >
<Zap className="h-5 w-5 shrink-0 text-pink-500" /> <Zap className="h-5 w-5 shrink-0 text-pink-500" />
<MyText weight="bold" className="flex-1 text-sm"> <p className="font-bold flex-1 text-sm">
1 hr Delivery 1 hr Delivery
</MyText> </p>
{selectedFlashDelivery ? ( {selectedFlashDelivery ? (
<svg className="h-6 w-6 shrink-0 text-pink-500" fill="currentColor" viewBox="0 0 24 24"> <svg className="h-6 w-6 shrink-0 text-pink-500" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" /> <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
@ -189,24 +189,24 @@ export default function AddToCartDialog() {
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" /> <path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" />
</svg> </svg>
)} )}
</MyTouchableOpacity> </div>
)} )}
<div className="mb-4"> <div className="mb-4">
<MyText weight="bold" className="mb-2 text-sm"> <p className="font-bold mb-2 text-sm">
Quantity Quantity
</MyText> </p>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Quantifier value={quantity} setValue={setQuantity} step={1} unit={product?.unitNotation} /> <Quantifier value={quantity} setValue={setQuantity} step={1} unit={product?.unitNotation} />
{isUpdate && ( {isUpdate && (
<MyTouchableOpacity <div
onClick={handleRemove} onClick={handleRemove}
className="rounded-lg border border-red-200 bg-red-50 p-2" className="rounded-lg border border-red-200 bg-red-50 p-2"
> >
<svg className="h-5 w-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="h-5 w-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg> </svg>
</MyTouchableOpacity> </div>
)} )}
</div> </div>
</div> </div>

View file

@ -1,6 +1,6 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { trpc } from '../lib/trpc-client' import { trpc } from '../lib/trpc-client'
import { MyText, MyTextInput, MyButton, MyTouchableOpacity } from 'web-components' import { p, pInput, MyButton, div } from 'web-components'
import { MapPin, X, Plus } from 'lucide-react' import { MapPin, X, Plus } from 'lucide-react'
import * as Yup from 'yup' import * as Yup from 'yup'
@ -117,62 +117,62 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
return ( return (
<div className="max-h-[80vh] overflow-y-auto p-6"> <div className="max-h-[80vh] overflow-y-auto p-6">
<div className="mb-6 flex items-center justify-between"> <div className="mb-6 flex items-center justify-between">
<MyText weight="bold" className="text-xl"> <p className="font-bold text-xl">
{isEdit ? 'Edit Address' : 'Add Address'} {isEdit ? 'Edit Address' : 'Add Address'}
</MyText> </p>
</div> </div>
{/* Service Area Notice */} {/* Service Area Notice */}
<div className="mb-4 rounded-lg border border-amber-200 bg-amber-50 p-3"> <div className="mb-4 rounded-lg border border-amber-200 bg-amber-50 p-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-amber-600"></span> <span className="text-amber-600"></span>
<MyText className="flex-1 text-sm text-amber-800"> <p className="flex-1 text-sm text-amber-800">
We currently serve only in Mahabubnagar town We currently serve only in Mahabubnagar town
</MyText> </p>
</div> </div>
</div> </div>
{/* Submit Error Message */} {/* Submit Error Message */}
{submitError && ( {submitError && (
<div className="mb-4 rounded-lg border border-red-200 bg-red-50 p-3"> <div className="mb-4 rounded-lg border border-red-200 bg-red-50 p-3">
<MyText className="text-sm text-red-600">{submitError}</MyText> <p className="text-sm text-red-600">{submitError}</p>
</div> </div>
)} )}
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<MyTextInput <pInput
placeholder="Name" placeholder="Name"
value={values.name} value={values.name}
onChange={(e) => handleChange('name', e.target.value)} onChange={(e) => handleChange('name', e.target.value)}
className={errors.name ? 'border-red-500' : ''} className={errors.name ? 'border-red-500' : ''}
/> />
{errors.name && <MyText className="mt-1 text-sm text-red-500">{errors.name}</MyText>} {errors.name && <p className="mt-1 text-sm text-red-500">{errors.name}</p>}
</div> </div>
<div> <div>
<MyTextInput <pInput
placeholder="Phone" placeholder="Phone"
type="tel" type="tel"
value={values.phone} value={values.phone}
onChange={(e) => handleChange('phone', e.target.value)} onChange={(e) => handleChange('phone', e.target.value)}
className={errors.phone ? 'border-red-500' : ''} className={errors.phone ? 'border-red-500' : ''}
/> />
{errors.phone && <MyText className="mt-1 text-sm text-red-500">{errors.phone}</MyText>} {errors.phone && <p className="mt-1 text-sm text-red-500">{errors.phone}</p>}
</div> </div>
<div> <div>
<MyTextInput <pInput
placeholder="Address Line 1" placeholder="Address Line 1"
value={values.addressLine1} value={values.addressLine1}
onChange={(e) => handleChange('addressLine1', e.target.value)} onChange={(e) => handleChange('addressLine1', e.target.value)}
className={errors.addressLine1 ? 'border-red-500' : ''} className={errors.addressLine1 ? 'border-red-500' : ''}
/> />
{errors.addressLine1 && <MyText className="mt-1 text-sm text-red-500">{errors.addressLine1}</MyText>} {errors.addressLine1 && <p className="mt-1 text-sm text-red-500">{errors.addressLine1}</p>}
</div> </div>
<div> <div>
<MyTextInput <pInput
placeholder="Address Line 2 (Optional)" placeholder="Address Line 2 (Optional)"
value={values.addressLine2} value={values.addressLine2}
onChange={(e) => handleChange('addressLine2', e.target.value)} onChange={(e) => handleChange('addressLine2', e.target.value)}
@ -181,7 +181,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<MyTextInput <pInput
placeholder="City" placeholder="City"
value={values.city} value={values.city}
disabled disabled
@ -189,7 +189,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
/> />
</div> </div>
<div> <div>
<MyTextInput <pInput
placeholder="State" placeholder="State"
value={values.state} value={values.state}
disabled disabled
@ -199,7 +199,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
</div> </div>
<div> <div>
<MyTextInput <pInput
placeholder="Pincode" placeholder="Pincode"
value={values.pincode} value={values.pincode}
disabled disabled
@ -208,25 +208,25 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
</div> </div>
{!showGoogleMapsField && ( {!showGoogleMapsField && (
<MyTouchableOpacity <div
onClick={() => setShowGoogleMapsField(true)} onClick={() => setShowGoogleMapsField(true)}
className="text-blue-500" className="text-blue-500"
> >
<MyText weight="medium" className="text-sm"> <p className="font-medium text-sm">
Attach with Google Maps Attach with Google Maps
</MyText> </p>
</MyTouchableOpacity> </div>
)} )}
{showGoogleMapsField && ( {showGoogleMapsField && (
<div className="space-y-2"> <div className="space-y-2">
<MyText className="text-xs text-gray-500"> <p className="text-xs text-gray-500">
1. Open Google Maps and Find location<br /> 1. Open Google Maps and Find location<br />
2. Long press the desired location<br /> 2. Long press the desired location<br />
3. Click on Share and Click on Copy<br /> 3. Click on Share and Click on Copy<br />
4. Paste the copied url here in the field. 4. Paste the copied url here in the field.
</MyText> </p>
<MyTextInput <pInput
placeholder="Google Maps Shared URL" placeholder="Google Maps Shared URL"
value={values.googleMapsUrl} value={values.googleMapsUrl}
onChange={(e) => handleChange('googleMapsUrl', e.target.value)} onChange={(e) => handleChange('googleMapsUrl', e.target.value)}
@ -241,7 +241,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
onChange={(e) => handleChange('isDefault', e.target.checked)} onChange={(e) => handleChange('isDefault', e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-brand-500 focus:ring-brand-500" className="h-4 w-4 rounded border-gray-300 text-brand-500 focus:ring-brand-500"
/> />
<MyText>Set as default address</MyText> <p>Set as default address</p>
</label> </label>
<MyButton <MyButton

View file

@ -0,0 +1,39 @@
import React from 'react'
import { BottomNavigation } from './BottomNavigation'
import { FloatingCartBar } from './FloatingCartBar'
import { useLocation } from '@tanstack/react-router'
interface AppLayoutProps {
children: React.ReactNode
showCartBar?: boolean
isFlashDelivery?: boolean
}
// Routes where bottom nav should be hidden
const hideBottomNavRoutes = ['/login', '/register', '/checkout', '/cart']
export function AppLayout({ children, showCartBar = true, isFlashDelivery = false }: AppLayoutProps) {
const location = useLocation()
const currentPath = location.pathname
const shouldShowBottomNav = !hideBottomNavRoutes.some((route) =>
currentPath === route || currentPath.startsWith(`${route}/`)
)
const shouldShowCartBar = showCartBar && shouldShowBottomNav
return (
<div className="relative min-h-screen pb-20">
{/* Main Content */}
<main>{children}</main>
{/* Floating Cart Bar - positioned above bottom nav */}
{shouldShowCartBar && (
<FloatingCartBar isFlashDelivery={isFlashDelivery} />
)}
{/* Bottom Navigation */}
{shouldShowBottomNav && <BottomNavigation />}
</div>
)
}

View file

@ -0,0 +1,120 @@
import React from 'react'
import { useLocation, useNavigate } from '@tanstack/react-router'
import { p, div } from 'web-components'
import { Home, Store, Zap, RotateCcw, User } from 'lucide-react'
interface TabItem {
name: string
path: string
label: string
icon: React.ReactNode
iconActive: React.ReactNode
isCenter?: boolean
}
export function BottomNavigation() {
const location = useLocation()
const navigate = useNavigate()
const currentPath = location.pathname
const tabs: TabItem[] = [
{
name: 'home',
path: '/home',
label: 'Home',
icon: <Home className="h-5 w-5" />,
iconActive: <Home className="h-5 w-5 fill-current" />,
},
{
name: 'stores',
path: '/stores',
label: 'Stores',
icon: <Store className="h-5 w-5" />,
iconActive: <Store className="h-5 w-5 fill-current" />,
},
{
name: 'flash',
path: '/flash',
label: '1 Hr Delivery',
icon: <Zap className="h-6 w-6" />,
iconActive: <Zap className="h-6 w-6 fill-current" />,
isCenter: true,
},
{
name: 'order-again',
path: '/me/orders',
label: 'Order Again',
icon: <RotateCcw className="h-5 w-5" />,
iconActive: <RotateCcw className="h-5 w-5 fill-current" />,
},
{
name: 'me',
path: '/me',
label: 'Me',
icon: <User className="h-5 w-5" />,
iconActive: <User className="h-5 w-5 fill-current" />,
},
]
const isActive = (path: string) => {
if (path === '/home') return currentPath === '/home' || currentPath.startsWith('/home/')
if (path === '/stores') return currentPath === '/stores' || currentPath.startsWith('/stores/')
if (path === '/flash') return currentPath === '/flash' || currentPath.startsWith('/flash/')
if (path === '/me/orders') return currentPath === '/me/orders' || currentPath.startsWith('/me/orders/')
if (path === '/me') return currentPath === '/me' || (currentPath.startsWith('/me/') && !currentPath.startsWith('/me/orders/'))
return currentPath === path || currentPath.startsWith(`${path}/`)
}
return (
<nav className="fixed bottom-0 left-0 right-0 z-50 border-t border-gray-200 bg-white pb-safe">
<div className="flex h-16 items-center justify-around px-2">
{tabs.map((tab) => {
const active = isActive(tab.path)
if (tab.isCenter) {
// Center elevated button for Flash Delivery
return (
<div
key={tab.name}
onClick={() => navigate({ to: tab.path })}
className="relative -top-3 flex flex-col items-center"
>
<div
className={`flex h-14 w-14 items-center justify-center rounded-full border-4 border-white shadow-lg transition-all ${
active
? 'bg-gradient-to-br from-brand-400 to-brand-600'
: 'bg-gradient-to-br from-gray-400 to-gray-600'
}`}
>
<span className={active ? 'text-white' : 'text-white/80'}>{tab.icon}</span>
</div>
<p
className={`mt-0.5 text-[10px] font-bold ${active ? 'text-brand-600' : 'text-gray-500'}`}
>
{tab.label}
</p>
</div>
)
}
return (
<div
key={tab.name}
onClick={() => navigate({ to: tab.path })}
className="flex flex-1 flex-col items-center justify-center py-2"
>
<span className={active ? 'text-brand-600' : 'text-gray-500'}>
{active ? tab.iconActive : tab.icon}
</span>
<p
className={`mt-1 text-xs font-medium ${active ? 'text-brand-600' : 'text-gray-500'}`}
>
{tab.label}
</p>
</div>
)
})}
</div>
</nav>
)
}

View file

@ -2,7 +2,7 @@ import React from 'react'
import { useCheckoutAddress } from '../hooks/checkout-hooks' import { useCheckoutAddress } from '../hooks/checkout-hooks'
import { trpc } from '../lib/trpc-client' import { trpc } from '../lib/trpc-client'
import { useQueryClient } from '@tanstack/react-query' import { useQueryClient } from '@tanstack/react-query'
import { MyText, MyTouchableOpacity } from 'web-components' import { p, div } from 'web-components'
import { MapPin, Home, Briefcase, Check, Plus, Edit2, Trash2 } from 'lucide-react' import { MapPin, Home, Briefcase, Check, Plus, Edit2, Trash2 } from 'lucide-react'
interface AddressSelectorProps { interface AddressSelectorProps {
@ -39,33 +39,33 @@ export function CheckoutAddressSelector({ onAddressSelect, onAddAddress, onEditA
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-50"> <div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-50">
<MapPin className="h-4 w-4 text-blue-500" /> <MapPin className="h-4 w-4 text-blue-500" />
</div> </div>
<MyText weight="bold" className="text-lg text-gray-900"> <p className="font-bold text-lg text-gray-900">
Delivery Address Delivery Address
</MyText> </p>
</div> </div>
<MyTouchableOpacity <div
onClick={onAddAddress} onClick={onAddAddress}
className="flex items-center gap-1 text-brand-500" className="flex items-center gap-1 text-brand-500"
> >
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />
<MyText weight="bold" className="text-sm"> <p className="font-bold text-sm">
Add New Add New
</MyText> </p>
</MyTouchableOpacity> </div>
</div> </div>
{sortedAddresses.length === 0 ? ( {sortedAddresses.length === 0 ? (
<div className="flex flex-col items-center justify-center rounded-xl border-2 border-dashed border-gray-200 bg-gray-50 p-8"> <div className="flex flex-col items-center justify-center rounded-xl border-2 border-dashed border-gray-200 bg-gray-50 p-8">
<MapPin className="mb-2 h-10 w-10 text-gray-400" /> <MapPin className="mb-2 h-10 w-10 text-gray-400" />
<MyText className="mb-1 text-gray-500">No addresses found</MyText> <p className="mb-1 text-gray-500">No addresses found</p>
<MyTouchableOpacity <div
onClick={onAddAddress} onClick={onAddAddress}
className="mt-3 rounded-lg bg-brand-500 px-4 py-2" className="mt-3 rounded-lg bg-brand-500 px-4 py-2"
> >
<MyText weight="bold" className="text-sm text-white"> <p className="font-bold text-sm text-white">
Add Address Add Address
</MyText> </p>
</MyTouchableOpacity> </div>
</div> </div>
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
@ -90,25 +90,25 @@ export function CheckoutAddressSelector({ onAddressSelect, onAddAddress, onEditA
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="mb-1 flex items-center gap-2"> <div className="mb-1 flex items-center gap-2">
<MyText weight="bold" className={selectedAddressId === address.id ? 'text-brand-600' : 'text-gray-900'}> <p className={`font-bold ${selectedAddressId === address.id ? 'text-brand-600' : 'text-gray-900'}`}>
{address.name} {address.name}
</MyText> </p>
{address.isDefault && ( {address.isDefault && (
<span className="rounded bg-green-100 px-2 py-0.5 text-xs font-medium text-green-700"> <span className="rounded bg-green-100 px-2 py-0.5 text-xs font-medium text-green-700">
Default Default
</span> </span>
)} )}
</div> </div>
<MyText className="text-sm leading-relaxed text-gray-600"> <p className="text-sm leading-relaxed text-gray-600">
{address.addressLine1} {address.addressLine1}
{address.addressLine2 ? `, ${address.addressLine2}` : ''} {address.addressLine2 ? `, ${address.addressLine2}` : ''}
</MyText> </p>
<MyText className="text-sm text-gray-600"> <p className="text-sm text-gray-600">
{address.city}, {address.state} - {address.pincode} {address.city}, {address.state} - {address.pincode}
</MyText> </p>
<MyText className="mt-1 text-xs text-gray-500"> <p className="mt-1 text-xs text-gray-500">
Phone: {address.phone} Phone: {address.phone}
</MyText> </p>
</div> </div>
</div> </div>
<div className="flex flex-col items-end gap-2"> <div className="flex flex-col items-end gap-2">
@ -118,7 +118,7 @@ export function CheckoutAddressSelector({ onAddressSelect, onAddAddress, onEditA
</div> </div>
)} )}
<div className="flex gap-1"> <div className="flex gap-1">
<MyTouchableOpacity <div
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
onEditAddress?.(address) onEditAddress?.(address)
@ -126,8 +126,8 @@ export function CheckoutAddressSelector({ onAddressSelect, onAddAddress, onEditA
className="flex h-8 w-8 items-center justify-center rounded-full text-gray-400 hover:bg-gray-100 hover:text-gray-600" className="flex h-8 w-8 items-center justify-center rounded-full text-gray-400 hover:bg-gray-100 hover:text-gray-600"
> >
<Edit2 className="h-4 w-4" /> <Edit2 className="h-4 w-4" />
</MyTouchableOpacity> </div>
<MyTouchableOpacity <div
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
if (confirm('Are you sure you want to delete this address?')) { if (confirm('Are you sure you want to delete this address?')) {
@ -137,7 +137,7 @@ export function CheckoutAddressSelector({ onAddressSelect, onAddAddress, onEditA
className="flex h-8 w-8 items-center justify-center rounded-full text-gray-400 hover:bg-red-50 hover:text-red-500" className="flex h-8 w-8 items-center justify-center rounded-full text-gray-400 hover:bg-red-50 hover:text-red-500"
> >
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" />
</MyTouchableOpacity> </div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,295 @@
import React, { useState, useEffect, useMemo } from 'react'
import { useNavigate, useLocation } from '@tanstack/react-router'
import { useGetCart, useUpdateCartItem, useRemoveFromCart, useAddToCart } from '../hooks/cart-query-hooks'
import { useAllProducts } from '../hooks/prominent-api-hooks'
import { useGetEssentialConsts } from '../hooks/prominent-api-hooks'
import { p, div, MiniQuantifier } from 'web-components'
import { Dialog } from './Dialog'
import { ShoppingCart, ChevronRight, Package, X, Clock, MapPin, Home, Store, Zap, RotateCcw, User } from 'lucide-react'
import dayjs from 'dayjs'
interface FloatingCartBarProps {
isFlashDelivery?: boolean
}
// Smart time window formatting function
const formatTimeRange = (deliveryTime: string | Date) => {
const time = dayjs(deliveryTime)
const endTime = time.add(1, 'hour')
const startPeriod = time.format('A')
const endPeriod = endTime.format('A')
let timeRange
if (startPeriod === endPeriod) {
timeRange = `${time.format('h')}-${endTime.format('h')} ${startPeriod}`
} else {
timeRange = `${time.format('h:mm')} ${startPeriod} - ${endTime.format('h:mm')} ${endPeriod}`
}
return `${time.format('ddd, DD MMM ')}${timeRange}`
}
export function FloatingCartBar({ isFlashDelivery = false }: FloatingCartBarProps) {
const navigate = useNavigate()
const [isExpanded, setIsExpanded] = useState(false)
const [quantities, setQuantities] = useState<Record<number, number>>({})
const cartType = isFlashDelivery ? 'flash' : 'regular'
const { data: cartData, refetch: refetchCart } = useGetCart(cartType)
const { data: constsData } = useGetEssentialConsts()
const { data: productsData } = useAllProducts()
const updateCartItem = useUpdateCartItem(cartType)
const removeFromCart = useRemoveFromCart(cartType)
const addToCartHook = useAddToCart(cartType)
const products = productsData?.products || []
const productsById: Record<number, any> = {}
products.forEach((p: any) => { productsById[p.id] = p })
const cartItems = cartData?.items || []
const itemCount = cartItems.length
useEffect(() => {
const initial: Record<number, number> = {}
cartItems.forEach((item) => {
initial[item.id] = item.quantity
})
setQuantities(initial)
}, [cartData])
// Calculate total cart value
const totalCartValue = cartItems.reduce(
(sum, item) => {
const product = productsById[item.productId]
const basePrice = product?.price ?? 0
const price = isFlashDelivery ? (product?.flashPrice ?? basePrice) : basePrice
return sum + price * item.quantity
},
0
)
const freeDeliveryThreshold = isFlashDelivery
? constsData?.flashFreeDeliveryThreshold
: constsData?.freeDeliveryThreshold
const remainingForFreeDelivery = Math.max(0, freeDeliveryThreshold - totalCartValue)
const cartBarColor = isFlashDelivery ? '#f81260' : 'var(--brand-600, #2563eb)'
const cartBarBorderColor = isFlashDelivery ? '#e11d48' : 'var(--brand-500, #3b82f6)'
return (
<>
{/* Collapsed Bar */}
<div
className="fixed bottom-20 left-4 right-4 z-40 rounded-lg border shadow-2xl"
style={{
backgroundColor: cartBarColor,
borderColor: cartBarBorderColor,
borderWidth: 1,
}}
>
<div
className="flex flex-row items-center justify-between py-3"
onClick={() => itemCount > 0 && setIsExpanded(true)}
>
<div className="flex flex-1 flex-row items-center px-2">
<div className="flex-1">
<div className="flex flex-row items-center">
<p className="font-bold mr-2 text-sm text-white">
{itemCount === 0 ? (
isFlashDelivery ? 'No Flash Items' : 'No Items In Cart'
) : (
<>
<span className="text-base font-black text-white">
{totalCartValue}
</span>
<span className="text-sm font-bold text-white">
{' '}&bull; {itemCount} {itemCount === 1 ? 'Item' : 'Items'}
</span>
</>
)}
</p>
{itemCount > 0 && <span className="text-white"></span>}
</div>
{remainingForFreeDelivery > 0 ? (
<p className="mt-1 text-[10px] font-bold text-white/80">
{remainingForFreeDelivery} more for <span className="text-emerald-300">FREE Delivery</span>
</p>
) : itemCount > 0 ? (
<div className="mt-0.5 flex flex-row items-center">
<span className="text-emerald-400"></span>
<p className="ml-1 text-[10px] font-black uppercase tracking-tighter text-emerald-300">
Free Delivery Unlocked
</p>
</div>
) : (
<p className="mt-0.5 text-[10px] text-white/60">
Shop for {freeDeliveryThreshold}+ for free shipping
</p>
)}
</div>
</div>
<div
className="rounded-2xl bg-white px-3 py-2 shadow-lg"
onClick={(e) => {
e.stopPropagation()
navigate({
to: isFlashDelivery ? '/flash/cart' : '/cart',
})
}}
>
<p className="font-bold text-sm" style={{ color: cartBarColor }}>
Go to Cart
</p>
</div>
</div>
</div>
{/* Expanded Dialog */}
<Dialog open={isExpanded} onClose={() => setIsExpanded(false)} title="">
<div className="flex max-h-[80vh] flex-col">
{/* Header */}
<div className="flex flex-row items-center justify-between border-b border-slate-100 px-6 py-5">
<div>
<p className="font-bold text-xl tracking-tight text-slate-900">
Your Cart
</p>
<p className="text-xs font-bold uppercase tracking-widest text-slate-400">
{itemCount} Items
</p>
</div>
<div
className="flex h-10 w-10 items-center justify-center rounded-2xl bg-slate-100"
onClick={() => setIsExpanded(false)}
>
<X className="h-6 w-6 text-slate-500" />
</div>
</div>
{/* Progress Bar Header */}
{remainingForFreeDelivery > 0 && (
<div className="flex flex-row items-center justify-between bg-emerald-50/50 px-6 py-3">
<div className="mr-4 flex-1">
<div className="mb-1.5 flex flex-row items-center justify-between">
<p className="text-[10px] font-black uppercase text-emerald-700">Free Delivery Progress</p>
<p className="text-[10px] font-black text-emerald-700">
{Math.round((totalCartValue / freeDeliveryThreshold) * 100)}%
</p>
</div>
<div className="h-1.5 overflow-hidden rounded-full border border-emerald-100 bg-white">
<div
className="h-full bg-emerald-500"
style={{ width: `${(totalCartValue / freeDeliveryThreshold) * 100}%` }}
/>
</div>
</div>
<div className="items-end">
<p className="text-[10px] font-bold text-slate-500">Needed</p>
<p className="text-sm font-black text-emerald-600">+{remainingForFreeDelivery}</p>
</div>
</div>
)}
{/* Items List */}
<div className="flex-1 overflow-y-auto px-6 py-4">
{cartItems.map((item, index) => (
<React.Fragment key={item.id}>
<div className="py-4">
<div className="flex flex-row items-center">
<img
src={productsById[item.productId]?.images?.[0]}
alt=""
className="h-8 w-8 rounded-lg border border-slate-100 bg-slate-50 object-cover"
/>
<div className="ml-4 flex-1">
<div className="mb-1 flex flex-row items-center justify-between">
<p className="font-bold flex-1 text-sm text-slate-900">
{productsById[item.productId]?.name || ''}{' '}
<span className="text-xs font-medium text-slate-500">
({productsById[item.productId]?.productQuantity || 0}
{productsById[item.productId]?.unitNotation || ''})
</span>
</p>
<MiniQuantifier
value={quantities[item.id] || item.quantity}
setValue={(value) => {
if (value === 0) {
removeFromCart.mutate(item.id)
} else {
setQuantities((prev) => ({ ...prev, [item.id]: value }))
updateCartItem.mutate({ productId: item.id, quantity: value })
}
}}
step={productsById[item.productId]?.incrementStep || 1}
/>
</div>
<div className="flex flex-row items-center justify-between">
{item.slotId && (
<div className="flex flex-row items-center rounded-lg border border-blue-100 bg-blue-50 px-2 py-1">
<Clock className="h-3 w-3 text-blue-600" />
<p className="ml-1 text-[9px] font-black uppercase text-blue-700">
{formatTimeRange(item.deliveryDate || new Date())}
</p>
</div>
)}
<p className="font-bold text-sm text-slate-900">
{(() => {
const product = productsById[item.productId]
const basePrice = product?.price ?? 0
const price = isFlashDelivery ? (product?.flashPrice ?? basePrice) : basePrice
return price * item.quantity
})()}
</p>
</div>
</div>
</div>
</div>
{index < cartItems.length - 1 && <div className="h-px w-full bg-slate-200" />}
</React.Fragment>
))}
</div>
{/* Footer */}
<div className="border-t border-slate-100 bg-white p-6">
<div className="mb-5 flex flex-row items-center justify-between">
<div>
<p className="text-[10px] font-black uppercase tracking-widest text-slate-400">Subtotal</p>
<p className="font-bold text-2xl text-slate-900">
{totalCartValue}
</p>
</div>
{remainingForFreeDelivery === 0 && (
<div className="flex flex-row items-center rounded-xl border border-emerald-100 bg-emerald-50 px-3 py-1.5">
<Package className="h-4 w-4 text-emerald-600" />
<p className="ml-1.5 text-[10px] font-black uppercase text-emerald-700">Free Delivery</p>
</div>
)}
</div>
<div
onClick={() => {
setIsExpanded(false)
navigate({
to: isFlashDelivery ? '/flash/cart' : '/cart',
})
}}
className="flex flex-row items-center justify-center rounded-2xl py-4 shadow-lg"
style={{
background: isFlashDelivery
? 'linear-gradient(90deg, #f81260, #c40e50)'
: 'linear-gradient(90deg, #1570EF, #194185)',
}}
>
<p className="font-bold text-base uppercase tracking-widest text-white">
Go to cart
</p>
<ChevronRight className="ml-1 h-5 w-5 text-white" />
</div>
</div>
</div>
</Dialog>
</>
)
}

View file

@ -1,7 +1,7 @@
import React, { useState, useMemo } from 'react' import React, { useState, useMemo } from 'react'
import { trpc } from '../lib/trpc-client' import { trpc } from '../lib/trpc-client'
import { useQueryClient } from '@tanstack/react-query' import { useQueryClient } from '@tanstack/react-query'
import { MyText, MyButton, MyTextInput, MyTouchableOpacity, LoadingDialog } from 'web-components' import { p, MyButton, pInput, div, LoadingDialog } from 'web-components'
import { useAllProducts } from '../hooks/prominent-api-hooks' import { useAllProducts } from '../hooks/prominent-api-hooks'
import { useGetEssentialConsts } from '../hooks/prominent-api-hooks' import { useGetEssentialConsts } from '../hooks/prominent-api-hooks'
import { clearLocalCart } from '../hooks/cart-query-hooks' import { clearLocalCart } from '../hooks/cart-query-hooks'
@ -157,18 +157,18 @@ export function PaymentAndOrderComponent({
{/* Back Button */} {/* Back Button */}
{onBack && ( {onBack && (
<div className="mb-4 rounded-2xl border border-gray-100 bg-white p-4 shadow-sm"> <div className="mb-4 rounded-2xl border border-gray-100 bg-white p-4 shadow-sm">
<MyTouchableOpacity onClick={onBack} className="flex items-center gap-2"> <div onClick={onBack} className="flex items-center gap-2">
<ArrowLeft className="h-5 w-5 text-gray-500" /> <ArrowLeft className="h-5 w-5 text-gray-500" />
<MyText className="font-medium text-gray-600">Back to Cart</MyText> <p className="font-medium text-gray-600">Back to Cart</p>
</MyTouchableOpacity> </div>
</div> </div>
)} )}
{/* Special Instructions */} {/* Special Instructions */}
<div className="mb-4 rounded-2xl border border-gray-100 bg-white p-5 shadow-sm"> <div className="mb-4 rounded-2xl border border-gray-100 bg-white p-5 shadow-sm">
<MyText weight="bold" className="mb-3 text-base text-gray-900"> <p className="font-bold mb-3 text-base text-gray-900">
Delivery Instructions Delivery Instructions
</MyText> </p>
<textarea <textarea
value={userNotes} value={userNotes}
onChange={(e) => setUserNotes(e.target.value)} onChange={(e) => setUserNotes(e.target.value)}
@ -180,9 +180,9 @@ export function PaymentAndOrderComponent({
{/* Payment Method */} {/* Payment Method */}
<div className="mb-4 rounded-2xl border border-gray-100 bg-white p-5 shadow-sm"> <div className="mb-4 rounded-2xl border border-gray-100 bg-white p-5 shadow-sm">
<MyText weight="bold" className="mb-4 text-lg text-gray-900"> <p className="font-bold mb-4 text-lg text-gray-900">
Payment Method Payment Method
</MyText> </p>
{/* Online Payment (Coming Soon) */} {/* Online Payment (Coming Soon) */}
<div className="mb-3 flex cursor-not-allowed items-center rounded-xl border border-gray-200 bg-gray-100 p-4 opacity-50"> <div className="mb-3 flex cursor-not-allowed items-center rounded-xl border border-gray-200 bg-gray-100 p-4 opacity-50">
@ -191,15 +191,15 @@ export function PaymentAndOrderComponent({
<CreditCard className="h-5 w-5 text-gray-400" /> <CreditCard className="h-5 w-5 text-gray-400" />
</div> </div>
<div> <div>
<MyText weight="bold" className="text-gray-500"> <p className="font-bold text-gray-500">
Pay Online (Coming Soon) Pay Online (Coming Soon)
</MyText> </p>
<MyText className="text-xs text-gray-400">UPI, Cards, Netbanking</MyText> <p className="text-xs text-gray-400">UPI, Cards, Netbanking</p>
</div> </div>
</div> </div>
{/* Cash on Delivery */} {/* Cash on Delivery */}
<MyTouchableOpacity <div
onClick={() => setPaymentMethod('cod')} onClick={() => setPaymentMethod('cod')}
className={`flex items-center rounded-xl border p-4 transition-all ${ className={`flex items-center rounded-xl border p-4 transition-all ${
paymentMethod === 'cod' ? 'border-brand-500 bg-blue-50' : 'border-gray-200' paymentMethod === 'cod' ? 'border-brand-500 bg-blue-50' : 'border-gray-200'
@ -216,64 +216,64 @@ export function PaymentAndOrderComponent({
<Banknote className="h-5 w-5 text-green-500" /> <Banknote className="h-5 w-5 text-green-500" />
</div> </div>
<div> <div>
<MyText weight="bold" className="text-gray-900"> <p className="font-bold text-gray-900">
Cash on Delivery Cash on Delivery
</MyText> </p>
<MyText className="text-xs text-gray-500">Pay when you receive</MyText> <p className="text-xs text-gray-500">Pay when you receive</p>
</div>
</div> </div>
</MyTouchableOpacity>
<div className="mt-3 flex items-center gap-4"> <div className="mt-3 flex items-center gap-4">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<Wallet className="h-3 w-3 text-purple-600" /> <Wallet className="h-3 w-3 text-purple-600" />
<MyText className="text-xs text-gray-500">UPI accepted during COD</MyText> <p className="text-xs text-gray-500">UPI accepted during COD</p>
</div> </div>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<Banknote className="h-3 w-3 text-green-500" /> <Banknote className="h-3 w-3 text-green-500" />
<MyText className="text-xs text-gray-500">Cash payment</MyText> <p className="text-xs text-gray-500">Cash payment</p>
</div> </div>
</div> </div>
</div> </div>
{/* Bill Details */} {/* Bill Details */}
<div className="mb-4 rounded-2xl border border-gray-100 bg-white p-5 shadow-sm"> <div className="mb-4 rounded-2xl border border-gray-100 bg-white p-5 shadow-sm">
<MyText weight="bold" className="mb-4 text-lg text-gray-900"> <p className="font-bold mb-4 text-lg text-gray-900">
Bill Details Bill Details
</MyText> </p>
{/* Item Total */} {/* Item Total */}
<div className="mb-2 flex items-center justify-between"> <div className="mb-2 flex items-center justify-between">
<MyText className="text-gray-500">Item Total</MyText> <p className="text-gray-500">Item Total</p>
<MyText weight="medium" className="text-gray-900"> <p className="font-medium text-gray-900">
{totalPrice} {totalPrice}
</MyText> </p>
</div> </div>
{/* Discount */} {/* Discount */}
{discountAmount > 0 && ( {discountAmount > 0 && (
<div className="mb-2 flex items-center justify-between"> <div className="mb-2 flex items-center justify-between">
<MyText className="text-gray-500">Product Discount</MyText> <p className="text-gray-500">Product Discount</p>
<MyText weight="medium" className="text-green-600"> <p className="font-medium text-green-600">
-{discountAmount.toFixed(2)} -{discountAmount.toFixed(2)}
</MyText> </p>
</div> </div>
)} )}
{/* Delivery Fee */} {/* Delivery Fee */}
<div className="mb-2 flex items-center justify-between"> <div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<MyText className="text-gray-500">Delivery Fee</MyText> <p className="text-gray-500">Delivery Fee</p>
<Info className="h-3.5 w-3.5 text-gray-400" /> <Info className="h-3.5 w-3.5 text-gray-400" />
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
{deliveryCharge === 0 && ( {deliveryCharge === 0 && (
<MyText className="mr-2 text-xs text-gray-400 line-through"> <p className="mr-2 text-xs text-gray-400 line-through">
{isFlashDelivery ? constsData?.flashDeliveryCharge : constsData?.deliveryCharge} {isFlashDelivery ? constsData?.flashDeliveryCharge : constsData?.deliveryCharge}
</MyText> </p>
)} )}
<MyText weight="medium" className={deliveryCharge === 0 ? 'text-green-600' : 'text-gray-900'}> <p className={`font-medium ${deliveryCharge === 0 ? 'text-green-600' : 'text-gray-900'}`}>
{deliveryCharge === 0 ? 'Free' : `${deliveryCharge}`} {deliveryCharge === 0 ? 'Free' : `${deliveryCharge}`}
</MyText> </p>
</div> </div>
</div> </div>
@ -285,9 +285,9 @@ export function PaymentAndOrderComponent({
return threshold > 0 && finalTotal < threshold ? ( return threshold > 0 && finalTotal < threshold ? (
<div className="mb-2 flex items-center gap-2 rounded-lg bg-blue-50 p-2.5"> <div className="mb-2 flex items-center gap-2 rounded-lg bg-blue-50 p-2.5">
<ShoppingBag className="h-4 w-4 text-blue-600" /> <ShoppingBag className="h-4 w-4 text-blue-600" />
<MyText weight="medium" className="flex-1 text-xs text-blue-700"> <p className="font-medium flex-1 text-xs text-blue-700">
Add products worth {(threshold - finalTotal).toFixed(0)} for free delivery Add products worth {(threshold - finalTotal).toFixed(0)} for free delivery
</MyText> </p>
</div> </div>
) : null ) : null
})()} })()}
@ -297,23 +297,23 @@ export function PaymentAndOrderComponent({
{/* Grand Total */} {/* Grand Total */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<MyText weight="bold" className="text-lg text-gray-900"> <p className="font-bold text-lg text-gray-900">
To Pay To Pay
</MyText> </p>
<MyText weight="bold" className="text-xl text-gray-900"> <p className="font-bold text-xl text-gray-900">
{finalTotalWithDelivery.toFixed(2)} {finalTotalWithDelivery.toFixed(2)}
</MyText> </p>
</div> </div>
{/* Savings Banner */} {/* Savings Banner */}
{(discountAmount > 0 || deliveryCharge === 0) && ( {(discountAmount > 0 || deliveryCharge === 0) && (
<div className="mt-4 flex items-center justify-center gap-1.5 rounded-lg bg-green-50 p-2"> <div className="mt-4 flex items-center justify-center gap-1.5 rounded-lg bg-green-50 p-2">
<Star className="h-4 w-4 text-green-600" /> <Star className="h-4 w-4 text-green-600" />
<MyText weight="bold" className="text-xs text-green-700"> <p className="font-bold text-xs text-green-700">
You saved You saved
{(discountAmount + (deliveryCharge === 0 ? (isFlashDelivery ? constsData?.flashDeliveryCharge : constsData?.deliveryCharge) : 0)).toFixed(2)}{' '} {(discountAmount + (deliveryCharge === 0 ? (isFlashDelivery ? constsData?.flashDeliveryCharge : constsData?.deliveryCharge) : 0)).toFixed(2)}{' '}
on this order on this order
</MyText> </p>
</div> </div>
)} )}
</div> </div>
@ -323,14 +323,14 @@ export function PaymentAndOrderComponent({
<div className="mb-4 rounded-2xl border border-gray-100 bg-white p-5 shadow-sm"> <div className="mb-4 rounded-2xl border border-gray-100 bg-white p-5 shadow-sm">
<div className="mb-3 flex items-center gap-2"> <div className="mb-3 flex items-center gap-2">
<Tag className="h-5 w-5 text-brand-500" /> <Tag className="h-5 w-5 text-brand-500" />
<MyText weight="bold" className="text-lg text-gray-900"> <p className="font-bold text-lg text-gray-900">
Apply Coupon Apply Coupon
</MyText> </p>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
{eligibleCoupons.map((coupon: any) => ( {eligibleCoupons.map((coupon: any) => (
<MyTouchableOpacity <div
key={coupon.id} key={coupon.id}
onClick={() => setSelectedCouponId(selectedCouponId === coupon.id ? null : coupon.id)} onClick={() => setSelectedCouponId(selectedCouponId === coupon.id ? null : coupon.id)}
className={`flex items-center justify-between rounded-xl border p-3 transition-all ${ className={`flex items-center justify-between rounded-xl border p-3 transition-all ${
@ -344,22 +344,22 @@ export function PaymentAndOrderComponent({
> >
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<MyText weight="bold" className="text-sm text-gray-900"> <p className="font-bold text-sm text-gray-900">
{coupon.code} {coupon.code}
</MyText> </p>
{!coupon.isEligible && ( {!coupon.isEligible && (
<span className="rounded bg-red-100 px-1.5 py-0.5 text-xs text-red-600"> <span className="rounded bg-red-100 px-1.5 py-0.5 text-xs text-red-600">
{coupon.ineligibilityReason} {coupon.ineligibilityReason}
</span> </span>
)} )}
</div> </div>
<MyText className="text-xs text-gray-500"> <p className="text-xs text-gray-500">
{coupon.discountType === 'percentage' {coupon.discountType === 'percentage'
? `${coupon.discountValue}% off` ? `${coupon.discountValue}% off`
: `${coupon.discountValue} off`} : `${coupon.discountValue} off`}
{coupon.maxValue ? ` up to ₹${coupon.maxValue}` : ''} {coupon.maxValue ? ` up to ₹${coupon.maxValue}` : ''}
{coupon.minOrder ? ` | Min order ₹${coupon.minOrder}` : ''} {coupon.minOrder ? ` | Min order ₹${coupon.minOrder}` : ''}
</MyText> </p>
</div> </div>
{selectedCouponId === coupon.id && ( {selectedCouponId === coupon.id && (
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-brand-500"> <div className="flex h-6 w-6 items-center justify-center rounded-full bg-brand-500">
@ -368,7 +368,7 @@ export function PaymentAndOrderComponent({
</svg> </svg>
</div> </div>
)} )}
</MyTouchableOpacity> </div>
))} ))}
</div> </div>
</div> </div>
@ -379,9 +379,9 @@ export function PaymentAndOrderComponent({
{!selectedAddress && ( {!selectedAddress && (
<div className="mb-3 flex items-center gap-2 rounded-lg bg-red-50 p-3"> <div className="mb-3 flex items-center gap-2 rounded-lg bg-red-50 p-3">
<Info className="h-4 w-4 text-red-500" /> <Info className="h-4 w-4 text-red-500" />
<MyText weight="medium" className="text-sm text-red-600"> <p className="font-medium text-sm text-red-600">
Please select a delivery address to place order Please select a delivery address to place order
</MyText> </p>
</div> </div>
)} )}

View file

@ -0,0 +1,224 @@
import React from 'react'
import { p, div, Quantifier, MiniQuantifier } from 'web-components'
import { useGetCart, useUpdateCartItem, useRemoveFromCart, useAddToCart } from '../hooks/cart-query-hooks'
import { useCartStore } from '../lib/stores/cart-store'
import { useCentralSlotStore } from '../lib/stores/central-slot-store'
import { useProductSlotIdentifier } from '../hooks/useProductSlotIdentifier'
import dayjs from 'dayjs'
import { Truck, ShoppingCart, ImageOff } from 'lucide-react'
interface ProductCardProps {
item: any
onPress?: () => void
showDeliveryInfo?: boolean
miniView?: boolean
useAddToCartDialog?: boolean
}
const formatQuantity = (quantity: number, unit: string): { value: string; display: string } => {
if (unit?.toLowerCase() === 'kg' && quantity < 1) {
return { value: `${Math.round(quantity * 1000)} g`, display: `${Math.round(quantity * 1000)}g` }
}
return { value: `${quantity} ${unit}(s)`, display: `${quantity}${unit}` }
}
export function ProductCard({
item,
onPress,
showDeliveryInfo = true,
miniView = false,
useAddToCartDialog = false,
}: ProductCardProps) {
const [imageError, setImageError] = React.useState(false)
const [imageLoading, setImageLoading] = React.useState(true)
const imageUri = item.images?.[0]
const { data: cartData } = useGetCart('regular')
const { getQuickestSlot } = useProductSlotIdentifier()
const { setAddedToCartProduct } = useCartStore()
const updateCartItem = useUpdateCartItem('regular')
const removeFromCart = useRemoveFromCart('regular')
const addToCart = useAddToCart('regular')
// Find current quantity from cart data
const cartItem = cartData?.items?.find((cartItem: any) => cartItem.productId === item.id)
const quantity = cartItem?.quantity || 0
// Get slots data from central store
const slots = useCentralSlotStore((state) => state.slots)
const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap)
// Create slot lookup map
const slotMap = React.useMemo(() => {
const map: Record<number, any> = {}
slots?.forEach((slot: any) => {
map[slot.id] = slot
})
return map
}, [slots])
// Get cart item's slot delivery time if item is in cart
const cartSlot = cartItem?.slotId ? slotMap[cartItem.slotId] : null
const displayDeliveryDate = cartSlot?.deliveryTime || item.nextDeliveryDate
// Precompute the next slot and determine display out of stock status
const slotId = getQuickestSlot(item.id)
const productSlotInfo = productSlotsMap[item.id]
const isOutOfStockFromSlots = productSlotInfo?.isOutOfStock
const displayIsOutOfStock = isOutOfStockFromSlots || !slotId
const handleQuantityChange = (newQuantity: number) => {
if (useAddToCartDialog) {
setAddedToCartProduct({ productId: item.id, product: item })
} else if (newQuantity === 0 && cartItem) {
removeFromCart.mutate(cartItem.id)
} else if (newQuantity === 1 && !cartItem) {
const slotId = getQuickestSlot(item.id)
if (!slotId) {
alert('No available delivery slot for this product')
return
}
const slot = slotMap[slotId]
const deliveryTime = slot ? dayjs(slot.deliveryTime).format('ddd, DD MMM • h:mm A') : ''
addToCart.mutate(
{ productId: item.id, quantity: 1, slotId, storeId: item.storeId },
{
onSuccess: () => {
alert(`Added ${item.name} for delivery at ${deliveryTime}`)
},
}
)
} else if (cartItem) {
updateCartItem.mutate({ productId: item.id, quantity: newQuantity })
}
}
return (
<div
className="flex max-w-75 flex-col overflow-hidden rounded-2xl bg-white pb-2 border border-gray-300"
onClick={onPress}
>
{/* Image Container */}
<div className="relative aspect-square w-full overflow-hidden bg-gray-100">
{imageUri && !imageError ? (
<img
src={imageUri}
alt={item.name}
className="h-full w-full object-cover"
onError={() => setImageError(true)}
onLoad={() => setImageLoading(false)}
/>
) : (
<div className="flex h-full w-full items-center justify-center bg-gray-100">
<ImageOff className="h-8 w-8 text-gray-400" />
</div>
)}
{imageLoading && imageUri && !imageError && (
<div className="absolute inset-0 flex items-center justify-center bg-gray-100">
<div className="h-6 w-6 animate-spin rounded-full border-2 border-brand-500 border-t-transparent" />
</div>
)}
{displayIsOutOfStock && (
<div className="absolute inset-0 flex items-center justify-center bg-black/40">
<div className="rounded-full bg-red-500 px-3 py-1">
<p className="text-xs font-bold text-white">Out of Stock</p>
</div>
</div>
)}
{miniView && (
<div className="absolute bottom-2 right-2">
{quantity > 0 ? (
<MiniQuantifier
value={quantity}
setValue={handleQuantityChange}
step={item.incrementStep}
/>
) : (
<div
className="flex h-8 w-8 items-center justify-center rounded-full bg-white shadow-md"
onClick={(e) => {
e.stopPropagation()
handleQuantityChange(1)
}}
>
<ShoppingCart className="h-4 w-4 text-brand-500" />
</div>
)}
</div>
)}
</div>
{/* Content */}
<div className="px-3 pt-3">
<p className="font-bold mb-1 text-sm text-gray-900">
{item.name}
</p>
<div className="mb-2 flex flex-row items-baseline">
<p className="font-bold text-base text-brand-500">
{item.price}
</p>
{item.marketPrice && Number(item.marketPrice) > Number(item.price) && (
<p className="ml-2 text-xs text-gray-400 line-through">
{item.marketPrice}
</p>
)}
</div>
<div className="mb-2 flex flex-row items-center">
<p className="text-xs font-medium text-gray-500">
Quantity:{' '}
<span className="font-semibold text-brand-500">
{formatQuantity(item.productQuantity || 1, item.unitNotation).display}
</span>
</p>
</div>
{showDeliveryInfo && displayDeliveryDate && (
<div className="mb-2 flex flex-row items-center self-start rounded-lg border border-brand-100 bg-brand-50 px-2 py-1.5">
<Truck className="h-3 w-3 text-brand-600" />
<p className="ml-1.5 text-[10px] font-bold text-brand-700">
{dayjs(displayDeliveryDate).format('ddd, DD MMM • h:mm A')}
</p>
</div>
)}
{!miniView && (
<>
{displayIsOutOfStock ? (
<div className="mt-1 rounded-lg bg-gray-100 py-2 text-center">
<p className="text-xs font-bold uppercase tracking-wide text-gray-400">
Unavailable
</p>
</div>
) : quantity > 0 ? (
<Quantifier
value={quantity}
setValue={handleQuantityChange}
step={item.incrementStep}
unit={item.unitNotation}
/>
) : (
<button
className="mt-1 flex w-full items-center justify-center gap-1 rounded-lg bg-brand-500 py-2 hover:bg-brand-600 active:bg-brand-700"
onClick={(e) => {
e.stopPropagation()
handleQuantityChange(1)
}}
>
<ShoppingCart className="h-4 w-4 text-white" />
<span className="text-xs font-bold uppercase tracking-wide text-white">
Add to Cart
</span>
</button>
)}
</>
)}
</div>
</div>
)
}

View file

@ -3,3 +3,7 @@ export { PaymentAndOrderComponent } from './PaymentAndOrderComponent'
export { AddressForm } from './AddressForm' export { AddressForm } from './AddressForm'
export { ProtectedRoute } from './ProtectedRoute' export { ProtectedRoute } from './ProtectedRoute'
export { Dialog } from './Dialog' export { Dialog } from './Dialog'
export { BottomNavigation } from './BottomNavigation'
export { FloatingCartBar } from './FloatingCartBar'
export { AppLayout } from './AppLayout'
export { ProductCard } from './ProductCard'

View file

@ -1 +1,4 @@
export { useCheckoutAddress } from './checkout-hooks' export { useCheckoutAddress } from './checkout-hooks'
export { useProductSlotIdentifier } from './useProductSlotIdentifier'
export { usePopulateCentralStores } from './usePopulateCentralStores'
export { usePopulateCentralProductStore } from './usePopulateCentralProductStore'

View file

@ -0,0 +1,14 @@
import { useEffect } from 'react'
import { useAllProducts } from './prominent-api-hooks'
import { useCentralProductStore } from '../lib/stores/central-product-store'
export function usePopulateCentralProductStore() {
const { data: productsData } = useAllProducts()
const setProducts = useCentralProductStore((state) => state.setProducts)
useEffect(() => {
if (productsData?.products) {
setProducts(productsData.products)
}
}, [productsData, setProducts])
}

View file

@ -0,0 +1,57 @@
import { useEffect } from 'react'
import { useCentralSlotStore } from '../lib/stores/central-slot-store'
import { useSlots } from './prominent-api-hooks'
import { useAllProducts } from './prominent-api-hooks'
export function usePopulateCentralStores() {
const { data: slotsData } = useSlots()
const { data: productsData } = useAllProducts()
const setSlots = useCentralSlotStore((state) => state.setSlots)
const setProductSlotsMap = useCentralSlotStore((state) => state.setProductSlotsMap)
useEffect(() => {
if (slotsData?.slots) {
// Transform slots to the format expected by the store
const formattedSlots = slotsData.slots.map((slot: any) => ({
id: slot.id,
deliveryDate: slot.deliveryDate,
deliveryTime: slot.deliveryTime,
displayDate: slot.displayDate || '',
displayTime: slot.displayTime || '',
}))
setSlots(formattedSlots)
// Build product slots map
const productMap: Record<number, any> = {}
const allProducts = productsData?.products || []
allProducts.forEach((product: any) => {
// Find slots that contain this product
const productSlots = slotsData.slots.filter((slot: any) =>
slot.products?.some((p: any) => p.id === product.id)
)
// Check if flash delivery is available
const isFlashAvailable = product.isFlashAvailable || false
// Check if out of stock (no slots available)
const isOutOfStock = productSlots.length === 0 && !isFlashAvailable
productMap[product.id] = {
slotId: productSlots.length > 0 ? productSlots[0].id : null,
slots: productSlots.map((s: any) => ({
id: s.id,
deliveryDate: s.deliveryDate,
deliveryTime: s.deliveryTime,
displayDate: s.displayDate || '',
displayTime: s.displayTime || '',
})),
isOutOfStock,
isFlashAvailable,
}
})
setProductSlotsMap(productMap)
}
}, [slotsData, productsData, setSlots, setProductSlotsMap])
}

View file

@ -0,0 +1,30 @@
import { useCentralSlotStore } from '../lib/stores/central-slot-store'
import { useMemo } from 'react'
export function useProductSlotIdentifier() {
const slots = useCentralSlotStore((state) => state.slots)
const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap)
const getQuickestSlot = useMemo(() => {
return (productId: number): number | null => {
const productSlotInfo = productSlotsMap[productId]
if (!productSlotInfo || productSlotInfo.isOutOfStock) {
return null
}
const availableSlots = productSlotInfo.slots || []
if (availableSlots.length === 0) {
return null
}
// Return the first (quickest) slot
return availableSlots[0].id
}
}, [productSlotsMap])
return {
getQuickestSlot,
slots,
productSlotsMap,
}
}

View file

@ -10,6 +10,7 @@
import { Route as rootRouteImport } from './routes/__root' import { Route as rootRouteImport } from './routes/__root'
import { Route as StoresRouteImport } from './routes/stores' import { Route as StoresRouteImport } from './routes/stores'
import { Route as SlotViewRouteImport } from './routes/slot-view'
import { Route as RegisterRouteImport } from './routes/register' import { Route as RegisterRouteImport } from './routes/register'
import { Route as MeRouteImport } from './routes/me' import { Route as MeRouteImport } from './routes/me'
import { Route as LoginRouteImport } from './routes/login' import { Route as LoginRouteImport } from './routes/login'
@ -43,6 +44,11 @@ const StoresRoute = StoresRouteImport.update({
path: '/stores', path: '/stores',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const SlotViewRoute = SlotViewRouteImport.update({
id: '/slot-view',
path: '/slot-view',
getParentRoute: () => rootRouteImport,
} as any)
const RegisterRoute = RegisterRouteImport.update({ const RegisterRoute = RegisterRouteImport.update({
id: '/register', id: '/register',
path: '/register', path: '/register',
@ -189,6 +195,7 @@ export interface FileRoutesByFullPath {
'/login': typeof LoginRoute '/login': typeof LoginRoute
'/me': typeof MeRouteWithChildren '/me': typeof MeRouteWithChildren
'/register': typeof RegisterRoute '/register': typeof RegisterRoute
'/slot-view': typeof SlotViewRoute
'/stores': typeof StoresRouteWithChildren '/stores': typeof StoresRouteWithChildren
'/flash/cart': typeof FlashCartRoute '/flash/cart': typeof FlashCartRoute
'/flash/checkout': typeof FlashCheckoutRoute '/flash/checkout': typeof FlashCheckoutRoute
@ -219,6 +226,7 @@ export interface FileRoutesByTo {
'/login': typeof LoginRoute '/login': typeof LoginRoute
'/me': typeof MeRouteWithChildren '/me': typeof MeRouteWithChildren
'/register': typeof RegisterRoute '/register': typeof RegisterRoute
'/slot-view': typeof SlotViewRoute
'/stores': typeof StoresRouteWithChildren '/stores': typeof StoresRouteWithChildren
'/flash/cart': typeof FlashCartRoute '/flash/cart': typeof FlashCartRoute
'/flash/checkout': typeof FlashCheckoutRoute '/flash/checkout': typeof FlashCheckoutRoute
@ -250,6 +258,7 @@ export interface FileRoutesById {
'/login': typeof LoginRoute '/login': typeof LoginRoute
'/me': typeof MeRouteWithChildren '/me': typeof MeRouteWithChildren
'/register': typeof RegisterRoute '/register': typeof RegisterRoute
'/slot-view': typeof SlotViewRoute
'/stores': typeof StoresRouteWithChildren '/stores': typeof StoresRouteWithChildren
'/flash/cart': typeof FlashCartRoute '/flash/cart': typeof FlashCartRoute
'/flash/checkout': typeof FlashCheckoutRoute '/flash/checkout': typeof FlashCheckoutRoute
@ -282,6 +291,7 @@ export interface FileRouteTypes {
| '/login' | '/login'
| '/me' | '/me'
| '/register' | '/register'
| '/slot-view'
| '/stores' | '/stores'
| '/flash/cart' | '/flash/cart'
| '/flash/checkout' | '/flash/checkout'
@ -312,6 +322,7 @@ export interface FileRouteTypes {
| '/login' | '/login'
| '/me' | '/me'
| '/register' | '/register'
| '/slot-view'
| '/stores' | '/stores'
| '/flash/cart' | '/flash/cart'
| '/flash/checkout' | '/flash/checkout'
@ -342,6 +353,7 @@ export interface FileRouteTypes {
| '/login' | '/login'
| '/me' | '/me'
| '/register' | '/register'
| '/slot-view'
| '/stores' | '/stores'
| '/flash/cart' | '/flash/cart'
| '/flash/checkout' | '/flash/checkout'
@ -373,6 +385,7 @@ export interface RootRouteChildren {
LoginRoute: typeof LoginRoute LoginRoute: typeof LoginRoute
MeRoute: typeof MeRouteWithChildren MeRoute: typeof MeRouteWithChildren
RegisterRoute: typeof RegisterRoute RegisterRoute: typeof RegisterRoute
SlotViewRoute: typeof SlotViewRoute
StoresRoute: typeof StoresRouteWithChildren StoresRoute: typeof StoresRouteWithChildren
} }
@ -385,6 +398,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof StoresRouteImport preLoaderRoute: typeof StoresRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/slot-view': {
id: '/slot-view'
path: '/slot-view'
fullPath: '/slot-view'
preLoaderRoute: typeof SlotViewRouteImport
parentRoute: typeof rootRouteImport
}
'/register': { '/register': {
id: '/register' id: '/register'
path: '/register' path: '/register'
@ -677,6 +697,7 @@ const rootRouteChildren: RootRouteChildren = {
LoginRoute: LoginRoute, LoginRoute: LoginRoute,
MeRoute: MeRouteWithChildren, MeRoute: MeRouteWithChildren,
RegisterRoute: RegisterRoute, RegisterRoute: RegisterRoute,
SlotViewRoute: SlotViewRoute,
StoresRoute: StoresRouteWithChildren, StoresRoute: StoresRouteWithChildren,
} }
export const routeTree = rootRouteImport export const routeTree = rootRouteImport

View file

@ -1,7 +1,7 @@
import { createFileRoute, useNavigate } from '@tanstack/react-router' import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { useGetCart, useUpdateCartItem, useRemoveFromCart } from '../hooks/cart-query-hooks' import { useGetCart, useUpdateCartItem, useRemoveFromCart } from '../hooks/cart-query-hooks'
import { useAllProducts } from '../hooks/prominent-api-hooks' import { useAllProducts } from '../hooks/prominent-api-hooks'
import { MyText, MyButton, Quantifier, AppContainer, MyTouchableOpacity } from 'web-components' import { p, MyButton, Quantifier, AppContainer, div } from 'web-components'
import { Trash2 } from 'lucide-react' import { Trash2 } from 'lucide-react'
export const Route = createFileRoute('/cart')({ component: CartPage }) export const Route = createFileRoute('/cart')({ component: CartPage })
@ -29,13 +29,13 @@ function CartPage() {
return ( return (
<AppContainer> <AppContainer>
<MyText weight="bold" className="mb-4 text-xl"> <p className="font-bold mb-4 text-xl">
Your Cart Your Cart
</MyText> </p>
{cartItems.length === 0 ? ( {cartItems.length === 0 ? (
<div className="flex flex-col items-center gap-4 py-20"> <div className="flex flex-col items-center gap-4 py-20">
<MyText className="text-gray-500">Your cart is empty</MyText> <p className="text-gray-500">Your cart is empty</p>
<MyButton <MyButton
textContent="Browse Products" textContent="Browse Products"
onClick={() => navigate({ to: '/home' })} onClick={() => navigate({ to: '/home' })}
@ -58,20 +58,20 @@ function CartPage() {
className="h-16 w-16 rounded-lg object-cover" className="h-16 w-16 rounded-lg object-cover"
/> />
<div className="flex-1"> <div className="flex-1">
<MyText weight="semibold" className="text-sm" numberOfLines={1}> <p className="font-semibold text-sm">
{product.name} {product.name}
</MyText> </p>
<MyText className="text-brand-600 text-sm font-bold"> <p className="text-brand-600 text-sm font-bold">
{price} {price}
</MyText> </p>
<Quantifier <Quantifier
value={item.quantity} value={item.quantity}
setValue={(q) => updateItem.mutate({ productId: item.productId, quantity: q })} setValue={(q) => updateItem.mutate({ productId: item.productId, quantity: q })}
/> />
</div> </div>
<MyTouchableOpacity onClick={() => removeItem.mutate(item.productId)}> <div onClick={() => removeItem.mutate(item.productId)}>
<Trash2 className="h-5 w-5 text-red-500" /> <Trash2 className="h-5 w-5 text-red-500" />
</MyTouchableOpacity> </div>
</div> </div>
) )
})} })}
@ -79,10 +79,10 @@ function CartPage() {
<div className="fixed bottom-0 left-0 right-0 border-t border-gray-200 bg-white p-4 shadow-lg"> <div className="fixed bottom-0 left-0 right-0 border-t border-gray-200 bg-white p-4 shadow-lg">
<div className="mb-3 flex items-center justify-between"> <div className="mb-3 flex items-center justify-between">
<MyText weight="bold">Total</MyText> <p className="font-bold">Total</p>
<MyText weight="bold" className="text-lg text-brand-600"> <p className="font-bold text-lg text-brand-600">
{total} {total}
</MyText> </p>
</div> </div>
<MyButton <MyButton
fullWidth fullWidth

View file

@ -1,5 +1,5 @@
import { createFileRoute, useNavigate } from '@tanstack/react-router' import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { useState, useMemo } from 'react' import { useState } from 'react'
import { useAuth } from '../lib/auth-context' import { useAuth } from '../lib/auth-context'
import { useGetCart } from '../hooks/cart-query-hooks' import { useGetCart } from '../hooks/cart-query-hooks'
import { useAllProducts } from '../hooks/prominent-api-hooks' import { useAllProducts } from '../hooks/prominent-api-hooks'
@ -10,8 +10,8 @@ import { AddressForm } from '../components/AddressForm'
import { Dialog } from '../components/Dialog' import { Dialog } from '../components/Dialog'
import { useAddressStore } from '../lib/stores/address-store' import { useAddressStore } from '../lib/stores/address-store'
import { useQueryClient } from '@tanstack/react-query' import { useQueryClient } from '@tanstack/react-query'
import { MyText, AppContainer, MyTouchableOpacity } from 'web-components' import { p, div } from 'web-components'
import { MapPin, ShoppingCart, ChevronLeft, Flashlight } from 'lucide-react' import { MapPin, ShoppingCart, ChevronLeft } from 'lucide-react'
export const Route = createFileRoute('/checkout')({ component: CheckoutPage }) export const Route = createFileRoute('/checkout')({ component: CheckoutPage })
@ -44,43 +44,43 @@ function CheckoutContent() {
// Handle empty cart case // Handle empty cart case
if (cartItems.length === 0) { if (cartItems.length === 0) {
return ( return (
<AppContainer className="flex min-h-screen flex-col items-center justify-center bg-gray-50 p-6"> <div className="flex min-h-screen flex-col items-center justify-center bg-gray-50 p-6">
<ShoppingCart className="mb-4 h-16 w-16 text-gray-400" /> <ShoppingCart className="mb-4 h-16 w-16 text-gray-400" />
<MyText weight="bold" className="mb-2 text-center text-xl text-gray-900"> <p className="font-bold mb-2 text-center text-xl text-gray-900">
Your cart is empty Your cart is empty
</MyText> </p>
<MyText className="mb-6 text-center text-gray-500"> <p className="mb-6 text-center text-gray-500">
Add some delicious items to your cart before checking out Add some delicious items to your cart before checking out
</MyText> </p>
<MyTouchableOpacity <div
onClick={() => navigate({ to: '/home' })} onClick={() => navigate({ to: '/home' })}
className="rounded-lg bg-brand-500 px-6 py-3" className="rounded-lg bg-brand-500 px-6 py-3"
> >
<MyText weight="bold" className="text-white"> <p className="font-bold text-white">
Back to Shopping Back to Shopping
</MyText> </p>
</MyTouchableOpacity> </div>
</AppContainer> </div>
) )
} }
return ( return (
<AppContainer className="min-h-screen bg-gray-50 pb-8"> <div className="min-h-screen bg-gray-50 pb-8">
{/* Checkout Header */} {/* Checkout Header */}
<div className="sticky top-0 z-10 mb-4 border-b border-gray-100 bg-white px-4 py-3"> <div className="sticky top-0 z-10 mb-4 border-b border-gray-100 bg-white px-4 py-3">
<div className="flex items-center"> <div className="flex items-center">
<MyTouchableOpacity <div
onClick={() => navigate({ to: '/cart' })} onClick={() => navigate({ to: '/cart' })}
className="-ml-2 mr-1 flex items-center p-2" className="-ml-2 mr-1 flex items-center p-2"
> >
<ChevronLeft className="h-7 w-7 text-gray-700" /> <ChevronLeft className="h-7 w-7 text-gray-700" />
</MyTouchableOpacity> </div>
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-blue-50"> <div className="flex h-10 w-10 items-center justify-center rounded-full bg-blue-50">
<MapPin className="h-5 w-5 text-blue-500" /> <MapPin className="h-5 w-5 text-blue-500" />
</div> </div>
<MyText weight="bold" className="ml-3 text-lg text-gray-800"> <p className="font-bold ml-3 text-lg text-gray-800">
Checkout Checkout
</MyText> </p>
</div> </div>
</div> </div>
@ -129,6 +129,6 @@ function CheckoutContent() {
isEdit={!!editingAddress} isEdit={!!editingAddress}
/> />
</Dialog> </Dialog>
</AppContainer> </div>
) )
} }

View file

@ -1,7 +1,7 @@
import { createFileRoute, useNavigate } from '@tanstack/react-router' import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { useGetCart, useUpdateCartItem, useRemoveFromCart } from '../hooks/cart-query-hooks' import { useGetCart, useUpdateCartItem, useRemoveFromCart } from '../hooks/cart-query-hooks'
import { useCentralProductStore } from '../lib/stores/central-product-store' import { useCentralProductStore } from '../lib/stores/central-product-store'
import { MyText, MyButton, Quantifier, AppContainer, MyTouchableOpacity } from 'web-components' import { p, MyButton, Quantifier, AppContainer, div } from 'web-components'
import { Trash2, Zap } from 'lucide-react' import { Trash2, Zap } from 'lucide-react'
export const Route = createFileRoute('/flash/cart')({ component: FlashCartPage }) export const Route = createFileRoute('/flash/cart')({ component: FlashCartPage })
@ -27,14 +27,14 @@ function FlashCartPage() {
<AppContainer> <AppContainer>
<div className="mb-4 flex items-center gap-2"> <div className="mb-4 flex items-center gap-2">
<Zap className="h-5 w-5 text-yellow-500" /> <Zap className="h-5 w-5 text-yellow-500" />
<MyText weight="bold" className="text-xl"> <p className="font-bold text-xl">
Flash Cart Flash Cart
</MyText> </p>
</div> </div>
{cartItems.length === 0 ? ( {cartItems.length === 0 ? (
<div className="flex flex-col items-center gap-4 py-20"> <div className="flex flex-col items-center gap-4 py-20">
<MyText className="text-gray-500">Your flash cart is empty</MyText> <p className="text-gray-500">Your flash cart is empty</p>
<MyButton <MyButton
textContent="Browse Flash Products" textContent="Browse Flash Products"
onClick={() => navigate({ to: '/flash' })} onClick={() => navigate({ to: '/flash' })}
@ -57,10 +57,10 @@ function FlashCartPage() {
className="h-16 w-16 rounded-lg object-cover" className="h-16 w-16 rounded-lg object-cover"
/> />
<div className="flex-1"> <div className="flex-1">
<MyText weight="semibold" className="text-sm" numberOfLines={1}> <p className="font-semibold text-sm">
{product.name} {product.name}
</MyText> </p>
<MyText className="text-brand-600 text-sm font-bold">{price}</MyText> <p className="text-brand-600 text-sm font-bold">{price}</p>
<Quantifier <Quantifier
value={item.quantity} value={item.quantity}
setValue={(q) => setValue={(q) =>
@ -68,9 +68,9 @@ function FlashCartPage() {
} }
/> />
</div> </div>
<MyTouchableOpacity onClick={() => removeItem.mutate(item.productId)}> <div onClick={() => removeItem.mutate(item.productId)}>
<Trash2 className="h-5 w-5 text-red-500" /> <Trash2 className="h-5 w-5 text-red-500" />
</MyTouchableOpacity> </div>
</div> </div>
) )
})} })}
@ -78,10 +78,10 @@ function FlashCartPage() {
<div className="fixed bottom-0 left-0 right-0 border-t border-gray-200 bg-white p-4 shadow-lg"> <div className="fixed bottom-0 left-0 right-0 border-t border-gray-200 bg-white p-4 shadow-lg">
<div className="mb-3 flex items-center justify-between"> <div className="mb-3 flex items-center justify-between">
<MyText weight="bold">Total</MyText> <p className="font-bold">Total</p>
<MyText weight="bold" className="text-lg text-brand-600"> <p className="font-bold text-lg text-brand-600">
{total} {total}
</MyText> </p>
</div> </div>
<MyButton <MyButton
fullWidth fullWidth

View file

@ -5,7 +5,7 @@ import { useGetCart } from '../hooks/cart-query-hooks'
import { useCentralProductStore } from '../lib/stores/central-product-store' import { useCentralProductStore } from '../lib/stores/central-product-store'
import { trpc } from '../lib/trpc-client' import { trpc } from '../lib/trpc-client'
import { clearLocalCart } from '../hooks/cart-query-hooks' import { clearLocalCart } from '../hooks/cart-query-hooks'
import { MyText, MyButton, LoadingDialog, AppContainer } from 'web-components' import { p, MyButton, LoadingDialog, AppContainer } from 'web-components'
import { useQueryClient } from '@tanstack/react-query' import { useQueryClient } from '@tanstack/react-query'
export const Route = createFileRoute('/flash/checkout')({ component: FlashCheckoutPage }) export const Route = createFileRoute('/flash/checkout')({ component: FlashCheckoutPage })
@ -63,14 +63,14 @@ function FlashCheckoutPage() {
return ( return (
<AppContainer> <AppContainer>
<MyText weight="bold" className="mb-4 text-xl"> <p className="font-bold mb-4 text-xl">
Flash Checkout Flash Checkout
</MyText> </p>
<div className="mb-6"> <div className="mb-6">
<MyText weight="semibold" className="mb-2"> <p className="font-semibold mb-2">
Delivery Address Delivery Address
</MyText> </p>
{addresses?.data?.map((addr: any) => ( {addresses?.data?.map((addr: any) => (
<button <button
key={addr.id} key={addr.id}
@ -81,39 +81,39 @@ function FlashCheckoutPage() {
: 'border-gray-200' : 'border-gray-200'
}`} }`}
> >
<MyText weight="semibold">{addr.name}</MyText> <p className="font-semibold">{addr.name}</p>
<MyText className="text-sm text-gray-600"> <p className="text-sm text-gray-600">
{addr.addressLine1}, {addr.city} {addr.addressLine1}, {addr.city}
</MyText> </p>
<MyText className="text-sm text-gray-500">{addr.phone}</MyText> <p className="text-sm text-gray-500">{addr.phone}</p>
</button> </button>
))} ))}
</div> </div>
<div className="mb-6"> <div className="mb-6">
<MyText weight="semibold" className="mb-2"> <p className="font-semibold mb-2">
Order Summary Order Summary
</MyText> </p>
{cartItems.map((item) => { {cartItems.map((item) => {
const product = productsById[item.productId] const product = productsById[item.productId]
if (!product) return null if (!product) return null
return ( return (
<div key={item.productId} className="flex items-center justify-between py-2"> <div key={item.productId} className="flex items-center justify-between py-2">
<MyText className="text-sm" numberOfLines={1}> <p className="text-sm">
{product.name} x{item.quantity} {product.name} x{item.quantity}
</MyText> </p>
<MyText className="text-sm font-bold"> <p className="text-sm font-bold">
{(product.discountedPrice ?? product.price) * item.quantity} {(product.discountedPrice ?? product.price) * item.quantity}
</MyText> </p>
</div> </div>
) )
})} })}
<div className="mt-2 border-t border-gray-200 pt-2"> <div className="mt-2 border-t border-gray-200 pt-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<MyText weight="bold">Total</MyText> <p className="font-bold">Total</p>
<MyText weight="bold" className="text-brand-600"> <p className="font-bold text-brand-600">
{total} {total}
</MyText> </p>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,5 +1,5 @@
import { createFileRoute, useNavigate, useSearch } from '@tanstack/react-router' import { createFileRoute, useNavigate, useSearch } from '@tanstack/react-router'
import { MyText, MyButton } from 'web-components' import { p, MyButton } from 'web-components'
import { Zap } from 'lucide-react' import { Zap } from 'lucide-react'
export const Route = createFileRoute('/flash/order-success')({ export const Route = createFileRoute('/flash/order-success')({
@ -19,11 +19,11 @@ function FlashOrderSuccessPage() {
<div className="mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-yellow-100"> <div className="mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-yellow-100">
<Zap className="h-10 w-10 text-yellow-600" /> <Zap className="h-10 w-10 text-yellow-600" />
</div> </div>
<MyText weight="bold" className="mb-2 text-2xl text-gray-900"> <p className="font-bold mb-2 text-2xl text-gray-900">
1 Hr Order Placed! 1 Hr Order Placed!
</MyText> </p>
<MyText className="mb-1 text-gray-600">Order ID: #{orderId}</MyText> <p className="mb-1 text-gray-600">Order ID: #{orderId}</p>
<MyText className="mb-8 text-gray-600">Total: {totalAmount}</MyText> <p className="mb-8 text-gray-600">Total: {totalAmount}</p>
<MyButton <MyButton
textContent="Continue Shopping" textContent="Continue Shopping"
onClick={() => navigate({ to: '/flash' })} onClick={() => navigate({ to: '/flash' })}

View file

@ -2,7 +2,7 @@ import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { useCentralProductStore } from '../lib/stores/central-product-store' import { useCentralProductStore } from '../lib/stores/central-product-store'
import { useAddToCart } from '../hooks/cart-query-hooks' import { useAddToCart } from '../hooks/cart-query-hooks'
import { useState } from 'react' import { useState } from 'react'
import { MyText, MyButton, Quantifier, AppContainer } from 'web-components' import { p, MyButton, Quantifier, AppContainer } from 'web-components'
import { ShoppingCart, Zap } from 'lucide-react' import { ShoppingCart, Zap } from 'lucide-react'
export const Route = createFileRoute('/flash/product/$id')({ export const Route = createFileRoute('/flash/product/$id')({
@ -29,7 +29,7 @@ function FlashProductDetailPage() {
if (!product) { if (!product) {
return ( return (
<AppContainer> <AppContainer>
<MyText>Product not found</MyText> <p>Product not found</p>
</AppContainer> </AppContainer>
) )
} }
@ -41,9 +41,9 @@ function FlashProductDetailPage() {
<AppContainer> <AppContainer>
<div className="mb-4 flex items-center gap-2"> <div className="mb-4 flex items-center gap-2">
<Zap className="h-5 w-5 text-yellow-500" /> <Zap className="h-5 w-5 text-yellow-500" />
<MyText className="text-sm font-semibold text-yellow-600"> <p className="text-sm font-semibold text-yellow-600">
1 Hr Delivery 1 Hr Delivery
</MyText> </p>
</div> </div>
{imageUrl && ( {imageUrl && (
@ -56,21 +56,21 @@ function FlashProductDetailPage() {
</div> </div>
)} )}
<MyText weight="bold" className="mb-1 text-xl"> <p className="font-bold mb-1 text-xl">
{product.name} {product.name}
</MyText> </p>
<MyText className="mb-4 text-sm text-gray-500"> <p className="mb-4 text-sm text-gray-500">
{product.unitValue}{product.unit} {product.unitValue}{product.unit}
</MyText> </p>
<div className="mb-4 flex items-baseline gap-2"> <div className="mb-4 flex items-baseline gap-2">
<MyText weight="bold" className="text-2xl text-brand-600"> <p className="font-bold text-2xl text-brand-600">
{price} {price}
</MyText> </p>
{product.discountedPrice && ( {product.discountedPrice && (
<MyText className="text-sm text-gray-400 line-through"> <p className="text-sm text-gray-400 line-through">
{product.price} {product.price}
</MyText> </p>
)} )}
</div> </div>

View file

@ -1,15 +1,15 @@
import { createFileRoute, useNavigate } from '@tanstack/react-router' import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { useState } from 'react' import { useState } from 'react'
import { import {
MyText, p,
MyButton, MyButton,
Quantifier, Quantifier,
AppContainer, div,
MyTouchableOpacity,
} from 'web-components' } from 'web-components'
import { useCentralProductStore } from '../lib/stores/central-product-store' import { useCentralProductStore } from '../lib/stores/central-product-store'
import { useCentralSlotStore } from '../lib/stores/central-slot-store' import { useCentralSlotStore } from '../lib/stores/central-slot-store'
import { useAddToCart } from '../hooks/cart-query-hooks' import { useAddToCart } from '../hooks/cart-query-hooks'
import { AppLayout } from '../components/AppLayout'
import { ShoppingCart, Zap } from 'lucide-react' import { ShoppingCart, Zap } from 'lucide-react'
export const Route = createFileRoute('/flash')({ component: FlashDeliveryPage }) export const Route = createFileRoute('/flash')({ component: FlashDeliveryPage })
@ -34,18 +34,19 @@ function FlashDeliveryPage() {
} }
return ( return (
<AppContainer> <AppLayout isFlashDelivery={true}>
<div className="p-4">
<div className="mb-4 flex items-center gap-2"> <div className="mb-4 flex items-center gap-2">
<Zap className="h-6 w-6 text-yellow-500" /> <Zap className="h-6 w-6 text-yellow-500" />
<MyText weight="bold" className="text-xl"> <p className="font-bold text-xl">
1 Hr Delivery 1 Hr Delivery
</MyText> </p>
</div> </div>
<div className="mb-4 rounded-xl bg-yellow-50 p-3"> <div className="mb-4 rounded-xl bg-yellow-50 p-3">
<MyText className="text-sm text-yellow-800"> <p className="text-sm text-yellow-800">
Get these products delivered within 1 hour! Only available for select items. Get these products delivered within 1 hour! Only available for select items.
</MyText> </p>
</div> </div>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
@ -66,12 +67,12 @@ function FlashDeliveryPage() {
/> />
)} )}
</div> </div>
<MyText weight="semibold" className="text-sm" numberOfLines={2}> <p className="font-semibold text-sm">
{product.name} {product.name}
</MyText> </p>
<MyText weight="bold" className="text-brand-600"> <p className="font-bold text-brand-600">
{price} {price}
</MyText> </p>
<div className="mt-2 flex items-center gap-2"> <div className="mt-2 flex items-center gap-2">
<Quantifier <Quantifier
value={qty} value={qty}
@ -96,9 +97,10 @@ function FlashDeliveryPage() {
{flashProducts.length === 0 && ( {flashProducts.length === 0 && (
<div className="py-20 text-center"> <div className="py-20 text-center">
<MyText className="text-gray-500">No flash delivery products available</MyText> <p className="text-gray-500">No flash delivery products available</p>
</div> </div>
)} )}
</AppContainer> </div>
</AppLayout>
) )
} }

View file

@ -1,7 +1,7 @@
import { createFileRoute, useNavigate } from '@tanstack/react-router' import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { useGetCart, useUpdateCartItem, useRemoveFromCart } from '../hooks/cart-query-hooks' import { useGetCart, useUpdateCartItem, useRemoveFromCart } from '../hooks/cart-query-hooks'
import { useAllProducts } from '../hooks/prominent-api-hooks' import { useAllProducts } from '../hooks/prominent-api-hooks'
import { MyText, MyButton, Quantifier, AppContainer, MyTouchableOpacity } from 'web-components' import { p, MyButton, Quantifier, AppContainer, div } from 'web-components'
import { Trash2 } from 'lucide-react' import { Trash2 } from 'lucide-react'
export const Route = createFileRoute('/home/cart')({ component: CartPage }) export const Route = createFileRoute('/home/cart')({ component: CartPage })
@ -29,13 +29,13 @@ function CartPage() {
return ( return (
<AppContainer> <AppContainer>
<MyText weight="bold" className="mb-4 text-xl"> <p className="font-bold mb-4 text-xl">
Your Cart Your Cart
</MyText> </p>
{cartItems.length === 0 ? ( {cartItems.length === 0 ? (
<div className="flex flex-col items-center gap-4 py-20"> <div className="flex flex-col items-center gap-4 py-20">
<MyText className="text-gray-500">Your cart is empty</MyText> <p className="text-gray-500">Your cart is empty</p>
<MyButton <MyButton
textContent="Browse Products" textContent="Browse Products"
onClick={() => navigate({ to: '/home' })} onClick={() => navigate({ to: '/home' })}
@ -58,20 +58,20 @@ function CartPage() {
className="h-16 w-16 rounded-lg object-cover" className="h-16 w-16 rounded-lg object-cover"
/> />
<div className="flex-1"> <div className="flex-1">
<MyText weight="semibold" className="text-sm" numberOfLines={1}> <p className="font-semibold text-sm">
{product.name} {product.name}
</MyText> </p>
<MyText className="text-brand-600 text-sm font-bold"> <p className="text-brand-600 text-sm font-bold">
{price} {price}
</MyText> </p>
<Quantifier <Quantifier
value={item.quantity} value={item.quantity}
setValue={(q) => updateItem.mutate({ productId: item.productId, quantity: q })} setValue={(q) => updateItem.mutate({ productId: item.productId, quantity: q })}
/> />
</div> </div>
<MyTouchableOpacity onClick={() => removeItem.mutate(item.productId)}> <div onClick={() => removeItem.mutate(item.productId)}>
<Trash2 className="h-5 w-5 text-red-500" /> <Trash2 className="h-5 w-5 text-red-500" />
</MyTouchableOpacity> </div>
</div> </div>
) )
})} })}
@ -79,10 +79,10 @@ function CartPage() {
<div className="fixed bottom-0 left-0 right-0 border-t border-gray-200 bg-white p-4 shadow-lg"> <div className="fixed bottom-0 left-0 right-0 border-t border-gray-200 bg-white p-4 shadow-lg">
<div className="mb-3 flex items-center justify-between"> <div className="mb-3 flex items-center justify-between">
<MyText weight="bold">Total</MyText> <p className="font-bold">Total</p>
<MyText weight="bold" className="text-lg text-brand-600"> <p className="font-bold text-lg text-brand-600">
{total} {total}
</MyText> </p>
</div> </div>
<MyButton <MyButton
fullWidth fullWidth

View file

@ -5,7 +5,7 @@ import { useGetCart } from '../hooks/cart-query-hooks'
import { useCentralProductStore } from '../lib/stores/central-product-store' import { useCentralProductStore } from '../lib/stores/central-product-store'
import { trpc } from '../lib/trpc-client' import { trpc } from '../lib/trpc-client'
import { clearLocalCart } from '../hooks/cart-query-hooks' import { clearLocalCart } from '../hooks/cart-query-hooks'
import { MyText, MyButton, LoadingDialog, AppContainer, MyTouchableOpacity } from 'web-components' import { p, MyButton, LoadingDialog, AppContainer, div } from 'web-components'
import { useQueryClient } from '@tanstack/react-query' import { useQueryClient } from '@tanstack/react-query'
export const Route = createFileRoute('/home/checkout')({ component: CheckoutPage }) export const Route = createFileRoute('/home/checkout')({ component: CheckoutPage })
@ -63,17 +63,17 @@ function CheckoutPage() {
return ( return (
<AppContainer> <AppContainer>
<MyText weight="bold" className="mb-4 text-xl"> <p className="font-bold mb-4 text-xl">
Checkout Checkout
</MyText> </p>
{/* Address Selection */} {/* Address Selection */}
<div className="mb-6"> <div className="mb-6">
<MyText weight="semibold" className="mb-2"> <p className="font-semibold mb-2">
Delivery Address Delivery Address
</MyText> </p>
{addresses?.data?.map((addr: any) => ( {addresses?.data?.map((addr: any) => (
<MyTouchableOpacity <div
key={addr.id} key={addr.id}
onClick={() => setSelectedAddressId(addr.id)} onClick={() => setSelectedAddressId(addr.id)}
className={`mb-2 rounded-xl border p-3 ${ className={`mb-2 rounded-xl border p-3 ${
@ -82,40 +82,40 @@ function CheckoutPage() {
: 'border-gray-200' : 'border-gray-200'
}`} }`}
> >
<MyText weight="semibold">{addr.name}</MyText> <p className="font-semibold">{addr.name}</p>
<MyText className="text-sm text-gray-600"> <p className="text-sm text-gray-600">
{addr.addressLine1}, {addr.city} {addr.addressLine1}, {addr.city}
</MyText> </p>
<MyText className="text-sm text-gray-500">{addr.phone}</MyText> <p className="text-sm text-gray-500">{addr.phone}</p>
</MyTouchableOpacity> </div>
))} ))}
</div> </div>
{/* Order Summary */} {/* Order Summary */}
<div className="mb-6"> <div className="mb-6">
<MyText weight="semibold" className="mb-2"> <p className="font-semibold mb-2">
Order Summary Order Summary
</MyText> </p>
{cartItems.map((item) => { {cartItems.map((item) => {
const product = productsById[item.productId] const product = productsById[item.productId]
if (!product) return null if (!product) return null
return ( return (
<div key={item.productId} className="flex items-center justify-between py-2"> <div key={item.productId} className="flex items-center justify-between py-2">
<MyText className="text-sm" numberOfLines={1}> <p className="text-sm">
{product.name} x{item.quantity} {product.name} x{item.quantity}
</MyText> </p>
<MyText className="text-sm font-bold"> <p className="text-sm font-bold">
{(product.discountedPrice ?? product.price) * item.quantity} {(product.discountedPrice ?? product.price) * item.quantity}
</MyText> </p>
</div> </div>
) )
})} })}
<div className="mt-2 border-t border-gray-200 pt-2"> <div className="mt-2 border-t border-gray-200 pt-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<MyText weight="bold">Total</MyText> <p className="font-bold">Total</p>
<MyText weight="bold" className="text-brand-600"> <p className="font-bold text-brand-600">
{total} {total}
</MyText> </p>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,5 +1,5 @@
import { createFileRoute, useNavigate, useSearch } from '@tanstack/react-router' import { createFileRoute, useNavigate, useSearch } from '@tanstack/react-router'
import { MyText, MyButton } from 'web-components' import { p, MyButton } from 'web-components'
import { Package } from 'lucide-react' import { Package } from 'lucide-react'
export const Route = createFileRoute('/home/order-success')({ export const Route = createFileRoute('/home/order-success')({
@ -19,15 +19,15 @@ function OrderSuccessPage() {
<div className="mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-green-100"> <div className="mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-green-100">
<Package className="h-10 w-10 text-green-600" /> <Package className="h-10 w-10 text-green-600" />
</div> </div>
<MyText weight="bold" className="mb-2 text-2xl text-gray-900"> <p className="font-bold mb-2 text-2xl text-gray-900">
Order Placed! Order Placed!
</MyText> </p>
<MyText className="mb-1 text-gray-600"> <p className="mb-1 text-gray-600">
Order ID: #{orderId} Order ID: #{orderId}
</MyText> </p>
<MyText className="mb-8 text-gray-600"> <p className="mb-8 text-gray-600">
Total: {totalAmount} Total: {totalAmount}
</MyText> </p>
<MyButton <MyButton
textContent="Continue Shopping" textContent="Continue Shopping"
onClick={() => navigate({ to: '/home' })} onClick={() => navigate({ to: '/home' })}

View file

@ -3,7 +3,7 @@ import { useCentralProductStore } from '../lib/stores/central-product-store'
import { trpc } from '../lib/trpc-client' import { trpc } from '../lib/trpc-client'
import { useAddToCart } from '../hooks/cart-query-hooks' import { useAddToCart } from '../hooks/cart-query-hooks'
import { useState } from 'react' import { useState } from 'react'
import { MyText, MyButton, Quantifier, AppContainer } from 'web-components' import { p, MyButton, Quantifier, AppContainer } from 'web-components'
import { ShoppingCart, Star } from 'lucide-react' import { ShoppingCart, Star } from 'lucide-react'
export const Route = createFileRoute('/home/product/$id')({ export const Route = createFileRoute('/home/product/$id')({
@ -35,7 +35,7 @@ function ProductDetailPage() {
if (!product) { if (!product) {
return ( return (
<AppContainer> <AppContainer>
<MyText>Product not found</MyText> <p>Product not found</p>
</AppContainer> </AppContainer>
) )
} }
@ -55,26 +55,26 @@ function ProductDetailPage() {
</div> </div>
)} )}
<MyText weight="bold" className="mb-1 text-xl"> <p className="font-bold mb-1 text-xl">
{product.name} {product.name}
</MyText> </p>
<MyText className="mb-2 text-sm text-gray-500"> <p className="mb-2 text-sm text-gray-500">
{product.unitValue}{product.unit} {product.unitValue}{product.unit}
</MyText> </p>
<div className="mb-4 flex items-baseline gap-2"> <div className="mb-4 flex items-baseline gap-2">
<MyText weight="bold" className="text-2xl text-brand-600"> <p className="font-bold text-2xl text-brand-600">
{price} {price}
</MyText> </p>
{product.discountedPrice && ( {product.discountedPrice && (
<MyText className="text-sm text-gray-400 line-through"> <p className="text-sm text-gray-400 line-through">
{product.price} {product.price}
</MyText> </p>
)} )}
</div> </div>
{product.description && ( {product.description && (
<MyText className="mb-4 text-gray-600">{product.description}</MyText> <p className="mb-4 text-gray-600">{product.description}</p>
)} )}
<div className="mb-6"> <div className="mb-6">
@ -94,9 +94,9 @@ function ProductDetailPage() {
{/* Reviews */} {/* Reviews */}
{reviews?.data && reviews.data.length > 0 && ( {reviews?.data && reviews.data.length > 0 && (
<div className="mt-8"> <div className="mt-8">
<MyText weight="bold" className="mb-3 text-lg"> <p className="font-bold mb-3 text-lg">
Reviews Reviews
</MyText> </p>
{reviews.data.map((review: any, i: number) => ( {reviews.data.map((review: any, i: number) => (
<div key={i} className="mb-3 rounded-lg border border-gray-100 p-3"> <div key={i} className="mb-3 rounded-lg border border-gray-100 p-3">
<div className="mb-1 flex items-center gap-1"> <div className="mb-1 flex items-center gap-1">
@ -104,7 +104,7 @@ function ProductDetailPage() {
<Star key={j} className="h-3 w-3 fill-yellow-400 text-yellow-400" /> <Star key={j} className="h-3 w-3 fill-yellow-400 text-yellow-400" />
))} ))}
</div> </div>
<MyText className="text-sm text-gray-600">{review.comment}</MyText> <p className="text-sm text-gray-600">{review.comment}</p>
</div> </div>
))} ))}
</div> </div>

View file

@ -2,7 +2,7 @@ import { createFileRoute, useNavigate, useSearch } from '@tanstack/react-router'
import { useState, useMemo } from 'react' import { useState, useMemo } from 'react'
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import { useCentralProductStore } from '../lib/stores/central-product-store' import { useCentralProductStore } from '../lib/stores/central-product-store'
import { MyText, SearchBar, AppContainer, MyTouchableOpacity } from 'web-components' import { p, SearchBar, AppContainer, div } from 'web-components'
export const Route = createFileRoute('/home/search')({ export const Route = createFileRoute('/home/search')({
component: SearchPage, component: SearchPage,
@ -41,7 +41,7 @@ function SearchPage() {
<div className="mt-4 grid grid-cols-2 gap-3"> <div className="mt-4 grid grid-cols-2 gap-3">
{results.map((product) => ( {results.map((product) => (
<MyTouchableOpacity <div
key={product.id} key={product.id}
onClick={() => onClick={() =>
navigate({ to: '/home/product/$id', params: { id: String(product.id) } }) navigate({ to: '/home/product/$id', params: { id: String(product.id) } })
@ -57,13 +57,13 @@ function SearchPage() {
/> />
)} )}
</div> </div>
<MyText weight="semibold" className="text-sm" numberOfLines={2}> <p className="font-semibold text-sm">
{product.name} {product.name}
</MyText> </p>
<MyText weight="bold" className="mt-1 text-brand-600"> <p className="font-bold mt-1 text-brand-600">
{product.discountedPrice ?? product.price} {product.discountedPrice ?? product.price}
</MyText> </p>
</MyTouchableOpacity> </div>
))} ))}
</div> </div>
</AppContainer> </AppContainer>

View file

@ -1,21 +1,27 @@
import { createFileRoute, useNavigate } from '@tanstack/react-router' import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { useState, useEffect } from 'react' import { useState, useEffect, useMemo } from 'react'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { import {
MyText, p,
MyTouchableOpacity, div,
SearchBar, SearchBar,
AppContainer,
} from 'web-components' } from 'web-components'
import { import {
useAllProducts, useAllProducts,
useStores, useStores,
useBanners, useBanners,
useSlots,
useGetEssentialConsts,
} from '../hooks/prominent-api-hooks' } from '../hooks/prominent-api-hooks'
import { useGetCart } from '../hooks/cart-query-hooks'
import { useCartStore } from '../lib/stores/cart-store' import { useCartStore } from '../lib/stores/cart-store'
import { useCentralSlotStore } from '../lib/stores/central-slot-store'
import { useCentralProductStore } from '../lib/stores/central-product-store'
import { AppLayout } from '../components/AppLayout'
import { ProductCard } from '../components/ProductCard'
import AddToCartDialog from '../components/AddToCartDialog' import AddToCartDialog from '../components/AddToCartDialog'
import { ShoppingCart, Truck } from 'lucide-react' import { useProductSlotIdentifier } from '../hooks/useProductSlotIdentifier'
import { usePopulateCentralStores } from '../hooks/usePopulateCentralStores'
import { Store, ImageOff } from 'lucide-react'
export const Route = createFileRoute('/home')({ component: HomePage }) export const Route = createFileRoute('/home')({ component: HomePage })
@ -24,45 +30,118 @@ function HomePage() {
const { data: productsData } = useAllProducts() const { data: productsData } = useAllProducts()
const { data: storesData } = useStores() const { data: storesData } = useStores()
const { data: bannersData } = useBanners() const { data: bannersData } = useBanners()
const { data: slotsData } = useSlots()
const { data: essentialConsts } = useGetEssentialConsts()
const { setAddedToCartProduct } = useCartStore() const { setAddedToCartProduct } = useCartStore()
const { getQuickestSlot } = useProductSlotIdentifier()
const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap)
// Populate central stores with slots and product data
usePopulateCentralStores()
const stores = storesData?.stores || [] const stores = storesData?.stores || []
const banners = bannersData?.banners || [] const banners = bannersData?.banners || []
const allProducts = productsData?.products || [] const allProducts = productsData?.products || []
const slots = slotsData?.slots || []
// Sort products: in-stock first, then by slot availability
const sortedProducts = useMemo(() => {
return [...allProducts]
.filter((p) => typeof p.id === 'number')
.sort((a, b) => {
const slotA = getQuickestSlot(a.id)
const slotB = getQuickestSlot(b.id)
if (slotA && !slotB) return -1
if (!slotA && slotB) return 1
const aOutOfStock = productSlotsMap[a.id]?.isOutOfStock
const bOutOfStock = productSlotsMap[b.id]?.isOutOfStock
if (aOutOfStock && !bOutOfStock) return 1
if (!aOutOfStock && bOutOfStock) return -1
return 0
})
}, [allProducts, productSlotsMap, getQuickestSlot])
// Get popular products from essential consts
const popularItemIds = useMemo(() => {
const popularItems = essentialConsts?.popularItems
if (!popularItems) return []
if (Array.isArray(popularItems)) {
return popularItems.map((id: any) => parseInt(id)).filter((id: number) => !isNaN(id))
} else if (typeof popularItems === 'string') {
return popularItems
.split(',')
.map((id: string) => parseInt(id.trim()))
.filter((id: number) => !isNaN(id))
}
return []
}, [essentialConsts?.popularItems])
const popularProducts = useMemo(() => {
return popularItemIds
.map((id) => allProducts.find((product) => product.id === id))
.filter((product): product is NonNullable<typeof product> => product != null)
}, [popularItemIds, allProducts])
// Sort slots by delivery time
const sortedSlots = useMemo(() => {
const now = dayjs()
return [...slots]
.filter((slot) => dayjs(slot.deliveryTime).isAfter(now))
.sort((a, b) => {
const deliveryDiff = dayjs(a.deliveryTime).diff(dayjs(b.deliveryTime))
if (deliveryDiff !== 0) return deliveryDiff
return dayjs(a.freezeTime).diff(dayjs(b.freezeTime))
})
}, [slots])
const handleProductPress = (id: number) => {
navigate({
to: '/home/product/$id',
params: { id: String(id) },
})
}
const handleAddToCart = (product: any) => { const handleAddToCart = (product: any) => {
setAddedToCartProduct({ productId: product.id, product }) setAddedToCartProduct({ productId: product.id, product })
} }
return ( return (
<div className="mx-auto min-h-screen max-w-7xl bg-white"> <AppLayout>
<div className="min-h-screen bg-white pb-24">
{/* Search Bar */} {/* Search Bar */}
<div className="sticky top-0 z-10 bg-white/95 backdrop-blur-sm px-4 md:px-6 lg:px-8 pt-4 pb-3 border-b border-gray-100"> <div className="sticky top-0 z-10 border-b border-gray-100 bg-white/95 px-4 pb-3 pt-4 backdrop-blur-sm">
<SearchBar <SearchBar
placeholder="Search products here..." placeholder="Search products here..."
onSearch={(q) => navigate({ to: '/home/search', search: { q } })} onSearch={(q) => navigate({ to: '/home/search', search: { q } })}
/> />
</div> </div>
<div className="px-4 md:px-6 lg:px-8"> <div className="px-4">
{/* Banner Carousel */} {/* Banner Carousel */}
{banners.length > 0 && ( {banners.length > 0 && (
<div className="mt-4 mb-8 overflow-hidden rounded-xl"> <div className="mb-6 mt-4 overflow-hidden rounded-xl">
<BannerCarousel banners={banners} /> <BannerCarousel banners={banners} />
</div> </div>
)} )}
{/* Stores Section */} {/* Stores Section */}
<div className="mb-8"> {stores.length > 0 && (
<div className="flex items-center justify-between mb-4"> <div className="mb-6">
<MyText weight="bold" className="text-lg md:text-xl"> <div className="mb-4 flex items-center justify-between">
<div>
<p className="font-bold text-xl text-gray-900">
Our Stores Our Stores
</MyText> </p>
<p className="mt-0.5 text-xs text-gray-500">
Fresh from our locations
</p>
</div> </div>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-3 md:gap-4"> </div>
<div className="flex flex-wrap gap-4">
{stores.map((store: any) => ( {stores.map((store: any) => (
<div key={store.id}>
<StoreCard <StoreCard
key={store.id}
store={store} store={store}
onClick={() => onClick={() =>
navigate({ navigate({
@ -71,39 +150,93 @@ function HomePage() {
}) })
} }
/> />
</div>
))} ))}
</div> </div>
</div> </div>
)}
{/* Products Section */} {/* Popular Items Section */}
<div className="mb-24"> {popularProducts.length > 0 && (
<div className="flex items-center justify-between mb-4"> <div className="mb-6">
<MyText weight="bold" className="text-lg md:text-xl"> <div className="mb-4">
All Products <p className="font-bold text-xl text-gray-900">
</MyText> Popular Items
</p>
<p className="mt-0.5 text-sm text-gray-500">
Trending fresh picks just for you
</p>
</div> </div>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-3 md:gap-4"> <div className="grid gap-4" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))' }}>
{allProducts.slice(0, 30).map((product) => ( {popularProducts.map((product) => (
<ProductCard <ProductCard
key={product.id} key={product.id}
product={product} item={product}
onClick={() => onPress={() => handleProductPress(product.id)}
navigate({ showDeliveryInfo={false}
to: '/home/product/$id', miniView={true}
params: { id: String(product.id) }, useAddToCartDialog={true}
})
}
onAddToCart={() => handleAddToCart(product)}
/> />
))} ))}
</div> </div>
</div> </div>
)}
{/* Upcoming Delivery Slots Section */}
{sortedSlots.length > 0 && (
<div className="mb-6">
<div className="mb-4">
<p className="font-bold text-xl text-gray-900">
Upcoming Delivery Slots
</p>
<p className="mt-0.5 text-sm text-gray-500">
Plan your fresh deliveries ahead
</p>
</div>
<div className="scrollbar-hide -mx-4 flex gap-4 overflow-x-auto px-4 pb-2">
{sortedSlots.slice(0, 5).map((slot) => (
<SlotCard key={slot.id} slot={slot} />
))}
</div>
</div>
)}
{/* All Products Section */}
<div className="rounded-t-3xl bg-white pt-4">
<div className="mb-4">
<p className="font-bold text-xl text-gray-900">
All Available Products
</p>
<p className="mt-0.5 text-sm text-gray-500">
Browse our complete selection
</p>
</div>
{/* Responsive Grid for Products - evenly distributed */}
<div className="grid gap-4" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))' }}>
{sortedProducts.map((product) => (
<ProductCard
key={product.id}
item={product}
onPress={() => handleProductPress(product.id)}
showDeliveryInfo={true}
miniView={false}
useAddToCartDialog={true}
/>
))}
</div>
{sortedProducts.length === 0 && (
<div className="py-8 text-center">
<p className="text-gray-500">No products available</p>
</div>
)}
</div>
</div> </div>
{/* Floating Cart Bar */}
<FloatingCartBar onClick={() => navigate({ to: '/cart' })} />
<AddToCartDialog /> <AddToCartDialog />
</div> </div>
</AppLayout>
) )
} }
@ -122,31 +255,32 @@ function BannerCarousel({ banners }: { banners: any[] }) {
if (images.length === 0) return null if (images.length === 0) return null
return ( return (
<div className="relative group"> <div className="flex justify-center">
<div className="group relative inline-block">
<img <img
src={images[index]} src={images[index]}
alt="Banner" alt="Banner"
className="h-36 sm:h-44 md:h-52 lg:h-64 w-full rounded-xl object-cover transition-all duration-500" className="max-h-[420px] w-auto max-w-full rounded-xl object-contain transition-all duration-500"
/> />
{images.length > 1 && ( {images.length > 1 && (
<> <>
<button <button
onClick={() => setIndex((i) => (i - 1 + images.length) % images.length)} onClick={() => setIndex((i) => (i - 1 + images.length) % images.length)}
className="absolute left-2 top-1/2 -translate-y-1/2 bg-black/30 hover:bg-black/50 text-white rounded-full p-1.5 opacity-0 group-hover:opacity-100 transition-opacity" className="absolute left-2 top-1/2 z-10 -translate-y-1/2 rounded-full bg-black/50 p-2 text-white transition-all hover:bg-black/70"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg> </svg>
</button> </button>
<button <button
onClick={() => setIndex((i) => (i + 1) % images.length)} onClick={() => setIndex((i) => (i + 1) % images.length)}
className="absolute right-2 top-1/2 -translate-y-1/2 bg-black/30 hover:bg-black/50 text-white rounded-full p-1.5 opacity-0 group-hover:opacity-100 transition-opacity" className="absolute right-2 top-1/2 z-10 -translate-y-1/2 rounded-full bg-black/50 p-2 text-white transition-all hover:bg-black/70"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg> </svg>
</button> </button>
<div className="absolute bottom-3 left-1/2 flex -translate-x-1/2 gap-1.5"> <div className="absolute bottom-3 left-1/2 z-10 flex -translate-x-1/2 gap-1.5">
{images.map((_: any, i: number) => ( {images.map((_: any, i: number) => (
<button <button
key={i} key={i}
@ -160,16 +294,17 @@ function BannerCarousel({ banners }: { banners: any[] }) {
</> </>
)} )}
</div> </div>
</div>
) )
} }
function StoreCard({ store, onClick }: { store: any; onClick: () => void }) { function StoreCard({ store, onClick }: { store: any; onClick: () => void }) {
return ( return (
<MyTouchableOpacity <div
onClick={onClick} onClick={onClick}
className="rounded-xl border border-gray-100 bg-white p-3 shadow-sm hover:shadow-md transition-shadow" className="flex flex-col items-center"
> >
<div className="mb-2 aspect-square w-full overflow-hidden rounded-lg bg-gray-100"> <div className="mb-2 flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border-2 border-white/30 bg-gray-100 shadow-lg">
{store.signedImageUrl ? ( {store.signedImageUrl ? (
<img <img
src={store.signedImageUrl} src={store.signedImageUrl}
@ -177,152 +312,110 @@ function StoreCard({ store, onClick }: { store: any; onClick: () => void }) {
className="h-full w-full object-cover" className="h-full w-full object-cover"
/> />
) : ( ) : (
<div className="flex h-full items-center justify-center text-gray-400"> <Store className="h-7 w-7 text-gray-400" />
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
</div>
)} )}
</div> </div>
<MyText weight="semibold" className="text-sm truncate"> <p className="font-bold text-center text-xs tracking-wide text-gray-800">
{store.name} {store.name.replace(/^The\s+/i, '')}
</MyText> </p>
<MyText className="text-xs text-gray-500"> </div>
{store.productCount || 0} products
</MyText>
</MyTouchableOpacity>
) )
} }
function ProductCard({ function SlotCard({ slot }: { slot: any }) {
product, const navigate = useNavigate()
onClick, const now = dayjs()
onAddToCart, const freezeTime = dayjs(slot.freezeTime)
}: { const isClosingSoon = freezeTime.diff(now, 'hour') < 4 && freezeTime.isAfter(now)
product: any
onClick: () => void const formatTimeRange = (deliveryTime: string) => {
onAddToCart?: () => void const time = dayjs(deliveryTime)
}) { const endTime = time.add(1, 'hour')
const imageUrl = product.images?.[0] const startPeriod = time.format('A')
const hasDiscount = const endPeriod = endTime.format('A')
product.marketPrice != null && product.marketPrice > product.price
if (startPeriod === endPeriod) {
return `${time.format('h')}-${endTime.format('h')} ${startPeriod}`
} else {
return `${time.format('h:mm')} ${startPeriod} - ${endTime.format('h:mm')} ${endPeriod}`
}
}
return ( return (
<div className="rounded-xl border border-gray-100 bg-white p-3 shadow-sm hover:shadow-md transition-shadow"> <div
<MyTouchableOpacity onClick={onClick}> onClick={() => navigate({ to: '/slot-view', search: { slotId: slot.id } })}
<div className="mb-2 aspect-square w-full overflow-hidden rounded-lg bg-gray-100"> className={`min-w-70 shrink-0 cursor-pointer rounded-3xl border border-slate-100 bg-white p-5 shadow-xl ${
{imageUrl ? ( isClosingSoon ? 'border-l-4 border-l-amber-400' : 'border-l-4 border-l-brand-500'
<img }`}
src={imageUrl}
alt={product.name}
className="h-full w-full object-cover"
/>
) : (
<div className="flex h-full items-center justify-center text-gray-300">
<svg className="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
)}
</div>
<MyText weight="semibold" className="text-sm leading-tight line-clamp-2 mb-1">
{product.name}
</MyText>
<div className="flex items-baseline gap-1.5">
<MyText weight="bold" className="text-brand-600 text-sm md:text-base">
{product.price}
</MyText>
{hasDiscount && (
<MyText className="text-xs text-gray-400 line-through">
{product.marketPrice}
</MyText>
)}
</div>
<MyText className="text-[11px] text-gray-400 mb-2">
/{product.unit}
</MyText>
{product.nextDeliveryDate && (
<div className="mb-3 flex items-center gap-1 self-start rounded-lg bg-brand-50 px-2 py-1 border border-brand-100">
<Truck className="h-3 w-3 text-brand-600" />
<MyText className="text-[10px] font-bold text-brand-700">
{dayjs(product.nextDeliveryDate).format('ddd, DD MMM • h:mm A')}
</MyText>
</div>
)}
</MyTouchableOpacity>
{onAddToCart && (
<button
onClick={(e) => {
e.stopPropagation()
onAddToCart()
}}
className="flex w-full items-center justify-center gap-2 rounded-lg bg-brand-500 py-2 text-sm font-bold text-white hover:bg-brand-600 transition-colors"
> >
<ShoppingCart className="h-4 w-4" /> <div className="mb-4 flex flex-row items-start justify-end">
Add to Cart {isClosingSoon && (
</button> <div className="flex flex-row items-center rounded-md border border-amber-100 bg-amber-50 px-2 py-0.5">
<div className="mr-1 h-1 w-1 rounded-full bg-amber-500" />
<p className="text-[10px] font-bold text-amber-700">CLOSING SOON</p>
</div>
)} )}
</div> </div>
)
}
function FloatingCartBar({ onClick }: { onClick: () => void }) { <div className="mb-5 flex flex-row justify-between">
const { data: cartData } = useGetCart('regular') <div className="mr-4 flex-1">
const { data: productsData } = useAllProducts() <div className="mb-1.5 flex flex-row items-center">
const products = productsData?.products || [] <div className="mr-1.5 rounded-md bg-brand-50 p-1">
const productsById: Record<number, any> = {} <svg className="h-3 w-3 text-brand-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
products.forEach((p: any) => { productsById[p.id] = p }) <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" />
const cartItems = cartData?.items || [] </svg>
const itemCount = cartItems.length </div>
<p className="text-[10px] font-bold uppercase text-brand-700">Delivery At</p>
</div>
<p className="font-bold text-sm text-slate-900">
{formatTimeRange(slot.deliveryTime)}
</p>
<p className="text-[11px] font-bold text-slate-500">
{dayjs(slot.deliveryTime).format('ddd, MMM DD')}
</p>
</div>
const totalCartValue = cartItems.reduce((sum, item) => {
const product = productsById[item.productId]
const price = product?.price ?? 0
return sum + price * item.quantity
}, 0)
const freeDeliveryThreshold = 149
const remainingForFreeDelivery = Math.max(0, freeDeliveryThreshold - totalCartValue)
return (
<div className="fixed bottom-0 left-0 right-0 z-20 bg-brand-600 px-4 py-2 md:px-6 md:py-3 shadow-lg">
<div className="mx-auto flex max-w-7xl items-center justify-between">
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center gap-2"> <div className="mb-1.5 flex flex-row items-center">
<MyText weight="bold" className="text-sm text-white"> <div className="mr-1.5 rounded-md bg-amber-50 p-1">
{totalCartValue} <svg className="h-3 w-3 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
</MyText> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
<MyText className="text-xs text-white/80">
{itemCount === 0
? 'No items in cart'
: `${itemCount} ${itemCount === 1 ? 'item' : 'items'}`}
</MyText>
</div>
{remainingForFreeDelivery > 0 ? (
<MyText className="text-[10px] font-bold text-white/70">
{remainingForFreeDelivery} more for FREE Delivery
</MyText>
) : itemCount > 0 ? (
<div className="flex items-center gap-1">
<svg className="h-3 w-3 text-emerald-300" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
</svg> </svg>
<MyText className="text-[10px] font-bold text-emerald-300">
Free Delivery Unlocked
</MyText>
</div> </div>
<p className="text-[10px] font-bold uppercase text-amber-700">Order By</p>
</div>
<p className="font-bold text-sm text-slate-900">
{dayjs(slot.freezeTime).format('h:mm A')}
</p>
<p className="text-[11px] font-bold text-slate-500">
{dayjs(slot.freezeTime).format('ddd, MMM DD')}
</p>
</div>
</div>
<div className="flex flex-row items-center">
<div className="mr-3 flex flex-row">
{slot.products?.slice(0, 3).map((p: any, i: number) => (
<div
key={p.id}
className={`h-8 w-8 overflow-hidden rounded-full border-2 border-white bg-slate-100 ${i > 0 ? '-ml-3' : ''}`}
>
{p.images?.[0] ? (
<img src={p.images[0]} alt="" className="h-full w-full object-cover" />
) : ( ) : (
<MyText className="text-[10px] text-white/50"> <div className="flex h-full items-center justify-center">
Shop for {freeDeliveryThreshold}+ for free shipping <ImageOff className="h-3.5 w-3.5 text-slate-400" />
</MyText> </div>
)} )}
</div> </div>
<button ))}
onClick={onClick} </div>
className="rounded-full bg-white px-4 py-2 text-sm font-bold text-brand-600 shadow-md hover:bg-gray-100 transition-colors" <p className="text-[11px] font-bold text-brand-600">
> View all {slot.products?.length || 0} items
Go to Cart </p>
</button> <svg className="ml-1 h-4 w-4 text-brand-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</div> </div>
</div> </div>
) )

View file

@ -3,7 +3,7 @@ import { useState, useEffect, useRef } from 'react'
import { useForm, Controller } from 'react-hook-form' import { useForm, Controller } from 'react-hook-form'
import { useAuth } from '../lib/auth-context' import { useAuth } from '../lib/auth-context'
import { trpc } from '../lib/trpc-client' import { trpc } from '../lib/trpc-client'
import { MyText, MyButton, MyTextInput, MyTouchableOpacity } from 'web-components' import { p, MyButton, pInput, div } from 'web-components'
export const Route = createFileRoute('/login')({ component: LoginPage }) export const Route = createFileRoute('/login')({ component: LoginPage })
@ -126,12 +126,12 @@ function LoginPage() {
return ( return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-b from-brand-400 to-brand-700 p-4"> <div className="flex min-h-screen items-center justify-center bg-gradient-to-b from-brand-400 to-brand-700 p-4">
<div className="w-full max-w-md"> <div className="w-full max-w-md">
<MyText weight="bold" className="mb-2 text-center text-4xl text-white"> <p className="font-bold mb-2 text-center text-4xl text-white">
Welcome Welcome
</MyText> </p>
<MyText className="mb-8 text-center text-lg text-blue-100"> <p className="mb-8 text-center text-lg text-blue-100">
Sign in to continue your journey Sign in to continue your journey
</MyText> </p>
<div className="rounded-2xl bg-white p-8 shadow-xl"> <div className="rounded-2xl bg-white p-8 shadow-xl">
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
@ -140,7 +140,7 @@ function LoginPage() {
control={control} control={control}
name="mobile" name="mobile"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<MyTextInput <pInput
placeholder="Enter your mobile number" placeholder="Enter your mobile number"
value={value} value={value}
onChange={(e) => { onChange={(e) => {
@ -155,9 +155,9 @@ function LoginPage() {
{step === 'otp' && ( {step === 'otp' && (
<div className="mb-6"> <div className="mb-6">
<MyText weight="semibold" className="mb-3 text-center text-base text-gray-800"> <p className="font-semibold mb-3 text-center text-base text-gray-800">
Enter 4-digit OTP Enter 4-digit OTP
</MyText> </p>
<div className="flex justify-center gap-2"> <div className="flex justify-center gap-2">
{[0, 1, 2, 3].map((i) => ( {[0, 1, 2, 3].map((i) => (
<input <input
@ -177,22 +177,21 @@ function LoginPage() {
))} ))}
</div> </div>
<div className="mt-4 flex items-center justify-between border-t border-gray-100 pt-4"> <div className="mt-4 flex items-center justify-between border-t border-gray-100 pt-4">
<MyTouchableOpacity <div
onClick={() => { setStep('choice'); setOtpCells(['', '', '', '']) }} onClick={() => { setStep('choice'); setOtpCells(['', '', '', '']) }}
> >
<MyText weight="medium" className="text-gray-500">Back</MyText> <p className="font-medium text-gray-500">Back</p>
</MyTouchableOpacity> </div>
<MyTouchableOpacity <div
onClick={() => sendOtpMutation.mutate({ mobile: selectedMobile })} onClick={() => sendOtpMutation.mutate({ mobile: selectedMobile })}
disabled={!canResend} disabled={!canResend}
> >
<MyText <p
weight="semibold" className={`font-semibold ${canResend ? 'text-brand-600' : 'text-gray-400'}`}
className={canResend ? 'text-brand-600' : 'text-gray-400'}
> >
{canResend ? 'Resend OTP' : `Resend in ${resendCountdown}s`} {canResend ? 'Resend OTP' : `Resend in ${resendCountdown}s`}
</MyText> </p>
</MyTouchableOpacity> </div>
</div> </div>
</div> </div>
)} )}
@ -202,7 +201,7 @@ function LoginPage() {
control={control} control={control}
name="password" name="password"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<MyTextInput <pInput
placeholder="Enter your password" placeholder="Enter your password"
value={value} value={value}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
@ -235,14 +234,14 @@ function LoginPage() {
</form> </form>
{step === 'otp' && ( {step === 'otp' && (
<MyTouchableOpacity <div
onClick={() => { setStep('password'); setOtpCells(['', '', '', '']) }} onClick={() => { setStep('password'); setOtpCells(['', '', '', '']) }}
className="mt-4 block text-center" className="mt-4 block text-center"
> >
<MyText weight="semibold" className="text-brand-600"> <p className="font-semibold text-brand-600">
Or login with Password Or login with Password
</MyText> </p>
</MyTouchableOpacity> </div>
)} )}
</div> </div>
</div> </div>

View file

@ -1,5 +1,5 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from '@tanstack/react-router'
import { MyText, AppContainer } from 'web-components' import { p, AppContainer } from 'web-components'
import { Heart, Shield, Truck, Leaf } from 'lucide-react' import { Heart, Shield, Truck, Leaf } from 'lucide-react'
export const Route = createFileRoute('/me/about')({ component: AboutPage }) export const Route = createFileRoute('/me/about')({ component: AboutPage })
@ -30,9 +30,9 @@ function AboutPage() {
return ( return (
<AppContainer> <AppContainer>
<MyText weight="bold" className="mb-6 text-2xl"> <p className="font-bold mb-6 text-2xl">
About Freshyo About Freshyo
</MyText> </p>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
{missions.map((mission) => ( {missions.map((mission) => (
@ -41,23 +41,23 @@ function AboutPage() {
className="rounded-xl border border-gray-100 bg-white p-4 shadow-sm" className="rounded-xl border border-gray-100 bg-white p-4 shadow-sm"
> >
<mission.icon className="mb-2 h-8 w-8 text-brand-500" /> <mission.icon className="mb-2 h-8 w-8 text-brand-500" />
<MyText weight="semibold" className="mb-1"> <p className="font-semibold mb-1">
{mission.title} {mission.title}
</MyText> </p>
<MyText className="text-sm text-gray-600">{mission.desc}</MyText> <p className="text-sm text-gray-600">{mission.desc}</p>
</div> </div>
))} ))}
</div> </div>
<div className="mt-8"> <div className="mt-8">
<MyText weight="bold" className="mb-3 text-lg"> <p className="font-bold mb-3 text-lg">
Sourcing & Quality Sourcing & Quality
</MyText> </p>
<MyText className="mb-6 text-sm leading-relaxed text-gray-600"> <p className="mb-6 text-sm leading-relaxed text-gray-600">
We partner with trusted local farmers who follow ethical and sustainable We partner with trusted local farmers who follow ethical and sustainable
farming practices. Every product undergoes rigorous quality checks to ensure farming practices. Every product undergoes rigorous quality checks to ensure
you receive only the freshest meat. you receive only the freshest meat.
</MyText> </p>
</div> </div>
</AppContainer> </AppContainer>
) )

View file

@ -1,6 +1,6 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from '@tanstack/react-router'
import { trpc } from '../lib/trpc-client' import { trpc } from '../lib/trpc-client'
import { MyText, MyButton, AppContainer } from 'web-components' import { p, MyButton, AppContainer } from 'web-components'
import { MapPin, Plus } from 'lucide-react' import { MapPin, Plus } from 'lucide-react'
import { useState } from 'react' import { useState } from 'react'
@ -16,14 +16,14 @@ function AddressesPage() {
return ( return (
<AppContainer> <AppContainer>
<MyText weight="bold" className="mb-4 text-xl"> <p className="font-bold mb-4 text-xl">
My Addresses My Addresses
</MyText> </p>
{addresses.length === 0 ? ( {addresses.length === 0 ? (
<div className="flex flex-col items-center gap-4 py-20"> <div className="flex flex-col items-center gap-4 py-20">
<MapPin className="h-12 w-12 text-gray-300" /> <MapPin className="h-12 w-12 text-gray-300" />
<MyText className="text-gray-500">No addresses saved</MyText> <p className="text-gray-500">No addresses saved</p>
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
@ -34,15 +34,15 @@ function AddressesPage() {
> >
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div className="flex-1"> <div className="flex-1">
<MyText weight="semibold">{addr.name}</MyText> <p className="font-semibold">{addr.name}</p>
<MyText className="text-sm text-gray-600"> <p className="text-sm text-gray-600">
{addr.addressLine1} {addr.addressLine1}
{addr.addressLine2 ? `, ${addr.addressLine2}` : ''} {addr.addressLine2 ? `, ${addr.addressLine2}` : ''}
</MyText> </p>
<MyText className="text-sm text-gray-600"> <p className="text-sm text-gray-600">
{addr.city}, {addr.state} - {addr.pincode} {addr.city}, {addr.state} - {addr.pincode}
</MyText> </p>
<MyText className="text-sm text-gray-500">{addr.phone}</MyText> <p className="text-sm text-gray-500">{addr.phone}</p>
{addr.isDefault && ( {addr.isDefault && (
<span className="mt-1 inline-block rounded-full bg-brand-100 px-2 py-0.5 text-xs text-brand-700"> <span className="mt-1 inline-block rounded-full bg-brand-100 px-2 py-0.5 text-xs text-brand-700">
Default Default

View file

@ -1,116 +1,179 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { trpc } from '../lib/trpc-client' import { trpc } from '../lib/trpc-client'
import { p, AppContainer, div } from 'web-components'
import { Phone, Mail, Headphones, CheckCircle, Clock, AlertCircle, ThumbsUp, MessageSquare } from 'lucide-react'
import { useGetEssentialConsts } from '../hooks/prominent-api-hooks'
import dayjs from 'dayjs'
import { Dialog } from '../components/Dialog'
import { useState } from 'react' import { useState } from 'react'
import {
MyText,
MyButton,
MyTextInput,
AppContainer,
MyTouchableOpacity,
} from 'web-components'
import { MessageSquare, Plus } from 'lucide-react'
export const Route = createFileRoute('/me/complaints')({ component: ComplaintsPage }) export const Route = createFileRoute('/me/complaints')({ component: ComplaintsPage })
function ComplaintsPage() { interface ComplaintItemProps {
const { data } = trpc.user.complaint.getAll.useQuery() item: any
const raiseMutation = trpc.user.complaint.raise.useMutation() }
const utils = trpc.useUtils()
const [showForm, setShowForm] = useState(false)
const [body, setBody] = useState('')
const complaints = data?.data || []
const handleSubmit = () => { function ComplaintItem({ item }: ComplaintItemProps) {
if (!body.trim()) return return (
raiseMutation.mutate( <div className="mb-4 rounded-2xl border border-gray-100 bg-white p-5 shadow-sm">
{ body: body.trim() }, {/* Header: ID, Date, Status */}
{ <div className="mb-3 flex flex-row items-start justify-between">
onSuccess: () => { <div>
setBody('') <div className="flex flex-row items-center">
setShowForm(false) <p className="text-base font-bold text-gray-900">Complaint #{item.id}</p>
utils.user.complaint.getAll.invalidate() {item.orderId && (
}, <div className="ml-2 rounded bg-gray-100 px-2 py-0.5">
<p className="text-[10px] font-bold text-gray-500">Order #{item.orderId}</p>
</div>
)}
</div>
<p className="mt-1 text-xs text-gray-400">
{dayjs(item.createdAt).format('MMM DD, YYYY • h:mm A')}
</p>
</div>
<div
className={`rounded-full px-3 py-1 ${
item.isResolved ? 'bg-green-100' : 'bg-amber-100'
}`}
>
<p
className={`text-xs font-bold ${
item.isResolved ? 'text-green-700' : 'text-amber-700'
}`}
>
{item.isResolved ? 'Resolved' : 'Pending'}
</p>
</div>
</div>
{/* Complaint Body */}
<p className="mb-4 text-sm leading-6 text-gray-700">{item.complaintBody}</p>
{/* Admin Response */}
{item.response && (
<div className="rounded-xl border border-blue-100 bg-blue-50 p-4">
<div className="mb-2 flex flex-row items-center">
<div className="mr-2 flex h-6 w-6 items-center justify-center rounded-full bg-blue-100">
<Headphones className="h-3.5 w-3.5 text-blue-600" />
</div>
<p className="text-xs font-bold uppercase tracking-wide text-blue-800">Support Response</p>
</div>
<p className="text-sm leading-5 text-blue-900">{item.response}</p>
</div>
)}
{!item.response && !item.isResolved && (
<div className="mt-2 flex flex-row items-center">
<Clock className="h-3.5 w-3.5 text-gray-400" />
<p className="ml-1 text-xs italic text-gray-400">We are reviewing your complaint...</p>
</div>
)}
</div>
)
}
function ComplaintsPage() {
const navigate = useNavigate()
const { data, isLoading, error, refetch } = trpc.user.complaint.getAll.useQuery()
const complaints = data?.complaints || []
const { data: constsData } = useGetEssentialConsts()
const [showContactDialog, setShowContactDialog] = useState(false)
if (isLoading) {
return (
<AppContainer>
<div className="flex min-h-full flex-1 items-center justify-center bg-gray-50">
<p className="font-medium text-gray-500">Loading complaints...</p>
</div>
</AppContainer>
)
} }
if (error) {
return (
<AppContainer>
<div className="flex min-h-full flex-1 flex-col items-center justify-center bg-gray-50">
<AlertCircle className="mb-4 h-12 w-12 text-red-500" />
<p className="text-lg font-bold text-gray-900">Oops!</p>
<p className="mt-2 text-gray-500">Failed to load complaints</p>
</div>
</AppContainer>
) )
} }
return ( return (
<AppContainer> <AppContainer>
<div className="mb-4 flex items-center justify-between"> <div className="min-h-full flex-1 bg-gray-50">
<MyText weight="bold" className="text-xl"> {/* Support Header */}
Help & Complaints <div className="bg-brand-600 px-4 py-5">
</MyText> <div className="mb-4 flex flex-row items-center">
<MyTouchableOpacity <div className="mr-3 flex h-10 w-10 items-center justify-center rounded-full bg-white/20">
onClick={() => setShowForm(!showForm)} <Headphones className="h-5 w-5 text-white" />
className="flex items-center gap-1 text-brand-600" </div>
<p className="text-lg font-bold text-white">Need Help?</p>
</div>
<div className="flex flex-col gap-3">
{/* Call Us Card */}
<a
href={`tel:${constsData?.supportMobile || '8688182552'}`}
className="flex w-full flex-row items-center rounded-xl bg-white p-3"
> >
<Plus className="h-4 w-4" /> <div className="mr-3 flex h-10 w-10 items-center justify-center rounded-full bg-green-100">
<MyText className="text-sm">New</MyText> <Phone className="h-5 w-5 text-green-600" />
</MyTouchableOpacity> </div>
<div>
<p className="text-[10px] font-bold uppercase text-gray-500">Call Us</p>
<p className="text-base font-bold text-gray-900">
{constsData?.supportMobile || '8688182552'}
</p>
</div>
</a>
{/* Email Card */}
<a
href={`mailto:${constsData?.supportEmail || 'qushammohd@gmail.com'}`}
className="flex w-full flex-row items-center rounded-xl bg-white p-3"
>
<div className="mr-3 flex h-10 w-10 items-center justify-center rounded-full bg-blue-100">
<Mail className="h-5 w-5 text-blue-600" />
</div>
<div>
<p className="text-[10px] font-bold uppercase text-gray-500">Email</p>
<p className="text-sm font-bold text-gray-900">
{constsData?.supportEmail || 'qushammohd@gmail.com'}
</p>
</div>
</a>
</div>
</div> </div>
{/* Raise Complaint Form */} {/* Complaints List */}
{showForm && ( <div className="px-4 py-6">
<div className="mb-6 rounded-xl border border-gray-100 bg-white p-4 shadow-sm">
<MyText weight="semibold" className="mb-3">
Raise a Complaint
</MyText>
<textarea
className="mb-3 min-h-24 w-full rounded-lg border border-gray-200 p-3 text-sm"
value={body}
onChange={(e) => setBody(e.target.value)}
placeholder="Describe your issue..."
rows={4}
/>
<MyButton
onClick={handleSubmit}
disabled={raiseMutation.isPending || !body.trim()}
textContent={raiseMutation.isPending ? 'Submitting...' : 'Submit'}
/>
</div>
)}
{/* Complaint List */}
{complaints.length === 0 ? ( {complaints.length === 0 ? (
<div className="flex flex-col items-center gap-4 py-20"> <div className="flex flex-1 flex-col items-center justify-center py-20">
<MessageSquare className="h-12 w-12 text-gray-300" /> <div className="mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-gray-100">
<MyText className="text-gray-500">No complaints yet</MyText> <ThumbsUp className="h-10 w-10 text-gray-400" />
</div>
<p className="text-xl font-bold text-gray-900">No Complaints</p>
<p className="mt-2 px-10 text-center leading-5 text-gray-500">
You haven't raised any complaints yet. That's great!
</p>
<button
onClick={() => navigate({ to: '/home' })}
className="mt-8 rounded-xl bg-brand-600 px-6 py-3 font-bold text-white shadow-sm"
>
Continue Shopping
</button>
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-3"> complaints.map((complaint: any) => (
{complaints.map((complaint: any) => ( <ComplaintItem key={complaint.id} item={complaint} />
<div ))
key={complaint.id}
className="rounded-xl border border-gray-100 bg-white p-4 shadow-sm"
>
<div className="mb-2 flex items-center justify-between">
<span
className={`rounded-full px-2 py-0.5 text-xs font-medium ${
complaint.status === 'resolved'
? 'bg-green-100 text-green-700'
: 'bg-yellow-100 text-yellow-700'
}`}
>
{complaint.status || 'pending'}
</span>
<MyText className="text-xs text-gray-400">
{complaint.createdAt
? new Date(complaint.createdAt).toLocaleDateString()
: ''}
</MyText>
</div>
<MyText className="text-sm text-gray-700">{complaint.body}</MyText>
{complaint.adminResponse && (
<div className="mt-2 rounded-lg bg-blue-50 p-2">
<MyText className="text-xs text-blue-600">
Response: {complaint.adminResponse}
</MyText>
</div>
)} )}
</div> </div>
))}
</div> </div>
)}
</AppContainer> </AppContainer>
) )
} }

View file

@ -1,80 +1,182 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from '@tanstack/react-router'
import { trpc } from '../lib/trpc-client' import { trpc } from '../lib/trpc-client'
import { useState } from 'react' import { useState } from 'react'
import { MyText, MyButton, MyTextInput, AppContainer } from 'web-components' import { p, MyButton, AppContainer, div } from 'web-components'
import { Ticket } from 'lucide-react' import { Ticket, AlertCircle } from 'lucide-react'
import dayjs from 'dayjs'
export const Route = createFileRoute('/me/coupons')({ component: CouponsPage }) export const Route = createFileRoute('/me/coupons')({ component: CouponsPage })
interface Coupon {
id: number
code: string
discountType: 'percentage' | 'flat'
discountValue: number
maxValue?: number
minOrder?: number
description: string
validTill?: string | Date
usageCount: number
maxLimitForUser?: number
isExpired: boolean
isUsedUp: boolean
}
interface CouponCardProps {
coupon: Coupon
}
function CouponCard({ coupon }: CouponCardProps) {
const getStatusColor = () => {
if (coupon.isExpired) return 'text-red-500'
if (coupon.isUsedUp) return 'text-orange-500'
return 'text-green-600'
}
const getStatusText = () => {
if (coupon.isExpired) return 'Expired'
if (coupon.isUsedUp) return 'Used'
return 'Available'
}
const formatDate = (date?: string | Date) => {
if (!date) return 'No expiry'
return dayjs(date).format('DD MMM YYYY')
}
return (
<div className="mb-3 rounded-lg border border-gray-100 bg-white p-4 shadow-sm">
<div className="mb-2 flex items-start justify-between">
<div className="flex-1">
<p className="mb-1 text-lg font-semibold text-gray-800">{coupon.code}</p>
<p className="mb-2 text-sm text-gray-600">{coupon.description}</p>
</div>
<div className="items-end text-right">
<p className={`mb-1 text-sm font-medium ${getStatusColor()}`}>{getStatusText()}</p>
{coupon.maxLimitForUser && (
<p className="text-xs text-gray-500">Used: {coupon.usageCount}/{coupon.maxLimitForUser}</p>
)}
</div>
</div>
<div className="flex items-center justify-between">
<p className="text-xs text-gray-500">Valid till: {formatDate(coupon.validTill)}</p>
<button
className={`rounded-full px-3 py-1 text-xs font-medium text-white ${
coupon.isExpired || coupon.isUsedUp
? 'cursor-not-allowed bg-gray-400'
: 'bg-blue-500 hover:bg-blue-600'
}`}
disabled={coupon.isExpired || coupon.isUsedUp}
>
{coupon.isExpired || coupon.isUsedUp ? 'Unavailable' : 'Apply'}
</button>
</div>
</div>
)
}
interface CouponSectionProps {
title: string
coupons: Coupon[]
emptyMessage: string
}
function CouponSection({ title, coupons, emptyMessage }: CouponSectionProps) {
return (
<div className="mb-6">
<p className="mb-3 text-lg font-semibold text-gray-800">{title}</p>
{coupons.length === 0 ? (
<div className="flex flex-col items-center rounded-lg bg-gray-50 p-6">
<Ticket className="mb-2 h-12 w-12 text-gray-400" />
<p className="text-center text-gray-500">{emptyMessage}</p>
</div>
) : (
coupons.map((coupon) => <CouponCard key={coupon.id} coupon={coupon} />)
)}
</div>
)
}
function CouponsPage() { function CouponsPage() {
const [code, setCode] = useState('') const [code, setCode] = useState('')
const { data } = trpc.user.coupon.getMyCoupons.useQuery() const { data, isLoading, error, refetch } = trpc.user.coupon.getMyCoupons.useQuery()
const redeemMutation = trpc.user.coupon.redeemReservedCoupon.useMutation() const redeemMutation = trpc.user.coupon.redeemReservedCoupon.useMutation({
const utils = trpc.useUtils()
const coupons = data?.data || []
const handleRedeem = () => {
if (!code.trim()) return
redeemMutation.mutate(
{ couponCode: code.trim() },
{
onSuccess: () => { onSuccess: () => {
setCode('') setCode('')
utils.user.coupon.getMyCoupons.invalidate() refetch()
}, },
})
const personalCoupons = data?.data?.personal || []
const generalCoupons = data?.data?.general || []
const handleRedeem = () => {
if (!code.trim() || code.trim().length < 4) return
redeemMutation.mutate({ secretCode: code.trim().toUpperCase() })
} }
if (isLoading) {
return (
<AppContainer>
<div className="flex flex-1 items-center justify-center">
<p className="text-gray-600">Loading coupons...</p>
</div>
</AppContainer>
)
}
if (error) {
return (
<AppContainer>
<div className="flex flex-1 flex-col items-center justify-center p-4">
<AlertCircle className="mb-2 h-12 w-12 text-red-500" />
<p className="text-center text-red-600">Failed to load coupons. Please try again.</p>
</div>
</AppContainer>
) )
} }
return ( return (
<AppContainer> <AppContainer>
<MyText weight="bold" className="mb-4 text-xl"> <div className="flex-1 p-4">
My Coupons {/* Add Coupon Card */}
</MyText> <div className="mb-6 rounded-lg border border-gray-100 bg-white p-4 shadow-sm">
<p className="mb-3 text-lg font-semibold text-gray-800">Add a Coupon</p>
{/* Redeem Code */} <input
<div className="mb-6 flex gap-2"> type="text"
<MyTextInput
placeholder="Enter coupon code" placeholder="Enter coupon code"
value={code} value={code}
onChange={(e) => setCode(e.target.value)} onChange={(e) => setCode(e.target.value)}
className="flex-1" className="mb-4 w-full rounded-lg border border-gray-200 px-4 py-2 text-sm focus:border-blue-500 focus:outline-none"
/> />
<MyButton <button
onClick={handleRedeem} onClick={handleRedeem}
disabled={redeemMutation.isPending || !code.trim()} disabled={redeemMutation.isPending || code.trim().length < 4}
textContent={redeemMutation.isPending ? 'Redeeming...' : 'Redeem'} className={`w-full rounded-lg px-4 py-3 text-center font-semibold text-white ${
/> redeemMutation.isPending || code.trim().length < 4
? 'cursor-not-allowed bg-gray-400 opacity-50'
: 'bg-blue-500 hover:bg-blue-600'
}`}
>
{redeemMutation.isPending ? 'Adding...' : 'Add Coupon'}
</button>
</div> </div>
{/* Coupon List */} {/* Coupon Sections */}
{coupons.length === 0 ? ( <CouponSection
<div className="flex flex-col items-center gap-4 py-20"> title="Only for Me"
<Ticket className="h-12 w-12 text-gray-300" /> coupons={personalCoupons}
<MyText className="text-gray-500">No coupons yet</MyText> emptyMessage="No personal coupons available"
/>
<CouponSection
title="Apply to All"
coupons={generalCoupons}
emptyMessage="No general coupons available"
/>
</div> </div>
) : (
<div className="flex flex-col gap-3">
{coupons.map((coupon: any) => (
<div
key={coupon.id}
className="rounded-xl border border-dashed border-brand-200 bg-brand-50 p-4"
>
<MyText weight="bold" className="text-brand-700">
{coupon.code}
</MyText>
<MyText className="text-sm text-gray-600">
{coupon.description || `${coupon.discountPercent || 0}% off`}
</MyText>
{coupon.expiresAt && (
<MyText className="mt-1 text-xs text-gray-400">
Expires: {new Date(coupon.expiresAt).toLocaleDateString()}
</MyText>
)}
</div>
))}
</div>
)}
</AppContainer> </AppContainer>
) )
} }

View file

@ -2,7 +2,9 @@ import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { useState } from 'react' import { useState } from 'react'
import { useAuth } from '../lib/auth-context' import { useAuth } from '../lib/auth-context'
import { trpc } from '../lib/trpc-client' import { trpc } from '../lib/trpc-client'
import { MyText, MyButton, MyTextInput, AppContainer } from 'web-components' import { p, AppContainer, div } from 'web-components'
import { AlertCircle, LogOut, Trash2, User, Mail, Phone, X } from 'lucide-react'
import { Dialog } from '../components/Dialog'
export const Route = createFileRoute('/me/edit-profile')({ component: EditProfilePage }) export const Route = createFileRoute('/me/edit-profile')({ component: EditProfilePage })
@ -11,17 +13,23 @@ function EditProfilePage() {
const { user, logout, loginWithToken } = useAuth() const { user, logout, loginWithToken } = useAuth()
const [name, setName] = useState(user?.name || '') const [name, setName] = useState(user?.name || '')
const [email, setEmail] = useState(user?.email || '') const [email, setEmail] = useState(user?.email || '')
const [showDeleteModal, setShowDeleteModal] = useState(false)
const [enteredMobile, setEnteredMobile] = useState('')
const updateMutation = trpc.user.auth.updateProfile.useMutation({ const updateMutation = trpc.user.auth.updateProfile.useMutation({
onSuccess: (data) => { onSuccess: (data) => {
if (data.token && data.user) { if (data.data.token && data.data.user) {
loginWithToken(data.token, data.user) loginWithToken(data.data.token, data.data.user)
} }
navigate({ to: '/me' })
}, },
}) })
const deleteMutation = trpc.user.auth.deleteAccount.useMutation({ const deleteMutation = trpc.user.auth.deleteAccount.useMutation({
onSuccess: () => logout(), onSuccess: () => {
setShowDeleteModal(false)
logout()
},
}) })
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
@ -29,65 +37,166 @@ function EditProfilePage() {
updateMutation.mutate({ name, email }) updateMutation.mutate({ name, email })
} }
const handleDeleteAccount = () => {
setShowDeleteModal(true)
}
const confirmDeleteAccount = () => {
if (!enteredMobile.trim()) {
alert('Please enter your mobile number')
return
}
deleteMutation.mutate({ mobile: enteredMobile.trim() })
}
return ( return (
<AppContainer> <AppContainer>
<MyText weight="bold" className="mb-4 text-xl"> <div className="flex min-h-full flex-col px-4 py-8">
Edit Profile {/* Header */}
</MyText> <div className="mb-8 mt-4 text-center">
<p className="mb-2 text-3xl font-bold text-gray-800">Edit Profile</p>
<p className="text-base text-gray-600">Update your account details</p>
</div>
{/* Profile Form */}
<form onSubmit={handleSubmit} className="flex flex-col gap-4"> <form onSubmit={handleSubmit} className="flex flex-col gap-4">
<MyTextInput {/* Name Field */}
placeholder="Name" <div className="rounded-lg border border-gray-100 bg-white p-4 shadow-sm">
<label className="mb-2 flex items-center gap-2 text-sm font-medium text-gray-700">
<User className="h-4 w-4" />
Full Name
</label>
<input
type="text"
placeholder="Enter your name"
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
/> className="w-full rounded-lg border border-gray-200 px-4 py-3 text-sm focus:border-blue-500 focus:outline-none"
<MyTextInput
placeholder="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<MyTextInput
placeholder="Mobile"
value={user?.mobile || ''}
disabled
className="bg-gray-50"
/>
<MyButton
type="submit"
fullWidth
disabled={updateMutation.isPending}
textContent={updateMutation.isPending ? 'Saving...' : 'Save Changes'}
className="bg-brand-500 text-white"
/>
</form>
<div className="mt-8 border-t border-gray-200 pt-8">
<MyButton
fullWidth
variant="red"
onClick={() => logout()}
textContent="Logout"
/>
<MyButton
fullWidth
variant="red"
onClick={() => {
if (window.confirm('Are you sure you want to delete your account?')) {
deleteMutation.mutate({ mobile: user?.mobile || '' })
}
}}
disabled={deleteMutation.isPending}
textContent={
deleteMutation.isPending
? 'Deleting...'
: 'Delete My Account'
}
className="mt-3 bg-red-600 text-white"
/> />
</div> </div>
{/* Email Field */}
<div className="rounded-lg border border-gray-100 bg-white p-4 shadow-sm">
<label className="mb-2 flex items-center gap-2 text-sm font-medium text-gray-700">
<Mail className="h-4 w-4" />
Email Address
</label>
<input
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full rounded-lg border border-gray-200 px-4 py-3 text-sm focus:border-blue-500 focus:outline-none"
/>
</div>
{/* Mobile Field (Disabled) */}
<div className="rounded-lg border border-gray-100 bg-white p-4 shadow-sm">
<label className="mb-2 flex items-center gap-2 text-sm font-medium text-gray-700">
<Phone className="h-4 w-4" />
Mobile Number
</label>
<input
type="tel"
value={user?.mobile || ''}
disabled
className="w-full cursor-not-allowed rounded-lg border border-gray-200 bg-gray-50 px-4 py-3 text-sm text-gray-500"
/>
<p className="mt-1 text-xs text-gray-400">Mobile number cannot be changed</p>
</div>
{/* Save Button */}
<button
type="submit"
disabled={updateMutation.isPending}
className={`mt-2 w-full rounded-lg px-4 py-3 font-semibold text-white transition-colors ${
updateMutation.isPending
? 'cursor-not-allowed bg-gray-400'
: 'bg-blue-600 hover:bg-blue-700'
}`}
>
{updateMutation.isPending ? 'Saving...' : 'Save Changes'}
</button>
</form>
{/* Action Buttons */}
<div className="mt-8 space-y-3">
{/* Logout Button */}
<button
onClick={logout}
className="flex w-full items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white px-4 py-3 font-medium text-red-600 transition-colors hover:bg-red-50"
>
<LogOut className="h-4 w-4" />
Logout
</button>
{/* Delete Account Button */}
<button
onClick={handleDeleteAccount}
className="flex w-full items-center justify-center gap-2 rounded-lg bg-red-600 px-4 py-3 font-medium text-white transition-colors hover:bg-red-700"
>
<Trash2 className="h-4 w-4" />
Delete Me and My Data
</button>
</div>
</div>
{/* Delete Account Modal */}
<Dialog open={showDeleteModal} onClose={() => setShowDeleteModal(false)}>
<div className="p-4">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2 text-red-600">
<AlertCircle className="h-6 w-6" />
<p className="text-xl font-bold">Delete Account Permanently</p>
</div>
<button
onClick={() => setShowDeleteModal(false)}
className="rounded-full p-1 hover:bg-gray-100"
>
<X className="h-5 w-5 text-gray-500" />
</button>
</div>
<p className="mb-4 text-center text-gray-700">
This action cannot be undone. All your data will be permanently deleted.
</p>
<p className="mb-4 text-sm text-gray-600">
Enter your registered mobile number to confirm:
</p>
<div className="mb-6">
<input
type="tel"
value={enteredMobile}
onChange={(e) => setEnteredMobile(e.target.value)}
placeholder="Enter mobile number"
className="w-full rounded-lg border border-gray-300 px-4 py-3 text-sm focus:border-red-500 focus:outline-none"
maxLength={15}
/>
</div>
<div className="flex gap-3">
<button
onClick={() => setShowDeleteModal(false)}
className="flex-1 rounded-lg border border-gray-200 bg-gray-100 px-4 py-3 font-medium text-gray-700 transition-colors hover:bg-gray-200"
>
Cancel
</button>
<button
onClick={confirmDeleteAccount}
disabled={deleteMutation.isPending || !enteredMobile.trim()}
className={`flex-1 rounded-lg px-4 py-3 font-medium text-white transition-colors ${
deleteMutation.isPending || !enteredMobile.trim()
? 'cursor-not-allowed bg-red-400'
: 'bg-red-600 hover:bg-red-700'
}`}
>
{deleteMutation.isPending ? 'Deleting...' : 'Delete Forever'}
</button>
</div>
</div>
</Dialog>
</AppContainer> </AppContainer>
) )
} }

View file

@ -1,147 +1,479 @@
import { createFileRoute, useNavigate } from '@tanstack/react-router' import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
import { trpc } from '../lib/trpc-client' import { trpc } from '../lib/trpc-client'
import { useState } from 'react' import { useState, useEffect } from 'react'
import { MyText, MyButton, LoadingDialog, AppContainer, MyTouchableOpacity } from 'web-components' import { p, AppContainer, div } from 'web-components'
import { ArrowLeft, Package } from 'lucide-react' import { ChevronLeft, CreditCard, Package, CheckCircle, XCircle, Clock, Zap, Calendar, Edit2, Tag, AlertCircle, X } from 'lucide-react'
import { Dialog } from '../components/Dialog'
import dayjs from 'dayjs'
export const Route = createFileRoute('/me/orders/$id')({ export const Route = createFileRoute('/me/orders/$id')({
component: OrderDetailPage, component: OrderDetailPage,
}) })
function OrderDetailPage() { function OrderDetailPage() {
const { id } = Route.useParams() const { id } = useParams({ from: '/me/orders/$id' })
const navigate = useNavigate() const navigate = useNavigate()
const orderId = Number(id) const orderId = Number(id)
const [showCancelDialog, setShowCancelDialog] = useState(false)
const { data } = trpc.user.order.getOrderById.useQuery({ orderId }) const { data: orderData, isLoading, error, refetch } = trpc.user.order.getOrderById.useQuery(
const cancelMutation = trpc.user.order.cancelOrder.useMutation() { orderId: orderId+'' },
const order = data?.data { enabled: !!orderId }
const handleCancel = () => {
cancelMutation.mutate(
{ orderId },
{ onSuccess: () => setShowCancelDialog(false) }
) )
const [isEditingNotes, setIsEditingNotes] = useState(false)
const [notesText, setNotesText] = useState('')
const [notesInput, setNotesInput] = useState('')
const [cancelDialogOpen, setCancelDialogOpen] = useState(false)
const [cancelReason, setCancelReason] = useState('')
const [complaintDialogOpen, setComplaintDialogOpen] = useState(false)
const [complaintBody, setComplaintBody] = useState('')
// const order = orderData?.data
const updateNotesMutation = trpc.user.order.updateUserNotes.useMutation({
onSuccess: () => {
setNotesText(notesInput)
setIsEditingNotes(false)
refetch()
},
})
const cancelOrderMutation = trpc.user.order.cancelOrder.useMutation({
onSuccess: () => {
setCancelDialogOpen(false)
setCancelReason('')
refetch()
},
})
const raiseComplaintMutation = trpc.user.complaint.raise.useMutation({
onSuccess: () => {
setComplaintDialogOpen(false)
setComplaintBody('')
refetch()
},
})
useEffect(() => {
if (orderData?.userNotes) {
setNotesText(orderData.userNotes)
setNotesInput(orderData.userNotes)
}
}, [orderData])
const handleCancelOrder = () => {
if (!cancelReason.trim()) {
alert('Please enter a reason for cancellation')
return
}
cancelOrderMutation.mutate({ id: orderId, reason: cancelReason })
} }
if (!order) { const handleRaiseComplaint = () => {
if (!complaintBody.trim()) {
alert('Please describe your complaint')
return
}
raiseComplaintMutation.mutate({ orderId: orderId+'', complaintBody: complaintBody.trim() })
}
if (isLoading) {
return ( return (
<AppContainer> <AppContainer>
<MyText>Loading...</MyText> <div className="flex min-h-full flex-1 items-center justify-center bg-white">
<p className="font-medium text-slate-400">Loading details...</p>
</div>
</AppContainer> </AppContainer>
) )
} }
if (error || !orderData) {
return ( return (
<AppContainer> <AppContainer>
<MyTouchableOpacity <div className="flex min-h-full flex-1 flex-col items-center justify-center bg-white p-8">
<AlertCircle className="mb-4 h-12 w-12 text-red-500" />
<p className="mt-4 text-lg font-bold text-slate-900">Failed to load</p>
<button
onClick={() => navigate({ to: '/me/orders' })} onClick={() => navigate({ to: '/me/orders' })}
className="mb-4 flex items-center gap-2" className="mt-6 rounded-xl bg-slate-900 px-6 py-2 font-bold text-white"
> >
<ArrowLeft className="h-5 w-5" /> Go Back
<MyText>Back to Orders</MyText> </button>
</MyTouchableOpacity> </div>
</AppContainer>
)
}
<div className="mb-4"> const getStatusConfig = (status: string) => {
<MyText weight="bold" className="text-xl"> const s = status.toLowerCase()
Order #{order.id} switch (s) {
</MyText> case 'delivered':
<span case 'success':
className={`mt-1 inline-block rounded-full px-3 py-1 text-xs font-medium ${ return { label: 'Delivered', color: '#10B981', bgColor: 'bg-green-50', textColor: 'text-green-700' }
order.status === 'delivered' case 'cancelled':
? 'bg-green-100 text-green-700' case 'failed':
: order.status === 'cancelled' return { label: 'Cancelled', color: '#EF4444', bgColor: 'bg-red-50', textColor: 'text-red-700' }
? 'bg-red-100 text-red-700' case 'pending':
: 'bg-yellow-100 text-yellow-700' case 'processing':
}`} return { label: 'Pending', color: '#F59E0B', bgColor: 'bg-yellow-50', textColor: 'text-yellow-700' }
default:
return { label: status, color: '#3B82F6', bgColor: 'bg-blue-50', textColor: 'text-blue-700' }
}
}
const orderAny = orderData as any
const subtotal = orderData.items?.reduce((sum: number, item: any) => sum + (item.amount || item.price * item.quantity), 0) || 0
const discountAmount = orderData.discountAmount || 0
const deliveryCharge = orderData.deliveryCharge || 0
const totalAmount = orderData.orderAmount || subtotal - discountAmount + deliveryCharge
const statusConfig = getStatusConfig(orderData.deliveryStatus || 'pending')
return (
<AppContainer>
<div className="flex min-h-full flex-1 flex-col bg-slate-50">
{/* Header */}
<div className="flex flex-row items-center justify-between border-b border-slate-100 bg-white px-6 pb-4 pt-4">
<div className="flex flex-row items-center">
<button
onClick={() => navigate({ to: '/me/orders' })}
className="-ml-2 mr-3 p-2"
> >
{order.status} <ChevronLeft className="h-6 w-6 text-slate-800" />
</span> </button>
<div>
<p className="text-lg font-bold text-slate-900">Order #{orderData.orderId || orderData.id}</p>
<p className="text-xs text-slate-400">
{dayjs(orderData.orderDate || orderData.createdAt).format('DD MMM, h:mm A')}
</p>
{orderData.isFlashDelivery && (
<p className="mt-1 text-xs font-bold text-amber-600"> 1 Hr Delivery</p>
)}
</div>
</div>
<div className="flex flex-row items-center gap-2">
<div
className={`rounded-full px-3 py-1 ${statusConfig.bgColor}`}
style={{ backgroundColor: statusConfig.color + '10' }}
>
<p className="text-[10px] font-bold uppercase" style={{ color: statusConfig.color }}>
{statusConfig.label}
</p>
</div>
{orderData.isFlashDelivery && (
<div className="rounded-full border border-amber-200 bg-amber-100 px-2 py-1">
<p className="text-[10px] font-black uppercase text-amber-700"></p>
</div>
)}
</div>
</div> </div>
{/* Items */} <div className="flex-1 overflow-y-auto p-4 pb-12">
<div className="mb-6"> {/* 1 Hr Delivery Banner */}
<MyText weight="semibold" className="mb-2"> {orderData.isFlashDelivery && (
Items <div className="mb-4 rounded-2xl border border-amber-200 bg-linear-to-r from-amber-50 to-yellow-50 p-4">
</MyText> <div className="flex flex-row items-center">
{(order.items || []).map((item: any, i: number) => ( <Zap className="h-6 w-6 text-amber-600" />
<div <div className="ml-3 flex-1">
key={i} <p className="text-sm font-bold text-amber-900">1 Hr Delivery Order</p>
className="flex items-center justify-between border-b border-gray-100 py-2" <p className="mt-1 text-xs text-amber-700">
Your order will be delivered within 1 hr of placement
</p>
</div>
</div>
</div>
)}
{/* Main Info Card */}
<div className="mb-4 rounded-2xl border border-slate-100 bg-white p-5">
<div className="mb-4 flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<div className="mr-3 flex h-10 w-10 items-center justify-center rounded-full bg-slate-50">
<CreditCard className="h-5 w-5 text-slate-500" />
</div>
<div>
<p className="text-[10px] font-bold uppercase text-slate-400">Payment Method</p>
<p className="font-bold text-slate-900">
{orderData.paymentMode?.toUpperCase() === 'COD' ? 'Cash on Delivery' : orderData.paymentMode}
</p>
</div>
</div>
<div className="items-end text-right">
<p className="text-[10px] font-bold uppercase text-slate-400">Status</p>
<p className="font-bold capitalize text-slate-900">{orderData.paymentStatus}</p>
</div>
</div>
{/* Delivery Date Info */}
{(orderData.deliveryDate || orderData.isFlashDelivery) &&
['delivered', 'success'].includes((orderData.deliveryStatus || '').toLowerCase()) && (
<div className="flex flex-row items-center border-t border-slate-50 pt-4">
{orderData.isFlashDelivery ? (
<Zap className="h-4 w-4 text-amber-600" />
) : (
<CheckCircle className="h-4 w-4 text-green-600" />
)}
<p className={`ml-2 text-xs font-medium ${orderData.isFlashDelivery ? 'text-amber-700' : 'text-slate-600'}`}>
{orderData.isFlashDelivery
? `Flash Delivered on ${dayjs(orderData.createdAt || orderData.orderDate).add(30, 'minutes').format('DD MMM YYYY, h:mm A')}`
: `Delivered on ${dayjs(orderData.deliveryDate).format('DD MMM YYYY, h:mm A')}`}
</p>
</div>
)}
{/* Pending 1 Hr Delivery Info */}
{orderData.isFlashDelivery &&
!['delivered', 'success'].includes((orderData.deliveryStatus || '').toLowerCase()) && (
<div className="flex flex-row items-center border-t border-slate-50 pt-4">
<Zap className="h-4 w-4 text-amber-600" />
<p className="ml-2 text-xs font-medium text-amber-700">
1 Hr Delivery: {dayjs(orderData.createdAt || orderData.orderDate).add(30, 'minutes').format('DD MMM YYYY, h:mm A')}
</p>
</div>
)}
</div>
{/* Special Instructions */}
<div className="mb-4 rounded-2xl border border-slate-100 bg-white p-5">
<div className="mb-3 flex flex-row items-center justify-between">
<p className="text-[10px] font-bold uppercase text-slate-400">Special Instructions</p>
{isEditingNotes ? (
<div className="flex flex-row gap-2">
<button
onClick={() => {
setIsEditingNotes(false)
setNotesInput(notesText)
}}
className="rounded-lg bg-slate-100 px-3 py-1"
> >
<MyText className="text-sm"> <p className="text-xs font-bold text-slate-600">Cancel</p>
{item.product?.name || `Product #${item.productId}`} x{item.quantity} </button>
</MyText> <button
<MyText className="text-sm font-bold">{item.price || 0}</MyText> onClick={() => {
updateNotesMutation.mutate({ id: orderId, userNotes: notesInput })
}}
disabled={updateNotesMutation.isPending}
className="rounded-lg bg-blue-500 px-3 py-1"
>
<p className="text-xs font-bold text-white">
{updateNotesMutation.isPending ? 'Saving...' : 'Save'}
</p>
</button>
</div>
) : (
<button
onClick={() => {
setNotesInput(notesText || '')
setIsEditingNotes(true)
}}
className="flex flex-row items-center rounded-lg bg-slate-50 px-2 py-1"
>
<Edit2 className="mr-1 h-3 w-3 text-slate-500" />
<p className="text-xs font-medium text-slate-500">{notesText ? 'Edit' : 'Add'}</p>
</button>
)}
</div>
{isEditingNotes ? (
<textarea
className="min-h-20 rounded-xl border border-slate-200 bg-slate-50 p-3 text-sm text-slate-700"
value={notesInput}
onChange={(e) => setNotesInput(e.target.value)}
placeholder="Add special delivery instructions..."
rows={4}
/>
) : (
<p className="text-sm leading-5 text-slate-700">
{notesText || 'No instructions added'}
</p>
)}
</div>
{/* Cancellation Detail */}
{orderData.cancelReason && (
<div className="mb-4 rounded-2xl border border-rose-100 bg-rose-50 p-5">
<p className="mb-2 text-[10px] font-bold uppercase text-rose-700">Cancellation Reason</p>
<p className="text-sm font-medium text-rose-900">{orderData.cancelReason}</p>
{orderData.refundAmount && (
<div className="mt-3 flex flex-row items-center justify-between border-t border-rose-200/50 pt-3">
<p className="text-xs text-rose-700">Refund Amount</p>
<p className="text-base font-bold text-rose-900">{orderData.refundAmount}</p>
</div>
)}
</div>
)}
{/* Items Section */}
<div className="mb-2 px-1">
<p className="mb-3 text-base font-bold text-slate-900">Order Items</p>
</div>
{orderData.items?.map((item: any, index: number) => (
<div
key={index}
className="mb-3 flex flex-row items-center rounded-2xl border border-slate-100 bg-white p-3 shadow-sm"
>
<div className="mr-4 flex h-14 w-14 items-center justify-center rounded-xl border border-slate-100 bg-slate-50 p-1">
{item.image ? (
<img
src={item.image}
alt=""
className="h-full w-full rounded-lg object-cover"
/>
) : (
<Package className="h-6 w-6 text-slate-400" />
)}
</div>
<div className="flex-1">
<p className="text-sm font-bold text-slate-900">{item.productName || item.product?.name || `Product #${item.productId}`}</p>
<p className="mt-1 text-xs text-slate-400">
{item.quantity} × {item.price}
</p>
</div>
<p className="ml-2 text-base font-bold text-slate-900">{item.amount || item.price * item.quantity}</p>
</div> </div>
))} ))}
<div className="flex items-center justify-between pt-2">
<MyText weight="bold">Total</MyText>
<MyText weight="bold" className="text-brand-600">
{order.totalAmount || 0}
</MyText>
</div>
</div>
{/* Address */} {/* Coupon */}
{order.address && ( {orderData.couponCode && (
<div className="mb-6"> <div className="mb-4 mt-2 flex flex-row items-center justify-between rounded-2xl border border-emerald-100 bg-emerald-50 p-4">
<MyText weight="semibold" className="mb-2"> <div className="flex flex-row items-center">
Delivery Address <Tag className="h-5 w-5 text-emerald-600" />
</MyText> <div className="ml-3">
<div className="rounded-xl border border-gray-100 bg-gray-50 p-3"> <p className="text-sm font-bold text-emerald-900">{orderData.couponCode}</p>
<MyText weight="semibold">{order.address.name}</MyText> <p className="text-[10px] text-emerald-600">Coupon Applied</p>
<MyText className="text-sm text-gray-600">
{order.address.addressLine1}, {order.address.city}
</MyText>
<MyText className="text-sm text-gray-500">{order.address.phone}</MyText>
</div> </div>
</div> </div>
<p className="font-bold text-emerald-700">-{orderData.discountAmount}</p>
</div>
)} )}
{/* Cancel Button */} {/* Summary Section */}
{order.status !== 'cancelled' && order.status !== 'delivered' && ( <div className="mb-6 rounded-2xl border border-slate-100 bg-white p-5">
<MyButton <div className="mb-3 flex flex-row justify-between">
variant="red" <p className="text-sm text-slate-500">Subtotal</p>
fullWidth <p className="font-medium text-slate-900">{subtotal}</p>
textContent={
cancelMutation.isPending ? 'Cancelling...' : 'Cancel Order'
}
onClick={() => setShowCancelDialog(true)}
disabled={cancelMutation.isPending}
/>
)}
{/* Cancel Confirmation Dialog */}
{showCancelDialog && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div className="mx-4 w-full max-w-sm rounded-xl bg-white p-6">
<MyText weight="bold" className="mb-2 text-lg">
Cancel Order?
</MyText>
<MyText className="mb-6 text-sm text-gray-600">
Are you sure you want to cancel this order?
</MyText>
<div className="flex gap-3">
<MyButton
textContent="No, Keep It"
onClick={() => setShowCancelDialog(false)}
className="flex-1 bg-gray-100 text-gray-700"
/>
<MyButton
variant="red"
textContent="Yes, Cancel"
onClick={handleCancel}
className="flex-1"
disabled={cancelMutation.isPending}
/>
</div>
</div> </div>
{discountAmount > 0 && (
<div className="mb-3 flex flex-row justify-between">
<p className="text-sm text-emerald-600">Discount</p>
<p className="font-medium text-emerald-600">-{discountAmount}</p>
</div> </div>
)} )}
<div className="mb-3 flex flex-row justify-between">
<p className="text-sm text-slate-500">Delivery</p>
<p className="font-medium text-slate-900">
{deliveryCharge > 0 ? `${deliveryCharge}` : 'Free'}
</p>
</div>
<div className="flex flex-row items-center justify-between border-t border-slate-50 pt-4">
<p className="text-base font-bold text-slate-900">Total Amount</p>
<p className="text-xl font-bold text-slate-900">{totalAmount}</p>
</div>
</div>
<LoadingDialog open={cancelMutation.isPending} message="Cancelling order..." /> {/* Footer Actions */}
<div className="flex flex-row gap-3">
<button
onClick={() => navigate({ to: '/me' })}
className="flex-1 rounded-xl bg-slate-100 py-3.5 text-center font-bold text-slate-600"
>
Back
</button>
<button
onClick={() => setComplaintDialogOpen(true)}
className="flex-1 rounded-xl bg-blue-600 py-3.5 text-center font-bold text-white"
>
Raise Complaint
</button>
</div>
{/* Cancel Order Button */}
{!['success', 'delivered', 'cancelled'].includes((orderData.deliveryStatus || '').toLowerCase()) && (
<div className="mt-4">
<button
onClick={() => setCancelDialogOpen(true)}
className="flex w-full flex-row items-center justify-center rounded-xl border border-red-100 bg-red-50 py-3.5"
>
<XCircle className="mr-2 h-5 w-5 text-red-600" />
<p className="font-bold text-red-600">Cancel Order</p>
</button>
</div>
)}
</div>
{/* Cancel Order Dialog */}
<Dialog open={cancelDialogOpen} onClose={() => setCancelDialogOpen(false)}>
<div className="p-6">
<div className="mb-4 flex flex-row items-center justify-between">
<p className="text-xl font-bold text-gray-900">Cancel Order</p>
<button onClick={() => setCancelDialogOpen(false)}>
<X className="h-6 w-6 text-gray-400" />
</button>
</div>
<div className="mb-4 rounded-xl border border-red-100 bg-red-50 p-4">
<p className="text-sm text-red-800">
Are you sure you want to cancel this order? This action cannot be undone.
</p>
</div>
<p className="mb-2 font-medium text-gray-700">Reason for cancellation</p>
<textarea
className="mb-6 min-h-24 w-full rounded-xl border border-gray-200 bg-gray-50 p-4 text-base text-gray-800"
value={cancelReason}
onChange={(e) => setCancelReason(e.target.value)}
placeholder="Please tell us why you are cancelling..."
rows={3}
/>
<button
className={`w-full rounded-xl py-4 text-center font-bold text-white shadow-sm ${
cancelOrderMutation.isPending ? 'bg-red-400 opacity-70' : 'bg-red-600'
}`}
onClick={handleCancelOrder}
disabled={cancelOrderMutation.isPending}
>
{cancelOrderMutation.isPending ? 'Cancelling...' : 'Confirm Cancellation'}
</button>
</div>
</Dialog>
{/* Raise Complaint Dialog */}
<Dialog open={complaintDialogOpen} onClose={() => setComplaintDialogOpen(false)}>
<div className="p-6">
<div className="mb-4 flex flex-row items-center justify-between">
<p className="text-xl font-bold text-gray-900">Raise Complaint</p>
<button onClick={() => setComplaintDialogOpen(false)}>
<X className="h-6 w-6 text-gray-400" />
</button>
</div>
<div className="mb-4 rounded-xl border border-amber-100 bg-amber-50 p-4">
<p className="text-sm text-amber-800">
Please describe your issue with this order. Our support team will get back to you within 24 hours.
</p>
</div>
<p className="mb-2 font-medium text-gray-700">Complaint Details</p>
<textarea
className="mb-6 min-h-32 w-full rounded-xl border border-gray-200 bg-gray-50 p-4 text-base text-gray-800"
value={complaintBody}
onChange={(e) => setComplaintBody(e.target.value)}
placeholder="Describe your issue in detail..."
rows={4}
/>
<button
className={`w-full rounded-xl py-4 text-center font-bold text-white shadow-sm ${
raiseComplaintMutation.isPending ? 'bg-blue-400 opacity-70' : 'bg-blue-600'
}`}
onClick={handleRaiseComplaint}
disabled={raiseComplaintMutation.isPending}
>
{raiseComplaintMutation.isPending ? 'Submitting...' : 'Submit Complaint'}
</button>
</div>
</Dialog>
</div>
</AppContainer> </AppContainer>
) )
} }

View file

@ -1,30 +1,30 @@
import { createFileRoute, useNavigate } from '@tanstack/react-router' import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { trpc } from '../lib/trpc-client' import { trpc } from '../lib/trpc-client'
import { MyText, AppContainer, MyTouchableOpacity } from 'web-components' import { p, AppContainer, div } from 'web-components'
import { Package, ChevronRight } from 'lucide-react' import { Package, ChevronRight } from 'lucide-react'
export const Route = createFileRoute('/me/orders')({ component: OrdersPage }) export const Route = createFileRoute('/me/orders')({ component: OrdersPage })
function OrdersPage() { function OrdersPage() {
const navigate = useNavigate() const navigate = useNavigate()
const { data } = trpc.user.order.getOrders.useQuery({ page: 0, limit: 20 }) const { data } = trpc.user.order.getOrders.useQuery({ page: 1, limit: 20 })
const orders = data?.data || [] const orders = data?.data || []
return ( return (
<AppContainer> <AppContainer>
<MyText weight="bold" className="mb-4 text-xl"> <p className="font-bold mb-4 text-xl">
My Orders My Orders
</MyText> </p>
{orders.length === 0 ? ( {orders.length === 0 ? (
<div className="flex flex-col items-center gap-4 py-20"> <div className="flex flex-col items-center gap-4 py-20">
<Package className="h-12 w-12 text-gray-300" /> <Package className="h-12 w-12 text-gray-300" />
<MyText className="text-gray-500">No orders yet</MyText> <p className="text-gray-500">No orders yet</p>
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
{orders.map((order: any) => ( {orders.map((order: any) => (
<MyTouchableOpacity <div
key={order.id} key={order.id}
onClick={() => onClick={() =>
navigate({ to: '/me/orders/$id', params: { id: String(order.id) } }) navigate({ to: '/me/orders/$id', params: { id: String(order.id) } })
@ -33,14 +33,14 @@ function OrdersPage() {
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<MyText weight="semibold" className="text-sm"> <p className="font-semibold text-sm">
Order #{order.id} Order #{order.id}
</MyText> </p>
<MyText className="text-xs text-gray-500"> <p className="text-xs text-gray-500">
{order.createdAt {order.createdAt
? new Date(order.createdAt).toLocaleDateString() ? new Date(order.createdAt).toLocaleDateString()
: ''} : ''}
</MyText> </p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span <span
@ -57,10 +57,10 @@ function OrdersPage() {
<ChevronRight className="h-4 w-4 text-gray-400" /> <ChevronRight className="h-4 w-4 text-gray-400" />
</div> </div>
</div> </div>
<MyText className="mt-1 text-xs text-gray-400"> <p className="mt-1 text-xs text-gray-400">
Total: {order.totalAmount || 0} Total: {order.totalAmount || 0}
</MyText> </p>
</MyTouchableOpacity> </div>
))} ))}
</div> </div>
)} )}

View file

@ -1,56 +1,56 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from '@tanstack/react-router'
import { MyText, AppContainer } from 'web-components' import { p, AppContainer } from 'web-components'
export const Route = createFileRoute('/me/terms')({ component: TermsPage }) export const Route = createFileRoute('/me/terms')({ component: TermsPage })
function TermsPage() { function TermsPage() {
return ( return (
<AppContainer> <AppContainer>
<MyText weight="bold" className="mb-6 text-2xl"> <p className="font-bold mb-6 text-2xl">
Terms & Conditions Terms & Conditions
</MyText> </p>
<div className="prose prose-sm max-w-none text-gray-600"> <div className="prose prose-sm max-w-none text-gray-600">
<MyText weight="semibold" className="mb-2 mt-4 text-gray-900"> <p className="font-semibold mb-2 mt-4 text-gray-900">
1. Acceptance of Terms 1. Acceptance of Terms
</MyText> </p>
<MyText className="mb-4"> <p className="mb-4">
By using Freshyo, you agree to these terms. If you do not agree, please By using Freshyo, you agree to these terms. If you do not agree, please
do not use our service. do not use our service.
</MyText> </p>
<MyText weight="semibold" className="mb-2 mt-4 text-gray-900"> <p className="font-semibold mb-2 mt-4 text-gray-900">
2. Orders and Payments 2. Orders and Payments
</MyText> </p>
<MyText className="mb-4"> <p className="mb-4">
All orders are subject to availability. We reserve the right to cancel All orders are subject to availability. We reserve the right to cancel
any order. Payments are collected at the time of delivery (COD). any order. Payments are collected at the time of delivery (COD).
</MyText> </p>
<MyText weight="semibold" className="mb-2 mt-4 text-gray-900"> <p className="font-semibold mb-2 mt-4 text-gray-900">
3. Delivery Policy 3. Delivery Policy
</MyText> </p>
<MyText className="mb-4"> <p className="mb-4">
Delivery times are estimates. We strive to deliver within the promised Delivery times are estimates. We strive to deliver within the promised
time window but delays may occur due to unforeseen circumstances. time window but delays may occur due to unforeseen circumstances.
</MyText> </p>
<MyText weight="semibold" className="mb-2 mt-4 text-gray-900"> <p className="font-semibold mb-2 mt-4 text-gray-900">
4. Returns and Refunds 4. Returns and Refunds
</MyText> </p>
<MyText className="mb-4"> <p className="mb-4">
If you are not satisfied with the quality of your order, please contact If you are not satisfied with the quality of your order, please contact
us within 24 hours of delivery. Refunds will be processed after quality us within 24 hours of delivery. Refunds will be processed after quality
assessment. assessment.
</MyText> </p>
<MyText weight="semibold" className="mb-2 mt-4 text-gray-900"> <p className="font-semibold mb-2 mt-4 text-gray-900">
5. Privacy 5. Privacy
</MyText> </p>
<MyText className="mb-4"> <p className="mb-4">
We respect your privacy. Your personal information is used only for We respect your privacy. Your personal information is used only for
order processing and delivery purposes. order processing and delivery purposes.
</MyText> </p>
</div> </div>
</AppContainer> </AppContainer>
) )

View file

@ -1,6 +1,7 @@
import { createFileRoute, useNavigate } from '@tanstack/react-router' import { createFileRoute, useNavigate, Outlet, useLocation } from '@tanstack/react-router'
import { useAuth } from '../lib/auth-context' import { useAuth } from '../lib/auth-context'
import { MyText, MyButton, AppContainer, ProfileImage, MyTouchableOpacity } from 'web-components' import { p, MyButton, ProfileImage } from 'web-components'
import { AppLayout } from '../components/AppLayout'
import { import {
Package, Package,
MapPin, MapPin,
@ -17,19 +18,23 @@ export const Route = createFileRoute('/me')({ component: MePage })
function MePage() { function MePage() {
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation()
const { user, logout } = useAuth() const { user, logout } = useAuth()
// Check if we're on the exact /me path (not a child route like /me/orders)
const isExactMePath = location.pathname === '/me'
if (!user) { if (!user) {
return ( return (
<AppContainer> <AppLayout>
<div className="flex flex-col items-center gap-4 py-20"> <div className="flex flex-col items-center gap-4 py-20">
<MyText>Please sign in</MyText> <p>Please sign in</p>
<MyButton <MyButton
textContent="Sign In" textContent="Sign In"
onClick={() => navigate({ to: '/login' })} onClick={() => navigate({ to: '/login' })}
/> />
</div> </div>
</AppContainer> </AppLayout>
) )
} }
@ -65,34 +70,36 @@ function MePage() {
] ]
return ( return (
<AppContainer> <AppLayout>
{isExactMePath ? (
<div className="p-4">
{/* Profile Header */} {/* Profile Header */}
<div className="mb-6 flex items-center gap-4 rounded-xl bg-brand-50 p-4"> <div className="mb-6 flex items-center gap-4 rounded-xl bg-brand-50 p-4">
<ProfileImage uri={user.profileImage} size={64} /> <ProfileImage uri={user.profileImage} size={64} />
<div> <div>
<MyText weight="bold" className="text-lg"> <p className="text-lg font-bold">
{user.name || 'User'} {user.name || 'User'}
</MyText> </p>
<MyText className="text-sm text-gray-500">{user.mobile}</MyText> <p className="text-sm text-gray-500">{user.mobile}</p>
</div> </div>
</div> </div>
{/* Menu */} {/* Menu */}
{menuItems.map((section) => ( {menuItems.map((section) => (
<div key={section.section} className="mb-6"> <div key={section.section} className="mb-6">
<MyText weight="semibold" className="mb-2 text-sm text-gray-500 uppercase tracking-wide"> <p className="mb-2 text-sm font-semibold uppercase tracking-wide text-gray-500">
{section.section} {section.section}
</MyText> </p>
<div className="rounded-xl border border-gray-100 bg-white shadow-sm"> <div className="rounded-xl border border-gray-100 bg-white shadow-sm">
{section.items.map((item) => ( {section.items.map((item) => (
<MyTouchableOpacity <div
key={item.label} key={item.label}
onClick={() => navigate({ to: item.to as any })} onClick={() => navigate({ to: item.to as any })}
className="flex w-full items-center gap-3 border-b border-gray-50 px-4 py-3.5 last:border-b-0" className="flex w-full items-center gap-3 border-b border-gray-50 px-4 py-3.5 last:border-b-0"
> >
<item.icon className="h-5 w-5 text-gray-400" /> <item.icon className="h-5 w-5 text-gray-400" />
<MyText className="flex-1 text-left text-sm">{item.label}</MyText> <p className="flex-1 text-left text-sm">{item.label}</p>
</MyTouchableOpacity> </div>
))} ))}
</div> </div>
</div> </div>
@ -107,9 +114,14 @@ function MePage() {
textContent="Logout" textContent="Logout"
/> />
<MyText className="mb-8 text-center text-xs text-gray-400"> <p className="mb-8 text-center text-xs text-gray-400">
Version 1.0.0 Version 1.0.0
</MyText> </p>
</AppContainer> </div>
) : (
/* Render child routes when not on exact /me path */
<Outlet />
)}
</AppLayout>
) )
} }

View file

@ -2,7 +2,7 @@ import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { useState } from 'react' import { useState } from 'react'
import { useAuth } from '../lib/auth-context' import { useAuth } from '../lib/auth-context'
import { trpc } from '../lib/trpc-client' import { trpc } from '../lib/trpc-client'
import { MyText, MyButton, MyTextInput, MyTextButton } from 'web-components' import { p, MyButton, pInput, pButton } from 'web-components'
export const Route = createFileRoute('/register')({ component: RegisterPage }) export const Route = createFileRoute('/register')({ component: RegisterPage })
@ -31,28 +31,28 @@ function RegisterPage() {
return ( return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-b from-brand-400 to-brand-700 p-4"> <div className="flex min-h-screen items-center justify-center bg-gradient-to-b from-brand-400 to-brand-700 p-4">
<div className="w-full max-w-md"> <div className="w-full max-w-md">
<MyText weight="bold" className="mb-2 text-center text-4xl text-white"> <p className="font-bold mb-2 text-center text-4xl text-white">
Create Account Create Account
</MyText> </p>
<MyText className="mb-8 text-center text-lg text-blue-100"> <p className="mb-8 text-center text-lg text-blue-100">
Join Freshyo today Join Freshyo today
</MyText> </p>
<div className="rounded-2xl bg-white p-8 shadow-xl"> <div className="rounded-2xl bg-white p-8 shadow-xl">
<form onSubmit={handleSubmit} className="flex flex-col gap-4"> <form onSubmit={handleSubmit} className="flex flex-col gap-4">
<MyTextInput <pInput
placeholder="Full Name" placeholder="Full Name"
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
required required
/> />
<MyTextInput <pInput
placeholder="Email" placeholder="Email"
type="email" type="email"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
/> />
<MyTextInput <pInput
placeholder="Mobile Number" placeholder="Mobile Number"
value={mobile} value={mobile}
onChange={(e) => { onChange={(e) => {
@ -61,7 +61,7 @@ function RegisterPage() {
}} }}
required required
/> />
<MyTextInput <pInput
placeholder="Password" placeholder="Password"
type="password" type="password"
value={password} value={password}

View file

@ -0,0 +1,465 @@
import {
createFileRoute,
useNavigate,
useSearch,
} from "@tanstack/react-router";
import { useState, useMemo, useEffect } from "react";
import dayjs from "dayjs";
import {
p,
div,
Quantifier,
MiniQuantifier,
} from "web-components";
import {
useSlots,
useAllProducts,
useStores,
} from "../hooks/prominent-api-hooks";
import { useCentralProductStore } from "../lib/stores/central-product-store";
import { useCentralSlotStore } from "../lib/stores/central-slot-store";
import {
useAddToCart,
useGetCart,
useUpdateCartItem,
useRemoveFromCart,
} from "../hooks/cart-query-hooks";
import { usePopulateCentralProductStore } from "../hooks/usePopulateCentralProductStore";
import { AppLayout } from "../components/AppLayout";
import { Truck, Store, Grid3X3, ChevronLeft, ShoppingCart } from "lucide-react";
export const Route = createFileRoute("/slot-view")({
component: SlotViewPage,
});
function SlotViewPage() {
const search = useSearch({ from: "/slot-view" }) as {
slotId?: string;
storeId?: string;
};
const slotId = search.slotId ? Number(search.slotId) : undefined;
const storeId = search.storeId ? Number(search.storeId) : undefined;
const navigate = useNavigate();
const { data: slotsData } = useSlots();
const { productsById } = useCentralProductStore();
const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap);
const { data: storesData } = useStores();
// Populate central product store with products data
usePopulateCentralProductStore();
const stores = storesData?.stores || [];
// Find the specific slot from cached data
const slot = slotsData?.slots?.find((s: any) => s.id === slotId);
const addToCart = useAddToCart("regular");
const { data: cartData } = useGetCart("regular");
const formatTimeRange = (deliveryTime: string) => {
const time = dayjs(deliveryTime);
const endTime = time.add(1, "hour");
const startPeriod = time.format("A");
const endPeriod = endTime.format("A");
if (startPeriod === endPeriod) {
return `${time.format("h")}-${endTime.format("h")} ${startPeriod}`;
} else {
return `${time.format("h:mm")} ${startPeriod} - ${endTime.format("h:mm")} ${endPeriod}`;
}
};
const handleAddToCart = (productId: number) => {
const item = filteredProducts.find((p) => p.id === productId);
const deliveryTime = slot?.deliveryTime
? dayjs(slot.deliveryTime).format("ddd, DD MMM • h:mm A")
: "";
addToCart.mutate(
{ productId, quantity: 1, slotId: slotId || 0, storeId: item?.storeId },
{
onSuccess: () => {
alert(
`Added ${item?.name || "item"} for delivery at ${deliveryTime}`,
);
},
},
);
};
// Get product details from central store using slot product IDs
const slotProducts =
slot?.products
?.map((p: any) => productsById[p.id])
?.filter(
(product: any): product is NonNullable<typeof product> =>
product !== null && product !== undefined,
)
?.filter((product: any) => !productSlotsMap[product.id]?.isOutOfStock) ||
[];
const filteredProducts = storeId
? slotProducts.filter((p: any) => p.storeId === storeId)
: slotProducts;
if (!slot) {
return (
<AppLayout>
<div className="flex min-h-screen flex-col items-center justify-center">
<p className="font-bold mb-4 text-2xl text-gray-900">
Slot View
</p>
<p className="text-gray-600">No delivery slot available.</p>
</div>
</AppLayout>
);
}
return (
<AppLayout>
<div className="min-h-screen bg-gray-50">
{/* Header */}
<div className="sticky top-0 z-10 border-b border-gray-200 bg-white px-4 py-3">
<div className="flex items-center gap-3">
<div
onClick={() => navigate({ to: "/home" })}
className="p-2"
>
<ChevronLeft className="h-6 w-6 text-gray-700" />
</div>
<div>
<p className="font-bold text-lg text-gray-900">
{dayjs(slot.deliveryTime).format("ddd, DD MMM")}
</p>
<p className="text-sm text-brand-600">
{formatTimeRange(slot.deliveryTime as any)}
</p>
</div>
</div>
</div>
<div className="flex md:flex-row">
<div className="w-24 sticky">
{/* <div className=""> */}
<StoreSidebar
stores={stores}
slotId={slotId}
storeId={storeId}
onStoreSelect={(newStoreId) =>
navigate({
to: "/slot-view",
search: { slotId, storeId: newStoreId },
})
}
onAllSelect={() =>
navigate({ to: "/slot-view", search: { slotId } })
}
/>
{/* </div> */}
</div>
{/* Products Grid */}
<div className="flex-1 p-4">
<div className="mb-4 flex items-center justify-between">
<p className="font-bold text-xl text-gray-900">
{storeId
? stores.find((s: any) => s.id === storeId)?.name ||
"Store Products"
: "All Products"}
</p>
<p className="text-sm text-gray-500">
{filteredProducts.length} items
</p>
</div>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{filteredProducts.map((product: any) => (
<CompactProductCard
key={product.id}
item={product}
handleAddToCart={handleAddToCart}
onPress={() =>
navigate({
to: "/home/product/$id",
params: { id: String(product.id) },
})
}
/>
))}
</div>
{filteredProducts.length === 0 && (
<div className="py-10 text-center">
<p className="font-medium text-gray-400">
{storeId
? "No products from this store in this slot."
: "No products available for this slot."}
</p>
</div>
)}
</div>
</div>
</div>
</AppLayout>
);
}
const formatQuantity = (
quantity: number,
unit: string,
): { value: string; display: string } => {
if (unit?.toLowerCase() === "kg" && quantity < 1) {
return {
value: `${Math.round(quantity * 1000)} g`,
display: `${Math.round(quantity * 1000)}g`,
};
}
return { value: `${quantity} ${unit}(s)`, display: `${quantity}${unit}` };
};
interface StoreSidebarProps {
stores: any[];
slotId?: number;
storeId?: number;
onStoreSelect: (storeId: number) => void;
onAllSelect: () => void;
}
function StoreSidebar({
stores,
slotId,
storeId,
onStoreSelect,
onAllSelect,
}: StoreSidebarProps) {
return (
<div className="w-full border-b border-gray-200 bg-white p-4 md:w-24 md:border-b-0 md:border-r">
<div className="flex gap-3 overflow-x-auto md:flex-col md:gap-3">
{/* All Products Item */}
<div
onClick={onAllSelect}
className={`flex flex-col items-center rounded-2xl p-3 ${
!storeId
? "bg-gradient-to-br from-brand-400 to-brand-600 text-white shadow-lg"
: "border border-gray-100 bg-white text-gray-500"
}`}
>
<div
className={`mb-1 flex h-10 w-10 items-center justify-center rounded-full border ${
!storeId ? "border-white/30 bg-white/20" : "bg-gray-50"
}`}
>
<Grid3X3
className={`h-5 w-5 ${!storeId ? "text-white" : "text-gray-500"}`}
/>
</div>
<p
className={`text-center text-[10px] font-bold ${!storeId ? "text-white" : "text-gray-500"}`}
>
ALL
</p>
</div>
<div className="hidden h-px bg-gray-200 md:my-1 md:block" />
{/* Store Items */}
{stores.map((store: any) => {
const isActive = storeId === store.id;
return (
<div
key={store.id}
onClick={() => onStoreSelect(store.id)}
className={`flex flex-col items-center rounded-2xl p-2 ${
isActive
? "bg-gradient-to-br from-brand-400 to-brand-600 text-white shadow-lg"
: "border border-gray-100 bg-white text-gray-500"
}`}
>
<div
className={`mb-2 flex h-12 w-12 items-center justify-center overflow-hidden rounded-full border-2 ${
isActive
? "border-white bg-white"
: "border-gray-100 bg-gray-50"
}`}
>
{store.signedImageUrl ? (
<img
src={store.signedImageUrl}
alt={store.name}
className="h-full w-full object-cover"
/>
) : (
<Store
className={`h-6 w-6 ${isActive ? "text-brand-500" : "text-gray-400"}`}
/>
)}
</div>
<p
className={`w-full text-center text-[10px] leading-tight ${
isActive
? "font-bold text-white"
: "font-medium text-gray-500"
}`}
>
{store.name.replace(/^The\s+/i, "")}
</p>
</div>
);
})}
{stores.map((store: any) => {
const isActive = storeId === store.id;
return (
<div
key={store.id}
onClick={() => onStoreSelect(store.id)}
className={`flex flex-col items-center rounded-2xl p-2 ${
isActive
? "bg-gradient-to-br from-brand-400 to-brand-600 text-white shadow-lg"
: "border border-gray-100 bg-white text-gray-500"
}`}
>
<div
className={`mb-2 flex h-12 w-12 items-center justify-center overflow-hidden rounded-full border-2 ${
isActive
? "border-white bg-white"
: "border-gray-100 bg-gray-50"
}`}
>
{store.signedImageUrl ? (
<img
src={store.signedImageUrl}
alt={store.name}
className="h-full w-full object-cover"
/>
) : (
<Store
className={`h-6 w-6 ${isActive ? "text-brand-500" : "text-gray-400"}`}
/>
)}
</div>
<p
className={`w-full text-center text-[10px] leading-tight ${
isActive
? "font-bold text-white"
: "font-medium text-gray-500"
}`}
>
{store.name.replace(/^The\s+/i, "")}
</p>
</div>
);
})}
</div>
</div>
);
}
interface CompactProductCardProps {
item: any;
handleAddToCart: (productId: number) => void;
onPress?: () => void;
}
function CompactProductCard({
item,
handleAddToCart,
onPress,
}: CompactProductCardProps) {
const { data: cartData } = useGetCart("regular");
const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap);
const updateCartItem = useUpdateCartItem("regular");
const removeFromCart = useRemoveFromCart("regular");
const cartItem = cartData?.items?.find(
(cartItem: any) => cartItem.productId === item.id,
);
const quantity = cartItem?.quantity || 0;
const isOutOfStock = productSlotsMap[item.id]?.isOutOfStock;
const handleQuantityChange = (newQuantity: number) => {
if (newQuantity === 0 && cartItem) {
removeFromCart.mutate(cartItem.id);
} else if (newQuantity === 1 && !cartItem) {
handleAddToCart(item.id);
} else if (cartItem) {
updateCartItem.mutate({ productId: cartItem.id, quantity: newQuantity });
}
};
return (
<div
onClick={onPress}
className="mb-2 overflow-hidden rounded-lg border border-gray-100 bg-white shadow-sm"
>
<div className="relative">
<img
src={item.images?.[0]}
alt={item.name}
className="aspect-square w-full object-cover"
/>
{isOutOfStock && (
<div className="absolute inset-0 flex items-center justify-center bg-black/30">
<p className="text-xs font-bold text-white">
Out of Stock
</p>
</div>
)}
<div className="absolute bottom-2 right-2">
{quantity > 0 ? (
<MiniQuantifier
value={quantity}
setValue={handleQuantityChange}
step={item.incrementStep}
/>
) : (
<div
className="flex h-8 w-8 items-center justify-center rounded-full bg-white shadow-md"
onClick={(e) => {
e.stopPropagation();
handleQuantityChange(1);
}}
>
<ShoppingCart className="h-4 w-4 text-brand-500" />
</div>
)}
</div>
</div>
<div className="p-2">
<p
className="font-medium mb-1 text-xs text-gray-900"
>
{item.name}
</p>
<div className="flex items-center justify-between">
<div className="flex flex-wrap items-baseline">
<p className="font-bold text-sm text-brand-500">
{item.price}
</p>
{item.marketPrice &&
Number(item.marketPrice) > Number(item.price) && (
<p className="ml-1 text-xs text-gray-400 line-through">
{item.marketPrice}
</p>
)}
<p className="ml-1 text-xs text-gray-600">
Qty:{" "}
<span className="font-semibold text-brand-500">
{
formatQuantity(
item.productQuantity || 1,
item.unit || item.unitNotation,
).display
}
</span>
</p>
</div>
</div>
</div>
</div>
);
}

View file

@ -2,7 +2,7 @@ import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { useCentralProductStore } from '../lib/stores/central-product-store' import { useCentralProductStore } from '../lib/stores/central-product-store'
import { useAddToCart } from '../hooks/cart-query-hooks' import { useAddToCart } from '../hooks/cart-query-hooks'
import { useState } from 'react' import { useState } from 'react'
import { MyText, MyButton, Quantifier, AppContainer, MyTouchableOpacity } from 'web-components' import { p, MyButton, Quantifier, AppContainer, div } from 'web-components'
import { ShoppingCart, ArrowLeft } from 'lucide-react' import { ShoppingCart, ArrowLeft } from 'lucide-react'
export const Route = createFileRoute('/stores/$storeId/product/$productId')({ export const Route = createFileRoute('/stores/$storeId/product/$productId')({
@ -30,7 +30,7 @@ function StoreProductDetailPage() {
if (!product) { if (!product) {
return ( return (
<AppContainer> <AppContainer>
<MyText>Product not found</MyText> <p>Product not found</p>
</AppContainer> </AppContainer>
) )
} }
@ -40,12 +40,12 @@ function StoreProductDetailPage() {
return ( return (
<AppContainer> <AppContainer>
<MyTouchableOpacity <div
onClick={() => navigate({ to: '/stores/$storeId', params: { storeId } })} onClick={() => navigate({ to: '/stores/$storeId', params: { storeId } })}
className="mb-4 flex items-center gap-2" className="mb-4 flex items-center gap-2"
> >
<ArrowLeft className="h-5 w-5" /> <ArrowLeft className="h-5 w-5" />
</MyTouchableOpacity> </div>
{imageUrl && ( {imageUrl && (
<div className="mb-4 aspect-square w-full overflow-hidden rounded-xl bg-gray-100"> <div className="mb-4 aspect-square w-full overflow-hidden rounded-xl bg-gray-100">
@ -57,26 +57,26 @@ function StoreProductDetailPage() {
</div> </div>
)} )}
<MyText weight="bold" className="mb-1 text-xl"> <p className="font-bold mb-1 text-xl">
{product.name} {product.name}
</MyText> </p>
<MyText className="mb-2 text-sm text-gray-500"> <p className="mb-2 text-sm text-gray-500">
{product.unitValue}{product.unit} {product.unitValue}{product.unit}
</MyText> </p>
<div className="mb-4 flex items-baseline gap-2"> <div className="mb-4 flex items-baseline gap-2">
<MyText weight="bold" className="text-2xl text-brand-600"> <p className="font-bold text-2xl text-brand-600">
{price} {price}
</MyText> </p>
{product.discountedPrice && ( {product.discountedPrice && (
<MyText className="text-sm text-gray-400 line-through"> <p className="text-sm text-gray-400 line-through">
{product.price} {product.price}
</MyText> </p>
)} )}
</div> </div>
{product.description && ( {product.description && (
<MyText className="mb-4 text-gray-600">{product.description}</MyText> <p className="mb-4 text-gray-600">{product.description}</p>
)} )}
<div className="mb-6"> <div className="mb-6">

View file

@ -1,95 +1,207 @@
import { createFileRoute, useNavigate } from '@tanstack/react-router' import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
import { useStoreWithProducts } from '../hooks/prominent-api-hooks' import { useStoreWithProducts, useAllProducts } from '../hooks/prominent-api-hooks'
import { useState, useMemo } from 'react' import { useState, useMemo } from 'react'
import { MyText, AppContainer, MyTouchableOpacity } from 'web-components' import { p } from 'web-components'
import { ArrowLeft } from 'lucide-react' import { AppLayout } from '../components/AppLayout'
import { ProductCard } from '../components/ProductCard'
import { usePopulateCentralStores } from '../hooks/usePopulateCentralStores'
import { ArrowLeft, Store, X, Grid3X3 } from 'lucide-react'
export const Route = createFileRoute('/stores/$storeId')({ export const Route = createFileRoute('/stores/$storeId')({
component: StoreDetailPage, component: StoreDetailPage,
}) })
interface Tag {
id: number
tagName: string
productIds?: number[]
}
function StoreDetailPage() { function StoreDetailPage() {
const { storeId } = Route.useParams() const { storeId } = useParams({ from: '/stores/$storeId' })
const navigate = useNavigate() const navigate = useNavigate()
const { data } = useStoreWithProducts(Number(storeId)) const storeIdNum = Number(storeId)
const [selectedTag, setSelectedTag] = useState<string | null>(null) const [selectedTagId, setSelectedTagId] = useState<number | null>(null)
const store = data?.store // Populate central stores with slots and product data for out-of-stock checking
const products = data?.products || [] usePopulateCentralStores()
const tags = useMemo(() => { const { data: storeData, isLoading, error, refetch } = useStoreWithProducts(storeIdNum)
const tagSet = new Set<string>() const { data: productsData, isLoading: isProductsLoading } = useAllProducts()
products.forEach((p: any) => {
const tag = p.category || 'All' const productById = useMemo(() => {
tagSet.add(tag) const map = new Map<number, any>()
productsData?.products?.forEach((product) => {
map.set(product.id, product)
}) })
return ['All', ...Array.from(tagSet)] return map
}, [products]) }, [productsData])
const filteredProducts = useMemo(() => { const storeProducts = useMemo(() => {
if (!selectedTag || selectedTag === 'All') return products if (!storeData?.products) return []
return products.filter((p: any) => (p.category || 'All') === selectedTag) return storeData.products
}, [products, selectedTag]) .map((product: any) => productById.get(product.id))
.filter(Boolean)
}, [storeData, productById])
// Filter products based on selected tag
const filteredProducts = selectedTagId
? storeProducts.filter((product: any) => {
const selectedTag = storeData?.tags.find((t: Tag) => t.id === selectedTagId)
return selectedTag?.productIds?.includes(product.id) ?? false
})
: storeProducts
const isMeatStore = storeData?.store?.name?.toLowerCase().includes('meat')
if (isLoading || isProductsLoading) {
return (
<AppLayout>
<div className="flex min-h-screen items-center justify-center bg-gray-50">
<p className="font-medium text-gray-500">
{isLoading ? 'Loading store...' : 'Loading products...'}
</p>
</div>
</AppLayout>
)
}
if (error || !storeData) {
return (
<AppLayout>
<div className="flex min-h-screen flex-col items-center justify-center bg-gray-50">
<svg className="mb-4 h-12 w-12 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<p className="font-bold mb-2 text-lg text-gray-900">
Oops!
</p>
<p className="text-gray-500">Store not found or error loading</p>
</div>
</AppLayout>
)
}
return ( return (
<AppContainer> <AppLayout>
<div className="mb-4 flex items-center gap-3"> <div className="min-h-screen bg-gray-50 pb-24">
<MyTouchableOpacity onClick={() => navigate({ to: '/stores' })}> {/* Back Button */}
<ArrowLeft className="h-5 w-5" /> <div className="sticky top-0 z-10 border-b border-gray-200 bg-white px-4 py-3">
</MyTouchableOpacity> <div className="flex items-center gap-3">
<MyText weight="bold" className="text-xl"> <div onClick={() => navigate({ to: '/stores' })} className="p-2">
{store?.name || 'Store'} <ArrowLeft className="h-5 w-5 text-gray-700" />
</MyText> </div>
<p className="font-bold text-lg text-gray-900">
{storeData?.store?.name || 'Store'}
</p>
</div>
</div> </div>
{/* Tag Filter */} <div className="px-4 pt-4">
<div className="mb-4 flex gap-2 overflow-x-auto pb-2">
{tags.map((tag) => ( {/* Store Info Card */}
<button <div className="flex items-center gap-2 mb-6 rounded-2xl border border-gray-100 bg-white p-6 text-center shadow-sm">
key={tag} <div className="mb-4 flex h-16 w-16 items-center justify-center self-center rounded-full bg-pink-50">
onClick={() => setSelectedTag(tag === 'All' ? null : tag)} <Store className="h-7 w-7 text-brand-500" />
className={`whitespace-nowrap rounded-full px-4 py-1.5 text-sm ${ </div>
(tag === 'All' && !selectedTag) || selectedTag === tag <p className="font-bold mb-2 text-center text-2xl text-gray-900">
? 'bg-brand-500 text-white' {storeData?.store?.name}
: 'bg-gray-100 text-gray-600' </p>
}`} {storeData?.store?.description && (
> <p className="px-4 text-center leading-5 text-gray-500">
{tag} {storeData?.store?.description}
</button> </p>
)}
</div>
{/* Tags Section */}
{storeData?.tags && storeData.tags.length > 0 && (
<div className="mb-6 flex gap-2 overflow-x-auto pb-2 scrollbar-hide">
{storeData.tags.map((tag: Tag) => (
<TagChip
key={tag.id}
tag={tag}
isSelected={selectedTagId === tag.id}
onPress={() => setSelectedTagId(selectedTagId === tag.id ? null : tag.id)}
/>
))} ))}
</div> </div>
)}
{/* Products */} {/* Products Count & Clear Filter */}
<div className="grid grid-cols-2 gap-3"> <div className="mb-4 flex items-center justify-between">
<div className="flex items-center">
<Grid3X3 className="mr-2 h-5 w-5 text-gray-700" />
<p className="font-bold text-lg text-gray-900">
{selectedTagId
? `${storeData?.tags.find((t: Tag) => t.id === selectedTagId)?.tagName} items`
: `${filteredProducts.length} products`}
</p>
</div>
{selectedTagId && (
<div
onClick={() => setSelectedTagId(null)}
className="flex items-center"
>
<p className="mr-1 text-sm font-medium text-brand-500">Clear</p>
<X className="h-4 w-4 text-brand-500" />
</div>
)}
</div>
{/* Products Grid */}
<div className="grid gap-4 sm:grid-cols-2" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))' }}>
{filteredProducts.map((product: any) => ( {filteredProducts.map((product: any) => (
<MyTouchableOpacity <ProductCard
key={product.id} key={product.id}
onClick={() => item={product}
onPress={() =>
navigate({ navigate({
to: '/stores/$storeId/product/$productId', to: '/stores/$storeId/product/$productId',
params: { storeId, productId: String(product.id) }, params: { storeId, productId: String(product.id) },
}) })
} }
className="rounded-xl border border-gray-100 bg-white p-3 shadow-sm" showDeliveryInfo={false}
> miniView={true}
<div className="mb-2 aspect-square w-full overflow-hidden rounded-lg bg-gray-100"> useAddToCartDialog={true}
{product.images?.[0] && (
<img
src={product.images[0].uri}
alt={product.name}
className="h-full w-full object-cover"
/> />
)}
</div>
<MyText weight="semibold" className="text-sm" numberOfLines={2}>
{product.name}
</MyText>
<MyText weight="bold" className="mt-1 text-brand-600">
{product.discountedPrice ?? product.price}
</MyText>
</MyTouchableOpacity>
))} ))}
</div> </div>
</AppContainer>
{filteredProducts.length === 0 && (
<div className="py-10 text-center">
<p className="font-medium text-gray-400">
{selectedTagId ? 'No products in this category' : 'No products available'}
</p>
</div>
)}
</div>
</div>
</AppLayout>
)
}
interface TagChipProps {
tag: Tag
isSelected: boolean
onPress: () => void
}
function TagChip({ tag, isSelected, onPress }: TagChipProps) {
const productCount = tag.productIds?.length || 0
return (
<button
onClick={onPress}
className={`whitespace-nowrap rounded-lg border px-4 py-2 ${
isSelected
? 'border-brand-500 bg-brand-500 text-white'
: 'border-brand-500 bg-white text-brand-500'
}`}
>
<span className={`text-sm font-medium ${isSelected ? 'text-white' : 'text-brand-500'}`}>
{tag.tagName} ({productCount})
</span>
</button>
) )
} }

View file

@ -1,53 +1,229 @@
import { createFileRoute, useNavigate } from '@tanstack/react-router' import { createFileRoute, useNavigate, Outlet, useLocation } from '@tanstack/react-router'
import { useStores } from '../hooks/prominent-api-hooks' import { useStores } from '../hooks/prominent-api-hooks'
import { MyText, AppContainer, MyTouchableOpacity } from 'web-components' import { p, div } from 'web-components'
import { Store } from 'lucide-react' import { AppLayout } from '../components/AppLayout'
import { Store, ArrowRight, Building2 } from 'lucide-react'
export const Route = createFileRoute('/stores')({ component: StoresPage }) export const Route = createFileRoute('/stores')({ component: StoresPage })
export const ASSETS_BASE_URL = 'http://localhost:4000/assets'
function StoresPage() { function StoresPage() {
const navigate = useNavigate() const navigate = useNavigate()
const { data } = useStores() const location = useLocation()
const stores = data?.data || [] const { data: storesData, isLoading, error } = useStores()
const stores = storesData?.stores || []
// Check if we're on the exact /stores path (not a child like /stores/123)
const isExactStoresPath = location.pathname === '/stores'
if (isLoading) {
return (
<AppLayout>
<div className="flex min-h-screen flex-col items-center justify-center bg-slate-50">
<div className="flex h-20 w-20 items-center justify-center">
<Building2 className="h-12 w-12 text-brand-200" />
</div>
<p className="mt-4 text-[10px] font-black uppercase tracking-widest text-slate-400">
Opening Marketplace...
</p>
</div>
</AppLayout>
)
}
if (error) {
return (
<AppLayout>
<div className="flex min-h-screen flex-col items-center justify-center bg-slate-50 p-10">
<div className="mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-rose-50">
<svg className="h-8 w-8 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<p className="font-bold mb-2 text-center text-xl text-slate-900">
Store Fetch Failed
</p>
<p className="mb-8 text-center font-medium leading-5 text-slate-500">
We couldn't reach our vendor network.
</p>
<button
onClick={() => window.location.reload()}
className="rounded-2xl bg-brand-600 px-8 py-3 text-xs font-black uppercase tracking-widest text-white shadow-lg shadow-brand-200"
>
Retry
</button>
</div>
</AppLayout>
)
}
return ( return (
<AppContainer> <AppLayout>
<MyText weight="bold" className="mb-4 text-xl"> <div className="min-h-screen bg-slate-50 pb-32">
{isExactStoresPath ? (
<>
<div className="px-3 pt-6">
{/* Header */}
<div className="mb-4 flex items-center">
<div className="mr-3 h-6 w-1 rounded-full bg-gradient-to-b from-brand-500 to-brand-700" />
<div>
<p className="text-[10px] font-black uppercase tracking-[0.2em] text-slate-400">
Our Outlets
</p>
<p className="font-bold text-3xl tracking-tight text-slate-900">
Our Stores Our Stores
</MyText> </p>
</div>
</div>
<p className="pr-4 text-sm font-medium leading-5 text-slate-500">
Experience the finest selection of premium meat, poultry, fresh fruits, vegetables, and dairy directly from our own stores.
</p>
</div>
<div className="grid grid-cols-2 gap-4"> {/* Store Cards */}
<div className="px-3 pt-4">
{stores.map((store: any) => ( {stores.map((store: any) => (
<MyTouchableOpacity <StoreCard key={store.id} store={store} />
key={store.id} ))}
onClick={() => </div>
{stores.length === 0 && (
<div className="flex flex-1 flex-col items-center justify-center py-20">
<div className="mb-6 flex h-24 w-24 items-center justify-center rounded-full bg-white shadow-sm">
<Building2 className="h-12 w-12 text-slate-400" />
</div>
<p className="font-bold text-center text-xl tracking-tight text-slate-900">
No Stores Available
</p>
</div>
)}
</>
) : (
/* Render child routes (e.g., store detail) when not on exact /stores path */
<Outlet />
)}
</div>
</AppLayout>
)
}
interface StoreCardProps {
store: any
}
function StoreCard({ store }: StoreCardProps) {
const navigate = useNavigate()
const sampleProducts = store.sampleProducts || []
const remainingCount = store.productCount - sampleProducts.length
const isMeatStore = store.name?.toLowerCase().includes('meat')
const navigateToStore = () => {
navigate({ navigate({
to: '/stores/$storeId', to: '/stores/$storeId',
params: { storeId: String(store.id) }, params: { storeId: String(store.id) },
}) })
} }
className="rounded-xl border border-gray-100 bg-white p-4 shadow-sm"
> return (
<div className="mb-3 flex h-32 w-full items-center justify-center overflow-hidden rounded-lg bg-gray-100"> <div className="mb-4 overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-lg shadow-slate-200">
{store.imageUrl ? ( {/* Meat Store Images - Show at top if store name contains 'meat' */}
{/* Top Header Section */}
<div onClick={navigateToStore} className="cursor-pointer p-4 pb-0">
<div className="mb-4 flex items-center">
<div className="h-12 w-12 rounded-xl border border-slate-200 bg-slate-50 p-0.5 shadow-sm">
<div className="relative h-full w-full overflow-hidden rounded-[10px]">
{store.signedImageUrl || store.imageUrl ? (
<img <img
src={store.imageUrl} src={store.signedImageUrl || store.imageUrl}
alt={store.name} alt={store.name}
className="h-full w-full object-cover" className="h-full w-full object-cover"
/> />
) : ( ) : (
<Store className="h-10 w-10 text-gray-400" /> <div className="flex h-full w-full items-center justify-center">
<Store className="h-5 w-5 text-slate-400" />
</div>
)} )}
</div> </div>
<MyText weight="semibold" className="text-sm">
{store.name}
</MyText>
<MyText className="text-xs text-gray-500">
{store.productCount || 0} products
</MyText>
</MyTouchableOpacity>
))}
</div> </div>
</AppContainer>
<div className="ml-3 flex-1">
<p className="font-bold text-lg text-slate-900">
{store.name}
</p>
</div>
<div className="flex items-center justify-center rounded-xl border border-brand-100 bg-brand-50 px-2.5 py-1.5">
<p className="font-bold text-sm text-brand-700">
{store.productCount}
</p>
<p className="ml-1 text-[8px] font-black uppercase tracking-tighter text-brand-600">
Items
</p>
</div>
</div>
</div>
{/* Horizontal Scrollable Product Collection */}
{sampleProducts.length > 0 && (
<div className="mb-5">
<div className="flex gap-3 overflow-x-auto px-4 pb-2 scrollbar-hide">
{sampleProducts.map((product: any) => (
<div
key={product.id}
onClick={navigateToStore}
className="w-24 shrink-0 cursor-pointer items-center"
>
<div className="mb-2 h-24 w-24 rounded-2xl border border-slate-200 bg-slate-50 p-1 shadow-sm">
<img
src={product.signedImageUrl || product.images?.[0]}
alt={product.name}
className="h-full w-full rounded-xl object-cover"
/>
</div>
<p
className="font-bold text-center text-[10px] leading-tight text-slate-900"
>
{product.name}
</p>
</div>
))}
{remainingCount > 0 && (
<div className="flex shrink-0 flex-col items-center justify-center">
<div
onClick={navigateToStore}
className="flex h-24 w-24 cursor-pointer flex-col items-center justify-center rounded-2xl bg-slate-900 shadow-md"
>
<p className="font-bold text-base text-white">
+{remainingCount}
</p>
<p className="text-[8px] font-black uppercase tracking-widest text-white/60">
Discover
</p>
<ArrowRight className="mt-1 h-4 w-4 text-white" />
</div>
<div className="h-8" />
</div>
)}
</div>
</div>
)}
{/* Explore Store Button */}
<div className="px-4 pb-4">
<div
onClick={navigateToStore}
className="flex flex-row items-center justify-center rounded-[18px] bg-brand-600 py-3 shadow-lg shadow-brand-200"
>
<p className="font-bold mr-2 text-sm uppercase tracking-wider text-white">
Explore Store
</p>
<ArrowRight className="h-4 w-4 text-white" />
</div>
</div>
</div>
) )
} }

View file

@ -2,6 +2,19 @@
@import "tailwindcss"; @import "tailwindcss";
@theme { @theme {
--color-brand-25: #EFF8FF;
--color-brand-50: #F5FAFF;
--color-brand-100: #D1E9FF;
--color-brand-200: #B2DDFF;
--color-brand-300: #84CAFF;
--color-brand-400: #53B1FD;
--color-brand-500: #2E90FA;
--color-brand-600: #1570EF;
--color-brand-700: #175CD3;
--color-brand-800: #1849A9;
--color-brand-900: #194185;
}
/* @theme {
--color-brand-25: #FFF5F6; --color-brand-25: #FFF5F6;
--color-brand-50: #FFE8EA; --color-brand-50: #FFE8EA;
--color-brand-100: #FFD1D6; --color-brand-100: #FFD1D6;
@ -13,7 +26,8 @@
--color-brand-700: #9E2630; --color-brand-700: #9E2630;
--color-brand-800: #771D24; --color-brand-800: #771D24;
--color-brand-900: #501318; --color-brand-900: #501318;
} } */
* { * {
box-sizing: border-box; box-sizing: border-box;
@ -29,3 +43,31 @@ body {
margin: 0; margin: 0;
} }
/* Safe area padding for mobile devices */
.pb-safe {
padding-bottom: env(safe-area-inset-bottom, 0px);
}
/* Hide scrollbar but allow scrolling */
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
/* Line clamp utilities */
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Safe area padding for mobile devices */
.pb-safe {
padding-bottom: env(safe-area-inset-bottom, 0px);
}

7
package-lock.json generated
View file

@ -133,6 +133,7 @@
"@trpc/server": "^11.6.0", "@trpc/server": "^11.6.0",
"@turf/turf": "^7.2.0", "@turf/turf": "^7.2.0",
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"aws4fetch": "^1.0.20",
"axios": "^1.11.0", "axios": "^1.11.0",
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"dayjs": "^1.11.18", "dayjs": "^1.11.18",
@ -9919,6 +9920,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/aws4fetch": {
"version": "1.0.20",
"resolved": "https://registry.npmjs.org/aws4fetch/-/aws4fetch-1.0.20.tgz",
"integrity": "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==",
"license": "MIT"
},
"node_modules/axios": { "node_modules/axios": {
"version": "1.13.6", "version": "1.13.6",
"license": "MIT", "license": "MIT",

View file

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { MyText } from './my-text' import { p } from './my-text'
import { MyButton } from './my-button' import { MyButton } from './my-button'
import { cn } from '../lib/utils' import { cn } from '../lib/utils'
@ -30,7 +30,7 @@ export function BottomDialog({
if (!visible && !open) return null if (!visible && !open) return null
return ( return (
<div className="fixed inset-0 z-50 flex items-end justify-center"> <div className="fixed inset-0 z-50 flex items-center justify-center">
<div <div
className={`absolute inset-0 bg-black/30 transition-opacity duration-200 ${ className={`absolute inset-0 bg-black/30 transition-opacity duration-200 ${
open ? 'opacity-100' : 'opacity-0' open ? 'opacity-100' : 'opacity-0'
@ -90,9 +90,9 @@ export function ConfirmationDialog({
return ( return (
<BottomDialog open={open} onClose={handleCancel}> <BottomDialog open={open} onClose={handleCancel}>
<div className="p-4"> <div className="p-4">
<MyText weight="bold" className="mb-4 text-lg"> <p weight="bold" className="mb-4 text-lg">
{title} {title}
</MyText> </p>
<p className="mb-6 text-gray-600">{message}</p> <p className="mb-6 text-gray-600">{message}</p>
{commentNeeded && ( {commentNeeded && (
<textarea <textarea

View file

@ -1,7 +1,7 @@
import React, { useRef } from 'react' import React, { useRef } from 'react'
import { cn } from '../lib/utils' import { cn } from '../lib/utils'
import { Plus, X } from 'lucide-react' import { Plus, X } from 'lucide-react'
import { MyTouchableOpacity } from './my-touchable-opacity' import { div } from './my-touchable-opacity'
export interface ImageUploaderNeoItem { export interface ImageUploaderNeoItem {
imgUrl: string imgUrl: string
@ -75,7 +75,7 @@ export function ImageUploaderNeo({
alt={`Upload ${index + 1}`} alt={`Upload ${index + 1}`}
className="h-full w-full rounded object-cover" className="h-full w-full rounded object-cover"
/> />
<MyTouchableOpacity <div
onClick={() => onClick={() =>
onImageRemove({ onImageRemove({
url: image.imgUrl, url: image.imgUrl,
@ -85,7 +85,7 @@ export function ImageUploaderNeo({
className="absolute right-1 top-1 rounded-full bg-red-500 p-1 text-white" className="absolute right-1 top-1 rounded-full bg-red-500 p-1 text-white"
> >
<X className="h-3 w-3" /> <X className="h-3 w-3" />
</MyTouchableOpacity> </div>
</div> </div>
))} ))}
{(!allowMultiple || totalCount < 1) && ( {(!allowMultiple || totalCount < 1) && (

View file

@ -43,10 +43,10 @@ export function MyButton({
) )
} }
interface MyTextButtonProps extends Omit<MyButtonProps, 'children'> { interface pButtonProps extends Omit<MyButtonProps, 'children'> {
text: string text: string
} }
export function MyTextButton({ text, ...props }: MyTextButtonProps) { export function pButton({ text, ...props }: pButtonProps) {
return <MyButton {...props}>{text}</MyButton> return <MyButton {...props}>{text}</MyButton>
} }

View file

@ -1,8 +1,8 @@
import React from 'react' import React from 'react'
import { cn } from '../lib/utils' import { cn } from '../lib/utils'
import { MyText } from './my-text' import { p } from './my-text'
interface MyTextInputProps extends React.InputHTMLAttributes<HTMLInputElement> { interface pInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
topLabel?: string topLabel?: string
fullWidth?: boolean fullWidth?: boolean
shrunkPadding?: boolean shrunkPadding?: boolean
@ -12,7 +12,7 @@ interface MyTextInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
style?: React.CSSProperties style?: React.CSSProperties
} }
export function MyTextInput({ export function pInput({
topLabel, topLabel,
fullWidth = true, fullWidth = true,
shrunkPadding = false, shrunkPadding = false,
@ -21,7 +21,7 @@ export function MyTextInput({
className, className,
style, style,
...props ...props
}: MyTextInputProps) { }: pInputProps) {
const inputClasses = cn( const inputClasses = cn(
'flex w-full rounded-md border border-input bg-background px-3 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', 'flex w-full rounded-md border border-input bg-background px-3 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
shrunkPadding ? 'py-1.5' : 'py-2', shrunkPadding ? 'py-1.5' : 'py-2',
@ -34,9 +34,9 @@ export function MyTextInput({
return ( return (
<div style={{ ...(fullWidth ? { width: '100%' } : {}), ...style }}> <div style={{ ...(fullWidth ? { width: '100%' } : {}), ...style }}>
{topLabel && ( {topLabel && (
<MyText weight="medium" className="mb-1 text-sm text-gray-500"> <p weight="medium" className="mb-1 text-sm text-gray-500">
{topLabel} {topLabel}
</MyText> </p>
)} )}
{multiline ? ( {multiline ? (
<textarea <textarea

View file

@ -3,7 +3,7 @@ import { cn } from '../lib/utils'
type Weight = 'normal' | 'medium' | 'semibold' | 'bold' type Weight = 'normal' | 'medium' | 'semibold' | 'bold'
interface MyTextProps extends React.HTMLAttributes<HTMLSpanElement> { interface pProps extends React.HTMLAttributes<HTMLSpanElement> {
weight?: Weight weight?: Weight
numberOfLines?: number numberOfLines?: number
style?: React.CSSProperties style?: React.CSSProperties
@ -16,14 +16,14 @@ const weightClasses: Record<Weight, string> = {
bold: 'font-bold', bold: 'font-bold',
} }
export function MyText({ export function p({
children, children,
weight = 'normal', weight = 'normal',
numberOfLines, numberOfLines,
className, className,
style, style,
...props ...props
}: MyTextProps) { }: pProps) {
return ( return (
<span <span
className={cn(weightClasses[weight], className)} className={cn(weightClasses[weight], className)}

View file

@ -1,19 +1,19 @@
import React from 'react' import React from 'react'
import { cn } from '../lib/utils' import { cn } from '../lib/utils'
interface MyTouchableOpacityProps interface divProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> { extends React.ButtonHTMLAttributes<HTMLButtonElement> {
activeOpacity?: number activeOpacity?: number
children: React.ReactNode children: React.ReactNode
} }
export function MyTouchableOpacity({ export function div({
activeOpacity = 0.7, activeOpacity = 0.7,
className, className,
children, children,
style, style,
...props ...props
}: MyTouchableOpacityProps) { }: divProps) {
return ( return (
<button <button
className={cn( className={cn(

View file

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import { cn } from '../lib/utils' import { cn } from '../lib/utils'
import { MyText } from './my-text' import { p } from './my-text'
import { Minus, Plus } from 'lucide-react' import { Minus, Plus } from 'lucide-react'
interface QuantifierProps { interface QuantifierProps {
@ -36,9 +36,9 @@ export function Quantifier({
> >
<Minus className="h-3.5 w-3.5" /> <Minus className="h-3.5 w-3.5" />
</button> </button>
<MyText weight="semibold" className="min-w-[32px] text-center text-sm"> <p weight="semibold" className="min-w-[32px] text-center text-sm">
{value} {value}
</MyText> </p>
<button <button
onClick={increase} onClick={increase}
disabled={value >= max} disabled={value >= max}
@ -66,9 +66,9 @@ export function MiniQuantifier({
> >
<Minus className="h-3 w-3" /> <Minus className="h-3 w-3" />
</button> </button>
<MyText weight="semibold" className="min-w-[24px] text-center text-xs"> <p weight="semibold" className="min-w-[24px] text-center text-xs">
{value} {value}
</MyText> </p>
<button <button
onClick={() => value < max && setValue(value + step)} onClick={() => value < max && setValue(value + step)}
disabled={value >= max} disabled={value >= max}

View file

@ -1,8 +1,8 @@
// Components // Components
export { MyText } from './components/my-text' export { p } from './components/my-text'
export { MyButton, MyTextButton } from './components/my-button' export { MyButton, pButton } from './components/my-button'
export { MyTextInput } from './components/my-text-input' export { pInput } from './components/my-text-input'
export { MyTouchableOpacity } from './components/my-touchable-opacity' export { div } from './components/my-touchable-opacity'
export { LoadingDialog } from './components/loading-dialog' export { LoadingDialog } from './components/loading-dialog'
export { BottomDialog, ConfirmationDialog } from './components/dialog' export { BottomDialog, ConfirmationDialog } from './components/dialog'
export { Checkbox } from './components/checkbox' export { Checkbox } from './components/checkbox'

View file

@ -1,89 +1,87 @@
import type { Config } from 'tailwindcss' import type { Config } from "tailwindcss";
const config: Config = { const config: Config = {
darkMode: ['class'], darkMode: ["class"],
content: [ content: ["./src/**/*.{ts,tsx}"],
'./src/**/*.{ts,tsx}',
],
theme: { theme: {
container: { container: {
center: true, center: true,
padding: '2rem', padding: "2rem",
screens: { screens: {
'2xl': '1400px', "2xl": "1400px",
}, },
}, },
extend: { extend: {
colors: { colors: {
border: 'hsl(var(--border))', border: "hsl(var(--border))",
input: 'hsl(var(--input))', input: "hsl(var(--input))",
ring: 'hsl(var(--ring))', ring: "hsl(var(--ring))",
background: 'hsl(var(--background))', background: "hsl(var(--background))",
foreground: 'hsl(var(--foreground))', foreground: "hsl(var(--foreground))",
primary: { primary: {
DEFAULT: 'hsl(var(--primary))', DEFAULT: "hsl(var(--primary))",
foreground: 'hsl(var(--primary-foreground))', foreground: "hsl(var(--primary-foreground))",
}, },
secondary: { secondary: {
DEFAULT: 'hsl(var(--secondary))', DEFAULT: "hsl(var(--secondary))",
foreground: 'hsl(var(--secondary-foreground))', foreground: "hsl(var(--secondary-foreground))",
}, },
destructive: { destructive: {
DEFAULT: 'hsl(var(--destructive))', DEFAULT: "hsl(var(--destructive))",
foreground: 'hsl(var(--destructive-foreground))', foreground: "hsl(var(--destructive-foreground))",
}, },
muted: { muted: {
DEFAULT: 'hsl(var(--muted))', DEFAULT: "hsl(var(--muted))",
foreground: 'hsl(var(--muted-foreground))', foreground: "hsl(var(--muted-foreground))",
}, },
accent: { accent: {
DEFAULT: 'hsl(var(--accent))', DEFAULT: "hsl(var(--accent))",
foreground: 'hsl(var(--accent-foreground))', foreground: "hsl(var(--accent-foreground))",
}, },
popover: { popover: {
DEFAULT: 'hsl(var(--popover))', DEFAULT: "hsl(var(--popover))",
foreground: 'hsl(var(--popover-foreground))', foreground: "hsl(var(--popover-foreground))",
}, },
card: { card: {
DEFAULT: 'hsl(var(--card))', DEFAULT: "hsl(var(--card))",
foreground: 'hsl(var(--card-foreground))', foreground: "hsl(var(--card-foreground))",
}, },
brand: { brand: {
25: '#FFF5F6', 25: "#F5FAFF",
50: '#FFE8EA', 50: "#EFF8FF",
100: '#FFD1D6', 100: "#D1E9FF",
200: '#FFA3AE', 200: "#B2DDFF",
300: '#FF7585', 300: "#84CAFF",
400: '#FF475D', 400: "#53B1FD",
500: '#E63946', 500: "#2E90FA",
600: '#C5303C', 600: "#1570EF",
700: '#9E2630', 700: "#175CD3",
800: '#771D24', 800: "#1849A9",
900: '#501318', 900: "#194185",
}, },
}, },
borderRadius: { borderRadius: {
lg: 'var(--radius)', lg: "var(--radius)",
md: 'calc(var(--radius) - 2px)', md: "calc(var(--radius) - 2px)",
sm: 'calc(var(--radius) - 4px)', sm: "calc(var(--radius) - 4px)",
}, },
keyframes: { keyframes: {
'accordion-down': { "accordion-down": {
from: { height: '0' }, from: { height: "0" },
to: { height: 'var(--radix-accordion-content-height)' }, to: { height: "var(--radix-accordion-content-height)" },
}, },
'accordion-up': { "accordion-up": {
from: { height: 'var(--radix-accordion-content-height)' }, from: { height: "var(--radix-accordion-content-height)" },
to: { height: '0' }, to: { height: "0" },
}, },
}, },
animation: { animation: {
'accordion-down': 'accordion-down 0.2s ease-out', "accordion-down": "accordion-down 0.2s ease-out",
'accordion-up': 'accordion-up 0.2s ease-out', "accordion-up": "accordion-up 0.2s ease-out",
}, },
}, },
}, },
plugins: [], plugins: [],
} };
export default config export default config;