Compare commits
2 commits
cd5905d40f
...
2929e7725a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2929e7725a | ||
|
|
46f5fa180c |
15 changed files with 232 additions and 85 deletions
|
|
@ -19,7 +19,7 @@ import { seed } from 'src/db/seed';
|
||||||
import './src/jobs/jobs-index';
|
import './src/jobs/jobs-index';
|
||||||
import { startAutomatedJobs } from './src/lib/automatedJobs';
|
import { startAutomatedJobs } from './src/lib/automatedJobs';
|
||||||
|
|
||||||
seed()
|
// seed()
|
||||||
initFunc()
|
initFunc()
|
||||||
startAutomatedJobs()
|
startAutomatedJobs()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,12 @@ interface SlotWithProducts {
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SlotInfo {
|
||||||
|
id: number;
|
||||||
|
deliveryTime: Date;
|
||||||
|
freezeTime: Date;
|
||||||
|
}
|
||||||
|
|
||||||
export async function initializeSlotStore(): Promise<void> {
|
export async function initializeSlotStore(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log('Initializing slot store in Redis...');
|
console.log('Initializing slot store in Redis...');
|
||||||
|
|
@ -82,6 +88,28 @@ export async function initializeSlotStore(): Promise<void> {
|
||||||
await redisClient.set(`slot:${slot.id}`, JSON.stringify(slot));
|
await redisClient.set(`slot:${slot.id}`, JSON.stringify(slot));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build and store product-slots map
|
||||||
|
// Group slots by productId
|
||||||
|
const productSlotsMap: Record<number, SlotInfo[]> = {};
|
||||||
|
|
||||||
|
for (const slot of slotsWithProducts) {
|
||||||
|
for (const product of slot.products) {
|
||||||
|
if (!productSlotsMap[product.id]) {
|
||||||
|
productSlotsMap[product.id] = [];
|
||||||
|
}
|
||||||
|
productSlotsMap[product.id].push({
|
||||||
|
id: slot.id,
|
||||||
|
deliveryTime: slot.deliveryTime,
|
||||||
|
freezeTime: slot.freezeTime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store each product's slots in Redis with key pattern "product:{id}:slots"
|
||||||
|
for (const [productId, slotInfos] of Object.entries(productSlotsMap)) {
|
||||||
|
await redisClient.set(`product:${productId}:slots`, JSON.stringify(slotInfos));
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Slot store initialized successfully');
|
console.log('Slot store initialized successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error initializing slot store:', error);
|
console.error('Error initializing slot store:', error);
|
||||||
|
|
@ -123,3 +151,70 @@ export async function getAllSlots(): Promise<SlotWithProducts[]> {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getProductSlots(productId: number): Promise<SlotInfo[]> {
|
||||||
|
try {
|
||||||
|
const key = `product:${productId}:slots`;
|
||||||
|
const data = await redisClient.get(key);
|
||||||
|
if (!data) return [];
|
||||||
|
return JSON.parse(data) as SlotInfo[];
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error getting slots for product ${productId}:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllProductsSlots(): Promise<Record<number, SlotInfo[]>> {
|
||||||
|
try {
|
||||||
|
// Get all keys matching the pattern "product:*:slots"
|
||||||
|
const keys = await redisClient.KEYS('product:*:slots');
|
||||||
|
|
||||||
|
if (keys.length === 0) return {};
|
||||||
|
|
||||||
|
// Get all product slots using MGET for better performance
|
||||||
|
const productsData = await redisClient.MGET(keys);
|
||||||
|
|
||||||
|
const result: Record<number, SlotInfo[]> = {};
|
||||||
|
for (const key of keys) {
|
||||||
|
// Extract productId from key "product:{id}:slots"
|
||||||
|
const match = key.match(/product:(\d+):slots/);
|
||||||
|
if (match) {
|
||||||
|
const productId = parseInt(match[1], 10);
|
||||||
|
const dataIndex = keys.indexOf(key);
|
||||||
|
if (productsData[dataIndex]) {
|
||||||
|
result[productId] = JSON.parse(productsData[dataIndex]) as SlotInfo[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting all products slots:', error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMultipleProductsSlots(productIds: number[]): Promise<Record<number, SlotInfo[]>> {
|
||||||
|
try {
|
||||||
|
if (productIds.length === 0) return {};
|
||||||
|
|
||||||
|
// Build keys for all productIds
|
||||||
|
const keys = productIds.map(id => `product:${id}:slots`);
|
||||||
|
|
||||||
|
// Use MGET for batch retrieval
|
||||||
|
const productsData = await redisClient.MGET(keys);
|
||||||
|
|
||||||
|
const result: Record<number, SlotInfo[]> = {};
|
||||||
|
for (let i = 0; i < productIds.length; i++) {
|
||||||
|
const data = productsData[i];
|
||||||
|
if (data) {
|
||||||
|
result[productIds[i]] = JSON.parse(data) as SlotInfo[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting products slots:', error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { router, publicProcedure, protectedProcedure } from '../trpc-index';
|
import { router, publicProcedure, protectedProcedure } from '../trpc-index';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { db } from '../../db/db_index';
|
import { db } from '../../db/db_index';
|
||||||
import { vendorSnippets, deliverySlotInfo, productInfo, orders, orderItems, users } from '../../db/schema';
|
import { vendorSnippets, deliverySlotInfo, productInfo, orders, orderItems, users, orderStatus } from '../../db/schema';
|
||||||
import { eq, and, inArray, isNotNull, gt, sql, asc } from 'drizzle-orm';
|
import { eq, and, inArray, isNotNull, gt, sql, asc, ne } from 'drizzle-orm';
|
||||||
import { appUrl } from '../../lib/env-exporter';
|
import { appUrl } from '../../lib/env-exporter';
|
||||||
|
|
||||||
const createSnippetSchema = z.object({
|
const createSnippetSchema = z.object({
|
||||||
|
|
@ -225,10 +225,7 @@ export const vendorSnippetsRouter = router({
|
||||||
|
|
||||||
// Query orders that match the snippet criteria
|
// Query orders that match the snippet criteria
|
||||||
const matchingOrders = await db.query.orders.findMany({
|
const matchingOrders = await db.query.orders.findMany({
|
||||||
where: and(
|
where: eq(orders.slotId, snippet.slotId!),
|
||||||
eq(orders.slotId, snippet.slotId!),
|
|
||||||
// We'll filter by products in the application logic
|
|
||||||
),
|
|
||||||
with: {
|
with: {
|
||||||
orderItems: {
|
orderItems: {
|
||||||
with: {
|
with: {
|
||||||
|
|
@ -239,6 +236,7 @@ export const vendorSnippetsRouter = router({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
orderStatus: true,
|
||||||
user: true,
|
user: true,
|
||||||
slot: true,
|
slot: true,
|
||||||
},
|
},
|
||||||
|
|
@ -247,6 +245,8 @@ export const vendorSnippetsRouter = router({
|
||||||
|
|
||||||
// Filter orders that contain at least one of the snippet's products
|
// Filter orders that contain at least one of the snippet's products
|
||||||
const filteredOrders = matchingOrders.filter(order => {
|
const filteredOrders = matchingOrders.filter(order => {
|
||||||
|
const status = order.orderStatus;
|
||||||
|
if (status[0].isCancelled) return false;
|
||||||
const orderProductIds = order.orderItems.map(item => item.productId);
|
const orderProductIds = order.orderItems.map(item => item.productId);
|
||||||
return snippet.productIds.some(productId => orderProductIds.includes(productId));
|
return snippet.productIds.some(productId => orderProductIds.includes(productId));
|
||||||
});
|
});
|
||||||
|
|
@ -271,11 +271,13 @@ export const vendorSnippetsRouter = router({
|
||||||
is_package_verified: item.is_package_verified,
|
is_package_verified: item.is_package_verified,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
orderId: `ORD${order.readableId.toString().padStart(3, '0')}`,
|
orderId: `ORD${order.readableId.toString().padStart(3, '0')}`,
|
||||||
orderDate: order.createdAt.toISOString(),
|
orderDate: order.createdAt.toISOString(),
|
||||||
customerName: order.user.name,
|
customerName: order.user.name,
|
||||||
totalAmount: order.totalAmount,
|
totalAmount: orderTotal,
|
||||||
slotInfo: order.slot ? {
|
slotInfo: order.slot ? {
|
||||||
time: order.slot.deliveryTime.toISOString(),
|
time: order.slot.deliveryTime.toISOString(),
|
||||||
sequence: order.slot.deliverySequence,
|
sequence: order.slot.deliverySequence,
|
||||||
|
|
@ -393,6 +395,7 @@ export const vendorSnippetsRouter = router({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
orderStatus: true,
|
||||||
user: true,
|
user: true,
|
||||||
slot: true,
|
slot: true,
|
||||||
},
|
},
|
||||||
|
|
@ -401,6 +404,8 @@ export const vendorSnippetsRouter = router({
|
||||||
|
|
||||||
// Filter orders that contain at least one of the snippet's products
|
// Filter orders that contain at least one of the snippet's products
|
||||||
const filteredOrders = matchingOrders.filter(order => {
|
const filteredOrders = matchingOrders.filter(order => {
|
||||||
|
const status = order.orderStatus;
|
||||||
|
if (status[0]?.isCancelled) return false;
|
||||||
const orderProductIds = order.orderItems.map(item => item.productId);
|
const orderProductIds = order.orderItems.map(item => item.productId);
|
||||||
return snippet.productIds.some(productId => orderProductIds.includes(productId));
|
return snippet.productIds.some(productId => orderProductIds.includes(productId));
|
||||||
});
|
});
|
||||||
|
|
@ -425,11 +430,13 @@ export const vendorSnippetsRouter = router({
|
||||||
is_package_verified: item.is_package_verified,
|
is_package_verified: item.is_package_verified,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
orderId: `ORD${order.readableId.toString().padStart(3, '0')}`,
|
orderId: `ORD${order.readableId.toString().padStart(3, '0')}`,
|
||||||
orderDate: order.createdAt.toISOString(),
|
orderDate: order.createdAt.toISOString(),
|
||||||
customerName: order.user.name,
|
customerName: order.user.name,
|
||||||
totalAmount: order.totalAmount,
|
totalAmount: orderTotal,
|
||||||
slotInfo: order.slot ? {
|
slotInfo: order.slot ? {
|
||||||
time: order.slot.deliveryTime.toISOString(),
|
time: order.slot.deliveryTime.toISOString(),
|
||||||
sequence: order.slot.deliverySequence,
|
sequence: order.slot.deliverySequence,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { cartItems, productInfo, units, productSlots, deliverySlotInfo } from '.
|
||||||
import { eq, and, sql, inArray, gt } from 'drizzle-orm';
|
import { eq, and, sql, inArray, gt } from 'drizzle-orm';
|
||||||
import { ApiError } from '../../lib/api-error';
|
import { ApiError } from '../../lib/api-error';
|
||||||
import { generateSignedUrlsFromS3Urls } from '../../lib/s3-client';
|
import { generateSignedUrlsFromS3Urls } from '../../lib/s3-client';
|
||||||
|
import { getProductSlots, getMultipleProductsSlots } from '../../stores/slot-store';
|
||||||
|
|
||||||
interface CartResponse {
|
interface CartResponse {
|
||||||
items: any[];
|
items: any[];
|
||||||
|
|
@ -181,6 +182,52 @@ export const cartRouter = router({
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// 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
|
||||||
getCartSlots: publicProcedure
|
getCartSlots: publicProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
productIds: z.array(z.number().int().positive())
|
productIds: z.array(z.number().int().positive())
|
||||||
|
|
@ -192,36 +239,6 @@ export const cartRouter = router({
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get slots for these products where freeze time is after current time
|
return await getMultipleProductsSlots(productIds);
|
||||||
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;
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
@ -712,7 +712,7 @@ export const orderRouter = router({
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
// id: z.string().regex(/^ORD\d+$/, "Invalid order ID format"),
|
// id: z.string().regex(/^ORD\d+$/, "Invalid order ID format"),
|
||||||
id: z.string(),
|
id: z.number(),
|
||||||
reason: z.string().min(1, "Cancellation reason is required"),
|
reason: z.string().min(1, "Cancellation reason is required"),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
@ -723,6 +723,9 @@ export const orderRouter = router({
|
||||||
|
|
||||||
const readableId = Number(id);
|
const readableId = Number(id);
|
||||||
|
|
||||||
|
console.log({id, reason})
|
||||||
|
|
||||||
|
|
||||||
// Check if order exists and belongs to user
|
// Check if order exists and belongs to user
|
||||||
const order = await db.query.orders.findFirst({
|
const order = await db.query.orders.findFirst({
|
||||||
where: eq(orders.id, Number(id)),
|
where: eq(orders.id, Number(id)),
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import { useUserDetails } from "@/src/contexts/AuthContext";
|
||||||
import TabLayoutWrapper from "@/components/TabLayoutWrapper";
|
import TabLayoutWrapper from "@/components/TabLayoutWrapper";
|
||||||
import { useNavigationStore } from "@/src/store/navigationStore";
|
import { useNavigationStore } from "@/src/store/navigationStore";
|
||||||
import { useGetEssentialConsts } from "@/src/api-hooks/essential-consts.api";
|
import { useGetEssentialConsts } from "@/src/api-hooks/essential-consts.api";
|
||||||
|
import NextOrderGlimpse from "@/components/NextOrderGlimpse";
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
// import { StatusBar } from "expo-status-bar";
|
// import { StatusBar } from "expo-status-bar";
|
||||||
|
|
||||||
|
|
@ -362,6 +363,10 @@ export default function Dashboard() {
|
||||||
setWhiteSectionLayout({ y });
|
setWhiteSectionLayout({ y });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
<View style={tw`py-2`}>
|
||||||
|
<NextOrderGlimpse />
|
||||||
|
</View>
|
||||||
{/* Section Title */}
|
{/* Section Title */}
|
||||||
<View style={tw`mb-4 pt-2 px-1`}>
|
<View style={tw`mb-4 pt-2 px-1`}>
|
||||||
<MyText style={tw`text-2xl font-extrabold text-gray-900 tracking-tight`}>
|
<MyText style={tw`text-2xl font-extrabold text-gray-900 tracking-tight`}>
|
||||||
|
|
@ -491,7 +496,7 @@ export default function Dashboard() {
|
||||||
<MyText
|
<MyText
|
||||||
style={tw`text-[11px] font-bold text-slate-500`}
|
style={tw`text-[11px] font-bold text-slate-500`}
|
||||||
>
|
>
|
||||||
Delivery Slot
|
{dayjs(slot.deliveryTime).format("ddd, MMM DD")}
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,28 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, ScrollView } from 'react-native';
|
import { View, ScrollView } from 'react-native';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
|
import { useFocusEffect } from '@react-navigation/native';
|
||||||
import { AppContainer, MyText, tw, MyTouchableOpacity, ProfileImage, colors } from 'common-ui';
|
import { AppContainer, MyText, tw, MyTouchableOpacity, ProfileImage, colors } from 'common-ui';
|
||||||
import TabLayoutWrapper from '@/components/TabLayoutWrapper';
|
import TabLayoutWrapper from '@/components/TabLayoutWrapper';
|
||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import NextOrderGlimpse from '@/components/NextOrderGlimpse';
|
import NextOrderGlimpse from '@/components/NextOrderGlimpse';
|
||||||
import { useAuth } from '@/src/contexts/AuthContext';
|
import { useAuth } from '@/src/contexts/AuthContext';
|
||||||
|
import { useNavigationTarget } from 'common-ui/hooks/useNavigationTarget';
|
||||||
|
|
||||||
export default function Me() {
|
export default function Me() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { logout } = useAuth();
|
const { logout } = useAuth();
|
||||||
|
const { getNavigationTarget } = useNavigationTarget();
|
||||||
|
|
||||||
|
useFocusEffect(() => {
|
||||||
|
const target = getNavigationTarget();
|
||||||
|
console.log({target})
|
||||||
|
|
||||||
|
if (target) {
|
||||||
|
router.replace(target as any);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const menuSections = [
|
const menuSections = [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@ export default function OrderDetails() {
|
||||||
Alert.alert('Success', 'Order cancelled successfully');
|
Alert.alert('Success', 'Order cancelled successfully');
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
|
console.log({error})
|
||||||
|
|
||||||
Alert.alert('Error', error.message || 'Failed to cancel order');
|
Alert.alert('Error', error.message || 'Failed to cancel order');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -59,7 +61,7 @@ export default function OrderDetails() {
|
||||||
Alert.alert('Error', 'Please enter a reason for cancellation');
|
Alert.alert('Error', 'Please enter a reason for cancellation');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cancelOrderMutation.mutate({ id: order.orderId, reason: cancelReason });
|
cancelOrderMutation.mutate({ id: order.id, reason: cancelReason });
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -126,6 +128,12 @@ export default function OrderDetails() {
|
||||||
{/* Simple Header */}
|
{/* Simple Header */}
|
||||||
<View style={tw`bg-white px-6 pt-4 pb-4 border-b border-slate-100 flex-row items-center justify-between`}>
|
<View style={tw`bg-white px-6 pt-4 pb-4 border-b border-slate-100 flex-row items-center justify-between`}>
|
||||||
<View style={tw`flex-row items-center`}>
|
<View style={tw`flex-row items-center`}>
|
||||||
|
<MyTouchableOpacity
|
||||||
|
onPress={() => router.back()}
|
||||||
|
style={tw`mr-3 p-2 -ml-2`}
|
||||||
|
>
|
||||||
|
<Ionicons name="chevron-back" size={24} color="#1E293B" />
|
||||||
|
</MyTouchableOpacity>
|
||||||
<View>
|
<View>
|
||||||
<MyText style={tw`text-slate-900 font-bold text-lg`}>Order #{order.orderId}</MyText>
|
<MyText style={tw`text-slate-900 font-bold text-lg`}>Order #{order.orderId}</MyText>
|
||||||
<MyText style={tw`text-slate-400 text-xs`}>{dayjs(order.orderDate).format("DD MMM, h:mm A")}</MyText>
|
<MyText style={tw`text-slate-400 text-xs`}>{dayjs(order.orderDate).format("DD MMM, h:mm A")}</MyText>
|
||||||
|
|
@ -342,10 +350,10 @@ export default function OrderDetails() {
|
||||||
{/* Footer Actions */}
|
{/* Footer Actions */}
|
||||||
<View style={tw`flex-row gap-3`}>
|
<View style={tw`flex-row gap-3`}>
|
||||||
<MyTouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={() => router.back()}
|
onPress={() => router.replace('/(drawer)/(tabs)/me')}
|
||||||
style={tw`flex-1 bg-slate-100 py-3.5 rounded-xl items-center`}
|
style={tw`flex-1 bg-slate-100 py-3.5 rounded-xl items-center`}
|
||||||
>
|
>
|
||||||
<MyText style={tw`text-slate-600 font-bold`}>Dismiss</MyText>
|
<MyText style={tw`text-slate-600 font-bold`}>Back</MyText>
|
||||||
</MyTouchableOpacity>
|
</MyTouchableOpacity>
|
||||||
<MyTouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={() => setComplaintDialogOpen(true)}
|
onPress={() => setComplaintDialogOpen(true)}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import { View, TouchableOpacity, ActivityIndicator } from 'react-native';
|
import { View, TouchableOpacity, ActivityIndicator } from 'react-native';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import { tw, MyText } from 'common-ui';
|
import { tw, MyText } from 'common-ui';
|
||||||
|
import { useNavigationTarget } from 'common-ui/hooks/useNavigationTarget';
|
||||||
import { MaterialIcons, Ionicons } from '@expo/vector-icons';
|
import { MaterialIcons, Ionicons } from '@expo/vector-icons';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '@/src/trpc-client';
|
||||||
|
|
@ -38,6 +39,7 @@ interface Order {
|
||||||
|
|
||||||
export default function NextOrderGlimpse() {
|
export default function NextOrderGlimpse() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { setNavigationTarget } = useNavigationTarget();
|
||||||
|
|
||||||
const { data: ordersData, isLoading: ordersLoading } = trpc.user.order.getOrders.useQuery({
|
const { data: ordersData, isLoading: ordersLoading } = trpc.user.order.getOrders.useQuery({
|
||||||
page: 1,
|
page: 1,
|
||||||
|
|
@ -97,7 +99,12 @@ export default function NextOrderGlimpse() {
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={tw`px-6 mb-4`}
|
style={tw`px-6 mb-4`}
|
||||||
onPress={() => router.push(`/(drawer)/(tabs)/me/my-orders/${nextOrder.id}`)}
|
onPress={() => {
|
||||||
|
console.log('from next press')
|
||||||
|
|
||||||
|
setNavigationTarget(`/(drawer)/(tabs)/me/my-orders/${nextOrder.id}`);
|
||||||
|
router.replace('/(drawer)/(tabs)/me');
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<View style={tw`bg-gradient-to-r from-amber-50 to-orange-50 rounded-2xl p-4 border-2 border-amber-300`}>
|
<View style={tw`bg-gradient-to-r from-amber-50 to-orange-50 rounded-2xl p-4 border-2 border-amber-300`}>
|
||||||
<View style={tw`flex-row items-center justify-between mb-3`}>
|
<View style={tw`flex-row items-center justify-between mb-3`}>
|
||||||
|
|
|
||||||
|
|
@ -161,37 +161,6 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// Process slots: flatten and unique
|
|
||||||
const availableSlots = React.useMemo(() => {
|
|
||||||
if (!slotsData) return [];
|
|
||||||
const allSlots = Object.values(slotsData).flat();
|
|
||||||
const uniqueSlots = allSlots.filter(
|
|
||||||
(slot, index, self) => index === self.findIndex((s) => s.id === slot.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Smart time window formatting function
|
|
||||||
const formatTimeRange = (deliveryTime: string) => {
|
|
||||||
const time = dayjs(deliveryTime);
|
|
||||||
const endTime = time.add(1, 'hour');
|
|
||||||
const startPeriod = time.format('A');
|
|
||||||
const endPeriod = endTime.format('A');
|
|
||||||
|
|
||||||
let timeRange;
|
|
||||||
if (startPeriod === endPeriod) {
|
|
||||||
timeRange = `${time.format('h')}-${endTime.format('h')} ${startPeriod}`;
|
|
||||||
} else {
|
|
||||||
timeRange = `${time.format('h:mm')} ${startPeriod} - ${endTime.format('h:mm')} ${endPeriod}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${time.format('ddd, DD MMM ')}${timeRange}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
return uniqueSlots.map((slot) => ({
|
|
||||||
label: `Delivery: ${formatTimeRange(slot.deliveryTime)} - Close time: ${dayjs(slot.freezeTime).format("h:mm a")}`,
|
|
||||||
value: slot.id,
|
|
||||||
}));
|
|
||||||
}, [slotsData]);
|
|
||||||
|
|
||||||
// Get available slots for a specific product
|
// Get available slots for a specific product
|
||||||
const getAvailableSlotsForProduct = React.useMemo(() => {
|
const getAvailableSlotsForProduct = React.useMemo(() => {
|
||||||
return (productId: number) => {
|
return (productId: number) => {
|
||||||
|
|
@ -330,7 +299,6 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (upcomingSlots.length > 0) {
|
if (upcomingSlots.length > 0) {
|
||||||
console.log({upcomingSlots, existingSlotId, cartSlotIds})
|
|
||||||
|
|
||||||
if (existingSlotId) {
|
if (existingSlotId) {
|
||||||
const slotStillValid = upcomingSlots.some(slot => slot.id === existingSlotId);
|
const slotStillValid = upcomingSlots.some(slot => slot.id === existingSlotId);
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,6 @@ export function useAuthenticatedRoute(options: AuthenticatedRouteOptions = {}) {
|
||||||
const { isAuthenticated, isLoading } = useAuth();
|
const { isAuthenticated, isLoading } = useAuth();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
console.log({ops: options.queryParams})
|
|
||||||
|
|
||||||
useFocusEffect(() => {
|
useFocusEffect(() => {
|
||||||
// Don't redirect while auth is loading
|
// Don't redirect while auth is loading
|
||||||
if (isLoading) return;
|
if (isLoading) return;
|
||||||
|
|
|
||||||
3
package-lock.json
generated
3
package-lock.json
generated
|
|
@ -24388,7 +24388,8 @@
|
||||||
"react-native-web": "~0.20.0",
|
"react-native-web": "~0.20.0",
|
||||||
"react-native-webview": "13.13.5",
|
"react-native-webview": "13.13.5",
|
||||||
"twrnc": "^4.9.1",
|
"twrnc": "^4.9.1",
|
||||||
"yup": "^1.7.0"
|
"yup": "^1.7.0",
|
||||||
|
"zustand": "^5.0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/ui/node_modules/buffer": {
|
"packages/ui/node_modules/buffer": {
|
||||||
|
|
|
||||||
25
packages/ui/hooks/useNavigationTarget.ts
Normal file
25
packages/ui/hooks/useNavigationTarget.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
interface NavigationTargetState {
|
||||||
|
navigationTarget: string | null;
|
||||||
|
setNavigationTarget: (target: string | null) => void;
|
||||||
|
getNavigationTarget: () => string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useNavigationTargetStore = create<NavigationTargetState>((set, get) => ({
|
||||||
|
navigationTarget: null,
|
||||||
|
setNavigationTarget: (target: string | null) => {
|
||||||
|
set({ navigationTarget: target })
|
||||||
|
},
|
||||||
|
getNavigationTarget: () => {
|
||||||
|
const target = get().navigationTarget;
|
||||||
|
if (target) {
|
||||||
|
set({ navigationTarget: null });
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export function useNavigationTarget() {
|
||||||
|
return useNavigationTargetStore();
|
||||||
|
}
|
||||||
|
|
@ -64,8 +64,8 @@ const isDevMode = Constants.executionEnvironment !== "standalone";
|
||||||
// const BASE_API_URL = 'http://10.0.2.2:4000';
|
// const BASE_API_URL = 'http://10.0.2.2:4000';
|
||||||
// const BASE_API_URL = 'http://192.168.100.101:4000';
|
// const BASE_API_URL = 'http://192.168.100.101:4000';
|
||||||
// const BASE_API_URL = 'http://192.168.1.14:4000';
|
// const BASE_API_URL = 'http://192.168.1.14:4000';
|
||||||
let BASE_API_URL = "https://mf.freshyo.in";
|
// let BASE_API_URL = "https://mf.freshyo.in";
|
||||||
// let BASE_API_URL = 'http://192.168.100.103:4000';
|
let BASE_API_URL = 'http://192.168.100.103:4000';
|
||||||
// let BASE_API_URL = 'http://192.168.29.219:4000';
|
// let BASE_API_URL = 'http://192.168.29.219:4000';
|
||||||
|
|
||||||
// if(isDevMode) {
|
// if(isDevMode) {
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@
|
||||||
"react-native-web": "~0.20.0",
|
"react-native-web": "~0.20.0",
|
||||||
"react-native-webview": "13.13.5",
|
"react-native-webview": "13.13.5",
|
||||||
"twrnc": "^4.9.1",
|
"twrnc": "^4.9.1",
|
||||||
"yup": "^1.7.0"
|
"yup": "^1.7.0",
|
||||||
|
"zustand": "^5.0.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue