old apis removal
This commit is contained in:
parent
fa56fd4a82
commit
b41980736a
22 changed files with 92 additions and 1005 deletions
|
|
@ -10,3 +10,4 @@
|
||||||
- Prefers embedding related data into existing cache files over creating or calling separate API endpoints. Confidence: 0.9
|
- 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
|
- 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
|
- 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
|
||||||
|
|
|
||||||
|
|
@ -98,9 +98,6 @@ export type {
|
||||||
UserAddressDeleteResponse,
|
UserAddressDeleteResponse,
|
||||||
UserBanner,
|
UserBanner,
|
||||||
UserBannersResponse,
|
UserBannersResponse,
|
||||||
UserCartProduct,
|
|
||||||
UserCartItem,
|
|
||||||
UserCartResponse,
|
|
||||||
UserComplaint,
|
UserComplaint,
|
||||||
UserComplaintsResponse,
|
UserComplaintsResponse,
|
||||||
UserRaiseComplaintResponse,
|
UserRaiseComplaintResponse,
|
||||||
|
|
@ -122,7 +119,6 @@ export type {
|
||||||
UserCreateReviewResponse,
|
UserCreateReviewResponse,
|
||||||
UserSlotProduct,
|
UserSlotProduct,
|
||||||
UserSlotWithProducts,
|
UserSlotWithProducts,
|
||||||
UserSlotData,
|
|
||||||
UserSlotAvailability,
|
UserSlotAvailability,
|
||||||
UserDeliverySlot,
|
UserDeliverySlot,
|
||||||
UserSlotsResponse,
|
UserSlotsResponse,
|
||||||
|
|
@ -136,7 +132,6 @@ export type {
|
||||||
UserAuthResult,
|
UserAuthResult,
|
||||||
UserOtpVerifyResponse,
|
UserOtpVerifyResponse,
|
||||||
UserPasswordUpdateResponse,
|
UserPasswordUpdateResponse,
|
||||||
UserProfileResponse,
|
|
||||||
UserDeleteAccountResponse,
|
UserDeleteAccountResponse,
|
||||||
UserCouponUsage,
|
UserCouponUsage,
|
||||||
UserCouponApplicableUser,
|
UserCouponApplicableUser,
|
||||||
|
|
@ -156,8 +151,6 @@ export type {
|
||||||
UserOrderDetail,
|
UserOrderDetail,
|
||||||
UserCancelOrderResponse,
|
UserCancelOrderResponse,
|
||||||
UserUpdateNotesResponse,
|
UserUpdateNotesResponse,
|
||||||
UserRecentProduct,
|
|
||||||
UserRecentProductsResponse,
|
|
||||||
// Store types
|
// Store types
|
||||||
StoreSummary,
|
StoreSummary,
|
||||||
StoresSummaryResponse,
|
StoresSummaryResponse,
|
||||||
|
|
|
||||||
|
|
@ -157,15 +157,6 @@
|
||||||
// hasOngoingOrdersForAddress,
|
// hasOngoingOrdersForAddress,
|
||||||
// // User - Banners
|
// // User - Banners
|
||||||
// getUserActiveBanners,
|
// getUserActiveBanners,
|
||||||
// // User - Cart
|
|
||||||
// getUserCartItemsWithProducts,
|
|
||||||
// getUserProductById,
|
|
||||||
// getUserCartItemByUserProduct,
|
|
||||||
// incrementUserCartItemQuantity,
|
|
||||||
// insertUserCartItem,
|
|
||||||
// updateUserCartItemQuantity,
|
|
||||||
// deleteUserCartItem,
|
|
||||||
// clearUserCart,
|
|
||||||
// // User - Complaint
|
// // User - Complaint
|
||||||
// getUserComplaints,
|
// getUserComplaints,
|
||||||
// createUserComplaint,
|
// createUserComplaint,
|
||||||
|
|
@ -235,9 +226,6 @@
|
||||||
// getUserOrderBasic,
|
// getUserOrderBasic,
|
||||||
// cancelUserOrderTransaction,
|
// cancelUserOrderTransaction,
|
||||||
// updateUserOrderNotes,
|
// updateUserOrderNotes,
|
||||||
// getUserRecentlyDeliveredOrderIds,
|
|
||||||
// getUserProductIdsFromOrders,
|
|
||||||
// getUserProductsForRecentOrders,
|
|
||||||
// // Store Helpers
|
// // Store Helpers
|
||||||
// getAllBannersForCache,
|
// getAllBannersForCache,
|
||||||
// getAllProductsForCache,
|
// getAllProductsForCache,
|
||||||
|
|
|
||||||
|
|
@ -168,15 +168,6 @@ export {
|
||||||
hasOngoingOrdersForAddress,
|
hasOngoingOrdersForAddress,
|
||||||
// User - Banners
|
// User - Banners
|
||||||
getUserActiveBanners,
|
getUserActiveBanners,
|
||||||
// User - Cart
|
|
||||||
getUserCartItemsWithProducts,
|
|
||||||
getUserProductById,
|
|
||||||
getUserCartItemByUserProduct,
|
|
||||||
incrementUserCartItemQuantity,
|
|
||||||
insertUserCartItem,
|
|
||||||
updateUserCartItemQuantity,
|
|
||||||
deleteUserCartItem,
|
|
||||||
clearUserCart,
|
|
||||||
// User - Complaint
|
// User - Complaint
|
||||||
getUserComplaints,
|
getUserComplaints,
|
||||||
createUserComplaint,
|
createUserComplaint,
|
||||||
|
|
@ -246,9 +237,6 @@ export {
|
||||||
getUserOrderBasic,
|
getUserOrderBasic,
|
||||||
cancelUserOrderTransaction,
|
cancelUserOrderTransaction,
|
||||||
updateUserOrderNotes,
|
updateUserOrderNotes,
|
||||||
getUserRecentlyDeliveredOrderIds,
|
|
||||||
getUserProductIdsFromOrders,
|
|
||||||
getUserProductsForRecentOrders,
|
|
||||||
// Store Helpers
|
// Store Helpers
|
||||||
getAllBannersForCache,
|
getAllBannersForCache,
|
||||||
getAllProductsForCache,
|
getAllProductsForCache,
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import type {
|
||||||
UserAuthResponse,
|
UserAuthResponse,
|
||||||
UserOtpVerifyResponse,
|
UserOtpVerifyResponse,
|
||||||
UserPasswordUpdateResponse,
|
UserPasswordUpdateResponse,
|
||||||
UserProfileResponse,
|
|
||||||
UserDeleteAccountResponse,
|
UserDeleteAccountResponse,
|
||||||
} from '@packages/shared'
|
} from '@packages/shared'
|
||||||
|
|
||||||
|
|
@ -396,31 +395,6 @@ export const authRouter = router({
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getProfile: protectedProcedure
|
|
||||||
.query(async ({ ctx }): Promise<UserProfileResponse> => {
|
|
||||||
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
|
deleteAccount: protectedProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
mobile: z.string().min(10, 'Mobile number is required'),
|
mobile: z.string().min(10, 'Mobile number is required'),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { publicProcedure, router } from '@/src/trpc/trpc-index'
|
|
||||||
import { scaffoldAssetUrl } from '@/src/lib/s3-client'
|
import { scaffoldAssetUrl } from '@/src/lib/s3-client'
|
||||||
import { getUserActiveBanners as getUserActiveBannersInDb } from '@/src/dbService'
|
import { getUserActiveBanners as getUserActiveBannersInDb } from '@/src/dbService'
|
||||||
import type { UserBannersResponse } from '@packages/shared'
|
import type { UserBannersResponse } from '@packages/shared'
|
||||||
|
|
@ -23,11 +22,3 @@ export async function scaffoldBanners(): Promise<UserBannersResponse> {
|
||||||
banners: bannersWithSignedUrls,
|
banners: bannersWithSignedUrls,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bannerRouter = router({
|
|
||||||
getBanners: publicProcedure
|
|
||||||
.query(async () => {
|
|
||||||
const response = await scaffoldBanners();
|
|
||||||
return response;
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -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 { 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 { 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<UserCartResponse> => {
|
|
||||||
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({
|
export const cartRouter = router({
|
||||||
getCart: protectedProcedure
|
|
||||||
.query(async ({ ctx }): Promise<UserCartResponse> => {
|
|
||||||
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<UserCartResponse> => {
|
|
||||||
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<UserCartResponse> => {
|
|
||||||
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<UserCartResponse> => {
|
|
||||||
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<UserCartResponse> => {
|
|
||||||
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<number, any[]> = {};
|
|
||||||
// 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
|
// Cache-based getCartSlots
|
||||||
getCartSlots: publicProcedure
|
getCartSlots: publicProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
|
|
|
||||||
|
|
@ -86,57 +86,6 @@ export const userCouponRouter = router({
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getProductCoupons: protectedProcedure
|
|
||||||
.input(z.object({ skuId: z.number().int().positive() }))
|
|
||||||
.query(async ({ input, ctx }): Promise<UserEligibleCouponsResponse> => {
|
|
||||||
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
|
getMyCoupons: protectedProcedure
|
||||||
.query(async ({ ctx }): Promise<UserMyCouponsResponse> => {
|
.query(async ({ ctx }): Promise<UserMyCouponsResponse> => {
|
||||||
const userId = ctx.user.userId;
|
const userId = ctx.user.userId;
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,6 @@ import {
|
||||||
getUserOrderByIdWithRelations,
|
getUserOrderByIdWithRelations,
|
||||||
getUserOrderCount,
|
getUserOrderCount,
|
||||||
getUserOrdersWithRelations,
|
getUserOrdersWithRelations,
|
||||||
getUserProductIdsFromOrders,
|
|
||||||
getUserProductsForRecentOrders,
|
|
||||||
getUserRecentlyDeliveredOrderIds,
|
|
||||||
getUserSlotCapacityStatus,
|
getUserSlotCapacityStatus,
|
||||||
orders,
|
orders,
|
||||||
orderItems,
|
orderItems,
|
||||||
|
|
@ -25,7 +22,6 @@ import {
|
||||||
updateUserOrderNotes,
|
updateUserOrderNotes,
|
||||||
validateAndGetUserCoupon,
|
validateAndGetUserCoupon,
|
||||||
} from "@/src/dbService";
|
} from "@/src/dbService";
|
||||||
import { getNextDeliveryDate } from "@/src/trpc/apis/common-apis/common";
|
|
||||||
import { scaffoldAssetUrl } from "@/src/lib/s3-client";
|
import { scaffoldAssetUrl } from "@/src/lib/s3-client";
|
||||||
import { ApiError } from "@/src/lib/api-error";
|
import { ApiError } from "@/src/lib/api-error";
|
||||||
import {
|
import {
|
||||||
|
|
@ -34,13 +30,11 @@ import {
|
||||||
} from "@/src/lib/notif-job";
|
} from "@/src/lib/notif-job";
|
||||||
import { CONST_KEYS, getConstant, getConstants } from "@/src/lib/const-store";
|
import { CONST_KEYS, getConstant, getConstants } from "@/src/lib/const-store";
|
||||||
import { publishFormattedOrder, publishCancellation } from "@/src/lib/post-order-handler";
|
import { publishFormattedOrder, publishCancellation } from "@/src/lib/post-order-handler";
|
||||||
import { getSlotById } from "@/src/stores/slot-store";
|
|
||||||
import type {
|
import type {
|
||||||
UserOrdersResponse,
|
UserOrdersResponse,
|
||||||
UserOrderDetail,
|
UserOrderDetail,
|
||||||
UserCancelOrderResponse,
|
UserCancelOrderResponse,
|
||||||
UserUpdateNotesResponse,
|
UserUpdateNotesResponse,
|
||||||
UserRecentProductsResponse,
|
|
||||||
} from "@/src/dbService";
|
} from "@/src/dbService";
|
||||||
|
|
||||||
const placeOrderUtil = async (params: {
|
const placeOrderUtil = async (params: {
|
||||||
|
|
@ -664,60 +658,4 @@ export const orderRouter = router({
|
||||||
|
|
||||||
return { success: true, message: "Notes updated successfully" };
|
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<UserRecentProductsResponse> => {
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-ind
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { claimUploadUrl, extractKeyFromPresignedUrl, scaffoldAssetUrl } from '@/src/lib/s3-client'
|
import { claimUploadUrl, extractKeyFromPresignedUrl, scaffoldAssetUrl } from '@/src/lib/s3-client'
|
||||||
import { ApiError } from '@/src/lib/api-error'
|
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 dayjs from 'dayjs'
|
||||||
import {
|
import {
|
||||||
getUserProductDetailById as getUserProductDetailByIdInDb,
|
getUserProductDetailById as getUserProductDetailByIdInDb,
|
||||||
|
|
@ -164,24 +164,6 @@ export const productRouter = router({
|
||||||
return { success: true, review: newReview }
|
return { success: true, review: newReview }
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
||||||
getAllProductsSummary: publicProcedure
|
|
||||||
.query(async (): Promise<UserProductDetail[]> => {
|
|
||||||
// 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
|
getOffersPage: publicProcedure
|
||||||
.query(async () => {
|
.query(async () => {
|
||||||
const data = await getOffersAndCombosInDb();
|
const data = await getOffersAndCombosInDb();
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,8 @@
|
||||||
import { router, publicProcedure } from "@/src/trpc/trpc-index"
|
import { router, publicProcedure } from "@/src/trpc/trpc-index"
|
||||||
import { z } from "zod"
|
import { getAllSlots as getAllSlotsFromCache } from "@/src/stores/slot-store"
|
||||||
import { getAllSlots as getAllSlotsFromCache, getSlotById as getSlotByIdFromCache } from "@/src/stores/slot-store"
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { getUserActiveSlotsList as getUserActiveSlotsListInDb, getUserProductAvailability as getUserProductAvailabilityInDb } from '@/src/dbService'
|
import { getUserActiveSlotsList as getUserActiveSlotsListInDb, getUserProductAvailability as getUserProductAvailabilityInDb } from '@/src/dbService'
|
||||||
import type { UserSlotData, UserSlotsListResponse, UserSlotsWithProductsResponse } from '@packages/shared'
|
import type { 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),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function scaffoldSlotsWithProducts(): Promise<UserSlotsWithProductsResponse> {
|
export async function scaffoldSlotsWithProducts(): Promise<UserSlotsWithProductsResponse> {
|
||||||
const allSlots = await getAllSlotsFromCache();
|
const allSlots = await getAllSlotsFromCache();
|
||||||
|
|
@ -82,15 +60,4 @@ export const slotsRouter = router({
|
||||||
count: slots.length,
|
count: slots.length,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getSlotsWithProducts: publicProcedure.query(async (): Promise<UserSlotsWithProductsResponse> => {
|
|
||||||
const response = await scaffoldSlotsWithProducts();
|
|
||||||
return response;
|
|
||||||
}),
|
|
||||||
|
|
||||||
getSlotById: publicProcedure
|
|
||||||
.input(z.object({ slotId: z.number() }))
|
|
||||||
.query(async ({ input }): Promise<UserSlotData | null> => {
|
|
||||||
return await getSlotData(input.slotId);
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { router, publicProcedure } from '@/src/trpc/trpc-index'
|
|
||||||
import { z } from 'zod'
|
|
||||||
import { scaffoldAssetUrl } from '@/src/lib/s3-client'
|
import { scaffoldAssetUrl } from '@/src/lib/s3-client'
|
||||||
import { ApiError } from '@/src/lib/api-error'
|
import { ApiError } from '@/src/lib/api-error'
|
||||||
import { getTagsByStoreId } from '@/src/stores/product-tag-store'
|
import { getTagsByStoreId } from '@/src/stores/product-tag-store'
|
||||||
|
|
@ -175,21 +173,3 @@ export async function scaffoldStoreWithProducts(storeId: number): Promise<UserSt
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const storesRouter = router({
|
|
||||||
getStores: publicProcedure
|
|
||||||
.query(async (): Promise<UserStoresResponse> => {
|
|
||||||
const response = await scaffoldStores();
|
|
||||||
return response;
|
|
||||||
}),
|
|
||||||
|
|
||||||
getStoreWithProducts: publicProcedure
|
|
||||||
.input(z.object({
|
|
||||||
storeId: z.number(),
|
|
||||||
}))
|
|
||||||
.query(async ({ input }): Promise<UserStoreDetail> => {
|
|
||||||
const { storeId } = input;
|
|
||||||
const response = await scaffoldStoreWithProducts(storeId);
|
|
||||||
return response;
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { router } from '@/src/trpc/trpc-index';
|
import { router } from '@/src/trpc/trpc-index';
|
||||||
import { addressRouter } from '@/src/trpc/apis/user-apis/apis/address';
|
import { addressRouter } from '@/src/trpc/apis/user-apis/apis/address';
|
||||||
import { authRouter } from '@/src/trpc/apis/user-apis/apis/auth';
|
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 { cartRouter } from '@/src/trpc/apis/user-apis/apis/cart';
|
||||||
import { complaintRouter } from '@/src/trpc/apis/user-apis/apis/complaint';
|
import { complaintRouter } from '@/src/trpc/apis/user-apis/apis/complaint';
|
||||||
import { orderRouter } from '@/src/trpc/apis/user-apis/apis/order';
|
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 { userRouter as userDataRouter } from '@/src/trpc/apis/user-apis/apis/user';
|
||||||
import { userCouponRouter } from '@/src/trpc/apis/user-apis/apis/coupon';
|
import { userCouponRouter } from '@/src/trpc/apis/user-apis/apis/coupon';
|
||||||
import { paymentRouter } from '@/src/trpc/apis/user-apis/apis/payments';
|
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 { fileUploadRouter } from '@/src/trpc/apis/user-apis/apis/file-upload';
|
||||||
import { tagsRouter } from '@/src/trpc/apis/user-apis/apis/tags';
|
|
||||||
|
|
||||||
export const userRouter = router({
|
export const userRouter = router({
|
||||||
address: addressRouter,
|
address: addressRouter,
|
||||||
auth: authRouter,
|
auth: authRouter,
|
||||||
banner: bannerRouter,
|
|
||||||
cart: cartRouter,
|
cart: cartRouter,
|
||||||
complaint: complaintRouter,
|
complaint: complaintRouter,
|
||||||
order: orderRouter,
|
order: orderRouter,
|
||||||
|
|
@ -26,9 +22,7 @@ export const userRouter = router({
|
||||||
user: userDataRouter,
|
user: userDataRouter,
|
||||||
coupon: userCouponRouter,
|
coupon: userCouponRouter,
|
||||||
payment: paymentRouter,
|
payment: paymentRouter,
|
||||||
stores: storesRouter,
|
|
||||||
fileUpload: fileUploadRouter,
|
fileUpload: fileUploadRouter,
|
||||||
tags: tagsRouter,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type UserRouter = typeof userRouter;
|
export type UserRouter = typeof userRouter;
|
||||||
|
|
|
||||||
|
|
@ -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) => (
|
||||||
|
<ScrollView
|
||||||
|
horizontal
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
contentContainerStyle={tw`gap-3 py-1 px-1`}
|
||||||
|
>
|
||||||
|
{dashboardTags.map((tag) => (
|
||||||
|
<ExploreTab
|
||||||
|
key={tag.id}
|
||||||
|
tag={tag}
|
||||||
|
isSelected={activeTagId === tag.id}
|
||||||
|
onPress={() => onSelectTag(tag.id)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ScrollView>
|
||||||
|
));
|
||||||
|
|
||||||
interface ExploreProductItemProps {
|
interface ExploreProductItemProps {
|
||||||
item: any;
|
item: any;
|
||||||
onPress: (id: number) => void;
|
onPress: (id: number) => void;
|
||||||
|
|
@ -307,6 +330,7 @@ interface ListHeaderProps {
|
||||||
activeTagId: number | null;
|
activeTagId: number | null;
|
||||||
activeTagProducts: any[];
|
activeTagProducts: any[];
|
||||||
onSelectTag: (id: number) => void;
|
onSelectTag: (id: number) => void;
|
||||||
|
onTabsSectionLayout: (layout: { y: number; height: number }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListHeader = memo(({
|
const ListHeader = memo(({
|
||||||
|
|
@ -320,6 +344,7 @@ const ListHeader = memo(({
|
||||||
activeTagId,
|
activeTagId,
|
||||||
activeTagProducts,
|
activeTagProducts,
|
||||||
onSelectTag,
|
onSelectTag,
|
||||||
|
onTabsSectionLayout,
|
||||||
}: ListHeaderProps) => {
|
}: ListHeaderProps) => {
|
||||||
const [showAllActiveProducts, setShowAllActiveProducts] = useState(false);
|
const [showAllActiveProducts, setShowAllActiveProducts] = useState(false);
|
||||||
const handleLayout = useCallback((event: any) => {
|
const handleLayout = useCallback((event: any) => {
|
||||||
|
|
@ -360,25 +385,20 @@ const ListHeader = memo(({
|
||||||
|
|
||||||
{dashboardTags.length > 0 && (
|
{dashboardTags.length > 0 && (
|
||||||
<View
|
<View
|
||||||
|
onLayout={(event) => {
|
||||||
|
const { y, height } = event.nativeEvent.layout;
|
||||||
|
onTabsSectionLayout({ y, height });
|
||||||
|
}}
|
||||||
style={[
|
style={[
|
||||||
tw`mx-4 mb-2 rounded-[28px] px-3 pt-3 pb-2`,
|
tw`mx-4 mb-2 rounded-[28px] px-3 pt-3 pb-2`,
|
||||||
{ backgroundColor: pageTint },
|
{ backgroundColor: pageTint },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<ScrollView
|
<ExploreTabsRow
|
||||||
horizontal
|
dashboardTags={dashboardTags}
|
||||||
showsHorizontalScrollIndicator={false}
|
activeTagId={activeTagId}
|
||||||
contentContainerStyle={tw`gap-3 py-1 px-1`}
|
onSelectTag={onSelectTag}
|
||||||
>
|
|
||||||
{dashboardTags.map((tag) => (
|
|
||||||
<ExploreTab
|
|
||||||
key={tag.id}
|
|
||||||
tag={tag}
|
|
||||||
isSelected={activeTagId === tag.id}
|
|
||||||
onPress={() => onSelectTag(tag.id)}
|
|
||||||
/>
|
/>
|
||||||
))}
|
|
||||||
</ScrollView>
|
|
||||||
{activeTagProducts.length > 0 ? (
|
{activeTagProducts.length > 0 ? (
|
||||||
<View style={[tw`mt-4 relative`, { backgroundColor: pageTint }]}>
|
<View style={[tw`mt-4 relative`, { backgroundColor: pageTint }]}>
|
||||||
<View style={tw`flex-row flex-wrap justify-between`}>
|
<View style={tw`flex-row flex-wrap justify-between`}>
|
||||||
|
|
@ -515,6 +535,9 @@ export default function Dashboard() {
|
||||||
const [displayedProducts, setDisplayedProducts] = useState<any[]>([]);
|
const [displayedProducts, setDisplayedProducts] = useState<any[]>([]);
|
||||||
const [hasMore, setHasMore] = useState(true);
|
const [hasMore, setHasMore] = useState(true);
|
||||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
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 { getQuickestSlot } = useProductSlotIdentifier();
|
||||||
const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap);
|
const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap);
|
||||||
const refetchProducts = useCentralProductStore((state) => state.refetchProducts);
|
const refetchProducts = useCentralProductStore((state) => state.refetchProducts);
|
||||||
|
|
@ -657,6 +680,19 @@ export default function Dashboard() {
|
||||||
router.push("/(drawer)/(tabs)/home/search-results");
|
router.push("/(drawer)/(tabs)/home/search-results");
|
||||||
}, [router]);
|
}, [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 }) => (
|
const renderProductItem = useCallback(({ item }: { item: any }) => (
|
||||||
<ProductItem item={item} onPress={handleProductPress} />
|
<ProductItem item={item} onPress={handleProductPress} />
|
||||||
// <Image style={{ width: 150, height: 235 }} source={{ uri: item.images[0]}} />
|
// <Image style={{ width: 150, height: 235 }} source={{ uri: item.images[0]}} />
|
||||||
|
|
@ -674,8 +710,9 @@ export default function Dashboard() {
|
||||||
activeTagId={activeTagId}
|
activeTagId={activeTagId}
|
||||||
activeTagProducts={activeTagProducts}
|
activeTagProducts={activeTagProducts}
|
||||||
onSelectTag={setSelectedTagId}
|
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 pageTint = activeTagId == null ? '#FFFFFF' : getTagColor(activeTagId).pageBg;
|
||||||
const pageTintStyle = useMemo(() => [
|
const pageTintStyle = useMemo(() => [
|
||||||
|
|
@ -720,7 +757,10 @@ export default function Dashboard() {
|
||||||
return (
|
return (
|
||||||
<TabLayoutWrapper style={{ backgroundColor: pageTint }}>
|
<TabLayoutWrapper style={{ backgroundColor: pageTint }}>
|
||||||
<View style={pageTintStyle}>
|
<View style={pageTintStyle}>
|
||||||
<View style={searchBarContainerStyle}>
|
<View
|
||||||
|
style={searchBarContainerStyle}
|
||||||
|
onLayout={(event) => setSearchBarHeight(event.nativeEvent.layout.height)}
|
||||||
|
>
|
||||||
<SearchBar
|
<SearchBar
|
||||||
value={""}
|
value={""}
|
||||||
onChangeText={() => { }}
|
onChangeText={() => { }}
|
||||||
|
|
@ -741,6 +781,8 @@ export default function Dashboard() {
|
||||||
keyExtractor={(item) => item.id.toString()}
|
keyExtractor={(item) => item.id.toString()}
|
||||||
numColumns={2}
|
numColumns={2}
|
||||||
style={{ backgroundColor: pageTint }}
|
style={{ backgroundColor: pageTint }}
|
||||||
|
onScroll={handleListScroll}
|
||||||
|
scrollEventThrottle={16}
|
||||||
contentContainerStyle={listContentContainerStyle}
|
contentContainerStyle={listContentContainerStyle}
|
||||||
columnWrapperStyle={staticStyles.columnWrapper}
|
columnWrapperStyle={staticStyles.columnWrapper}
|
||||||
renderItem={renderProductItem}
|
renderItem={renderProductItem}
|
||||||
|
|
@ -765,6 +807,33 @@ export default function Dashboard() {
|
||||||
updateCellsBatchingPeriod={50}
|
updateCellsBatchingPeriod={50}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{showStickyTabs && dashboardTags.length > 0 && (
|
||||||
|
<View
|
||||||
|
pointerEvents="box-none"
|
||||||
|
style={[
|
||||||
|
tw`absolute left-0 right-0 z-20 px-4 pb-2`,
|
||||||
|
{
|
||||||
|
top: searchBarHeight,
|
||||||
|
backgroundColor: pageTint,
|
||||||
|
elevation: 8,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
tw`rounded-[28px] px-3 pt-2 pb-1`,
|
||||||
|
{ backgroundColor: pageTint },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<ExploreTabsRow
|
||||||
|
dashboardTags={dashboardTags}
|
||||||
|
activeTagId={activeTagId}
|
||||||
|
onSelectTag={setSelectedTagId}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
<LoadingDialog open={isLoadingDialogOpen} message="Adding to cart..." />
|
<LoadingDialog open={isLoadingDialogOpen} message="Adding to cart..." />
|
||||||
<AddToCartDialog />
|
<AddToCartDialog />
|
||||||
<View style={tw`absolute bottom-2 left-4 right-4`}>
|
<View style={tw`absolute bottom-2 left-4 right-4`}>
|
||||||
|
|
|
||||||
|
|
@ -202,18 +202,6 @@ export {
|
||||||
getActiveBanners as getUserActiveBanners,
|
getActiveBanners as getUserActiveBanners,
|
||||||
} from './src/user-apis/banners';
|
} 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 {
|
export {
|
||||||
// User Complaint
|
// User Complaint
|
||||||
getUserComplaints as getUserComplaints,
|
getUserComplaints as getUserComplaints,
|
||||||
|
|
@ -308,9 +296,6 @@ export {
|
||||||
getOrderBasic as getUserOrderBasic,
|
getOrderBasic as getUserOrderBasic,
|
||||||
cancelOrderTransaction as cancelUserOrderTransaction,
|
cancelOrderTransaction as cancelUserOrderTransaction,
|
||||||
updateOrderNotes as updateUserOrderNotes,
|
updateOrderNotes as updateUserOrderNotes,
|
||||||
getRecentlyDeliveredOrderIds as getUserRecentlyDeliveredOrderIds,
|
|
||||||
getProductIdsFromOrders as getUserProductIdsFromOrders,
|
|
||||||
getProductsForRecentOrders as getUserProductsForRecentOrders,
|
|
||||||
// Post-order handler helpers
|
// Post-order handler helpers
|
||||||
getOrdersByIdsWithFullData,
|
getOrdersByIdsWithFullData,
|
||||||
getOrderByIdWithFullData,
|
getOrderByIdWithFullData,
|
||||||
|
|
|
||||||
|
|
@ -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<UserCartItem[]> {
|
|
||||||
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<void> {
|
|
||||||
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<void> {
|
|
||||||
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<boolean> {
|
|
||||||
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<void> {
|
|
||||||
await db.delete(cartItems).where(eq(cartItems.userId, userId))
|
|
||||||
}
|
|
||||||
|
|
@ -14,11 +14,10 @@ import {
|
||||||
userDetails,
|
userDetails,
|
||||||
deliverySlotInfo,
|
deliverySlotInfo,
|
||||||
} from '../db/schema'
|
} from '../db/schema'
|
||||||
import { and, eq, inArray, desc, gte, lte } from 'drizzle-orm'
|
import { and, eq, inArray, desc, lte } from 'drizzle-orm'
|
||||||
import type {
|
import type {
|
||||||
UserOrderSummary,
|
UserOrderSummary,
|
||||||
UserOrderDetail,
|
UserOrderDetail,
|
||||||
UserRecentProduct,
|
|
||||||
} from '@packages/shared'
|
} from '@packages/shared'
|
||||||
|
|
||||||
export interface OrderItemInput {
|
export interface OrderItemInput {
|
||||||
|
|
@ -552,77 +551,6 @@ export async function updateOrderNotes(
|
||||||
.where(eq(orders.id, orderId))
|
.where(eq(orders.id, orderId))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRecentlyDeliveredOrderIds(
|
|
||||||
userId: number,
|
|
||||||
limit: number,
|
|
||||||
since: Date
|
|
||||||
): Promise<number[]> {
|
|
||||||
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<number[]> {
|
|
||||||
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<RecentProductData[]> {
|
|
||||||
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)
|
// Post-Order Handler Helpers (for Telegram notifications)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -201,18 +201,6 @@ export {
|
||||||
getActiveBanners as getUserActiveBanners,
|
getActiveBanners as getUserActiveBanners,
|
||||||
} from './src/user-apis/banners'
|
} 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 {
|
export {
|
||||||
// User Complaint
|
// User Complaint
|
||||||
getUserComplaints as getUserComplaints,
|
getUserComplaints as getUserComplaints,
|
||||||
|
|
@ -312,9 +300,6 @@ export {
|
||||||
getOrderBasic as getUserOrderBasic,
|
getOrderBasic as getUserOrderBasic,
|
||||||
cancelOrderTransaction as cancelUserOrderTransaction,
|
cancelOrderTransaction as cancelUserOrderTransaction,
|
||||||
updateOrderNotes as updateUserOrderNotes,
|
updateOrderNotes as updateUserOrderNotes,
|
||||||
getRecentlyDeliveredOrderIds as getUserRecentlyDeliveredOrderIds,
|
|
||||||
getSkuIdsFromOrders as getUserProductIdsFromOrders,
|
|
||||||
getProductsForRecentOrders as getUserProductsForRecentOrders,
|
|
||||||
// Post-order handler helpers
|
// Post-order handler helpers
|
||||||
getOrdersByIdsWithFullData,
|
getOrdersByIdsWithFullData,
|
||||||
getOrderByIdWithFullData,
|
getOrderByIdWithFullData,
|
||||||
|
|
|
||||||
|
|
@ -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<UserCartItem[]> {
|
|
||||||
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<void> {
|
|
||||||
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<void> {
|
|
||||||
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<boolean> {
|
|
||||||
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<void> {
|
|
||||||
await db.delete(cartItems).where(eq(cartItems.userId, userId))
|
|
||||||
}
|
|
||||||
|
|
@ -15,11 +15,10 @@ import {
|
||||||
userDetails,
|
userDetails,
|
||||||
deliverySlotInfo,
|
deliverySlotInfo,
|
||||||
} from '../db/schema'
|
} from '../db/schema'
|
||||||
import { and, eq, inArray, desc, gte, sql } from 'drizzle-orm'
|
import { and, eq, inArray, desc, sql } from 'drizzle-orm'
|
||||||
import type {
|
import type {
|
||||||
UserOrderSummary,
|
UserOrderSummary,
|
||||||
UserOrderDetail,
|
UserOrderDetail,
|
||||||
UserRecentProduct,
|
|
||||||
} from '@packages/shared'
|
} from '@packages/shared'
|
||||||
import { coerceDate } from '../lib/date'
|
import { coerceDate } from '../lib/date'
|
||||||
import { runBatched } from '../lib/run-batched'
|
import { runBatched } from '../lib/run-batched'
|
||||||
|
|
@ -611,93 +610,6 @@ export async function updateOrderNotes(
|
||||||
.where(eq(orders.id, orderId))
|
.where(eq(orders.id, orderId))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRecentlyDeliveredOrderIds(
|
|
||||||
userId: number,
|
|
||||||
limit: number,
|
|
||||||
since: Date
|
|
||||||
): Promise<number[]> {
|
|
||||||
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<number[]> {
|
|
||||||
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<RecentProductData[]> {
|
|
||||||
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)
|
// Post-Order Handler Helpers (for Telegram notifications)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -133,32 +133,6 @@ export interface UserBannersResponse {
|
||||||
banners: UserBanner[];
|
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 {
|
export interface UserComplaint {
|
||||||
id: number;
|
id: number;
|
||||||
complaintBody: string;
|
complaintBody: string;
|
||||||
|
|
@ -367,13 +341,6 @@ export interface UserSlotWithProducts {
|
||||||
products: UserSlotProduct[];
|
products: UserSlotProduct[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserSlotData {
|
|
||||||
slotId: number;
|
|
||||||
deliveryTime: Date;
|
|
||||||
freezeTime: Date;
|
|
||||||
products: UserSlotProduct[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserSlotAvailability {
|
export interface UserSlotAvailability {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -464,16 +431,6 @@ export interface UserPasswordUpdateResponse {
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserProfileResponse {
|
|
||||||
success: boolean;
|
|
||||||
data: {
|
|
||||||
id: number;
|
|
||||||
name: string | null;
|
|
||||||
email: string | null;
|
|
||||||
mobile: string | null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserDeleteAccountResponse {
|
export interface UserDeleteAccountResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
message: string;
|
message: string;
|
||||||
|
|
@ -638,20 +595,3 @@ export interface UserUpdateNotesResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
message: string;
|
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[];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue