DEAD_CODE_CLEAN #7

Merged
shafi merged 30 commits from DEAD_CODE_CLEAN into master 2026-09-14 04:32:52 +00:00
50 changed files with 260 additions and 180 deletions
Showing only changes of commit b5fd64e5ed - Show all commits

View file

@ -63,7 +63,7 @@ export default function Complaints() {
if (!selectedComplaintId) return; if (!selectedComplaintId) return;
resolveComplaint.mutate( resolveComplaint.mutate(
{ id: String(selectedComplaintId), response }, { id: String(selectedComplaintId), response: response ?? '' },
{ {
onSuccess: () => { onSuccess: () => {
Alert.alert("Success", "Complaint marked as resolved"); Alert.alert("Success", "Complaint marked as resolved");

View file

@ -94,7 +94,7 @@ export default function AllItemsOrder() {
// Get current order from constants // Get current order from constants
const { data: constants, isLoading: isLoadingConstants, error: constantsError } = trpc.admin.const.getConstants.useQuery(); const { data: constants, isLoading: isLoadingConstants, error: constantsError } = trpc.admin.const.getConstants.useQuery();
const { data: allProducts, isLoading: isLoadingProducts, error: productsError } = trpc.common.product.getAllProductsSummary.useQuery({}); const { data: allProducts, isLoading: isLoadingProducts, error: productsError } = trpc.common.product.getAllProductsSummary.useQuery();
const updateConstants = trpc.admin.const.updateConstants.useMutation(); const updateConstants = trpc.admin.const.updateConstants.useMutation();
// Initialize products from constants // Initialize products from constants

View file

@ -34,7 +34,7 @@ type PopularProduct = Omit<
> & { > & {
price: number; price: number;
marketPrice: number | null; marketPrice: number | null;
images: string[]; images: string[] | null;
storeId: number | null; storeId: number | null;
nextDeliveryDate: string | null; nextDeliveryDate: string | null;
}; };
@ -121,7 +121,7 @@ export default function CustomizePopularItems() {
// Get current popular items from constants // Get current popular items from constants
const { data: constants, isLoading: isLoadingConstants, error: constantsError } = trpc.admin.const.getConstants.useQuery(); const { data: constants, isLoading: isLoadingConstants, error: constantsError } = trpc.admin.const.getConstants.useQuery();
const { data: allProducts, isLoading: isLoadingProducts, error: productsError } = trpc.common.product.getAllProductsSummary.useQuery({}); const { data: allProducts, isLoading: isLoadingProducts, error: productsError } = trpc.common.product.getAllProductsSummary.useQuery();
const updateConstants = trpc.admin.const.updateConstants.useMutation(); const updateConstants = trpc.admin.const.updateConstants.useMutation();
// Initialize popular products from constants // Initialize popular products from constants

View file

@ -33,6 +33,7 @@ export default function EditProduct() {
shortDescription: '', shortDescription: '',
longDescription: '', longDescription: '',
storeId: 0, storeId: 0,
productType: 'item' as const,
variants: [], variants: [],
} }
} }

View file

@ -10,7 +10,9 @@ import type { AdminSku } from '@packages/shared';
type FilterType = 'all' | 'in-stock' | 'out-of-stock'; type FilterType = 'all' | 'in-stock' | 'out-of-stock';
function getDefaultSku(product: { skus: AdminSku[] }): AdminSku | null { type SerializedAdminSku = Omit<AdminSku, 'createdAt'> & { createdAt: string | Date }
function getDefaultSku(product: { skus: SerializedAdminSku[] }): SerializedAdminSku | null {
return product.skus?.[0] ?? null return product.skus?.[0] ?? null
} }
@ -114,7 +116,6 @@ export default function Products() {
<SearchBar <SearchBar
value={searchTerm} value={searchTerm}
onChangeText={setSearchTerm} onChangeText={setSearchTerm}
onSearch={() => {}}
placeholder="Search products..." placeholder="Search products..."
containerStyle={tw`mb-0`} containerStyle={tw`mb-0`}
/> />

View file

@ -25,7 +25,7 @@ const ProductGroupForm: React.FC<ProductGroupFormProps> = ({
onSuccess, onSuccess,
}) => { }) => {
// Fetch products // Fetch products
const { data: productsData } = trpc.common.product.getAllProductsSummary.useQuery({}); const { data: productsData } = trpc.common.product.getAllProductsSummary.useQuery();
const createGroup = trpc.admin.product.createGroup.useMutation(); const createGroup = trpc.admin.product.createGroup.useMutation();
const updateGroup = trpc.admin.product.updateGroup.useMutation(); const updateGroup = trpc.admin.product.updateGroup.useMutation();

View file

@ -45,7 +45,7 @@ export default function ProductsSelector({
selectedGroupIds = [], selectedGroupIds = [],
onGroupChange, onGroupChange,
}: ProductsSelectorProps) { }: ProductsSelectorProps) {
const { data: skusData } = trpc.common.product.getAllSkusSummary.useQuery({}); const { data: skusData } = trpc.common.product.getAllSkusSummary.useQuery();
const allSkus: SkuSummary[] = skusData?.skus || []; const allSkus: SkuSummary[] = skusData?.skus || [];
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');

View file

@ -182,7 +182,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
value: store.id, value: store.id,
})) || [] })) || []
const { data: skusData } = trpc.common.product.getAllSkusSummary.useQuery({}) const { data: skusData } = trpc.common.product.getAllSkusSummary.useQuery()
const skuOptions = (skusData?.skus || []).map((sku) => ({ const skuOptions = (skusData?.skus || []).map((sku) => ({
label: sku.label, label: sku.label,
value: sku.id, value: sku.id,

View file

@ -1,4 +1,5 @@
import { Hono } from 'hono' import { Hono } from 'hono'
import type { Context } from 'hono'
import { cors } from 'hono/cors' import { cors } from 'hono/cors'
import { logger } from 'hono/logger' import { logger } from 'hono/logger'
import { trpcServer } from '@hono/trpc-server' import { trpcServer } from '@hono/trpc-server'
@ -26,7 +27,7 @@ export const createApp = () => {
// tRPC middleware // tRPC middleware
app.use('/api/trpc/*', trpcServer({ app.use('/api/trpc/*', trpcServer({
router: appRouter, router: appRouter,
createContext: async ({ req, c }) => { createContext: async ({ req }, c) => {
let user = null let user = null
let staffUser = null let staffUser = null
const authHeader = req.headers.get('authorization') const authHeader = req.headers.get('authorization')

View file

@ -1,7 +1,7 @@
import '@/src/lib/notif-job' import '@/src/lib/notif-job'
import { initializeAllStores } from '@/src/stores/store-initializer' import { initializeAllStores } from '@/src/stores/store-initializer'
import { initializeUserNegativityStore } from '@/src/stores/user-negativity-store' import { initializeUserNegativityStore } from '@/src/stores/user-negativity-store'
import { startOrderHandler, startCancellationHandler, publishOrder } from '@/src/lib/post-order-handler' import { publishOrder } from '@/src/lib/post-order-handler'
import { deleteOrders } from '@/src/lib/delete-orders' import { deleteOrders } from '@/src/lib/delete-orders'
import { createAllCacheFiles } from '@/src/lib/cloud_cache' import { createAllCacheFiles } from '@/src/lib/cloud_cache'
@ -22,8 +22,6 @@ export const initFunc = async (): Promise<void> => {
await Promise.all([ await Promise.all([
initializeAllStores(), initializeAllStores(),
initializeUserNegativityStore(), initializeUserNegativityStore(),
startOrderHandler(),
startCancellationHandler(),
]); ]);
// Create all cache files after stores are initialized // Create all cache files after stores are initialized

View file

@ -50,7 +50,7 @@ export const imageUploadS3 = async(body: Buffer, type: string, key: string) => {
headers: { headers: {
'Content-Type': type, 'Content-Type': type,
}, },
body, body: body as unknown as BodyInit,
}) })
if (!resp.ok) { if (!resp.ok) {
const responseBody = await resp.text().catch(() => '') const responseBody = await resp.text().catch(() => '')
@ -121,8 +121,8 @@ export async function generateSignedUrlFromS3Url(s3UrlRaw: string|null, expiresI
const url = buildObjectUrl(getS3BucketName(), s3Url) const url = buildObjectUrl(getS3BucketName(), s3Url)
const signedRequest = await client.sign(url, { const signedRequest = await client.sign(url, {
method: 'GET', method: 'GET',
signQuery: true, headers: { 'X-Amz-Expires': String(expiresIn) },
expires: expiresIn, aws: { signQuery: true },
}) })
return signedRequest.url return signedRequest.url
} catch (error) { } catch (error) {
@ -173,11 +173,11 @@ export async function generateUploadUrl(key: string, mimeType: string, expiresIn
const url = buildObjectUrl(getS3BucketName(), key) const url = buildObjectUrl(getS3BucketName(), key)
const signedRequest = await client.sign(url, { const signedRequest = await client.sign(url, {
method: 'PUT', method: 'PUT',
signQuery: true,
expires: expiresIn,
headers: { headers: {
'Content-Type': mimeType, 'Content-Type': mimeType,
'X-Amz-Expires': String(expiresIn),
}, },
aws: { signQuery: true },
}) })
return signedRequest.url return signedRequest.url

View file

@ -3,7 +3,7 @@
// Re-export database connection // Re-export database connection
import type { D1Database } from '@cloudflare/workers-types' import type { D1Database } from '@cloudflare/workers-types'
import { db, initDb as initDbBase } from 'sqliteService' import { db, initDb as initDbBase } from '../../../packages/db_helper_sqlite'
export { db } export { db }
let dbInitialized = false let dbInitialized = false
@ -15,7 +15,7 @@ export const initDb = (database: D1Database) => {
} }
// Re-export all schema exports // Re-export all schema exports
export * from 'sqliteService' export * from '../../../packages/db_helper_sqlite'
// Re-export all helper methods from sqliteService // Re-export all helper methods from sqliteService
export { export {
@ -280,4 +280,4 @@ export {
// Upload URL Helpers // Upload URL Helpers
createUploadUrlStatus, createUploadUrlStatus,
claimUploadUrlStatus, claimUploadUrlStatus,
} from 'sqliteService' } from '../../../packages/db_helper_sqlite'

View file

@ -59,6 +59,10 @@ export async function scaffoldProducts() {
productType: product.productType || 'item', productType: product.productType || 'item',
isComboOnly: product.isComboOnly, isComboOnly: product.isComboOnly,
isOffer: product.isOffer, isOffer: product.isOffer,
price: Number(product.price ?? 0),
marketPrice: product.marketPrice != null ? Number(product.marketPrice) : null,
flashPrice: product.flashPrice != null ? Number(product.flashPrice) : null,
isFlashAvailable: product.isFlashAvailable,
}; };
}) })
); );

View file

@ -44,7 +44,7 @@
}, },
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [""], /* Specify multiple folders that act like './node_modules/@types'. */ // "typeRoots": [""], /* Specify multiple folders that act like './node_modules/@types'. */
"typeRoots": ["./node_modules/@types", "../shared-types"], "typeRoots": ["../../node_modules/@types", "./node_modules/@types", "../shared-types"],
// "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */

View file

@ -4,7 +4,6 @@ import { trpc } from "../trpc/client";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import dayjs from "dayjs"; import dayjs from "dayjs";
import items from "razorpay/dist/types/items";
interface VendorOrder { interface VendorOrder {
orderId: string; orderId: string;

View file

@ -22,7 +22,7 @@ type ProductSummary = Omit<
> & { > & {
isFlashAvailable: boolean isFlashAvailable: boolean
name?: string name?: string
flashPrice?: string | null flashPrice?: number | null
images?: string[] | null images?: string[] | null
productQuantity?: number productQuantity?: number
unitNotation?: string unitNotation?: string

View file

@ -1,7 +1,4 @@
import { hydrateRoot } from 'react-dom/client' import { hydrateRoot } from 'react-dom/client'
import { StartClient } from '@tanstack/react-start/client' import { StartClient } from '@tanstack/react-start/client'
import { getRouter } from './router'
const router = getRouter() hydrateRoot(document, <StartClient />)
hydrateRoot(document, <StartClient router={router} />)

View file

@ -42,7 +42,7 @@ export default function AddToCartDialog() {
useEffect(() => { useEffect(() => {
if (isOpen && product) { if (isOpen && product) {
const cartItem = cartData?.items?.find((item: any) => item.productId === product.id) const cartItem = cartData?.items?.find((item: any) => item.skuId === product.id)
const cartQuantity = cartItem?.quantity || 0 const cartQuantity = cartItem?.quantity || 0
setQuantity(cartQuantity === 0 ? 1 : cartQuantity) setQuantity(cartQuantity === 0 ? 1 : cartQuantity)
setSelectedSlotId(cartItem?.slotId || null) setSelectedSlotId(cartItem?.slotId || null)
@ -75,7 +75,7 @@ export default function AddToCartDialog() {
.filter(Boolean) .filter(Boolean)
.filter((slot) => dayjs(slot.deliveryTime).isAfter(dayjs())) .filter((slot) => dayjs(slot.deliveryTime).isAfter(dayjs()))
const cartItem = cartData?.items?.find((item: any) => item.productId === product?.id) const cartItem = cartData?.items?.find((item: any) => item.skuId === product?.id)
const isUpdate = (cartItem?.quantity || 0) >= 1 const isUpdate = (cartItem?.quantity || 0) >= 1
const productAvailability = productSlotsMap[product?.id] const productAvailability = productSlotsMap[product?.id]
@ -89,14 +89,14 @@ export default function AddToCartDialog() {
} }
if (isUpdate && cartItem) { if (isUpdate && cartItem) {
updateItem.mutate( updateItem.mutate(
{ productId: product.id, quantity, slotId: selectedSlotId, deliveryDate: selectedSlotId ? slotMap[selectedSlotId]?.deliveryTime : null }, { skuId: product.id, quantity, slotId: selectedSlotId, deliveryDate: selectedSlotId ? slotMap[selectedSlotId]?.deliveryTime : null },
{ onSuccess: () => clearAddedToCartProduct() } { onSuccess: () => clearAddedToCartProduct() }
) )
} else { } else {
const slotId = selectedSlotId ?? availableSlotIds[0] ?? 0 const slotId = selectedSlotId ?? availableSlotIds[0] ?? 0
addToCart.mutate( addToCart.mutate(
{ {
productId: product.id, skuId: product.id,
quantity, quantity,
storeId: product.storeId || 1, storeId: product.storeId || 1,
slotId, slotId,

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 { p, pInput, MyButton, div } from 'web-components' import { p, pInput as 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'
import type { AddressFormProps } from '@packages/shared' import type { AddressFormProps } from '@packages/shared'
@ -123,7 +123,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<pInput <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)}
@ -133,7 +133,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
</div> </div>
<div> <div>
<pInput <PInput
placeholder="Phone" placeholder="Phone"
type="tel" type="tel"
value={values.phone} value={values.phone}
@ -144,7 +144,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
</div> </div>
<div> <div>
<pInput <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)}
@ -154,7 +154,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
</div> </div>
<div> <div>
<pInput <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)}
@ -163,7 +163,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>
<pInput <PInput
placeholder="City" placeholder="City"
value={values.city} value={values.city}
disabled disabled
@ -171,7 +171,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
/> />
</div> </div>
<div> <div>
<pInput <PInput
placeholder="State" placeholder="State"
value={values.state} value={values.state}
disabled disabled
@ -181,7 +181,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
</div> </div>
<div> <div>
<pInput <PInput
placeholder="Pincode" placeholder="Pincode"
value={values.pincode} value={values.pincode}
disabled disabled
@ -208,7 +208,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
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.
</p> </p>
<pInput <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)}

View file

@ -55,7 +55,7 @@ export function FloatingCartBar({ isFlashDelivery = false }: FlashDeliveryProps)
// Calculate total cart value // Calculate total cart value
const totalCartValue = cartItems.reduce( const totalCartValue = cartItems.reduce(
(sum, item) => { (sum, item) => {
const product = productsById[item.productId] const product = productsById[item.skuId]
const basePrice = product?.price ?? 0 const basePrice = product?.price ?? 0
const price = isFlashDelivery ? (product?.flashPrice ?? basePrice) : basePrice const price = isFlashDelivery ? (product?.flashPrice ?? basePrice) : basePrice
return sum + price * item.quantity return sum + price * item.quantity
@ -142,17 +142,17 @@ export function FloatingCartBar({ isFlashDelivery = false }: FlashDeliveryProps)
<React.Fragment key={item.id}> <React.Fragment key={item.id}>
<div className="flex items-center gap-4 py-4"> <div className="flex items-center gap-4 py-4">
<img <img
src={productsById[item.productId]?.images?.[0]} src={productsById[item.skuId]?.images?.[0]}
alt="" alt=""
className="h-14 w-14 shrink-0 rounded-lg border border-gray-100 bg-gray-50 object-cover" className="h-14 w-14 shrink-0 rounded-lg border border-gray-100 bg-gray-50 object-cover"
/> />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<p className="truncate text-sm font-bold text-gray-900"> <p className="truncate text-sm font-bold text-gray-900">
{productsById[item.productId]?.name || ''} {productsById[item.skuId]?.name || ''}
</p> </p>
<p className="text-xs font-medium text-gray-500"> <p className="text-xs font-medium text-gray-500">
{productsById[item.productId]?.productQuantity || 0} {productsById[item.skuId]?.productQuantity || 0}
{productsById[item.productId]?.unitNotation || ''} {productsById[item.skuId]?.unitNotation || ''}
</p> </p>
{item.slotId && ( {item.slotId && (
<div className="mt-1 flex items-center gap-1 rounded border border-brand-100 bg-brand-25 px-1.5 py-0.5"> <div className="mt-1 flex items-center gap-1 rounded border border-brand-100 bg-brand-25 px-1.5 py-0.5">
@ -167,17 +167,17 @@ export function FloatingCartBar({ isFlashDelivery = false }: FlashDeliveryProps)
value={quantities[item.id] || item.quantity} value={quantities[item.id] || item.quantity}
setValue={(value) => { setValue={(value) => {
if (value === 0) { if (value === 0) {
removeFromCart.mutate(item.productId) removeFromCart.mutate(item.skuId)
} else { } else {
setQuantities((prev) => ({ ...prev, [item.id]: value })) setQuantities((prev) => ({ ...prev, [item.id]: value }))
updateCartItem.mutate({ productId: item.productId, quantity: value }) updateCartItem.mutate({ skuId: item.skuId, quantity: value })
} }
}} }}
step={productsById[item.productId]?.incrementStep || 1} step={productsById[item.skuId]?.incrementStep || 1}
/> />
<p className="text-[13px] font-bold text-gray-900 sm:text-sm"> <p className="text-[13px] font-bold text-gray-900 sm:text-sm">
{(() => { {(() => {
const product = productsById[item.productId] const product = productsById[item.skuId]
const basePrice = product?.price ?? 0 const basePrice = product?.price ?? 0
const price = isFlashDelivery ? (product?.flashPrice ?? basePrice) : basePrice const price = isFlashDelivery ? (product?.flashPrice ?? basePrice) : basePrice
return price * item.quantity return price * item.quantity

View file

@ -36,7 +36,7 @@ export function PaymentAndOrderComponent({
// Calculate totals // Calculate totals
const totalPrice = cartItems.reduce((sum, item) => { const totalPrice = cartItems.reduce((sum, item) => {
const product = productsById[item.productId] const product = productsById[item.skuId]
if (!product) return sum if (!product) return sum
const price = isFlashDelivery ? (product.flashPrice ?? product.price ?? 0) : (product.price || 0) const price = isFlashDelivery ? (product.flashPrice ?? product.price ?? 0) : (product.price || 0)
return sum + price * item.quantity return sum + price * item.quantity
@ -61,7 +61,7 @@ export function PaymentAndOrderComponent({
navigate({ navigate({
to: '/home/order-success', to: '/home/order-success',
search: { orderId: firstOrder?.id, totalAmount: finalTotalWithDelivery }, search: { orderId: String(firstOrder?.id), totalAmount: String(finalTotalWithDelivery) },
}) })
}, },
onError: (error: any) => { onError: (error: any) => {
@ -87,7 +87,7 @@ export function PaymentAndOrderComponent({
const orderData = { const orderData = {
selectedItems: cartItems.map((item) => ({ selectedItems: cartItems.map((item) => ({
productId: item.productId, skuId: item.skuId,
quantity: item.quantity, quantity: item.quantity,
slotId: isFlashDelivery ? null : item.slotId, slotId: isFlashDelivery ? null : item.slotId,
})), })),

View file

@ -42,7 +42,7 @@ export function ProductCard({
const addToCart = useAddToCart('regular') const addToCart = useAddToCart('regular')
// Find current quantity from cart data // Find current quantity from cart data
const cartItem = cartData?.items?.find((cartItem: any) => cartItem.productId === item.id) const cartItem = cartData?.items?.find((cartItem: any) => cartItem.skuId === item.id)
const quantity = cartItem?.quantity || 0 const quantity = cartItem?.quantity || 0
// Get slots data from central store // Get slots data from central store
@ -82,7 +82,7 @@ export function ProductCard({
const slot = slotMap[slotId] const slot = slotMap[slotId]
const deliveryTime = slot ? dayjs(slot.deliveryTime).format('ddd, DD MMM • h:mm A') : '' const deliveryTime = slot ? dayjs(slot.deliveryTime).format('ddd, DD MMM • h:mm A') : ''
addToCart.mutate( addToCart.mutate(
{ productId: item.id, quantity: 1, slotId, storeId: item.storeId }, { skuId: item.id, quantity: 1, slotId, storeId: item.storeId },
{ {
onSuccess: () => { onSuccess: () => {
alert(`Added ${item.name} for delivery at ${deliveryTime}`) alert(`Added ${item.name} for delivery at ${deliveryTime}`)
@ -90,7 +90,7 @@ export function ProductCard({
} }
) )
} else if (cartItem) { } else if (cartItem) {
updateCartItem.mutate({ productId: item.id, quantity: newQuantity }) updateCartItem.mutate({ skuId: item.id, quantity: newQuantity })
} }
} }

View file

@ -5,9 +5,9 @@ type CartType = 'regular' | 'flash'
interface CartItem { interface CartItem {
id: number id: number
productId: number skuId: number
quantity: number quantity: number
storeId: number storeId: number | null
addedAt: number addedAt: number
slotId?: number | null slotId?: number | null
deliveryDate?: string | null deliveryDate?: string | null
@ -50,20 +50,20 @@ export function useAddToCart(cartType: CartType = 'regular') {
return useMutation({ return useMutation({
mutationFn: async ({ mutationFn: async ({
productId, skuId,
quantity, quantity,
storeId, storeId,
slotId, slotId,
deliveryDate, deliveryDate,
}: { }: {
productId: number skuId: number
quantity: number quantity: number
storeId: number storeId?: number | null
slotId?: number | null slotId?: number | null
deliveryDate?: string | null deliveryDate?: string | null
}) => { }) => {
const items = readCart(cartType) const items = readCart(cartType)
const existing = items.find((i) => i.productId === productId) const existing = items.find((i) => i.skuId === skuId)
if (existing) { if (existing) {
existing.quantity += quantity existing.quantity += quantity
if (slotId) existing.slotId = slotId if (slotId) existing.slotId = slotId
@ -71,9 +71,9 @@ export function useAddToCart(cartType: CartType = 'regular') {
} else { } else {
items.push({ items.push({
id: Date.now(), id: Date.now(),
productId, skuId,
quantity, quantity,
storeId, storeId: storeId ?? null,
addedAt: Date.now(), addedAt: Date.now(),
slotId: slotId ?? null, slotId: slotId ?? null,
deliveryDate: deliveryDate ?? null, deliveryDate: deliveryDate ?? null,
@ -95,22 +95,22 @@ export function useUpdateCartItem(cartType: CartType = 'regular') {
return useMutation({ return useMutation({
mutationFn: async ({ mutationFn: async ({
productId, skuId,
quantity, quantity,
slotId, slotId,
deliveryDate, deliveryDate,
}: { }: {
productId: number skuId: number
quantity: number quantity: number
slotId?: number | null slotId?: number | null
deliveryDate?: string | null deliveryDate?: string | null
}) => { }) => {
let items = readCart(cartType) let items = readCart(cartType)
const existing = items.find((i) => i.productId === productId) const existing = items.find((i) => i.skuId === skuId)
if (!existing) return if (!existing) return
if (quantity <= 0) { if (quantity <= 0) {
// Quantity hit 0 — remove the item from the cart entirely // Quantity hit 0 — remove the item from the cart entirely
items = items.filter((i) => i.productId !== productId) items = items.filter((i) => i.skuId !== skuId)
} else { } else {
existing.quantity = quantity existing.quantity = quantity
if (slotId !== undefined) existing.slotId = slotId if (slotId !== undefined) existing.slotId = slotId
@ -129,9 +129,9 @@ export function useRemoveFromCart(cartType: CartType = 'regular') {
const queryClient = useQueryClient() const queryClient = useQueryClient()
return useMutation({ return useMutation({
mutationFn: async (productId: number) => { mutationFn: async (skuId: number) => {
let items = readCart(cartType) let items = readCart(cartType)
items = items.filter((i) => i.productId !== productId) items = items.filter((i) => i.skuId !== skuId)
writeCart(cartType, items) writeCart(cartType, items)
queryClient.setQueryData([getCartKey(cartType)], toCartData(items)) queryClient.setQueryData([getCartKey(cartType)], toCartData(items))
}, },

View file

@ -7,7 +7,7 @@ import type { User as SharedUser, AuthStateCore } from '@packages/shared'
type User = Pick<SharedUser, 'id' | 'email'> & type User = Pick<SharedUser, 'id' | 'email'> &
Partial<Omit<SharedUser, 'id' | 'email' | 'createdAt' | 'mobile'>> & { Partial<Omit<SharedUser, 'id' | 'email' | 'createdAt' | 'mobile'>> & {
mobile: string | null mobile: string | null
profileImage: string | null profileImage?: string | null
} }
interface AuthState extends AuthStateCore { interface AuthState extends AuthStateCore {

View file

@ -1,19 +1,10 @@
import { create } from 'zustand' import { create } from 'zustand'
import type { CentralProductState as CentralProductsState } from '@packages/shared' import type { CentralProductState as CentralProductsState } from '@packages/shared'
import type { MergedProduct } from '../../hooks/prominent-api-hooks'
interface Product { // Central cache holds the API product (MergedProduct) — single product shape
id: number // across the app (numeric pricing, string images).
name: string type Product = MergedProduct
price: number
discountedPrice: number | null
images: { uri: string }[]
unit: string
unitValue: number
storeId: number
category: string
isActive: boolean
description: string | null
}
type CentralProductStore = CentralProductsState<Product> type CentralProductStore = CentralProductsState<Product>

View file

@ -18,11 +18,11 @@ function CartPage() {
const productsById: Record<number, any> = {} const productsById: Record<number, any> = {}
products.forEach((p: any) => { productsById[p.id] = p }) products.forEach((p: any) => { productsById[p.id] = p })
const cartItems = (cart?.items || []).filter((item) => productsById[item.productId]) const cartItems = (cart?.items || []).filter((item) => productsById[item.skuId])
let total = 0 let total = 0
cartItems.forEach((item) => { cartItems.forEach((item) => {
const product = productsById[item.productId] const product = productsById[item.skuId]
if (product) { if (product) {
total += product.price * item.quantity total += product.price * item.quantity
} }
@ -57,11 +57,11 @@ function CartPage() {
{/* Items */} {/* Items */}
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
{cartItems.map((item) => { {cartItems.map((item) => {
const product = productsById[item.productId] const product = productsById[item.skuId]
const price = product.price const price = product.price
return ( return (
<div <div
key={item.productId} key={item.skuId}
className="flex items-center gap-4 rounded-xl border border-gray-200 bg-white p-4" className="flex items-center gap-4 rounded-xl border border-gray-200 bg-white p-4"
> >
<img <img
@ -77,7 +77,7 @@ function CartPage() {
<div className="mt-2 flex items-center gap-3"> <div className="mt-2 flex items-center gap-3">
<Quantifier <Quantifier
value={item.quantity} value={item.quantity}
setValue={(q) => updateItem.mutate({ productId: item.productId, quantity: q })} setValue={(q) => updateItem.mutate({ skuId: item.skuId, quantity: q })}
/> />
<p className="text-sm font-bold text-gray-900">{price}</p> <p className="text-sm font-bold text-gray-900">{price}</p>
</div> </div>
@ -85,7 +85,7 @@ function CartPage() {
<div className="flex flex-col items-end gap-2"> <div className="flex flex-col items-end gap-2">
<p className="text-sm font-extrabold text-brand-600 sm:text-base">{price * item.quantity}</p> <p className="text-sm font-extrabold text-brand-600 sm:text-base">{price * item.quantity}</p>
<button <button
onClick={() => removeItem.mutate(item.productId)} onClick={() => removeItem.mutate(item.skuId)}
className="flex h-9 w-9 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-red-50 hover:text-red-500" className="flex h-9 w-9 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-red-50 hover:text-red-500"
aria-label={`Remove ${product.name}`} aria-label={`Remove ${product.name}`}
> >

View file

@ -38,7 +38,7 @@ function CheckoutContent() {
const productsById: Record<number, any> = {} const productsById: Record<number, any> = {}
products.forEach((p: any) => { productsById[p.id] = p }) products.forEach((p: any) => { productsById[p.id] = p })
const cartItems = (cartData?.items || []).filter((item) => productsById[item.productId]) const cartItems = (cartData?.items || []).filter((item) => productsById[item.skuId])
// Handle empty cart case // Handle empty cart case
if (cartItems.length === 0) { if (cartItems.length === 0) {

View file

@ -14,13 +14,13 @@ function FlashCartPage() {
const removeItem = useRemoveFromCart('flash') const removeItem = useRemoveFromCart('flash')
const productsById = useCentralProductStore((s) => s.productsById) const productsById = useCentralProductStore((s) => s.productsById)
const cartItems = (cart?.items || []).filter((item) => productsById[item.productId]) const cartItems = (cart?.items || []).filter((item) => productsById[item.skuId])
let total = 0 let total = 0
cartItems.forEach((item) => { cartItems.forEach((item) => {
const product = productsById[item.productId] const product = productsById[item.skuId]
if (product) { if (product) {
total += (product.discountedPrice ?? product.price) * item.quantity total += product.price * item.quantity
} }
}) })
@ -56,27 +56,27 @@ function FlashCartPage() {
{/* Items */} {/* Items */}
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
{cartItems.map((item) => { {cartItems.map((item) => {
const product = productsById[item.productId] const product = productsById[item.skuId]
const price = product.discountedPrice ?? product.price const price = product.price
return ( return (
<div <div
key={item.productId} key={item.skuId}
className="flex items-center gap-4 rounded-xl border border-gray-200 bg-white p-4" className="flex items-center gap-4 rounded-xl border border-gray-200 bg-white p-4"
> >
<img <img
src={product.images?.[0]?.uri || product.images?.[0] as any} src={product.images?.[0] ?? ''}
alt={product.name} alt={product.name}
className="h-20 w-20 shrink-0 rounded-lg object-cover" className="h-20 w-20 shrink-0 rounded-lg object-cover"
/> />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<p className="text-sm font-bold text-gray-900">{product.name}</p> <p className="text-sm font-bold text-gray-900">{product.name}</p>
<p className="mt-0.5 text-xs text-gray-500"> <p className="mt-0.5 text-xs text-gray-500">
{product.unitValue || 1}{product.unit || ''} per unit {product.unitNotation || ''} per unit
</p> </p>
<div className="mt-2 flex items-center gap-3"> <div className="mt-2 flex items-center gap-3">
<Quantifier <Quantifier
value={item.quantity} value={item.quantity}
setValue={(q) => updateItem.mutate({ productId: item.productId, quantity: q })} setValue={(q) => updateItem.mutate({ skuId: item.skuId, quantity: q })}
/> />
<p className="text-sm font-bold text-gray-900">{price}</p> <p className="text-sm font-bold text-gray-900">{price}</p>
</div> </div>
@ -84,7 +84,7 @@ function FlashCartPage() {
<div className="flex flex-col items-end gap-2"> <div className="flex flex-col items-end gap-2">
<p className="text-base font-extrabold text-flash-500">{price * item.quantity}</p> <p className="text-base font-extrabold text-flash-500">{price * item.quantity}</p>
<button <button
onClick={() => removeItem.mutate(item.productId)} onClick={() => removeItem.mutate(item.skuId)}
className="flex h-9 w-9 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-red-50 hover:text-red-500" className="flex h-9 w-9 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-red-50 hover:text-red-500"
aria-label={`Remove ${product.name}`} aria-label={`Remove ${product.name}`}
> >

View file

@ -30,19 +30,19 @@ function FlashCheckoutPage() {
queryClient.invalidateQueries({ queryKey: ['local-cart-flash'] }) queryClient.invalidateQueries({ queryKey: ['local-cart-flash'] })
navigate({ navigate({
to: '/flash/order-success', to: '/flash/order-success',
search: { orderId: order?.id, totalAmount: total }, search: { orderId: String(order?.id), totalAmount: String(total) },
}) })
}, },
onSettled: () => setIsLoading(false), onSettled: () => setIsLoading(false),
}) })
const cartItems = (cart?.items || []).filter((item) => productsById[item.productId]) const cartItems = (cart?.items || []).filter((item) => productsById[item.skuId])
let total = 0 let total = 0
cartItems.forEach((item) => { cartItems.forEach((item) => {
const product = productsById[item.productId] const product = productsById[item.skuId]
if (product) { if (product) {
total += (product.discountedPrice ?? product.price) * item.quantity total += product.price * item.quantity
} }
}) })
@ -51,7 +51,7 @@ function FlashCheckoutPage() {
setIsLoading(true) setIsLoading(true)
placeOrderMutation.mutate({ placeOrderMutation.mutate({
selectedItems: cartItems.map((item) => ({ selectedItems: cartItems.map((item) => ({
productId: item.productId, skuId: item.skuId,
quantity: item.quantity, quantity: item.quantity,
slotId: null, slotId: null,
})), })),
@ -95,15 +95,15 @@ function FlashCheckoutPage() {
Order Summary Order Summary
</p> </p>
{cartItems.map((item) => { {cartItems.map((item) => {
const product = productsById[item.productId] const product = productsById[item.skuId]
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.skuId} className="flex items-center justify-between py-2">
<p className="text-sm"> <p className="text-sm">
{product.name} x{item.quantity} {product.name} x{item.quantity}
</p> </p>
<p className="text-sm font-bold"> <p className="text-sm font-bold">
{(product.discountedPrice ?? product.price) * item.quantity} {product.price * item.quantity}
</p> </p>
</div> </div>
) )

View file

@ -21,7 +21,7 @@ function FlashProductDetailPage() {
const handleAddToCart = () => { const handleAddToCart = () => {
if (!product) return if (!product) return
addToCart.mutate( addToCart.mutate(
{ productId: product.id, quantity, storeId: product.storeId }, { skuId: product.id, quantity, storeId: product.storeId },
{ onSuccess: () => navigate({ to: '/flash/cart' }) } { onSuccess: () => navigate({ to: '/flash/cart' }) }
) )
} }
@ -34,7 +34,7 @@ function FlashProductDetailPage() {
) )
} }
const price = product.discountedPrice ?? product.price const price = product.price
const imageUrl = product.images?.[0] const imageUrl = product.images?.[0]
return ( return (
@ -60,18 +60,13 @@ function FlashProductDetailPage() {
{product.name} {product.name}
</p> </p>
<p className="mb-4 text-sm text-gray-500"> <p className="mb-4 text-sm text-gray-500">
{product.unitValue}{product.unit} {product.unitNotation}
</p> </p>
<div className="mb-4 flex items-baseline gap-2"> <div className="mb-4 flex items-baseline gap-2">
<p className="font-bold text-2xl text-brand-600"> <p className="font-bold text-2xl text-brand-600">
{price} {price}
</p> </p>
{product.discountedPrice && (
<p className="text-sm text-gray-400 line-through">
{product.price}
</p>
)}
</div> </div>
<div className="mb-6"> <div className="mb-6">

View file

@ -82,7 +82,7 @@ function FlashDeliveryPage() {
const handleAddToCart = (productId: number) => { const handleAddToCart = (productId: number) => {
const item = filteredProducts.find((p: any) => p.id === productId) const item = filteredProducts.find((p: any) => p.id === productId)
addToCart.mutate( addToCart.mutate(
{ productId, quantity: 1, storeId: item?.storeId ?? 0 }, { skuId: productId, quantity: 1, storeId: item?.storeId ?? 0 },
{ {
onSuccess: () => { onSuccess: () => {
alert(`Added ${item?.name || 'item'} for 1 Hr Delivery`) alert(`Added ${item?.name || 'item'} for 1 Hr Delivery`)
@ -215,7 +215,7 @@ function CompactProductCard({
const removeFromCart = useRemoveFromCart('flash'); const removeFromCart = useRemoveFromCart('flash');
const cartItem = cartData?.items?.find( const cartItem = cartData?.items?.find(
(cartItem: any) => cartItem.productId === item.id, (cartItem: any) => cartItem.skuId === item.id,
); );
const quantity = cartItem?.quantity || 0; const quantity = cartItem?.quantity || 0;
const isOutOfStock = productSlotsMap[item.id]?.isOutOfStock; const isOutOfStock = productSlotsMap[item.id]?.isOutOfStock;
@ -226,7 +226,7 @@ function CompactProductCard({
} else if (newQuantity === 1 && !cartItem) { } else if (newQuantity === 1 && !cartItem) {
handleAddToCart(item.id); handleAddToCart(item.id);
} else if (cartItem) { } else if (cartItem) {
updateCartItem.mutate({ productId: cartItem.id, quantity: newQuantity }); updateCartItem.mutate({ skuId: cartItem.skuId, quantity: newQuantity });
} }
}; };

View file

@ -18,11 +18,11 @@ function CartPage() {
const productsById: Record<number, any> = {} const productsById: Record<number, any> = {}
products.forEach((p: any) => { productsById[p.id] = p }) products.forEach((p: any) => { productsById[p.id] = p })
const cartItems = (cart?.items || []).filter((item) => productsById[item.productId]) const cartItems = (cart?.items || []).filter((item) => productsById[item.skuId])
let total = 0 let total = 0
cartItems.forEach((item) => { cartItems.forEach((item) => {
const product = productsById[item.productId] const product = productsById[item.skuId]
if (product) { if (product) {
total += product.price * item.quantity total += product.price * item.quantity
} }
@ -57,11 +57,11 @@ function CartPage() {
{/* Items */} {/* Items */}
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
{cartItems.map((item) => { {cartItems.map((item) => {
const product = productsById[item.productId] const product = productsById[item.skuId]
const price = product.price const price = product.price
return ( return (
<div <div
key={item.productId} key={item.skuId}
className="flex items-center gap-4 rounded-xl border border-gray-200 bg-white p-4" className="flex items-center gap-4 rounded-xl border border-gray-200 bg-white p-4"
> >
<img <img
@ -77,7 +77,7 @@ function CartPage() {
<div className="mt-2 flex items-center gap-3"> <div className="mt-2 flex items-center gap-3">
<Quantifier <Quantifier
value={item.quantity} value={item.quantity}
setValue={(q) => updateItem.mutate({ productId: item.productId, quantity: q })} setValue={(q) => updateItem.mutate({ skuId: item.skuId, quantity: q })}
/> />
<p className="text-sm font-bold text-gray-900">{price}</p> <p className="text-sm font-bold text-gray-900">{price}</p>
</div> </div>
@ -85,7 +85,7 @@ function CartPage() {
<div className="flex flex-col items-end gap-2"> <div className="flex flex-col items-end gap-2">
<p className="text-sm font-extrabold text-brand-600 sm:text-base">{price * item.quantity}</p> <p className="text-sm font-extrabold text-brand-600 sm:text-base">{price * item.quantity}</p>
<button <button
onClick={() => removeItem.mutate(item.productId)} onClick={() => removeItem.mutate(item.skuId)}
className="flex h-9 w-9 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-red-50 hover:text-red-500" className="flex h-9 w-9 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-red-50 hover:text-red-500"
aria-label={`Remove ${product.name}`} aria-label={`Remove ${product.name}`}
> >

View file

@ -30,19 +30,19 @@ function CheckoutPage() {
queryClient.invalidateQueries({ queryKey: ['local-cart-regular'] }) queryClient.invalidateQueries({ queryKey: ['local-cart-regular'] })
navigate({ navigate({
to: '/home/order-success', to: '/home/order-success',
search: { orderId: order?.id, totalAmount: total }, search: { orderId: String(order?.id), totalAmount: String(total) },
}) })
}, },
onSettled: () => setIsLoading(false), onSettled: () => setIsLoading(false),
}) })
const cartItems = (cart?.items || []).filter((item) => productsById[item.productId]) const cartItems = (cart?.items || []).filter((item) => productsById[item.skuId])
let total = 0 let total = 0
cartItems.forEach((item) => { cartItems.forEach((item) => {
const product = productsById[item.productId] const product = productsById[item.skuId]
if (product) { if (product) {
total += (product.discountedPrice ?? product.price) * item.quantity total += product.price * item.quantity
} }
}) })
@ -51,7 +51,7 @@ function CheckoutPage() {
setIsLoading(true) setIsLoading(true)
placeOrderMutation.mutate({ placeOrderMutation.mutate({
selectedItems: cartItems.map((item) => ({ selectedItems: cartItems.map((item) => ({
productId: item.productId, skuId: item.skuId,
quantity: item.quantity, quantity: item.quantity,
slotId: null, slotId: null,
})), })),
@ -97,15 +97,15 @@ function CheckoutPage() {
Order Summary Order Summary
</p> </p>
{cartItems.map((item) => { {cartItems.map((item) => {
const product = productsById[item.productId] const product = productsById[item.skuId]
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.skuId} className="flex items-center justify-between py-2">
<p className="text-sm"> <p className="text-sm">
{product.name} x{item.quantity} {product.name} x{item.quantity}
</p> </p>
<p className="text-sm font-bold"> <p className="text-sm font-bold">
{(product.discountedPrice ?? product.price) * item.quantity} {product.price * item.quantity}
</p> </p>
</div> </div>
) )

View file

@ -67,7 +67,7 @@ function ProductDetailPage() {
}, [slotsData, productDetail]) }, [slotsData, productDetail])
const cartItem = productDetail const cartItem = productDetail
? cartData?.items?.find((item: any) => item.productId === productDetail.id) ? cartData?.items?.find((item: any) => item.skuId === productDetail.id)
: null : null
const quantity = cartItem?.quantity || 0 const quantity = cartItem?.quantity || 0
@ -83,7 +83,7 @@ function ProductDetailPage() {
} else if (newQuantity === 1 && !cartItem) { } else if (newQuantity === 1 && !cartItem) {
handleAddToCart() handleAddToCart()
} else if (cartItem) { } else if (cartItem) {
updateCartItem.mutate({ productId: cartItem.id, quantity: newQuantity }) updateCartItem.mutate({ skuId: cartItem.skuId, quantity: newQuantity })
} }
} }
@ -95,7 +95,7 @@ function ProductDetailPage() {
return return
} }
addToCart.mutate( addToCart.mutate(
{ productId: productDetail.id, quantity: 1, slotId, storeId: productDetail.storeId }, { skuId: productDetail.id, quantity: 1, slotId, storeId: productDetail.store?.id },
{ {
onSuccess: () => { onSuccess: () => {
const slot = slotsData?.slots?.find((s: any) => s.id === slotId) const slot = slotsData?.slots?.find((s: any) => s.id === slotId)
@ -111,7 +111,7 @@ function ProductDetailPage() {
const handleBuyNow = () => { const handleBuyNow = () => {
if (!productDetail) return if (!productDetail) return
addToFlashCart.mutate( addToFlashCart.mutate(
{ productId: productDetail.id, quantity: 1, storeId: productDetail.storeId }, { skuId: productDetail.id, quantity: 1, storeId: productDetail.store?.id },
{ {
onSuccess: () => { onSuccess: () => {
navigate({ to: '/flash/cart' }) navigate({ to: '/flash/cart' })
@ -125,18 +125,18 @@ function ProductDetailPage() {
if (cartItem) { if (cartItem) {
removeFromCart.mutate( removeFromCart.mutate(
{ itemId: cartItem.id }, cartItem.skuId,
{ {
onSuccess: () => { onSuccess: () => {
addToCart.mutate( addToCart.mutate(
{ productId: productDetail.id, quantity: cartItem.quantity + 1, slotId: selectedSlotId, storeId: productDetail.storeId } { skuId: productDetail.id, quantity: cartItem.quantity + 1, slotId: selectedSlotId, storeId: productDetail.store?.id }
) )
}, },
} }
) )
} else { } else {
addToCart.mutate( addToCart.mutate(
{ productId: productDetail.id, quantity: 1, slotId: selectedSlotId, storeId: productDetail.storeId } { skuId: productDetail.id, quantity: 1, slotId: selectedSlotId, storeId: productDetail.store?.id }
) )
} }
setShowAllSlots(false) setShowAllSlots(false)

View file

@ -8,7 +8,7 @@ export const Route = createFileRoute('/me/orders')({ component: OrdersPage })
function OrdersPage() { function OrdersPage() {
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
const { data } = trpc.user.order.getOrders.useQuery({ page: 1, limit: 20 }) const { data } = trpc.user.order.getOrders.useQuery({ page: 1 })
const orders = data?.data || [] const orders = data?.data || []
// Check if we're on the exact /me/orders path (not a child route like /me/orders/123) // Check if we're on the exact /me/orders path (not a child route like /me/orders/123)

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 { p, MyButton, pInput, pButton } from 'web-components' import { p, MyButton, pInput as PInput, pButton } from 'web-components'
export const Route = createFileRoute('/register')({ component: RegisterPage }) export const Route = createFileRoute('/register')({ component: RegisterPage })
@ -16,8 +16,8 @@ function RegisterPage() {
const registerMutation = trpc.user.auth.register.useMutation({ const registerMutation = trpc.user.auth.register.useMutation({
onSuccess: (data) => { onSuccess: (data) => {
if (data.token && data.user) { if (data.data.token && data.data.user) {
authRegister(data.token, data.user) authRegister(data.data.token, data.data.user)
navigate({ to: '/home' }) navigate({ to: '/home' })
} }
}, },
@ -40,19 +40,19 @@ function RegisterPage() {
<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">
<pInput <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
/> />
<pInput <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)}
/> />
<pInput <PInput
placeholder="Mobile Number" placeholder="Mobile Number"
value={mobile} value={mobile}
onChange={(e) => { onChange={(e) => {
@ -61,7 +61,7 @@ function RegisterPage() {
}} }}
required required
/> />
<pInput <PInput
placeholder="Password" placeholder="Password"
type="password" type="password"
value={password} value={password}

View file

@ -112,7 +112,7 @@ function SlotViewPage() {
? dayjs(slot.deliveryTime).format("ddd, DD MMM • h:mm A") ? dayjs(slot.deliveryTime).format("ddd, DD MMM • h:mm A")
: ""; : "";
addToCart.mutate( addToCart.mutate(
{ productId, quantity: 1, slotId: slotId || 0, storeId: item?.storeId }, { skuId: productId, quantity: 1, slotId: slotId || 0, storeId: item?.storeId },
{ {
onSuccess: () => { onSuccess: () => {
alert( alert(
@ -420,7 +420,7 @@ function CompactProductCard({
} else if (newQuantity === 1 && !cartItem) { } else if (newQuantity === 1 && !cartItem) {
handleAddToCart(item.id); handleAddToCart(item.id);
} else if (cartItem) { } else if (cartItem) {
updateCartItem.mutate({ productId: cartItem.id, quantity: newQuantity }); updateCartItem.mutate({ skuId: cartItem.skuId, quantity: newQuantity });
} }
}; };

View file

@ -22,7 +22,7 @@ function StoreProductDetailPage() {
const handleAddToCart = () => { const handleAddToCart = () => {
if (!product) return if (!product) return
addToCart.mutate( addToCart.mutate(
{ productId: product.id, quantity, storeId: product.storeId }, { skuId: product.id, quantity, storeId: product.storeId },
{ onSuccess: () => navigate({ to: '/cart' }) } { onSuccess: () => navigate({ to: '/cart' }) }
) )
} }
@ -35,7 +35,7 @@ function StoreProductDetailPage() {
) )
} }
const price = product.discountedPrice ?? product.price const price = product.price
const imageUrl = product.images?.[0] const imageUrl = product.images?.[0]
return ( return (
@ -61,24 +61,15 @@ function StoreProductDetailPage() {
{product.name} {product.name}
</p> </p>
<p className="mb-2 text-sm text-gray-500"> <p className="mb-2 text-sm text-gray-500">
{product.unitValue}{product.unit} {product.unitNotation}
</p> </p>
<div className="mb-4 flex items-baseline gap-2"> <div className="mb-4 flex items-baseline gap-2">
<p className="font-bold text-2xl text-brand-600"> <p className="font-bold text-2xl text-brand-600">
{price} {price}
</p> </p>
{product.discountedPrice && (
<p className="text-sm text-gray-400 line-through">
{product.price}
</p>
)}
</div> </div>
{product.description && (
<p className="mb-4 text-gray-600">{product.description}</p>
)}
<div className="mb-6"> <div className="mb-6">
<Quantifier value={quantity} setValue={setQuantity} max={10} /> <Quantifier value={quantity} setValue={setQuantity} max={10} />
</div> </div>

View file

@ -2,8 +2,7 @@ import {
createStartHandler, createStartHandler,
defaultStreamHandler, defaultStreamHandler,
} from '@tanstack/react-start/server' } from '@tanstack/react-start/server'
import { getRouter } from './router'
export default createStartHandler({ export default createStartHandler({
createRouter: getRouter, handler: defaultStreamHandler,
})(defaultStreamHandler) })

View file

@ -14,7 +14,7 @@
"@backend/*": ["../../apps/backend/src/*"] "@backend/*": ["../../apps/backend/src/*"]
}, },
"lib": ["ES2022", "DOM", "DOM.Iterable"], "lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["vite/client"], "types": ["vite/client", "node"],
/* Bundler mode */ /* Bundler mode */
"moduleResolution": "bundler", "moduleResolution": "bundler",

View file

@ -1416,3 +1416,21 @@ packages/web-components/tsconfig.json:
package.json (root): + "typecheck": "bash typecheck" package.json (root): + "typecheck": "bash typecheck"
[2026-09-05 17:55:00] VERIFIED ./typecheck: all 9 targets report exactly their known baselines — backend 11, user-ui 106, admin-ui 122, web-ui 150, fallback-ui 107, db_helper_sqlite 4, db_helper_postgres 45, migrator 0 ✅, web-components 3 (the pre-existing `<p weight>` custom-prop errors; standalone count went 21 → 3 after removing the declaration-emit settings). Script exits 1 while any target fails; run via `./typecheck` or `npm run typecheck` / `bun run typecheck`. [2026-09-05 17:55:00] VERIFIED ./typecheck: all 9 targets report exactly their known baselines — backend 11, user-ui 106, admin-ui 122, web-ui 150, fallback-ui 107, db_helper_sqlite 4, db_helper_postgres 45, migrator 0 ✅, web-components 3 (the pre-existing `<p weight>` custom-prop errors; standalone count went 21 → 3 after removing the declaration-emit settings). Script exits 1 while any target fails; run via `./typecheck` or `npm run typecheck` / `bun run typecheck`.
Phase 3 (2026-09-05 18:40) — admin-ui debt paydown (15 → 0) + API completeness (rule 1):
apps/backend/src/trpc/apis/common-apis/common.ts:
- scaffoldProducts response gains the numeric pricing fields it was missing (they came only from the availability feed): + price: Number(product.price ?? 0); + marketPrice (number|null); + flashPrice (number|null); + isFlashAvailable. Additive — MergedProduct/BaseProduct now carry numeric pricing consistently.
packages/ui/shared-types.ts (c: coupons edit screen):
- CreateCouponPayload + skuIds?: number[] (the edit form pre-fills coupon.skuIds; create flow already forwarded it).
apps/admin-ui:
- all-items-order.tsx(97), popular-items.tsx(124), ProductGroupForm.tsx(28), ProductsSelector.tsx(48), ProductForm.tsx(185): trpc void-input procedures called with `.useQuery({})` → `.useQuery()`
- complaints/index.tsx(66): resolveComplaint response `?? ''`
- coupons/edit/[id].tsx(92): unblocked by CreateCouponPayload.skuIds
- products/index.tsx(41/99×2/100): getDefaultSku param typed to the trpc-serialized SKU (`Omit<AdminSku,'createdAt'> & { createdAt: string | Date }`)
- products/index.tsx(117): SearchBar usage passed a non-existent onSearch prop (ignored at runtime) → removed
- products/edit.tsx(215): empty-branch initialValues gains productType: 'item'
- popular-items.tsx(150/191): unblocked by the scaffoldProducts pricing fields; PopularProduct.images loosened to string[]|null (Item already optional-chains images)
[2026-09-05 19:00:00] TYPE-DEBT PAYDOWN COMPLETE. ./typecheck: 8/9 targets ✅ clean (backend, user-ui, admin-ui, web-ui, fallback-ui, db_helper_sqlite, migrator, web-components); db_helper_postgres intentionally left red (45 pre-existing errors) per user decision — documented exception. Total debt went 455 → 45.
Genuine runtime bugs the types caught and fixed along the way: (1) backend init.ts called non-existent startOrderHandler/startCancellationHandler → startup crash removed (queue consumers live in worker); (2) s3-client passed signQuery/expires at the top level of aws4fetch's sign() where they were silently ignored → presigned URLs were header-signed and useless; now signed via aws:{signQuery} + X-Amz-Expires header; (3) web-ui register read the pre-wrapper response shape (data.token) → registration never navigated; now data.data.token; (4) web-ui localStorage cart stored sku ids under a `productId` field and matched update/remove by cart-line id — quantity updates and removals silently did nothing; renamed to skuId end-to-end; (5) web-ui used lowercase <pInput>/<p> JSX (unknown HTML elements — styled components never rendered); renamed to <PInput>/<P>; (6) availability cache wrote string prices while every consumer Number()-cast → cache now numeric (flashPrice number|null per rule 2).
Phase-1 note: backend's 'sqliteService' paths-only alias failed under the app tsconfigs (TS2307 → export * yielded nothing → hundreds of cascade errors); switched to relative imports, which is what collapsed the 455-error flood to 68 and then to 45.

View file

@ -137,7 +137,7 @@ export async function seed() {
if (!existing) { if (!existing) {
await db.insert(keyValStore).values({ await db.insert(keyValStore).values({
key: constant.key, key: constant.key,
value: constant.value, value: String(constant.value),
}) })
} }
} }

View file

@ -128,7 +128,7 @@ export async function getAvailabilityForCache(): Promise<AvailabilityCacheData[]
id: stat.skuId, id: stat.skuId,
price: Number(stat.ourPrice ?? 0), price: Number(stat.ourPrice ?? 0),
marketPrice: stat.marketPrice != null ? Number(stat.marketPrice) : null, marketPrice: stat.marketPrice != null ? Number(stat.marketPrice) : null,
flashPrice: stat.flashPrice ? String(stat.flashPrice) : null, flashPrice: stat.flashPrice != null ? Number(stat.flashPrice) : null,
isFlashAvailable: stat.isFlashAvailable, isFlashAvailable: stat.isFlashAvailable,
isOutOfStock: stat.isOutOfStock, isOutOfStock: stat.isOutOfStock,
isSuspended: stat.isSuspended, isSuspended: stat.isSuspended,

View file

@ -584,7 +584,6 @@ export async function getOrdersByIdsWithFullData(
sku: { sku: {
columns: { columns: {
name: true, name: true,
price: true,
}, },
with: { with: {
product: { product: {
@ -604,7 +603,7 @@ export async function getOrdersByIdsWithFullData(
}) })
}) })
return orderChunks.flat() as OrderWithFullData[] return orderChunks.flat() as unknown as OrderWithFullData[]
} }
export interface OrderWithCancellationData extends OrderWithFullData { export interface OrderWithCancellationData extends OrderWithFullData {

View file

@ -349,6 +349,7 @@ const mapOffersPageProduct = (sku: {
isOutOfStock: boolean isOutOfStock: boolean
} | null } | null
images: unknown images: unknown
name: string | null
product: { name: string; incrementStep: number | null } | null product: { name: string; incrementStep: number | null } | null
features: Array<{ featureValue: string }> features: Array<{ featureValue: string }>
}): OffersPageProductData => { }): OffersPageProductData => {

View file

@ -0,0 +1,84 @@
/**
* UI component prop types shared between common-ui (RN) and web-components.
* Type-only module consumers use `import type`, so nothing ships at runtime.
*/
import type { ReactNode } from 'react'
// Dropdown option — identical in packages/ui dropdown.tsx + packages/web-components dropdown.tsx
export interface DropdownOption {
label: string
value: string | number
}
// Base dialog shell — packages/ui `DialogProps` + packages/web-components `BottomDialogProps`
// + web-ui `Dialog` (optional title).
export interface DialogProps {
open: boolean
onClose: () => void
children: ReactNode
enableDismiss?: boolean
title?: string
}
// Confirm dialog — identical in both packages' dialog.tsx
export interface ConfirmationDialogProps {
open: boolean
positiveAction: (comment?: string) => void
commentNeeded?: boolean
negativeAction?: () => void
title?: string
message?: string
confirmText?: string
cancelText?: string
isLoading?: boolean
}
// Loading dialog — identical in both packages' loading-dialog.tsx
export interface LoadingDialogProps {
open: boolean
message?: string
}
// Quantity stepper — identical in both packages' quantifier.tsx
export interface QuantifierProps {
value: number
setValue: (value: number) => void
step?: number
unit?: string | { shortNotation: string }
min?: number
max?: number
}
// Legacy image uploader — identical in both packages' image-uploader components
export interface ImageUploaderProps {
images: { uri?: string }[]
onAddImage: () => void
onRemoveImage: (uri: string) => void
existingImageUrls?: string[]
onRemoveExistingImage?: (url: string) => void
allowMultiple?: boolean
}
// Neo image uploader — identical in both packages' image-uploader-neo components
export interface ImageUploaderNeoItem {
imgUrl: string
mimeType: string | null
}
export interface ImageUploaderNeoPayload {
url: string
mimeType: string | null
}
export interface ImageUploaderNeoProps {
images: ImageUploaderNeoItem[]
onImageAdd: (images: ImageUploaderNeoPayload[]) => void
onImageRemove: (image: ImageUploaderNeoPayload) => void
allowMultiple?: boolean
}
// Text-button extras — packages/ui `MyTextButtonProps` + packages/web-components `pButtonProps`
export interface TextButtonProps {
text: string
}

View file

@ -280,7 +280,7 @@ export interface UserProductDetail extends UserProductDetailData {
export interface ProductPriceFlags { export interface ProductPriceFlags {
price: number; price: number;
marketPrice: number | null; marketPrice: number | null;
flashPrice: string | null; flashPrice: number | null;
isFlashAvailable: boolean; isFlashAvailable: boolean;
isOutOfStock: boolean; isOutOfStock: boolean;
isSuspended: boolean; isSuspended: boolean;

View file

@ -10,6 +10,7 @@ export interface CreateCouponPayload {
isUserBased: boolean; isUserBased: boolean;
targetUsers?: number[]; targetUsers?: number[];
productIds?: number[]; productIds?: number[];
skuIds?: number[];
applicableUsers?: number[]; applicableUsers?: number[];
applicableProducts?: number[]; applicableProducts?: number[];
exclusiveApply?: boolean; exclusiveApply?: boolean;

View file

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import type { ConfirmationDialogProps, DialogProps } from '@packages/shared' import type { ConfirmationDialogProps, DialogProps } from '@packages/shared'
import { p } from './my-text' import { p as P } from './my-text'
import { MyButton } from './my-button' import { MyButton } from './my-button'
import { cn } from '../lib/utils' import { cn } from '../lib/utils'
@ -72,9 +72,9 @@ export function ConfirmationDialog({
return ( return (
<BottomDialog open={open} onClose={handleCancel}> <BottomDialog open={open} onClose={handleCancel}>
<div className="p-4"> <div className="p-4">
<p weight="bold" className="mb-4 text-lg"> <P weight="bold" className="mb-4 text-lg">
{title} {title}
</p> </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,6 +1,6 @@
import React from 'react' import React from 'react'
import { cn } from '../lib/utils' import { cn } from '../lib/utils'
import { p } from './my-text' import { p as P } from './my-text'
import type { QuantifierProps } from '@packages/shared' import type { QuantifierProps } from '@packages/shared'
import { Minus, Plus } from 'lucide-react' import { Minus, Plus } from 'lucide-react'
@ -32,9 +32,9 @@ export function Quantifier({
> >
<Minus className="h-3.5 w-3.5" /> <Minus className="h-3.5 w-3.5" />
</button> </button>
<p weight="semibold" className="min-w-[32px] text-center text-sm"> <P weight="semibold" className="min-w-[32px] text-center text-sm">
{value} {value}
</p> </P>
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
@ -68,9 +68,9 @@ export function MiniQuantifier({
> >
<Minus className="h-3 w-3" /> <Minus className="h-3 w-3" />
</button> </button>
<p weight="semibold" className="min-w-[24px] text-center text-xs"> <P weight="semibold" className="min-w-[24px] text-center text-xs">
{value} {value}
</p> </P>
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()