diff --git a/.commandcode/taste/taste/taste.md b/.commandcode/taste/taste/taste.md index a61ab62..39d3aab 100644 --- a/.commandcode/taste/taste/taste.md +++ b/.commandcode/taste/taste/taste.md @@ -10,3 +10,4 @@ - Prefers embedding related data into existing cache files over creating or calling separate API endpoints. Confidence: 0.9 - Prefers using icons from Hicons (rather than custom/hand-authored SVGs or other icon libraries). Confidence: 0.9 - When a feature or page is repurposed, wants file names, route names, and code references renamed to match the new purpose (not just UI content/icons). Confidence: 0.8 +- Wants analysis findings and plans persisted in a markdown file before any implementation begins, with explicit confirmation that implementation is paused until instructed. Confidence: 0.8 diff --git a/apps/backend/src/dbService.ts b/apps/backend/src/dbService.ts index ccf32ee..3a870cc 100644 --- a/apps/backend/src/dbService.ts +++ b/apps/backend/src/dbService.ts @@ -98,9 +98,6 @@ export type { UserAddressDeleteResponse, UserBanner, UserBannersResponse, - UserCartProduct, - UserCartItem, - UserCartResponse, UserComplaint, UserComplaintsResponse, UserRaiseComplaintResponse, @@ -122,7 +119,6 @@ export type { UserCreateReviewResponse, UserSlotProduct, UserSlotWithProducts, - UserSlotData, UserSlotAvailability, UserDeliverySlot, UserSlotsResponse, @@ -136,7 +132,6 @@ export type { UserAuthResult, UserOtpVerifyResponse, UserPasswordUpdateResponse, - UserProfileResponse, UserDeleteAccountResponse, UserCouponUsage, UserCouponApplicableUser, @@ -156,8 +151,6 @@ export type { UserOrderDetail, UserCancelOrderResponse, UserUpdateNotesResponse, - UserRecentProduct, - UserRecentProductsResponse, // Store types StoreSummary, StoresSummaryResponse, diff --git a/apps/backend/src/postgresImporter.ts b/apps/backend/src/postgresImporter.ts index 7296535..3645f61 100644 --- a/apps/backend/src/postgresImporter.ts +++ b/apps/backend/src/postgresImporter.ts @@ -157,15 +157,6 @@ // hasOngoingOrdersForAddress, // // User - Banners // getUserActiveBanners, -// // User - Cart -// getUserCartItemsWithProducts, -// getUserProductById, -// getUserCartItemByUserProduct, -// incrementUserCartItemQuantity, -// insertUserCartItem, -// updateUserCartItemQuantity, -// deleteUserCartItem, -// clearUserCart, // // User - Complaint // getUserComplaints, // createUserComplaint, @@ -235,9 +226,6 @@ // getUserOrderBasic, // cancelUserOrderTransaction, // updateUserOrderNotes, -// getUserRecentlyDeliveredOrderIds, -// getUserProductIdsFromOrders, -// getUserProductsForRecentOrders, // // Store Helpers // getAllBannersForCache, // getAllProductsForCache, diff --git a/apps/backend/src/sqliteImporter.ts b/apps/backend/src/sqliteImporter.ts index b813fb7..111fac3 100644 --- a/apps/backend/src/sqliteImporter.ts +++ b/apps/backend/src/sqliteImporter.ts @@ -168,15 +168,6 @@ export { hasOngoingOrdersForAddress, // User - Banners getUserActiveBanners, - // User - Cart - getUserCartItemsWithProducts, - getUserProductById, - getUserCartItemByUserProduct, - incrementUserCartItemQuantity, - insertUserCartItem, - updateUserCartItemQuantity, - deleteUserCartItem, - clearUserCart, // User - Complaint getUserComplaints, createUserComplaint, @@ -246,9 +237,6 @@ export { getUserOrderBasic, cancelUserOrderTransaction, updateUserOrderNotes, - getUserRecentlyDeliveredOrderIds, - getUserProductIdsFromOrders, - getUserProductsForRecentOrders, // Store Helpers getAllBannersForCache, getAllProductsForCache, diff --git a/apps/backend/src/trpc/apis/user-apis/apis/auth.ts b/apps/backend/src/trpc/apis/user-apis/apis/auth.ts index 97b81db..6de039d 100644 --- a/apps/backend/src/trpc/apis/user-apis/apis/auth.ts +++ b/apps/backend/src/trpc/apis/user-apis/apis/auth.ts @@ -24,7 +24,6 @@ import type { UserAuthResponse, UserOtpVerifyResponse, UserPasswordUpdateResponse, - UserProfileResponse, UserDeleteAccountResponse, } from '@packages/shared' @@ -396,31 +395,6 @@ export const authRouter = router({ } }), - getProfile: protectedProcedure - .query(async ({ ctx }): Promise => { - const userId = ctx.user.userId; - - if (!userId) { - throw new ApiError('User not authenticated', 401); - } - - const user = await getUserAuthByIdInDb(userId) - - if (!user) { - throw new ApiError('User not found', 404); - } - - return { - success: true, - data: { - id: user.id, - name: user.name, - email: user.email, - mobile: user.mobile, - }, - } - }), - deleteAccount: protectedProcedure .input(z.object({ mobile: z.string().min(10, 'Mobile number is required'), diff --git a/apps/backend/src/trpc/apis/user-apis/apis/banners.ts b/apps/backend/src/trpc/apis/user-apis/apis/banners.ts index 960e3b6..6b30311 100644 --- a/apps/backend/src/trpc/apis/user-apis/apis/banners.ts +++ b/apps/backend/src/trpc/apis/user-apis/apis/banners.ts @@ -1,4 +1,3 @@ -import { publicProcedure, router } from '@/src/trpc/trpc-index' import { scaffoldAssetUrl } from '@/src/lib/s3-client' import { getUserActiveBanners as getUserActiveBannersInDb } from '@/src/dbService' import type { UserBannersResponse } from '@packages/shared' @@ -23,11 +22,3 @@ export async function scaffoldBanners(): Promise { banners: bannersWithSignedUrls, } } - -export const bannerRouter = router({ - getBanners: publicProcedure - .query(async () => { - const response = await scaffoldBanners(); - return response; - }), -}); diff --git a/apps/backend/src/trpc/apis/user-apis/apis/cart.ts b/apps/backend/src/trpc/apis/user-apis/apis/cart.ts index 3d09b52..ea1899a 100644 --- a/apps/backend/src/trpc/apis/user-apis/apis/cart.ts +++ b/apps/backend/src/trpc/apis/user-apis/apis/cart.ts @@ -1,264 +1,8 @@ -import { router, protectedProcedure, publicProcedure } from '@/src/trpc/trpc-index' +import { router, publicProcedure } from '@/src/trpc/trpc-index' import { z } from 'zod' -import { ApiError } from '@/src/lib/api-error' -import { scaffoldAssetUrl } from '@/src/lib/s3-client' import { getMultipleProductsSlots } from '@/src/stores/slot-store' -import { - getUserCartItemsWithProducts as getUserCartItemsWithProductsInDb, - getUserProductById as getUserProductByIdInDb, - getUserCartItemByUserProduct as getUserCartItemByUserProductInDb, - incrementUserCartItemQuantity as incrementUserCartItemQuantityInDb, - insertUserCartItem as insertUserCartItemInDb, - updateUserCartItemQuantity as updateUserCartItemQuantityInDb, - deleteUserCartItem as deleteUserCartItemInDb, - clearUserCart as clearUserCartInDb, -} from '@/src/dbService' -import type { UserCartResponse } from '@packages/shared' - -const getCartData = async (userId: number): Promise => { - const cartItemsWithProducts = await getUserCartItemsWithProductsInDb(userId) - - /* - // Old implementation - direct DB queries: - const cartItemsWithProducts = await db - .select({ - cartId: cartItems.id, - productId: productInfo.id, - productName: productInfo.name, - productPrice: productInfo.price, - productImages: productInfo.images, - productQuantity: productInfo.productQuantity, - isOutOfStock: productInfo.isOutOfStock, - unitShortNotation: units.shortNotation, - quantity: cartItems.quantity, - addedAt: cartItems.addedAt, - }) - .from(cartItems) - .innerJoin(productInfo, eq(cartItems.productId, productInfo.id)) - .innerJoin(units, eq(productInfo.unitId, units.id)) - .where(eq(cartItems.userId, userId)); - */ - - const cartWithSignedUrls = cartItemsWithProducts.map((item) => ({ - ...item, - product: { - ...item.product, - images: scaffoldAssetUrl(item.product.images || []), - }, - })) - - const totalAmount = cartWithSignedUrls.reduce((sum, item) => sum + item.subtotal, 0) - - return { - items: cartWithSignedUrls, - totalItems: cartWithSignedUrls.length, - totalAmount, - } -} export const cartRouter = router({ - getCart: protectedProcedure - .query(async ({ ctx }): Promise => { - const userId = ctx.user.userId; - return await getCartData(userId); - }), - - addToCart: protectedProcedure - .input(z.object({ - productId: z.number().int().positive(), - quantity: z.number().int().positive(), - })) - .mutation(async ({ input, ctx }): Promise => { - const userId = ctx.user.userId; - const { productId, quantity } = input; - - // Validate input - if (!productId || !quantity || quantity <= 0) { - throw new ApiError("Product ID and positive quantity required", 400); - } - - // Check if product exists - const product = await getUserProductByIdInDb(productId) - - if (!product) { - throw new ApiError('Product not found', 404) - } - - const existingItem = await getUserCartItemByUserProductInDb(userId, productId) - - if (existingItem) { - await incrementUserCartItemQuantityInDb(existingItem.id, quantity) - } else { - await insertUserCartItemInDb(userId, productId, quantity) - } - - /* - // Old implementation - direct DB queries: - const product = await db.query.productInfo.findFirst({ - where: eq(productInfo.id, productId), - }); - - if (!product) { - throw new ApiError("Product not found", 404); - } - - const existingItem = await db.query.cartItems.findFirst({ - where: and(eq(cartItems.userId, userId), eq(cartItems.productId, productId)), - }); - - if (existingItem) { - await db.update(cartItems) - .set({ - quantity: sql`${cartItems.quantity} + ${quantity}`, - }) - .where(eq(cartItems.id, existingItem.id)); - } else { - await db.insert(cartItems).values({ - userId, - productId, - quantity: quantity.toString(), - }); - } - */ - - // Return updated cart - return await getCartData(userId) - }), - - updateCartItem: protectedProcedure - .input(z.object({ - itemId: z.number().int().positive(), - quantity: z.number().int().min(0), - })) - .mutation(async ({ input, ctx }): Promise => { - const userId = ctx.user.userId; - const { itemId, quantity } = input; - - if (!quantity || quantity <= 0) { - throw new ApiError("Positive quantity required", 400); - } - - const updated = await updateUserCartItemQuantityInDb(userId, itemId, quantity) - - /* - // Old implementation - direct DB queries: - const [updatedItem] = await db.update(cartItems) - .set({ quantity: quantity.toString() }) - .where(and( - eq(cartItems.id, itemId), - eq(cartItems.userId, userId) - )) - .returning(); - - if (!updatedItem) { - throw new ApiError("Cart item not found", 404); - } - */ - - if (!updated) { - throw new ApiError('Cart item not found', 404) - } - - // Return updated cart - return await getCartData(userId) - }), - - removeFromCart: protectedProcedure - .input(z.object({ - itemId: z.number().int().positive(), - })) - .mutation(async ({ input, ctx }): Promise => { - const userId = ctx.user.userId; - const { itemId } = input; - - const deleted = await deleteUserCartItemInDb(userId, itemId) - - /* - // Old implementation - direct DB queries: - const [deletedItem] = await db.delete(cartItems) - .where(and( - eq(cartItems.id, itemId), - eq(cartItems.userId, userId) - )) - .returning(); - - if (!deletedItem) { - throw new ApiError("Cart item not found", 404); - } - */ - - if (!deleted) { - throw new ApiError('Cart item not found', 404) - } - - // Return updated cart - return await getCartData(userId) - }), - - clearCart: protectedProcedure - .mutation(async ({ ctx }): Promise => { - const userId = ctx.user.userId; - - await clearUserCartInDb(userId) - - /* - // Old implementation - direct DB query: - await db.delete(cartItems).where(eq(cartItems.userId, userId)); - */ - - return { - items: [], - totalItems: 0, - totalAmount: 0, - message: "Cart cleared successfully", - } - }), - - // Original DB-based getCartSlots (commented out) - // getCartSlots: publicProcedure - // .input(z.object({ - // productIds: z.array(z.number().int().positive()) - // })) - // .query(async ({ input }) => { - // const { productIds } = input; - // - // if (productIds.length === 0) { - // return {}; - // } - // - // // Get slots for these products where freeze time is after current time - // const slotsData = await db - // .select({ - // productId: productSlots.productId, - // slotId: deliverySlotInfo.id, - // deliveryTime: deliverySlotInfo.deliveryTime, - // freezeTime: deliverySlotInfo.freezeTime, - // isActive: deliverySlotInfo.isActive, - // }) - // .from(productSlots) - // .innerJoin(deliverySlotInfo, eq(productSlots.slotId, deliverySlotInfo.id)) - // .where(and( - // inArray(productSlots.productId, productIds), - // gt(deliverySlotInfo.freezeTime, sql`NOW()`), - // eq(deliverySlotInfo.isActive, true) - // )); - // - // // Group by productId - // const result: Record = {}; - // slotsData.forEach(slot => { - // if (!result[slot.productId]) { - // result[slot.productId] = []; - // } - // result[slot.productId].push({ - // id: slot.slotId, - // deliveryTime: slot.deliveryTime, - // freezeTime: slot.freezeTime, - // }); - // }); - // - // return result; - // }), - // Cache-based getCartSlots getCartSlots: publicProcedure .input(z.object({ diff --git a/apps/backend/src/trpc/apis/user-apis/apis/coupon.ts b/apps/backend/src/trpc/apis/user-apis/apis/coupon.ts index a7b66c3..8f807e3 100644 --- a/apps/backend/src/trpc/apis/user-apis/apis/coupon.ts +++ b/apps/backend/src/trpc/apis/user-apis/apis/coupon.ts @@ -86,57 +86,6 @@ export const userCouponRouter = router({ } }), - getProductCoupons: protectedProcedure - .input(z.object({ skuId: z.number().int().positive() })) - .query(async ({ input, ctx }): Promise => { - const userId = ctx.user.userId; - const { skuId } = input; - - // Get all active, non-expired coupons - const allCoupons = await getUserActiveCouponsWithRelationsInDb(userId) - - /* - // Old implementation - direct DB queries: - const allCoupons = await db.query.coupons.findMany({ - where: and( - eq(coupons.isInvalidated, false), - or( - isNull(coupons.validTill), - gt(coupons.validTill, new Date()) - ) - ), - with: { - usages: { - where: eq(couponUsage.userId, userId) - }, - applicableUsers: { - with: { - user: true - } - }, - applicableProducts: { - with: { - product: true - } - }, - } - }); - */ - - // Filter to only coupons applicable to current user and product - const applicableCoupons = allCoupons.filter(coupon => { - const applicableUsers = coupon.applicableUsers || []; - const userApplicable = !coupon.isUserBased || applicableUsers.some(au => au.userId === userId); - - const applicableProducts = coupon.applicableProducts || []; - const productApplicable = applicableProducts.length === 0 || applicableProducts.some(ap => ap.skuId === skuId); - - return userApplicable && productApplicable; - }); - - return { success: true, data: applicableCoupons }; - }), - getMyCoupons: protectedProcedure .query(async ({ ctx }): Promise => { const userId = ctx.user.userId; diff --git a/apps/backend/src/trpc/apis/user-apis/apis/order.ts b/apps/backend/src/trpc/apis/user-apis/apis/order.ts index 32548fc..5e2874b 100644 --- a/apps/backend/src/trpc/apis/user-apis/apis/order.ts +++ b/apps/backend/src/trpc/apis/user-apis/apis/order.ts @@ -13,9 +13,6 @@ import { getUserOrderByIdWithRelations, getUserOrderCount, getUserOrdersWithRelations, - getUserProductIdsFromOrders, - getUserProductsForRecentOrders, - getUserRecentlyDeliveredOrderIds, getUserSlotCapacityStatus, orders, orderItems, @@ -25,7 +22,6 @@ import { updateUserOrderNotes, validateAndGetUserCoupon, } from "@/src/dbService"; -import { getNextDeliveryDate } from "@/src/trpc/apis/common-apis/common"; import { scaffoldAssetUrl } from "@/src/lib/s3-client"; import { ApiError } from "@/src/lib/api-error"; import { @@ -34,13 +30,11 @@ import { } from "@/src/lib/notif-job"; import { CONST_KEYS, getConstant, getConstants } from "@/src/lib/const-store"; import { publishFormattedOrder, publishCancellation } from "@/src/lib/post-order-handler"; -import { getSlotById } from "@/src/stores/slot-store"; import type { UserOrdersResponse, UserOrderDetail, UserCancelOrderResponse, UserUpdateNotesResponse, - UserRecentProductsResponse, } from "@/src/dbService"; const placeOrderUtil = async (params: { @@ -664,60 +658,4 @@ export const orderRouter = router({ return { success: true, message: "Notes updated successfully" }; }), - - getRecentlyOrderedProducts: protectedProcedure - .input( - z - .object({ - limit: z.number().min(1).max(50).default(20), - }) - .optional() - ) - .query(async ({ input, ctx }): Promise => { - const { limit = 20 } = input || {}; - const userId = ctx.user.userId; - - const thirtyDaysAgo = new Date(); - thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); - - const recentOrderIds = await getUserRecentlyDeliveredOrderIds(userId, 10, thirtyDaysAgo); - - if (recentOrderIds.length === 0) { - return { success: true, products: [] }; - } - - const productIds = await getUserProductIdsFromOrders(recentOrderIds); - - if (productIds.length === 0) { - return { success: true, products: [] }; - } - - const productsWithUnits = await getUserProductsForRecentOrders(productIds, limit); - - const formattedProducts = await Promise.all( - productsWithUnits.map(async (product) => { - const nextDeliveryDate = await getNextDeliveryDate(product.id); - return { - id: product.id, - name: product.name, - shortDescription: product.shortDescription, - price: product.price, - unit: product.unitShortNotation, - incrementStep: product.incrementStep, - isOutOfStock: product.isOutOfStock, - nextDeliveryDate: nextDeliveryDate - ? nextDeliveryDate.toISOString() - : null, - images: scaffoldAssetUrl( - (product.images as string[]) || [] - ), - }; - }) - ); - - return { - success: true, - products: formattedProducts, - }; - }), }); diff --git a/apps/backend/src/trpc/apis/user-apis/apis/product.ts b/apps/backend/src/trpc/apis/user-apis/apis/product.ts index be8145a..e7fd996 100644 --- a/apps/backend/src/trpc/apis/user-apis/apis/product.ts +++ b/apps/backend/src/trpc/apis/user-apis/apis/product.ts @@ -2,7 +2,7 @@ import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-ind import { z } from 'zod' import { claimUploadUrl, extractKeyFromPresignedUrl, scaffoldAssetUrl } from '@/src/lib/s3-client' import { ApiError } from '@/src/lib/api-error' -import { getProductById as getProductByIdFromCache, getAllProducts as getAllProductsFromCache } from '@/src/stores/product-store' +import { getProductById as getProductByIdFromCache } from '@/src/stores/product-store' import dayjs from 'dayjs' import { getUserProductDetailById as getUserProductDetailByIdInDb, @@ -164,24 +164,6 @@ export const productRouter = router({ return { success: true, review: newReview } }), - - getAllProductsSummary: publicProcedure - .query(async (): Promise => { - // Get all products from cache - const allCachedProducts = await getAllProductsFromCache(); - - // Transform the cached products to match the expected summary format - // (with empty deliverySlots and specialDeals arrays for summary view) - const transformedProducts: UserProductDetail[] = allCachedProducts.map(product => ({ - ...product, - images: product.images || [], - deliverySlots: [], - specialDeals: [], - })) - - return transformedProducts - }), - getOffersPage: publicProcedure .query(async () => { const data = await getOffersAndCombosInDb(); diff --git a/apps/backend/src/trpc/apis/user-apis/apis/slots.ts b/apps/backend/src/trpc/apis/user-apis/apis/slots.ts index 265f42e..a938534 100644 --- a/apps/backend/src/trpc/apis/user-apis/apis/slots.ts +++ b/apps/backend/src/trpc/apis/user-apis/apis/slots.ts @@ -1,30 +1,8 @@ import { router, publicProcedure } from "@/src/trpc/trpc-index" -import { z } from "zod" -import { getAllSlots as getAllSlotsFromCache, getSlotById as getSlotByIdFromCache } from "@/src/stores/slot-store" +import { getAllSlots as getAllSlotsFromCache } from "@/src/stores/slot-store" import dayjs from 'dayjs' import { getUserActiveSlotsList as getUserActiveSlotsListInDb, getUserProductAvailability as getUserProductAvailabilityInDb } from '@/src/dbService' -import type { UserSlotData, UserSlotsListResponse, UserSlotsWithProductsResponse } from '@packages/shared' - -// Helper method to get formatted slot data by ID -async function getSlotData(slotId: number) { - const slot = await getSlotByIdFromCache(slotId); - - if (!slot) { - return null; - } - - const currentTime = new Date(); - if (dayjs(slot.freezeTime).isBefore(currentTime)) { - return null; - } - - return { - deliveryTime: slot.deliveryTime, - freezeTime: slot.freezeTime, - slotId: slot.id, - products: slot.products.filter((product) => !product.isOutOfStock), - }; -} +import type { UserSlotsListResponse, UserSlotsWithProductsResponse } from '@packages/shared' export async function scaffoldSlotsWithProducts(): Promise { const allSlots = await getAllSlotsFromCache(); @@ -82,15 +60,4 @@ export const slotsRouter = router({ count: slots.length, } }), - - getSlotsWithProducts: publicProcedure.query(async (): Promise => { - const response = await scaffoldSlotsWithProducts(); - return response; - }), - - getSlotById: publicProcedure - .input(z.object({ slotId: z.number() })) - .query(async ({ input }): Promise => { - return await getSlotData(input.slotId); - }), }); diff --git a/apps/backend/src/trpc/apis/user-apis/apis/stores.ts b/apps/backend/src/trpc/apis/user-apis/apis/stores.ts index fe2c751..aa03238 100644 --- a/apps/backend/src/trpc/apis/user-apis/apis/stores.ts +++ b/apps/backend/src/trpc/apis/user-apis/apis/stores.ts @@ -1,5 +1,3 @@ -import { router, publicProcedure } from '@/src/trpc/trpc-index' -import { z } from 'zod' import { scaffoldAssetUrl } from '@/src/lib/s3-client' import { ApiError } from '@/src/lib/api-error' import { getTagsByStoreId } from '@/src/stores/product-tag-store' @@ -175,21 +173,3 @@ export async function scaffoldStoreWithProducts(storeId: number): Promise => { - const response = await scaffoldStores(); - return response; - }), - - getStoreWithProducts: publicProcedure - .input(z.object({ - storeId: z.number(), - })) - .query(async ({ input }): Promise => { - const { storeId } = input; - const response = await scaffoldStoreWithProducts(storeId); - return response; - }), -}); diff --git a/apps/backend/src/trpc/apis/user-apis/apis/tags.ts b/apps/backend/src/trpc/apis/user-apis/apis/tags.ts deleted file mode 100644 index d21b229..0000000 --- a/apps/backend/src/trpc/apis/user-apis/apis/tags.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { router, publicProcedure } from '@/src/trpc/trpc-index'; -import { z } from 'zod'; -import { getTagsByStoreId } from '@/src/stores/product-tag-store'; -import { ApiError } from '@/src/lib/api-error'; - -export const tagsRouter = router({ - getTagsByStore: publicProcedure - .input(z.object({ - storeId: z.number(), - })) - .query(async ({ input }) => { - const { storeId } = input; - - // Get tags from cache that are related to this store - const tags = await getTagsByStoreId(storeId); - - - return { - tags: tags.map(tag => ({ - id: tag.id, - tagName: tag.tagName, - tagDescription: tag.tagDescription, - imageUrl: tag.imageUrl, - productIds: tag.productIds, - })), - }; - }), -}); diff --git a/apps/backend/src/trpc/apis/user-apis/apis/user-trpc-index.ts b/apps/backend/src/trpc/apis/user-apis/apis/user-trpc-index.ts index 52e6531..bb1d70a 100644 --- a/apps/backend/src/trpc/apis/user-apis/apis/user-trpc-index.ts +++ b/apps/backend/src/trpc/apis/user-apis/apis/user-trpc-index.ts @@ -1,7 +1,6 @@ import { router } from '@/src/trpc/trpc-index'; import { addressRouter } from '@/src/trpc/apis/user-apis/apis/address'; import { authRouter } from '@/src/trpc/apis/user-apis/apis/auth'; -import { bannerRouter } from '@/src/trpc/apis/user-apis/apis/banners'; import { cartRouter } from '@/src/trpc/apis/user-apis/apis/cart'; import { complaintRouter } from '@/src/trpc/apis/user-apis/apis/complaint'; import { orderRouter } from '@/src/trpc/apis/user-apis/apis/order'; @@ -10,14 +9,11 @@ import { slotsRouter } from '@/src/trpc/apis/user-apis/apis/slots'; import { userRouter as userDataRouter } from '@/src/trpc/apis/user-apis/apis/user'; import { userCouponRouter } from '@/src/trpc/apis/user-apis/apis/coupon'; import { paymentRouter } from '@/src/trpc/apis/user-apis/apis/payments'; -import { storesRouter } from '@/src/trpc/apis/user-apis/apis/stores'; import { fileUploadRouter } from '@/src/trpc/apis/user-apis/apis/file-upload'; -import { tagsRouter } from '@/src/trpc/apis/user-apis/apis/tags'; export const userRouter = router({ address: addressRouter, auth: authRouter, - banner: bannerRouter, cart: cartRouter, complaint: complaintRouter, order: orderRouter, @@ -26,9 +22,7 @@ export const userRouter = router({ user: userDataRouter, coupon: userCouponRouter, payment: paymentRouter, - stores: storesRouter, fileUpload: fileUploadRouter, - tags: tagsRouter, }); export type UserRouter = typeof userRouter; diff --git a/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx b/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx index 59bc521..e277b1a 100755 --- a/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx +++ b/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx @@ -247,6 +247,29 @@ const ExploreTab = memo(({ tag, isSelected, onPress }: ExploreTabProps) => { ); }); +interface ExploreTabsRowProps { + dashboardTags: any[]; + activeTagId: number | null; + onSelectTag: (id: number) => void; +} + +const ExploreTabsRow = memo(({ dashboardTags, activeTagId, onSelectTag }: ExploreTabsRowProps) => ( + + {dashboardTags.map((tag) => ( + onSelectTag(tag.id)} + /> + ))} + +)); + interface ExploreProductItemProps { item: any; onPress: (id: number) => void; @@ -307,6 +330,7 @@ interface ListHeaderProps { activeTagId: number | null; activeTagProducts: any[]; onSelectTag: (id: number) => void; + onTabsSectionLayout: (layout: { y: number; height: number }) => void; } const ListHeader = memo(({ @@ -320,6 +344,7 @@ const ListHeader = memo(({ activeTagId, activeTagProducts, onSelectTag, + onTabsSectionLayout, }: ListHeaderProps) => { const [showAllActiveProducts, setShowAllActiveProducts] = useState(false); const handleLayout = useCallback((event: any) => { @@ -360,25 +385,20 @@ const ListHeader = memo(({ {dashboardTags.length > 0 && ( { + const { y, height } = event.nativeEvent.layout; + onTabsSectionLayout({ y, height }); + }} style={[ tw`mx-4 mb-2 rounded-[28px] px-3 pt-3 pb-2`, { backgroundColor: pageTint }, ]} > - - {dashboardTags.map((tag) => ( - onSelectTag(tag.id)} - /> - ))} - + {activeTagProducts.length > 0 ? ( @@ -515,6 +535,9 @@ export default function Dashboard() { const [displayedProducts, setDisplayedProducts] = useState([]); const [hasMore, setHasMore] = useState(true); const [isLoadingMore, setIsLoadingMore] = useState(false); + const [searchBarHeight, setSearchBarHeight] = useState(0); + const [tabsSectionLayout, setTabsSectionLayout] = useState({ y: 0, height: 0 }); + const [showStickyTabs, setShowStickyTabs] = useState(false); const { getQuickestSlot } = useProductSlotIdentifier(); const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap); const refetchProducts = useCentralProductStore((state) => state.refetchProducts); @@ -657,6 +680,19 @@ export default function Dashboard() { router.push("/(drawer)/(tabs)/home/search-results"); }, [router]); + const handleTabsSectionLayout = useCallback((layout: { y: number; height: number }) => { + setTabsSectionLayout(layout); + }, []); + + const handleListScroll = useCallback((event: any) => { + const scrollY = event.nativeEvent.contentOffset.y; + const stickyStart = tabsSectionLayout.y; + const stickyEnd = tabsSectionLayout.y + tabsSectionLayout.height - 56; + const shouldShowStickyTabs = tabsSectionLayout.height > 0 && scrollY > stickyStart && scrollY < stickyEnd; + + setShowStickyTabs((current) => current === shouldShowStickyTabs ? current : shouldShowStickyTabs); + }, [tabsSectionLayout]); + const renderProductItem = useCallback(({ item }: { item: any }) => ( // @@ -674,8 +710,9 @@ export default function Dashboard() { activeTagId={activeTagId} activeTagProducts={activeTagProducts} onSelectTag={setSelectedTagId} + onTabsSectionLayout={handleTabsSectionLayout} /> - ), [gradientHeight, handleGradientLayout, storesData, popularProducts, sortedSlots, handleProductPress, dashboardTags, activeTagId, activeTagProducts]); + ), [gradientHeight, handleGradientLayout, storesData, popularProducts, sortedSlots, handleProductPress, dashboardTags, activeTagId, activeTagProducts, handleTabsSectionLayout]); const pageTint = activeTagId == null ? '#FFFFFF' : getTagColor(activeTagId).pageBg; const pageTintStyle = useMemo(() => [ @@ -720,7 +757,10 @@ export default function Dashboard() { return ( - + setSearchBarHeight(event.nativeEvent.layout.height)} + > { }} @@ -741,6 +781,8 @@ export default function Dashboard() { keyExtractor={(item) => item.id.toString()} numColumns={2} style={{ backgroundColor: pageTint }} + onScroll={handleListScroll} + scrollEventThrottle={16} contentContainerStyle={listContentContainerStyle} columnWrapperStyle={staticStyles.columnWrapper} renderItem={renderProductItem} @@ -765,6 +807,33 @@ export default function Dashboard() { updateCellsBatchingPeriod={50} /> + {showStickyTabs && dashboardTags.length > 0 && ( + + + + + + )} + diff --git a/packages/db_helper_postgres/index.ts b/packages/db_helper_postgres/index.ts index fe62a34..09b1a4b 100644 --- a/packages/db_helper_postgres/index.ts +++ b/packages/db_helper_postgres/index.ts @@ -202,18 +202,6 @@ export { getActiveBanners as getUserActiveBanners, } from './src/user-apis/banners'; -export { - // User Cart - getCartItemsWithProducts as getUserCartItemsWithProducts, - getProductById as getUserProductById, - getCartItemByUserProduct as getUserCartItemByUserProduct, - incrementCartItemQuantity as incrementUserCartItemQuantity, - insertCartItem as insertUserCartItem, - updateCartItemQuantity as updateUserCartItemQuantity, - deleteCartItem as deleteUserCartItem, - clearUserCart, -} from './src/user-apis/cart'; - export { // User Complaint getUserComplaints as getUserComplaints, @@ -308,9 +296,6 @@ export { getOrderBasic as getUserOrderBasic, cancelOrderTransaction as cancelUserOrderTransaction, updateOrderNotes as updateUserOrderNotes, - getRecentlyDeliveredOrderIds as getUserRecentlyDeliveredOrderIds, - getProductIdsFromOrders as getUserProductIdsFromOrders, - getProductsForRecentOrders as getUserProductsForRecentOrders, // Post-order handler helpers getOrdersByIdsWithFullData, getOrderByIdWithFullData, diff --git a/packages/db_helper_postgres/src/user-apis/cart.ts b/packages/db_helper_postgres/src/user-apis/cart.ts deleted file mode 100644 index 211699c..0000000 --- a/packages/db_helper_postgres/src/user-apis/cart.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { db } from '../db/db_index' -import { cartItems, productInfo, units } from '../db/schema' -import { and, eq, sql } from 'drizzle-orm' -import type { UserCartItem } from '@packages/shared' - -const getStringArray = (value: unknown): string[] => { - if (!Array.isArray(value)) return [] - return value.map((item) => String(item)) -} - -export async function getCartItemsWithProducts(userId: number): Promise { - const cartItemsWithProducts = await db - .select({ - cartId: cartItems.id, - productId: productInfo.id, - productName: productInfo.name, - productPrice: productInfo.price, - productImages: productInfo.images, - productQuantity: productInfo.productQuantity, - isOutOfStock: productInfo.isOutOfStock, - unitShortNotation: units.shortNotation, - quantity: cartItems.quantity, - addedAt: cartItems.addedAt, - }) - .from(cartItems) - .innerJoin(productInfo, eq(cartItems.productId, productInfo.id)) - .innerJoin(units, eq(productInfo.unitId, units.id)) - .where(eq(cartItems.userId, userId)) - - return cartItemsWithProducts.map((item) => ({ - id: item.cartId, - productId: item.productId, - quantity: parseFloat(item.quantity), - addedAt: item.addedAt, - product: { - id: item.productId, - name: item.productName, - price: item.productPrice.toString(), - productQuantity: item.productQuantity, - unit: item.unitShortNotation, - isOutOfStock: item.isOutOfStock, - images: getStringArray(item.productImages), - }, - subtotal: parseFloat(item.productPrice.toString()) * parseFloat(item.quantity), - })) -} - -export async function getProductById(productId: number) { - return db.query.productInfo.findFirst({ - where: eq(productInfo.id, productId), - }) -} - -export async function getCartItemByUserProduct(userId: number, productId: number) { - return db.query.cartItems.findFirst({ - where: and(eq(cartItems.userId, userId), eq(cartItems.productId, productId)), - }) -} - -export async function incrementCartItemQuantity(itemId: number, quantity: number): Promise { - await db.update(cartItems) - .set({ - quantity: sql`${cartItems.quantity} + ${quantity}`, - }) - .where(eq(cartItems.id, itemId)) -} - -export async function insertCartItem(userId: number, productId: number, quantity: number): Promise { - await db.insert(cartItems).values({ - userId, - productId, - quantity: quantity.toString(), - }) -} - -export async function updateCartItemQuantity(userId: number, itemId: number, quantity: number) { - const [updatedItem] = await db.update(cartItems) - .set({ quantity: quantity.toString() }) - .where(and(eq(cartItems.id, itemId), eq(cartItems.userId, userId))) - .returning({ id: cartItems.id }) - - return !!updatedItem -} - -export async function deleteCartItem(userId: number, itemId: number): Promise { - const [deletedItem] = await db.delete(cartItems) - .where(and(eq(cartItems.id, itemId), eq(cartItems.userId, userId))) - .returning({ id: cartItems.id }) - - return !!deletedItem -} - -export async function clearUserCart(userId: number): Promise { - await db.delete(cartItems).where(eq(cartItems.userId, userId)) -} diff --git a/packages/db_helper_postgres/src/user-apis/order.ts b/packages/db_helper_postgres/src/user-apis/order.ts index cc0f922..eb9d8e6 100644 --- a/packages/db_helper_postgres/src/user-apis/order.ts +++ b/packages/db_helper_postgres/src/user-apis/order.ts @@ -14,11 +14,10 @@ import { userDetails, deliverySlotInfo, } from '../db/schema' -import { and, eq, inArray, desc, gte, lte } from 'drizzle-orm' +import { and, eq, inArray, desc, lte } from 'drizzle-orm' import type { UserOrderSummary, UserOrderDetail, - UserRecentProduct, } from '@packages/shared' export interface OrderItemInput { @@ -552,77 +551,6 @@ export async function updateOrderNotes( .where(eq(orders.id, orderId)) } -export async function getRecentlyDeliveredOrderIds( - userId: number, - limit: number, - since: Date -): Promise { - const recentOrders = await db - .select({ id: orders.id }) - .from(orders) - .innerJoin(orderStatus, eq(orders.id, orderStatus.orderId)) - .where( - and( - eq(orders.userId, userId), - eq(orderStatus.isDelivered, true), - gte(orders.createdAt, since) - ) - ) - .orderBy(desc(orders.createdAt)) - .limit(limit) - - return recentOrders.map((order) => order.id) -} - -export async function getProductIdsFromOrders( - orderIds: number[] -): Promise { - const orderItemsResult = await db - .select({ productId: orderItems.productId }) - .from(orderItems) - .where(inArray(orderItems.orderId, orderIds)) - - return [...new Set(orderItemsResult.map((item) => item.productId))] -} - -export interface RecentProductData { - id: number - name: string - shortDescription: string | null - price: string - images: unknown - isOutOfStock: boolean - unitShortNotation: string - incrementStep: number -} - -export async function getProductsForRecentOrders( - productIds: number[], - limit: number -): Promise { - return db - .select({ - id: productInfo.id, - name: productInfo.name, - shortDescription: productInfo.shortDescription, - price: productInfo.price, - images: productInfo.images, - isOutOfStock: productInfo.isOutOfStock, - unitShortNotation: units.shortNotation, - incrementStep: productInfo.incrementStep, - }) - .from(productInfo) - .innerJoin(units, eq(productInfo.unitId, units.id)) - .where( - and( - inArray(productInfo.id, productIds), - eq(productInfo.isSuspended, false) - ) - ) - .orderBy(desc(productInfo.createdAt)) - .limit(limit) -} - // ============================================================================ // Post-Order Handler Helpers (for Telegram notifications) // ============================================================================ diff --git a/packages/db_helper_sqlite/index.ts b/packages/db_helper_sqlite/index.ts index 53b73f6..bc6aad0 100644 --- a/packages/db_helper_sqlite/index.ts +++ b/packages/db_helper_sqlite/index.ts @@ -201,18 +201,6 @@ export { getActiveBanners as getUserActiveBanners, } from './src/user-apis/banners' -export { - // User Cart - getCartItemsWithProducts as getUserCartItemsWithProducts, - getProductById as getUserProductById, - getCartItemByUserProduct as getUserCartItemByUserProduct, - incrementCartItemQuantity as incrementUserCartItemQuantity, - insertCartItem as insertUserCartItem, - updateCartItemQuantity as updateUserCartItemQuantity, - deleteCartItem as deleteUserCartItem, - clearUserCart, -} from './src/user-apis/cart' - export { // User Complaint getUserComplaints as getUserComplaints, @@ -312,9 +300,6 @@ export { getOrderBasic as getUserOrderBasic, cancelOrderTransaction as cancelUserOrderTransaction, updateOrderNotes as updateUserOrderNotes, - getRecentlyDeliveredOrderIds as getUserRecentlyDeliveredOrderIds, - getSkuIdsFromOrders as getUserProductIdsFromOrders, - getProductsForRecentOrders as getUserProductsForRecentOrders, // Post-order handler helpers getOrdersByIdsWithFullData, getOrderByIdWithFullData, diff --git a/packages/db_helper_sqlite/src/user-apis/cart.ts b/packages/db_helper_sqlite/src/user-apis/cart.ts deleted file mode 100644 index fc396ab..0000000 --- a/packages/db_helper_sqlite/src/user-apis/cart.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { db } from '../db/db_index' -import { cartItems, productSkus } from '../db/schema' -import { and, eq, sql } from 'drizzle-orm' -import type { UserCartItem } from '@packages/shared' -import { composeSkuName, composeUnitNotation } from '../lib/sku-features' - -const getStringArray = (value: unknown): string[] => { - if (!Array.isArray(value)) return [] - return value.map((item) => String(item)) -} - -export async function getCartItemsWithProducts(userId: number): Promise { - const cartItemsWithProducts = await db.query.cartItems.findMany({ - where: eq(cartItems.userId, userId), - with: { - sku: { - with: { - product: true, - features: true, - }, - }, - }, - }) - - return cartItemsWithProducts - .filter((item) => item.sku && !item.sku.isSuspended) - .map((item) => { - const sku = item.sku - const features = sku?.features || [] - const priceValue = sku?.price ?? '0' - const quantityValue = item.quantity ?? '0' - return { - id: item.id, - skuId: item.skuId, - quantity: parseFloat(quantityValue), - addedAt: item.addedAt, - product: { - id: sku?.id ?? 0, - name: composeSkuName(sku?.product?.name ?? 'Unknown', features), - price: String(priceValue), - productQuantity: 1, - unit: composeUnitNotation(features), - isOutOfStock: sku?.isOutOfStock ?? false, - images: getStringArray(sku?.images), - }, - subtotal: parseFloat(String(priceValue)) * parseFloat(quantityValue), - } - }) -} - -export async function getProductById(skuId: number) { - return db.query.productSkus.findFirst({ - where: and(eq(productSkus.id, skuId), eq(productSkus.isSuspended, false)), - }) -} - -export async function getCartItemByUserProduct(userId: number, skuId: number) { - return db.query.cartItems.findFirst({ - where: and(eq(cartItems.userId, userId), eq(cartItems.skuId, skuId)), - }) -} - -export async function incrementCartItemQuantity(itemId: number, quantity: number): Promise { - await db.update(cartItems) - .set({ - quantity: sql`${cartItems.quantity} + ${quantity}`, - }) - .where(eq(cartItems.id, itemId)) -} - -export async function insertCartItem(userId: number, skuId: number, quantity: number): Promise { - await db.insert(cartItems).values({ - userId, - skuId, - quantity: quantity.toString(), - }) -} - -export async function updateCartItemQuantity(userId: number, itemId: number, quantity: number) { - const [updatedItem] = await db.update(cartItems) - .set({ quantity: quantity.toString() }) - .where(and(eq(cartItems.id, itemId), eq(cartItems.userId, userId))) - .returning({ id: cartItems.id }) - - return !!updatedItem -} - -export async function deleteCartItem(userId: number, itemId: number): Promise { - const [deletedItem] = await db.delete(cartItems) - .where(and(eq(cartItems.id, itemId), eq(cartItems.userId, userId))) - .returning({ id: cartItems.id }) - - return !!deletedItem -} - -export async function clearUserCart(userId: number): Promise { - await db.delete(cartItems).where(eq(cartItems.userId, userId)) -} diff --git a/packages/db_helper_sqlite/src/user-apis/order.ts b/packages/db_helper_sqlite/src/user-apis/order.ts index 341034f..8456968 100644 --- a/packages/db_helper_sqlite/src/user-apis/order.ts +++ b/packages/db_helper_sqlite/src/user-apis/order.ts @@ -15,11 +15,10 @@ import { userDetails, deliverySlotInfo, } from '../db/schema' -import { and, eq, inArray, desc, gte, sql } from 'drizzle-orm' +import { and, eq, inArray, desc, sql } from 'drizzle-orm' import type { UserOrderSummary, UserOrderDetail, - UserRecentProduct, } from '@packages/shared' import { coerceDate } from '../lib/date' import { runBatched } from '../lib/run-batched' @@ -611,93 +610,6 @@ export async function updateOrderNotes( .where(eq(orders.id, orderId)) } -export async function getRecentlyDeliveredOrderIds( - userId: number, - limit: number, - since: Date -): Promise { - const recentOrders = await db - .select({ id: orders.id }) - .from(orders) - .innerJoin(orderStatus, eq(orders.id, orderStatus.orderId)) - .where( - and( - eq(orders.userId, userId), - eq(orderStatus.isDelivered, true), - gte(orders.createdAt, since) - ) - ) - .orderBy(desc(orders.createdAt)) - .limit(limit) - - return recentOrders.map((order) => order.id) -} - -export async function getSkuIdsFromOrders( - orderIds: number[] -): Promise { - if (orderIds.length === 0) return [] - - const skuChunks = await runBatched(db, orderIds, 10, async (tx, chunk) => { - return tx - .select({ skuId: orderItems.skuId }) - .from(orderItems) - .where(inArray(orderItems.orderId, chunk)) - }) - - return [...new Set(skuChunks.flat().map((item) => item.skuId))] -} - -export interface RecentProductData { - id: number - name: string - shortDescription: string | null - price: string - images: unknown - isOutOfStock: boolean - unitShortNotation: string - incrementStep: number -} - -export async function getProductsForRecentOrders( - productIds: number[], - limit: number -): Promise { - if (productIds.length === 0) return [] - - const skuChunks = await runBatched(db, productIds, 10, async (tx, chunk) => { - return tx.query.productSkus.findMany({ - where: and( - inArray(productSkus.id, chunk), - eq(productSkus.isSuspended, false) - ), - with: { - product: true, - features: true, - }, - }) - }) - - const skus = skuChunks - .flat() - .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) - .slice(0, limit) - - return skus.map((sku) => { - const features = sku.features || [] - return { - id: sku.id, - name: composeSkuName(sku.product?.name ?? 'Unknown', features), - shortDescription: sku.product?.shortDescription ?? null, - price: String(sku.price ?? '0'), - images: sku.images, - isOutOfStock: sku.isOutOfStock, - unitShortNotation: composeUnitNotation(features), - incrementStep: sku.product?.incrementStep ?? 1, - } - }) -} - // ============================================================================ // Post-Order Handler Helpers (for Telegram notifications) // ============================================================================ diff --git a/packages/shared/types/user.ts b/packages/shared/types/user.ts index 019ca2e..1efc02b 100644 --- a/packages/shared/types/user.ts +++ b/packages/shared/types/user.ts @@ -133,32 +133,6 @@ export interface UserBannersResponse { banners: UserBanner[]; } -export interface UserCartProduct { - id: number; - name: string; - price: string; - productQuantity: number; - unit: string; - isOutOfStock: boolean; - images: string[]; -} - -export interface UserCartItem { - id: number; - skuId: number; - quantity: number; - addedAt: Date; - product: UserCartProduct; - subtotal: number; -} - -export interface UserCartResponse { - items: UserCartItem[]; - totalItems: number; - totalAmount: number; - message?: string; -} - export interface UserComplaint { id: number; complaintBody: string; @@ -367,13 +341,6 @@ export interface UserSlotWithProducts { products: UserSlotProduct[]; } -export interface UserSlotData { - slotId: number; - deliveryTime: Date; - freezeTime: Date; - products: UserSlotProduct[]; -} - export interface UserSlotAvailability { id: number; name: string; @@ -464,16 +431,6 @@ export interface UserPasswordUpdateResponse { message: string; } -export interface UserProfileResponse { - success: boolean; - data: { - id: number; - name: string | null; - email: string | null; - mobile: string | null; - }; -} - export interface UserDeleteAccountResponse { success: boolean; message: string; @@ -638,20 +595,3 @@ export interface UserUpdateNotesResponse { success: boolean; message: string; } - -export interface UserRecentProduct { - id: number; - name: string; - shortDescription: string | null; - price: string; - images: string[]; - isOutOfStock: boolean; - unit: string; - incrementStep: number; - nextDeliveryDate: string | null; -} - -export interface UserRecentProductsResponse { - success: boolean; - products: UserRecentProduct[]; -}