DEAD_CODE_CLEAN #6
50 changed files with 260 additions and 180 deletions
|
|
@ -63,7 +63,7 @@ export default function Complaints() {
|
|||
if (!selectedComplaintId) return;
|
||||
|
||||
resolveComplaint.mutate(
|
||||
{ id: String(selectedComplaintId), response },
|
||||
{ id: String(selectedComplaintId), response: response ?? '' },
|
||||
{
|
||||
onSuccess: () => {
|
||||
Alert.alert("Success", "Complaint marked as resolved");
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export default function AllItemsOrder() {
|
|||
|
||||
// Get current order from constants
|
||||
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();
|
||||
|
||||
// Initialize products from constants
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ type PopularProduct = Omit<
|
|||
> & {
|
||||
price: number;
|
||||
marketPrice: number | null;
|
||||
images: string[];
|
||||
images: string[] | null;
|
||||
storeId: number | null;
|
||||
nextDeliveryDate: string | null;
|
||||
};
|
||||
|
|
@ -121,7 +121,7 @@ export default function CustomizePopularItems() {
|
|||
|
||||
// Get current popular items from constants
|
||||
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();
|
||||
|
||||
// Initialize popular products from constants
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export default function EditProduct() {
|
|||
shortDescription: '',
|
||||
longDescription: '',
|
||||
storeId: 0,
|
||||
productType: 'item' as const,
|
||||
variants: [],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ import type { AdminSku } from '@packages/shared';
|
|||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +116,6 @@ export default function Products() {
|
|||
<SearchBar
|
||||
value={searchTerm}
|
||||
onChangeText={setSearchTerm}
|
||||
onSearch={() => {}}
|
||||
placeholder="Search products..."
|
||||
containerStyle={tw`mb-0`}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ const ProductGroupForm: React.FC<ProductGroupFormProps> = ({
|
|||
onSuccess,
|
||||
}) => {
|
||||
// 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 updateGroup = trpc.admin.product.updateGroup.useMutation();
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export default function ProductsSelector({
|
|||
selectedGroupIds = [],
|
||||
onGroupChange,
|
||||
}: ProductsSelectorProps) {
|
||||
const { data: skusData } = trpc.common.product.getAllSkusSummary.useQuery({});
|
||||
const { data: skusData } = trpc.common.product.getAllSkusSummary.useQuery();
|
||||
const allSkus: SkuSummary[] = skusData?.skus || [];
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
|
|||
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) => ({
|
||||
label: sku.label,
|
||||
value: sku.id,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Hono } from 'hono'
|
||||
import type { Context } from 'hono'
|
||||
import { cors } from 'hono/cors'
|
||||
import { logger } from 'hono/logger'
|
||||
import { trpcServer } from '@hono/trpc-server'
|
||||
|
|
@ -26,7 +27,7 @@ export const createApp = () => {
|
|||
// tRPC middleware
|
||||
app.use('/api/trpc/*', trpcServer({
|
||||
router: appRouter,
|
||||
createContext: async ({ req, c }) => {
|
||||
createContext: async ({ req }, c) => {
|
||||
let user = null
|
||||
let staffUser = null
|
||||
const authHeader = req.headers.get('authorization')
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import '@/src/lib/notif-job'
|
||||
import { initializeAllStores } from '@/src/stores/store-initializer'
|
||||
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 { createAllCacheFiles } from '@/src/lib/cloud_cache'
|
||||
|
||||
|
|
@ -22,8 +22,6 @@ export const initFunc = async (): Promise<void> => {
|
|||
await Promise.all([
|
||||
initializeAllStores(),
|
||||
initializeUserNegativityStore(),
|
||||
startOrderHandler(),
|
||||
startCancellationHandler(),
|
||||
]);
|
||||
|
||||
// Create all cache files after stores are initialized
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export const imageUploadS3 = async(body: Buffer, type: string, key: string) => {
|
|||
headers: {
|
||||
'Content-Type': type,
|
||||
},
|
||||
body,
|
||||
body: body as unknown as BodyInit,
|
||||
})
|
||||
if (!resp.ok) {
|
||||
const responseBody = await resp.text().catch(() => '')
|
||||
|
|
@ -121,8 +121,8 @@ export async function generateSignedUrlFromS3Url(s3UrlRaw: string|null, expiresI
|
|||
const url = buildObjectUrl(getS3BucketName(), s3Url)
|
||||
const signedRequest = await client.sign(url, {
|
||||
method: 'GET',
|
||||
signQuery: true,
|
||||
expires: expiresIn,
|
||||
headers: { 'X-Amz-Expires': String(expiresIn) },
|
||||
aws: { signQuery: true },
|
||||
})
|
||||
return signedRequest.url
|
||||
} catch (error) {
|
||||
|
|
@ -173,11 +173,11 @@ export async function generateUploadUrl(key: string, mimeType: string, expiresIn
|
|||
const url = buildObjectUrl(getS3BucketName(), key)
|
||||
const signedRequest = await client.sign(url, {
|
||||
method: 'PUT',
|
||||
signQuery: true,
|
||||
expires: expiresIn,
|
||||
headers: {
|
||||
'Content-Type': mimeType,
|
||||
'X-Amz-Expires': String(expiresIn),
|
||||
},
|
||||
aws: { signQuery: true },
|
||||
})
|
||||
|
||||
return signedRequest.url
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
// Re-export database connection
|
||||
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 }
|
||||
|
||||
let dbInitialized = false
|
||||
|
|
@ -15,7 +15,7 @@ export const initDb = (database: D1Database) => {
|
|||
}
|
||||
|
||||
// Re-export all schema exports
|
||||
export * from 'sqliteService'
|
||||
export * from '../../../packages/db_helper_sqlite'
|
||||
|
||||
// Re-export all helper methods from sqliteService
|
||||
export {
|
||||
|
|
@ -280,4 +280,4 @@ export {
|
|||
// Upload URL Helpers
|
||||
createUploadUrlStatus,
|
||||
claimUploadUrlStatus,
|
||||
} from 'sqliteService'
|
||||
} from '../../../packages/db_helper_sqlite'
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@ export async function scaffoldProducts() {
|
|||
productType: product.productType || 'item',
|
||||
isComboOnly: product.isComboOnly,
|
||||
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,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
},
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "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. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { trpc } from "../trpc/client";
|
|||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import dayjs from "dayjs";
|
||||
import items from "razorpay/dist/types/items";
|
||||
|
||||
interface VendorOrder {
|
||||
orderId: string;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ type ProductSummary = Omit<
|
|||
> & {
|
||||
isFlashAvailable: boolean
|
||||
name?: string
|
||||
flashPrice?: string | null
|
||||
flashPrice?: number | null
|
||||
images?: string[] | null
|
||||
productQuantity?: number
|
||||
unitNotation?: string
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
import { hydrateRoot } from 'react-dom/client'
|
||||
import { StartClient } from '@tanstack/react-start/client'
|
||||
import { getRouter } from './router'
|
||||
|
||||
const router = getRouter()
|
||||
|
||||
hydrateRoot(document, <StartClient router={router} />)
|
||||
hydrateRoot(document, <StartClient />)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export default function AddToCartDialog() {
|
|||
|
||||
useEffect(() => {
|
||||
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
|
||||
setQuantity(cartQuantity === 0 ? 1 : cartQuantity)
|
||||
setSelectedSlotId(cartItem?.slotId || null)
|
||||
|
|
@ -75,7 +75,7 @@ export default function AddToCartDialog() {
|
|||
.filter(Boolean)
|
||||
.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 productAvailability = productSlotsMap[product?.id]
|
||||
|
|
@ -89,14 +89,14 @@ export default function AddToCartDialog() {
|
|||
}
|
||||
if (isUpdate && cartItem) {
|
||||
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() }
|
||||
)
|
||||
} else {
|
||||
const slotId = selectedSlotId ?? availableSlotIds[0] ?? 0
|
||||
addToCart.mutate(
|
||||
{
|
||||
productId: product.id,
|
||||
skuId: product.id,
|
||||
quantity,
|
||||
storeId: product.storeId || 1,
|
||||
slotId,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState } from 'react'
|
||||
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 * as Yup from 'yup'
|
||||
import type { AddressFormProps } from '@packages/shared'
|
||||
|
|
@ -123,7 +123,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
|
|||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<pInput
|
||||
<PInput
|
||||
placeholder="Name"
|
||||
value={values.name}
|
||||
onChange={(e) => handleChange('name', e.target.value)}
|
||||
|
|
@ -133,7 +133,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<pInput
|
||||
<PInput
|
||||
placeholder="Phone"
|
||||
type="tel"
|
||||
value={values.phone}
|
||||
|
|
@ -144,7 +144,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<pInput
|
||||
<PInput
|
||||
placeholder="Address Line 1"
|
||||
value={values.addressLine1}
|
||||
onChange={(e) => handleChange('addressLine1', e.target.value)}
|
||||
|
|
@ -154,7 +154,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<pInput
|
||||
<PInput
|
||||
placeholder="Address Line 2 (Optional)"
|
||||
value={values.addressLine2}
|
||||
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>
|
||||
<pInput
|
||||
<PInput
|
||||
placeholder="City"
|
||||
value={values.city}
|
||||
disabled
|
||||
|
|
@ -171,7 +171,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<pInput
|
||||
<PInput
|
||||
placeholder="State"
|
||||
value={values.state}
|
||||
disabled
|
||||
|
|
@ -181,7 +181,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<pInput
|
||||
<PInput
|
||||
placeholder="Pincode"
|
||||
value={values.pincode}
|
||||
disabled
|
||||
|
|
@ -208,7 +208,7 @@ export function AddressForm({ onSuccess, initialValues, isEdit = false }: Addres
|
|||
3. Click on Share and Click on Copy<br />
|
||||
4. Paste the copied url here in the field.
|
||||
</p>
|
||||
<pInput
|
||||
<PInput
|
||||
placeholder="Google Maps Shared URL"
|
||||
value={values.googleMapsUrl}
|
||||
onChange={(e) => handleChange('googleMapsUrl', e.target.value)}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export function FloatingCartBar({ isFlashDelivery = false }: FlashDeliveryProps)
|
|||
// Calculate total cart value
|
||||
const totalCartValue = cartItems.reduce(
|
||||
(sum, item) => {
|
||||
const product = productsById[item.productId]
|
||||
const product = productsById[item.skuId]
|
||||
const basePrice = product?.price ?? 0
|
||||
const price = isFlashDelivery ? (product?.flashPrice ?? basePrice) : basePrice
|
||||
return sum + price * item.quantity
|
||||
|
|
@ -142,17 +142,17 @@ export function FloatingCartBar({ isFlashDelivery = false }: FlashDeliveryProps)
|
|||
<React.Fragment key={item.id}>
|
||||
<div className="flex items-center gap-4 py-4">
|
||||
<img
|
||||
src={productsById[item.productId]?.images?.[0]}
|
||||
src={productsById[item.skuId]?.images?.[0]}
|
||||
alt=""
|
||||
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">
|
||||
<p className="truncate text-sm font-bold text-gray-900">
|
||||
{productsById[item.productId]?.name || ''}
|
||||
{productsById[item.skuId]?.name || ''}
|
||||
</p>
|
||||
<p className="text-xs font-medium text-gray-500">
|
||||
{productsById[item.productId]?.productQuantity || 0}
|
||||
{productsById[item.productId]?.unitNotation || ''}
|
||||
{productsById[item.skuId]?.productQuantity || 0}
|
||||
{productsById[item.skuId]?.unitNotation || ''}
|
||||
</p>
|
||||
{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">
|
||||
|
|
@ -167,17 +167,17 @@ export function FloatingCartBar({ isFlashDelivery = false }: FlashDeliveryProps)
|
|||
value={quantities[item.id] || item.quantity}
|
||||
setValue={(value) => {
|
||||
if (value === 0) {
|
||||
removeFromCart.mutate(item.productId)
|
||||
removeFromCart.mutate(item.skuId)
|
||||
} else {
|
||||
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">
|
||||
₹{(() => {
|
||||
const product = productsById[item.productId]
|
||||
const product = productsById[item.skuId]
|
||||
const basePrice = product?.price ?? 0
|
||||
const price = isFlashDelivery ? (product?.flashPrice ?? basePrice) : basePrice
|
||||
return price * item.quantity
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export function PaymentAndOrderComponent({
|
|||
|
||||
// Calculate totals
|
||||
const totalPrice = cartItems.reduce((sum, item) => {
|
||||
const product = productsById[item.productId]
|
||||
const product = productsById[item.skuId]
|
||||
if (!product) return sum
|
||||
const price = isFlashDelivery ? (product.flashPrice ?? product.price ?? 0) : (product.price || 0)
|
||||
return sum + price * item.quantity
|
||||
|
|
@ -61,7 +61,7 @@ export function PaymentAndOrderComponent({
|
|||
|
||||
navigate({
|
||||
to: '/home/order-success',
|
||||
search: { orderId: firstOrder?.id, totalAmount: finalTotalWithDelivery },
|
||||
search: { orderId: String(firstOrder?.id), totalAmount: String(finalTotalWithDelivery) },
|
||||
})
|
||||
},
|
||||
onError: (error: any) => {
|
||||
|
|
@ -87,7 +87,7 @@ export function PaymentAndOrderComponent({
|
|||
|
||||
const orderData = {
|
||||
selectedItems: cartItems.map((item) => ({
|
||||
productId: item.productId,
|
||||
skuId: item.skuId,
|
||||
quantity: item.quantity,
|
||||
slotId: isFlashDelivery ? null : item.slotId,
|
||||
})),
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export function ProductCard({
|
|||
const addToCart = useAddToCart('regular')
|
||||
|
||||
// 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
|
||||
|
||||
// Get slots data from central store
|
||||
|
|
@ -82,7 +82,7 @@ export function ProductCard({
|
|||
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 },
|
||||
{ skuId: item.id, quantity: 1, slotId, storeId: item.storeId },
|
||||
{
|
||||
onSuccess: () => {
|
||||
alert(`Added ${item.name} for delivery at ${deliveryTime}`)
|
||||
|
|
@ -90,7 +90,7 @@ export function ProductCard({
|
|||
}
|
||||
)
|
||||
} else if (cartItem) {
|
||||
updateCartItem.mutate({ productId: item.id, quantity: newQuantity })
|
||||
updateCartItem.mutate({ skuId: item.id, quantity: newQuantity })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ type CartType = 'regular' | 'flash'
|
|||
|
||||
interface CartItem {
|
||||
id: number
|
||||
productId: number
|
||||
skuId: number
|
||||
quantity: number
|
||||
storeId: number
|
||||
storeId: number | null
|
||||
addedAt: number
|
||||
slotId?: number | null
|
||||
deliveryDate?: string | null
|
||||
|
|
@ -50,20 +50,20 @@ export function useAddToCart(cartType: CartType = 'regular') {
|
|||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
productId,
|
||||
skuId,
|
||||
quantity,
|
||||
storeId,
|
||||
slotId,
|
||||
deliveryDate,
|
||||
}: {
|
||||
productId: number
|
||||
skuId: number
|
||||
quantity: number
|
||||
storeId: number
|
||||
storeId?: number | null
|
||||
slotId?: number | null
|
||||
deliveryDate?: string | null
|
||||
}) => {
|
||||
const items = readCart(cartType)
|
||||
const existing = items.find((i) => i.productId === productId)
|
||||
const existing = items.find((i) => i.skuId === skuId)
|
||||
if (existing) {
|
||||
existing.quantity += quantity
|
||||
if (slotId) existing.slotId = slotId
|
||||
|
|
@ -71,9 +71,9 @@ export function useAddToCart(cartType: CartType = 'regular') {
|
|||
} else {
|
||||
items.push({
|
||||
id: Date.now(),
|
||||
productId,
|
||||
skuId,
|
||||
quantity,
|
||||
storeId,
|
||||
storeId: storeId ?? null,
|
||||
addedAt: Date.now(),
|
||||
slotId: slotId ?? null,
|
||||
deliveryDate: deliveryDate ?? null,
|
||||
|
|
@ -95,22 +95,22 @@ export function useUpdateCartItem(cartType: CartType = 'regular') {
|
|||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
productId,
|
||||
skuId,
|
||||
quantity,
|
||||
slotId,
|
||||
deliveryDate,
|
||||
}: {
|
||||
productId: number
|
||||
skuId: number
|
||||
quantity: number
|
||||
slotId?: number | null
|
||||
deliveryDate?: string | null
|
||||
}) => {
|
||||
let items = readCart(cartType)
|
||||
const existing = items.find((i) => i.productId === productId)
|
||||
const existing = items.find((i) => i.skuId === skuId)
|
||||
if (!existing) return
|
||||
if (quantity <= 0) {
|
||||
// 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 {
|
||||
existing.quantity = quantity
|
||||
if (slotId !== undefined) existing.slotId = slotId
|
||||
|
|
@ -129,9 +129,9 @@ export function useRemoveFromCart(cartType: CartType = 'regular') {
|
|||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (productId: number) => {
|
||||
mutationFn: async (skuId: number) => {
|
||||
let items = readCart(cartType)
|
||||
items = items.filter((i) => i.productId !== productId)
|
||||
items = items.filter((i) => i.skuId !== skuId)
|
||||
writeCart(cartType, items)
|
||||
queryClient.setQueryData([getCartKey(cartType)], toCartData(items))
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import type { User as SharedUser, AuthStateCore } from '@packages/shared'
|
|||
type User = Pick<SharedUser, 'id' | 'email'> &
|
||||
Partial<Omit<SharedUser, 'id' | 'email' | 'createdAt' | 'mobile'>> & {
|
||||
mobile: string | null
|
||||
profileImage: string | null
|
||||
profileImage?: string | null
|
||||
}
|
||||
|
||||
interface AuthState extends AuthStateCore {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,10 @@
|
|||
import { create } from 'zustand'
|
||||
import type { CentralProductState as CentralProductsState } from '@packages/shared'
|
||||
import type { MergedProduct } from '../../hooks/prominent-api-hooks'
|
||||
|
||||
interface Product {
|
||||
id: number
|
||||
name: string
|
||||
price: number
|
||||
discountedPrice: number | null
|
||||
images: { uri: string }[]
|
||||
unit: string
|
||||
unitValue: number
|
||||
storeId: number
|
||||
category: string
|
||||
isActive: boolean
|
||||
description: string | null
|
||||
}
|
||||
// Central cache holds the API product (MergedProduct) — single product shape
|
||||
// across the app (numeric pricing, string images).
|
||||
type Product = MergedProduct
|
||||
|
||||
type CentralProductStore = CentralProductsState<Product>
|
||||
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ function CartPage() {
|
|||
const productsById: Record<number, any> = {}
|
||||
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
|
||||
cartItems.forEach((item) => {
|
||||
const product = productsById[item.productId]
|
||||
const product = productsById[item.skuId]
|
||||
if (product) {
|
||||
total += product.price * item.quantity
|
||||
}
|
||||
|
|
@ -57,11 +57,11 @@ function CartPage() {
|
|||
{/* Items */}
|
||||
<div className="flex flex-col gap-3">
|
||||
{cartItems.map((item) => {
|
||||
const product = productsById[item.productId]
|
||||
const product = productsById[item.skuId]
|
||||
const price = product.price
|
||||
return (
|
||||
<div
|
||||
key={item.productId}
|
||||
key={item.skuId}
|
||||
className="flex items-center gap-4 rounded-xl border border-gray-200 bg-white p-4"
|
||||
>
|
||||
<img
|
||||
|
|
@ -77,7 +77,7 @@ function CartPage() {
|
|||
<div className="mt-2 flex items-center gap-3">
|
||||
<Quantifier
|
||||
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>
|
||||
</div>
|
||||
|
|
@ -85,7 +85,7 @@ function CartPage() {
|
|||
<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>
|
||||
<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"
|
||||
aria-label={`Remove ${product.name}`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ function CheckoutContent() {
|
|||
const productsById: Record<number, any> = {}
|
||||
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
|
||||
if (cartItems.length === 0) {
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@ function FlashCartPage() {
|
|||
const removeItem = useRemoveFromCart('flash')
|
||||
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
|
||||
cartItems.forEach((item) => {
|
||||
const product = productsById[item.productId]
|
||||
const product = productsById[item.skuId]
|
||||
if (product) {
|
||||
total += (product.discountedPrice ?? product.price) * item.quantity
|
||||
total += product.price * item.quantity
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -56,27 +56,27 @@ function FlashCartPage() {
|
|||
{/* Items */}
|
||||
<div className="flex flex-col gap-3">
|
||||
{cartItems.map((item) => {
|
||||
const product = productsById[item.productId]
|
||||
const price = product.discountedPrice ?? product.price
|
||||
const product = productsById[item.skuId]
|
||||
const price = product.price
|
||||
return (
|
||||
<div
|
||||
key={item.productId}
|
||||
key={item.skuId}
|
||||
className="flex items-center gap-4 rounded-xl border border-gray-200 bg-white p-4"
|
||||
>
|
||||
<img
|
||||
src={product.images?.[0]?.uri || product.images?.[0] as any}
|
||||
src={product.images?.[0] ?? ''}
|
||||
alt={product.name}
|
||||
className="h-20 w-20 shrink-0 rounded-lg object-cover"
|
||||
/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-bold text-gray-900">{product.name}</p>
|
||||
<p className="mt-0.5 text-xs text-gray-500">
|
||||
{product.unitValue || 1}{product.unit || ''} per unit
|
||||
{product.unitNotation || ''} per unit
|
||||
</p>
|
||||
<div className="mt-2 flex items-center gap-3">
|
||||
<Quantifier
|
||||
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>
|
||||
</div>
|
||||
|
|
@ -84,7 +84,7 @@ function FlashCartPage() {
|
|||
<div className="flex flex-col items-end gap-2">
|
||||
<p className="text-base font-extrabold text-flash-500">₹{price * item.quantity}</p>
|
||||
<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"
|
||||
aria-label={`Remove ${product.name}`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -30,19 +30,19 @@ function FlashCheckoutPage() {
|
|||
queryClient.invalidateQueries({ queryKey: ['local-cart-flash'] })
|
||||
navigate({
|
||||
to: '/flash/order-success',
|
||||
search: { orderId: order?.id, totalAmount: total },
|
||||
search: { orderId: String(order?.id), totalAmount: String(total) },
|
||||
})
|
||||
},
|
||||
onSettled: () => setIsLoading(false),
|
||||
})
|
||||
|
||||
const cartItems = (cart?.items || []).filter((item) => productsById[item.productId])
|
||||
const cartItems = (cart?.items || []).filter((item) => productsById[item.skuId])
|
||||
|
||||
let total = 0
|
||||
cartItems.forEach((item) => {
|
||||
const product = productsById[item.productId]
|
||||
const product = productsById[item.skuId]
|
||||
if (product) {
|
||||
total += (product.discountedPrice ?? product.price) * item.quantity
|
||||
total += product.price * item.quantity
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ function FlashCheckoutPage() {
|
|||
setIsLoading(true)
|
||||
placeOrderMutation.mutate({
|
||||
selectedItems: cartItems.map((item) => ({
|
||||
productId: item.productId,
|
||||
skuId: item.skuId,
|
||||
quantity: item.quantity,
|
||||
slotId: null,
|
||||
})),
|
||||
|
|
@ -95,15 +95,15 @@ function FlashCheckoutPage() {
|
|||
Order Summary
|
||||
</p>
|
||||
{cartItems.map((item) => {
|
||||
const product = productsById[item.productId]
|
||||
const product = productsById[item.skuId]
|
||||
if (!product) return null
|
||||
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">
|
||||
{product.name} x{item.quantity}
|
||||
</p>
|
||||
<p className="text-sm font-bold">
|
||||
₹{(product.discountedPrice ?? product.price) * item.quantity}
|
||||
₹{product.price * item.quantity}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ function FlashProductDetailPage() {
|
|||
const handleAddToCart = () => {
|
||||
if (!product) return
|
||||
addToCart.mutate(
|
||||
{ productId: product.id, quantity, storeId: product.storeId },
|
||||
{ skuId: product.id, quantity, storeId: product.storeId },
|
||||
{ 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]
|
||||
|
||||
return (
|
||||
|
|
@ -60,18 +60,13 @@ function FlashProductDetailPage() {
|
|||
{product.name}
|
||||
</p>
|
||||
<p className="mb-4 text-sm text-gray-500">
|
||||
{product.unitValue}{product.unit}
|
||||
{product.unitNotation}
|
||||
</p>
|
||||
|
||||
<div className="mb-4 flex items-baseline gap-2">
|
||||
<p className="font-bold text-2xl text-brand-600">
|
||||
₹{price}
|
||||
</p>
|
||||
{product.discountedPrice && (
|
||||
<p className="text-sm text-gray-400 line-through">
|
||||
₹{product.price}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ function FlashDeliveryPage() {
|
|||
const handleAddToCart = (productId: number) => {
|
||||
const item = filteredProducts.find((p: any) => p.id === productId)
|
||||
addToCart.mutate(
|
||||
{ productId, quantity: 1, storeId: item?.storeId ?? 0 },
|
||||
{ skuId: productId, quantity: 1, storeId: item?.storeId ?? 0 },
|
||||
{
|
||||
onSuccess: () => {
|
||||
alert(`Added ${item?.name || 'item'} for 1 Hr Delivery`)
|
||||
|
|
@ -215,7 +215,7 @@ function CompactProductCard({
|
|||
const removeFromCart = useRemoveFromCart('flash');
|
||||
|
||||
const cartItem = cartData?.items?.find(
|
||||
(cartItem: any) => cartItem.productId === item.id,
|
||||
(cartItem: any) => cartItem.skuId === item.id,
|
||||
);
|
||||
const quantity = cartItem?.quantity || 0;
|
||||
const isOutOfStock = productSlotsMap[item.id]?.isOutOfStock;
|
||||
|
|
@ -226,7 +226,7 @@ function CompactProductCard({
|
|||
} else if (newQuantity === 1 && !cartItem) {
|
||||
handleAddToCart(item.id);
|
||||
} else if (cartItem) {
|
||||
updateCartItem.mutate({ productId: cartItem.id, quantity: newQuantity });
|
||||
updateCartItem.mutate({ skuId: cartItem.skuId, quantity: newQuantity });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ function CartPage() {
|
|||
const productsById: Record<number, any> = {}
|
||||
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
|
||||
cartItems.forEach((item) => {
|
||||
const product = productsById[item.productId]
|
||||
const product = productsById[item.skuId]
|
||||
if (product) {
|
||||
total += product.price * item.quantity
|
||||
}
|
||||
|
|
@ -57,11 +57,11 @@ function CartPage() {
|
|||
{/* Items */}
|
||||
<div className="flex flex-col gap-3">
|
||||
{cartItems.map((item) => {
|
||||
const product = productsById[item.productId]
|
||||
const product = productsById[item.skuId]
|
||||
const price = product.price
|
||||
return (
|
||||
<div
|
||||
key={item.productId}
|
||||
key={item.skuId}
|
||||
className="flex items-center gap-4 rounded-xl border border-gray-200 bg-white p-4"
|
||||
>
|
||||
<img
|
||||
|
|
@ -77,7 +77,7 @@ function CartPage() {
|
|||
<div className="mt-2 flex items-center gap-3">
|
||||
<Quantifier
|
||||
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>
|
||||
</div>
|
||||
|
|
@ -85,7 +85,7 @@ function CartPage() {
|
|||
<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>
|
||||
<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"
|
||||
aria-label={`Remove ${product.name}`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -30,19 +30,19 @@ function CheckoutPage() {
|
|||
queryClient.invalidateQueries({ queryKey: ['local-cart-regular'] })
|
||||
navigate({
|
||||
to: '/home/order-success',
|
||||
search: { orderId: order?.id, totalAmount: total },
|
||||
search: { orderId: String(order?.id), totalAmount: String(total) },
|
||||
})
|
||||
},
|
||||
onSettled: () => setIsLoading(false),
|
||||
})
|
||||
|
||||
const cartItems = (cart?.items || []).filter((item) => productsById[item.productId])
|
||||
const cartItems = (cart?.items || []).filter((item) => productsById[item.skuId])
|
||||
|
||||
let total = 0
|
||||
cartItems.forEach((item) => {
|
||||
const product = productsById[item.productId]
|
||||
const product = productsById[item.skuId]
|
||||
if (product) {
|
||||
total += (product.discountedPrice ?? product.price) * item.quantity
|
||||
total += product.price * item.quantity
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ function CheckoutPage() {
|
|||
setIsLoading(true)
|
||||
placeOrderMutation.mutate({
|
||||
selectedItems: cartItems.map((item) => ({
|
||||
productId: item.productId,
|
||||
skuId: item.skuId,
|
||||
quantity: item.quantity,
|
||||
slotId: null,
|
||||
})),
|
||||
|
|
@ -97,15 +97,15 @@ function CheckoutPage() {
|
|||
Order Summary
|
||||
</p>
|
||||
{cartItems.map((item) => {
|
||||
const product = productsById[item.productId]
|
||||
const product = productsById[item.skuId]
|
||||
if (!product) return null
|
||||
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">
|
||||
{product.name} x{item.quantity}
|
||||
</p>
|
||||
<p className="text-sm font-bold">
|
||||
₹{(product.discountedPrice ?? product.price) * item.quantity}
|
||||
₹{product.price * item.quantity}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ function ProductDetailPage() {
|
|||
}, [slotsData, productDetail])
|
||||
|
||||
const cartItem = productDetail
|
||||
? cartData?.items?.find((item: any) => item.productId === productDetail.id)
|
||||
? cartData?.items?.find((item: any) => item.skuId === productDetail.id)
|
||||
: null
|
||||
const quantity = cartItem?.quantity || 0
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ function ProductDetailPage() {
|
|||
} else if (newQuantity === 1 && !cartItem) {
|
||||
handleAddToCart()
|
||||
} else if (cartItem) {
|
||||
updateCartItem.mutate({ productId: cartItem.id, quantity: newQuantity })
|
||||
updateCartItem.mutate({ skuId: cartItem.skuId, quantity: newQuantity })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ function ProductDetailPage() {
|
|||
return
|
||||
}
|
||||
addToCart.mutate(
|
||||
{ productId: productDetail.id, quantity: 1, slotId, storeId: productDetail.storeId },
|
||||
{ skuId: productDetail.id, quantity: 1, slotId, storeId: productDetail.store?.id },
|
||||
{
|
||||
onSuccess: () => {
|
||||
const slot = slotsData?.slots?.find((s: any) => s.id === slotId)
|
||||
|
|
@ -111,7 +111,7 @@ function ProductDetailPage() {
|
|||
const handleBuyNow = () => {
|
||||
if (!productDetail) return
|
||||
addToFlashCart.mutate(
|
||||
{ productId: productDetail.id, quantity: 1, storeId: productDetail.storeId },
|
||||
{ skuId: productDetail.id, quantity: 1, storeId: productDetail.store?.id },
|
||||
{
|
||||
onSuccess: () => {
|
||||
navigate({ to: '/flash/cart' })
|
||||
|
|
@ -125,18 +125,18 @@ function ProductDetailPage() {
|
|||
|
||||
if (cartItem) {
|
||||
removeFromCart.mutate(
|
||||
{ itemId: cartItem.id },
|
||||
cartItem.skuId,
|
||||
{
|
||||
onSuccess: () => {
|
||||
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 {
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export const Route = createFileRoute('/me/orders')({ component: OrdersPage })
|
|||
function OrdersPage() {
|
||||
const navigate = useNavigate()
|
||||
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 || []
|
||||
|
||||
// Check if we're on the exact /me/orders path (not a child route like /me/orders/123)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
|||
import { useState } from 'react'
|
||||
import { useAuth } from '../lib/auth-context'
|
||||
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 })
|
||||
|
||||
|
|
@ -16,8 +16,8 @@ function RegisterPage() {
|
|||
|
||||
const registerMutation = trpc.user.auth.register.useMutation({
|
||||
onSuccess: (data) => {
|
||||
if (data.token && data.user) {
|
||||
authRegister(data.token, data.user)
|
||||
if (data.data.token && data.data.user) {
|
||||
authRegister(data.data.token, data.data.user)
|
||||
navigate({ to: '/home' })
|
||||
}
|
||||
},
|
||||
|
|
@ -40,19 +40,19 @@ function RegisterPage() {
|
|||
|
||||
<div className="rounded-2xl bg-white p-8 shadow-xl">
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
||||
<pInput
|
||||
<PInput
|
||||
placeholder="Full Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<pInput
|
||||
<PInput
|
||||
placeholder="Email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<pInput
|
||||
<PInput
|
||||
placeholder="Mobile Number"
|
||||
value={mobile}
|
||||
onChange={(e) => {
|
||||
|
|
@ -61,7 +61,7 @@ function RegisterPage() {
|
|||
}}
|
||||
required
|
||||
/>
|
||||
<pInput
|
||||
<PInput
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
value={password}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ function SlotViewPage() {
|
|||
? dayjs(slot.deliveryTime).format("ddd, DD MMM • h:mm A")
|
||||
: "";
|
||||
addToCart.mutate(
|
||||
{ productId, quantity: 1, slotId: slotId || 0, storeId: item?.storeId },
|
||||
{ skuId: productId, quantity: 1, slotId: slotId || 0, storeId: item?.storeId },
|
||||
{
|
||||
onSuccess: () => {
|
||||
alert(
|
||||
|
|
@ -420,7 +420,7 @@ function CompactProductCard({
|
|||
} else if (newQuantity === 1 && !cartItem) {
|
||||
handleAddToCart(item.id);
|
||||
} else if (cartItem) {
|
||||
updateCartItem.mutate({ productId: cartItem.id, quantity: newQuantity });
|
||||
updateCartItem.mutate({ skuId: cartItem.skuId, quantity: newQuantity });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ function StoreProductDetailPage() {
|
|||
const handleAddToCart = () => {
|
||||
if (!product) return
|
||||
addToCart.mutate(
|
||||
{ productId: product.id, quantity, storeId: product.storeId },
|
||||
{ skuId: product.id, quantity, storeId: product.storeId },
|
||||
{ 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]
|
||||
|
||||
return (
|
||||
|
|
@ -61,24 +61,15 @@ function StoreProductDetailPage() {
|
|||
{product.name}
|
||||
</p>
|
||||
<p className="mb-2 text-sm text-gray-500">
|
||||
{product.unitValue}{product.unit}
|
||||
{product.unitNotation}
|
||||
</p>
|
||||
|
||||
<div className="mb-4 flex items-baseline gap-2">
|
||||
<p className="font-bold text-2xl text-brand-600">
|
||||
₹{price}
|
||||
</p>
|
||||
{product.discountedPrice && (
|
||||
<p className="text-sm text-gray-400 line-through">
|
||||
₹{product.price}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{product.description && (
|
||||
<p className="mb-4 text-gray-600">{product.description}</p>
|
||||
)}
|
||||
|
||||
<div className="mb-6">
|
||||
<Quantifier value={quantity} setValue={setQuantity} max={10} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ import {
|
|||
createStartHandler,
|
||||
defaultStreamHandler,
|
||||
} from '@tanstack/react-start/server'
|
||||
import { getRouter } from './router'
|
||||
|
||||
export default createStartHandler({
|
||||
createRouter: getRouter,
|
||||
})(defaultStreamHandler)
|
||||
handler: defaultStreamHandler,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
"@backend/*": ["../../apps/backend/src/*"]
|
||||
},
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"types": ["vite/client"],
|
||||
"types": ["vite/client", "node"],
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
|
|
|
|||
|
|
@ -1416,3 +1416,21 @@ packages/web-components/tsconfig.json:
|
|||
|
||||
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`.
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ export async function seed() {
|
|||
if (!existing) {
|
||||
await db.insert(keyValStore).values({
|
||||
key: constant.key,
|
||||
value: constant.value,
|
||||
value: String(constant.value),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export async function getAvailabilityForCache(): Promise<AvailabilityCacheData[]
|
|||
id: stat.skuId,
|
||||
price: Number(stat.ourPrice ?? 0),
|
||||
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,
|
||||
isOutOfStock: stat.isOutOfStock,
|
||||
isSuspended: stat.isSuspended,
|
||||
|
|
|
|||
|
|
@ -584,7 +584,6 @@ export async function getOrdersByIdsWithFullData(
|
|||
sku: {
|
||||
columns: {
|
||||
name: true,
|
||||
price: true,
|
||||
},
|
||||
with: {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -349,6 +349,7 @@ const mapOffersPageProduct = (sku: {
|
|||
isOutOfStock: boolean
|
||||
} | null
|
||||
images: unknown
|
||||
name: string | null
|
||||
product: { name: string; incrementStep: number | null } | null
|
||||
features: Array<{ featureValue: string }>
|
||||
}): OffersPageProductData => {
|
||||
|
|
|
|||
84
packages/shared/types/ui-common.types.ts
Normal file
84
packages/shared/types/ui-common.types.ts
Normal 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
|
||||
}
|
||||
|
|
@ -280,7 +280,7 @@ export interface UserProductDetail extends UserProductDetailData {
|
|||
export interface ProductPriceFlags {
|
||||
price: number;
|
||||
marketPrice: number | null;
|
||||
flashPrice: string | null;
|
||||
flashPrice: number | null;
|
||||
isFlashAvailable: boolean;
|
||||
isOutOfStock: boolean;
|
||||
isSuspended: boolean;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export interface CreateCouponPayload {
|
|||
isUserBased: boolean;
|
||||
targetUsers?: number[];
|
||||
productIds?: number[];
|
||||
skuIds?: number[];
|
||||
applicableUsers?: number[];
|
||||
applicableProducts?: number[];
|
||||
exclusiveApply?: boolean;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
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 { cn } from '../lib/utils'
|
||||
|
||||
|
|
@ -72,9 +72,9 @@ export function ConfirmationDialog({
|
|||
return (
|
||||
<BottomDialog open={open} onClose={handleCancel}>
|
||||
<div className="p-4">
|
||||
<p weight="bold" className="mb-4 text-lg">
|
||||
<P weight="bold" className="mb-4 text-lg">
|
||||
{title}
|
||||
</p>
|
||||
</P>
|
||||
<p className="mb-6 text-gray-600">{message}</p>
|
||||
{commentNeeded && (
|
||||
<textarea
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import { cn } from '../lib/utils'
|
||||
import { p } from './my-text'
|
||||
import { p as P } from './my-text'
|
||||
import type { QuantifierProps } from '@packages/shared'
|
||||
import { Minus, Plus } from 'lucide-react'
|
||||
|
||||
|
|
@ -32,9 +32,9 @@ export function Quantifier({
|
|||
>
|
||||
<Minus className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<p weight="semibold" className="min-w-[32px] text-center text-sm">
|
||||
<P weight="semibold" className="min-w-[32px] text-center text-sm">
|
||||
{value}
|
||||
</p>
|
||||
</P>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
|
|
@ -68,9 +68,9 @@ export function MiniQuantifier({
|
|||
>
|
||||
<Minus className="h-3 w-3" />
|
||||
</button>
|
||||
<p weight="semibold" className="min-w-[24px] text-center text-xs">
|
||||
<P weight="semibold" className="min-w-[24px] text-center text-xs">
|
||||
{value}
|
||||
</p>
|
||||
</P>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue