This commit is contained in:
shafi54 2026-03-14 17:25:41 +05:30
parent 5df040de9a
commit 2d37726c62
17 changed files with 223 additions and 203 deletions

View file

@ -1,6 +1,6 @@
ENV_MODE=PROD
# DATABASE_URL=postgresql://postgres:meatfarmer_master_password@57.128.212.174:7447/meatfarmer #technocracy
DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
DATABASE_URL=postgresql://postgres:meatfarmer_master_password@57.128.212.174:7447/meatfarmer #technocracy
# DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
PHONE_PE_CLIENT_VERSION=1
@ -22,7 +22,8 @@ EXPO_ACCESS_TOKEN=Asvpy8cByRh6T4ksnWScO6PLcio2n35-BwES5zK-
JWT_SECRET=my_meatfarmer_jwt_secret_key
ASSETS_DOMAIN=https://assets.freshyo.in/
API_CACHE_KEY=api-cache-dev
CLOUDFLARE_API_TOKEN=I8Vp4E9TX58E8qEDeH0nTFDS2d2zXNYiXvbs4Ckj
# CLOUDFLARE_API_TOKEN=I8Vp4E9TX58E8qEDeH0nTFDS2d2zXNYiXvbs4Ckj
CLOUDFLARE_API_TOKEN=N7jAg5X-RUj_fVfMW6zbfJ8qIYc81TSIKKlbZ6oh
CLOUDFLARE_ZONE_ID=edefbf750bfc3ff26ccd11e8e28dc8d7
# REDIS_URL=redis://default:redis_shafi_password@5.223.55.14:6379
REDIS_URL=redis://default:redis_shafi_password@57.128.212.174:6379

File diff suppressed because one or more lines are too long

View file

@ -6,7 +6,7 @@ import { ApiError } from "@/src/lib/api-error";
import { imageUploadS3, generateSignedUrlFromS3Url } from "@/src/lib/s3-client";
import { deleteS3Image } from "@/src/lib/delete-image";
import { initializeAllStores } from '@/src/stores/store-initializer';
import { createStoresFile } from '@/src/lib/cloud_cache';
/**
* Create a new product tag
@ -66,11 +66,6 @@ export const createTag = async (req: Request, res: Response) => {
tag: newTag,
message: "Tag created successfully",
});
// Then regenerate stores cache (fire-and-forget)
createStoresFile().catch(error => {
console.error('Failed to regenerate stores cache after tag creation:', error)
})
};
/**
@ -191,11 +186,6 @@ export const updateTag = async (req: Request, res: Response) => {
tag: updatedTag,
message: "Tag updated successfully",
});
// Then regenerate stores cache (fire-and-forget)
createStoresFile().catch(error => {
console.error('Failed to regenerate stores cache after tag update:', error)
})
};
/**
@ -233,9 +223,4 @@ export const deleteTag = async (req: Request, res: Response) => {
res.status(200).json({
message: "Tag deleted successfully",
});
// Then regenerate stores cache (fire-and-forget)
createStoresFile().catch(error => {
console.error('Failed to regenerate stores cache after tag deletion:', error)
})
};

View file

@ -7,7 +7,7 @@ import { imageUploadS3, getOriginalUrlFromSignedUrl } from "@/src/lib/s3-client"
import { deleteS3Image } from "@/src/lib/delete-image";
import type { SpecialDeal } from "@/src/db/types";
import { initializeAllStores } from '@/src/stores/store-initializer';
import { createProductsFile } from '@/src/lib/cloud_cache';
type CreateDeal = {
quantity: number;
@ -117,11 +117,6 @@ export const createProduct = async (req: Request, res: Response) => {
deals: createdDeals,
message: "Product created successfully",
});
// Then regenerate products cache (fire-and-forget)
createProductsFile().catch(error => {
console.error('Failed to regenerate products cache after create:', error)
})
};
/**
@ -308,9 +303,4 @@ export const updateProduct = async (req: Request, res: Response) => {
product: updatedProduct,
message: "Product updated successfully",
});
// Then regenerate products cache (fire-and-forget)
createProductsFile().catch(error => {
console.error('Failed to regenerate products cache after update:', error)
})
};

View file

@ -193,6 +193,117 @@ export async function createAllStoresFiles(): Promise<string[]> {
return results
}
export interface CreateAllCacheFilesResult {
products: string
essentialConsts: string
stores: string
slots: string
banners: string
individualStores: string[]
}
export async function createAllCacheFiles(): Promise<CreateAllCacheFilesResult> {
console.log('Starting creation of all cache files...')
// Create all global cache files in parallel
const [
productsKey,
essentialConstsKey,
storesKey,
slotsKey,
bannersKey,
individualStoreKeys,
] = await Promise.all([
createProductsFileInternal(),
createEssentialConstsFileInternal(),
createStoresFileInternal(),
createSlotsFileInternal(),
createBannersFileInternal(),
createAllStoresFilesInternal(),
])
// Collect all URLs for batch cache purge
const urls = [
constructCacheUrl(CACHE_FILENAMES.products),
constructCacheUrl(CACHE_FILENAMES.essentialConsts),
constructCacheUrl(CACHE_FILENAMES.stores),
constructCacheUrl(CACHE_FILENAMES.slots),
constructCacheUrl(CACHE_FILENAMES.banners),
...individualStoreKeys.map((_, index) => constructCacheUrl(`stores/${index + 1}.json`)),
]
// Purge all caches in one batch with retry
try {
await retryWithExponentialBackoff(() => clearUrlCache(urls))
console.log(`Cache purged for all ${urls.length} files`)
} catch (error) {
console.error(`Failed to purge cache for all files after 3 retries`, error)
}
console.log('All cache files created successfully')
return {
products: productsKey,
essentialConsts: essentialConstsKey,
stores: storesKey,
slots: slotsKey,
banners: bannersKey,
individualStores: individualStoreKeys,
}
}
// Internal versions that skip cache purging (for batch operations)
async function createProductsFileInternal(): Promise<string> {
const productsData = await scaffoldProducts()
const jsonContent = JSON.stringify(productsData, null, 2)
const buffer = Buffer.from(jsonContent, 'utf-8')
return await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.products}`)
}
async function createEssentialConstsFileInternal(): Promise<string> {
const essentialConstsData = await scaffoldEssentialConsts()
const jsonContent = JSON.stringify(essentialConstsData, null, 2)
const buffer = Buffer.from(jsonContent, 'utf-8')
return await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.essentialConsts}`)
}
async function createStoresFileInternal(): Promise<string> {
const storesData = await scaffoldStores()
const jsonContent = JSON.stringify(storesData, null, 2)
const buffer = Buffer.from(jsonContent, 'utf-8')
return await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.stores}`)
}
async function createSlotsFileInternal(): Promise<string> {
const slotsData = await scaffoldSlotsWithProducts()
const jsonContent = JSON.stringify(slotsData, null, 2)
const buffer = Buffer.from(jsonContent, 'utf-8')
return await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.slots}`)
}
async function createBannersFileInternal(): Promise<string> {
const bannersData = await scaffoldBanners()
const jsonContent = JSON.stringify(bannersData, null, 2)
const buffer = Buffer.from(jsonContent, 'utf-8')
return await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/${CACHE_FILENAMES.banners}`)
}
async function createAllStoresFilesInternal(): Promise<string[]> {
const stores = await db.select({ id: storeInfo.id }).from(storeInfo)
const results: string[] = []
for (const store of stores) {
const storeData = await scaffoldStoreWithProducts(store.id)
const jsonContent = JSON.stringify(storeData, null, 2)
const buffer = Buffer.from(jsonContent, 'utf-8')
const s3Key = await imageUploadS3(buffer, 'application/json', `${apiCacheKey}/stores/${store.id}.json`)
results.push(s3Key)
}
console.log(`Created ${results.length} store cache files`)
return results
}
export async function clearUrlCache(urls: string[]): Promise<{ success: boolean; errors?: string[] }> {
if (!cloudflareApiToken || !cloudflareZoneId) {
console.warn('Cloudflare credentials not configured, skipping cache clear')
@ -222,6 +333,7 @@ export async function clearUrlCache(urls: string[]): Promise<{ success: boolean;
console.log(`Successfully purged ${urls.length} URLs from Cloudflare cache: ${urls.join(', ')}`)
return { success: true }
} catch (error) {
console.log(error)
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
console.error(`Error clearing Cloudflare cache for URLs: ${urls.join(', ')}`, errorMessage)
return { success: false, errors: [errorMessage] }

View file

@ -3,7 +3,7 @@ import { initializeAllStores } from '@/src/stores/store-initializer'
import { initializeUserNegativityStore } from '@/src/stores/user-negativity-store'
import { startOrderHandler, startCancellationHandler, publishOrder } from '@/src/lib/post-order-handler'
import { deleteOrders } from '@/src/lib/delete-orders'
import { createProductsFile, createEssentialConstsFile, createStoresFile, createSlotsFile, createBannersFile, createAllStoresFiles } from '@/src/lib/cloud_cache'
import { createAllCacheFiles } from '@/src/lib/cloud_cache'
/**
* Initialize all application services
@ -26,15 +26,8 @@ export const initFunc = async (): Promise<void> => {
startCancellationHandler(),
]);
// Create cache files in parallel after stores are initialized
await Promise.all([
createProductsFile(),
createEssentialConstsFile(),
createStoresFile(),
createSlotsFile(),
createBannersFile(),
createAllStoresFiles(),
]);
// Create all cache files after stores are initialized
await createAllCacheFiles();
console.log('Cache files created successfully');
console.log('Application initialization completed successfully');

View file

@ -4,6 +4,7 @@ import { initializeProducts } from '@/src/stores/product-store'
import { initializeProductTagStore } from '@/src/stores/product-tag-store'
import { initializeSlotStore } from '@/src/stores/slot-store'
import { initializeBannerStore } from '@/src/stores/banner-store'
import { createAllCacheFiles } from '@/src/lib/cloud_cache'
/**
* Initialize all application stores
@ -29,6 +30,11 @@ export const initializeAllStores = async (): Promise<void> => {
]);
console.log('All application stores initialized successfully');
// Regenerate all cache files (fire-and-forget)
createAllCacheFiles().catch(error => {
console.error('Failed to regenerate cache files during store initialization:', error)
})
} catch (error) {
console.error('Application stores initialization failed:', error);
throw error;

View file

@ -7,6 +7,7 @@ import { extractKeyFromPresignedUrl, generateSignedUrlFromS3Url } from '@/src/li
import { ApiError } from '@/src/lib/api-error';
import { initializeAllStores } from '@/src/stores/store-initializer'
export const bannerRouter = router({
// Get all banners
getBanners: protectedProcedure

View file

@ -8,7 +8,7 @@ import { imageUploadS3, generateSignedUrlsFromS3Urls, getOriginalUrlFromSignedUr
import { deleteS3Image } from '@/src/lib/delete-image'
import type { SpecialDeal } from '@/src/db/types'
import { initializeAllStores } from '@/src/stores/store-initializer'
import { createProductsFile } from '@/src/lib/cloud_cache'
type CreateDeal = {
quantity: number;
@ -105,11 +105,6 @@ export const productRouter = router({
// Reinitialize stores to reflect changes
await initializeAllStores();
// Regenerate products cache (fire-and-forget)
createProductsFile().catch(error => {
console.error('Failed to regenerate products cache after delete:', error)
})
return {
message: "Product deleted successfully",
};
@ -197,11 +192,6 @@ export const productRouter = router({
// Reinitialize stores to reflect changes
await initializeAllStores();
// Regenerate products cache (fire-and-forget)
createProductsFile().catch(error => {
console.error('Failed to regenerate products cache after slot products update:', error)
})
return {
message: "Slot products updated successfully",
added: productsToAdd.length,
@ -404,11 +394,6 @@ export const productRouter = router({
// Reinitialize stores to reflect changes
await initializeAllStores();
// Regenerate products cache (fire-and-forget)
createProductsFile().catch(error => {
console.error('Failed to regenerate products cache after group creation:', error)
})
return {
group: newGroup,
message: 'Group created successfully',
@ -457,11 +442,6 @@ export const productRouter = router({
// Reinitialize stores to reflect changes
await initializeAllStores();
// Regenerate products cache (fire-and-forget)
createProductsFile().catch(error => {
console.error('Failed to regenerate products cache after group update:', error)
})
return {
group: updatedGroup,
message: 'Group updated successfully',
@ -491,12 +471,6 @@ export const productRouter = router({
// Reinitialize stores to reflect changes
await initializeAllStores();
// Regenerate products cache (non-blocking)
// Regenerate products cache (fire-and-forget)
createProductsFile().catch(error => {
console.error('Failed to regenerate products cache after group deletion:', error)
})
return {
message: 'Group deleted successfully',
};
@ -550,17 +524,12 @@ export const productRouter = router({
await Promise.all(updatePromises);
// Reinitialize stores to reflect changes
await initializeAllStores();
// Reinitialize stores to reflect changes
await initializeAllStores();
// Regenerate products cache (fire-and-forget)
createProductsFile().catch(error => {
console.error('Failed to regenerate products cache after price update:', error)
})
return {
message: `Updated prices for ${updates.length} product(s)`,
updatedCount: updates.length,
};
}),
});
return {
message: `Updated prices for ${updates.length} product(s)`,
updatedCount: updates.length,
};
}),
});

View file

@ -9,7 +9,7 @@ import { appUrl } from "@/src/lib/env-exporter"
import redisClient from "@/src/lib/redis-client"
import { getSlotSequenceKey } from "@/src/lib/redisKeyGetters"
import { initializeAllStores } from '@/src/stores/store-initializer'
import { createSlotsFile } from '@/src/lib/cloud_cache'
interface CachedDeliverySequence {
[userId: string]: number[];
@ -218,11 +218,6 @@ export const slotsRouter = router({
// Reinitialize stores to reflect changes
await initializeAllStores();
// Regenerate slots cache (fire-and-forget)
createSlotsFile().catch(error => {
console.error('Failed to regenerate slots cache after slot products update:', error)
})
return {
message: "Slot products updated successfully",
added: productsToAdd.length,
@ -306,11 +301,6 @@ export const slotsRouter = router({
// Reinitialize stores to reflect changes (outside transaction)
await initializeAllStores();
// Regenerate slots cache (fire-and-forget)
createSlotsFile().catch(error => {
console.error('Failed to regenerate slots cache after slot creation:', error)
})
return result;
}),
@ -470,11 +460,6 @@ export const slotsRouter = router({
// Reinitialize stores to reflect changes (outside transaction)
await initializeAllStores();
// Regenerate slots cache (fire-and-forget)
createSlotsFile().catch(error => {
console.error('Failed to regenerate slots cache after slot update:', error)
})
return result;
}
catch(e) {
@ -505,11 +490,6 @@ export const slotsRouter = router({
// Reinitialize stores to reflect changes
await initializeAllStores();
// Regenerate slots cache (fire-and-forget)
createSlotsFile().catch(error => {
console.error('Failed to regenerate slots cache after slot deletion:', error)
})
return {
message: "Slot deleted successfully",
};
@ -590,11 +570,6 @@ export const slotsRouter = router({
console.warn('Redis cache write failed:', cacheError);
}
// Regenerate slots cache (fire-and-forget)
createSlotsFile().catch(error => {
console.error('Failed to regenerate slots cache after delivery sequence update:', error)
})
return {
slot: updatedSlot,
message: "Delivery sequence updated successfully",
@ -626,11 +601,6 @@ export const slotsRouter = router({
// Reinitialize stores to reflect changes
await initializeAllStores();
// Regenerate slots cache (fire-and-forget)
createSlotsFile().catch(error => {
console.error('Failed to regenerate slots cache after slot capacity update:', error)
})
return {
success: true,
slot: updatedSlot,

View file

@ -7,7 +7,7 @@ import { ApiError } from '@/src/lib/api-error'
import { extractKeyFromPresignedUrl, deleteImageUtil, generateSignedUrlFromS3Url } from '@/src/lib/s3-client'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { initializeAllStores } from '@/src/stores/store-initializer'
import { createStoresFile } from '@/src/lib/cloud_cache'
export const storeRouter = router({
getStores: protectedProcedure
@ -88,11 +88,6 @@ export const storeRouter = router({
// Reinitialize stores to reflect changes
await initializeAllStores();
// Regenerate stores cache (fire-and-forget)
createStoresFile().catch(error => {
console.error('Failed to regenerate stores cache after store creation:', error)
})
return {
store: newStore,
message: "Store created successfully",
@ -169,19 +164,14 @@ export const storeRouter = router({
}
}
// Reinitialize stores to reflect changes
await initializeAllStores();
// Reinitialize stores to reflect changes
await initializeAllStores();
// Regenerate stores cache (fire-and-forget)
createStoresFile().catch(error => {
console.error('Failed to regenerate stores cache after store update:', error)
})
return {
store: updatedStore,
message: "Store updated successfully",
};
}),
return {
store: updatedStore,
message: "Store updated successfully",
};
}),
deleteStore: protectedProcedure
.input(z.object({
@ -212,14 +202,9 @@ export const storeRouter = router({
};
});
// Reinitialize stores to reflect changes (outside transaction)
await initializeAllStores();
// Reinitialize stores to reflect changes (outside transaction)
await initializeAllStores();
// Regenerate stores cache (fire-and-forget)
createStoresFile().catch(error => {
console.error('Failed to regenerate stores cache after store deletion:', error)
})
return result;
}),
});
return result;
}),
});

View file

@ -7,7 +7,7 @@ import {
productInfo,
units,
} from "@/src/db/schema";
import { eq, and, gt, asc } from "drizzle-orm";
import { eq, and } from "drizzle-orm";
import { getAllSlots as getAllSlotsFromCache, getSlotById as getSlotByIdFromCache } from "@/src/stores/slot-store";
import dayjs from 'dayjs';
@ -84,26 +84,6 @@ export const slotsRouter = router({
return response;
}),
nextMajorDelivery: publicProcedure.query(async () => {
const now = new Date();
// Find the next upcoming active delivery slot ID
const nextSlot = await db.query.deliverySlotInfo.findFirst({
where: and(
eq(deliverySlotInfo.isActive, true),
gt(deliverySlotInfo.deliveryTime, now),
),
orderBy: asc(deliverySlotInfo.deliveryTime),
});
if (!nextSlot) {
return null; // No upcoming delivery slots
}
// Get formatted data using helper method
return await getSlotData(nextSlot.id);
}),
getSlotById: publicProcedure
.input(z.object({ slotId: z.number() }))
.query(async ({ input }) => {

View file

@ -24,6 +24,7 @@ import { trpc } from "@/src/trpc-client";
import { useAllProducts, useStores, useSlots, useGetEssentialConsts } from "@/src/hooks/prominent-api-hooks";
import { useProductSlotIdentifier } from "@/hooks/useProductSlotIdentifier";
import { useCentralSlotStore } from "@/src/store/centralSlotStore";
import { useCentralProductStore } from "@/src/store/centralProductStore";
import FloatingCartBar from "@/components/floating-cart-bar";
import BannerCarousel from "@/components/BannerCarousel";
import { useUserDetails } from "@/src/contexts/AuthContext";
@ -369,19 +370,20 @@ export default function Dashboard() {
const { backgroundColor } = useStatusBarStore();
const { getQuickestSlot } = useProductSlotIdentifier();
const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap);
const refetchProducts = useCentralProductStore((state) => state.refetchProducts);
const refetchSlotsFromStore = useCentralSlotStore((state) => state.refetchSlots);
const [isRefreshing, setIsRefreshing] = useState(false);
const {
data: productsData,
isLoading,
error,
refetch,
} = useAllProducts();
const { data: essentialConsts, isLoading: isLoadingConsts, error: constsError, refetch: refetchConsts } = useGetEssentialConsts();
const { data: storesData, refetch: refetchStores } = useStores();
const { data: slotsData, refetch: refetchSlots } = useSlots();
const { data: slotsData } = useSlots();
const products = productsData?.products || [];
@ -440,11 +442,22 @@ export default function Dashboard() {
const handleRefresh = useCallback(async () => {
setIsRefreshing(true);
try {
await Promise.all([refetch(), refetchStores(), refetchSlots(), refetchConsts()]);
const promises = [];
if (refetchProducts) {
promises.push(refetchProducts());
}
if (refetchSlotsFromStore) {
promises.push(refetchSlotsFromStore());
}
promises.push(refetchStores());
promises.push(refetchConsts());
await Promise.all(promises);
} finally {
setIsRefreshing(false);
}
}, [refetch, refetchStores, refetchSlots, refetchConsts]);
}, [refetchProducts, refetchSlotsFromStore, refetchStores, refetchConsts]);
useManualRefresh(() => {
handleRefresh();

View file

@ -7,9 +7,11 @@ import { useRouter, usePathname } from 'expo-router';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import { tw, theme, MyText, MyTouchableOpacity, MyFlatList, AppContainer, MiniQuantifier } from 'common-ui';
import { trpc } from '@/src/trpc-client';
import { useAllProducts } from '@/src/hooks/prominent-api-hooks';
import { useAllProducts, useStores, useSlots } from '@/src/hooks/prominent-api-hooks';
import { AllProductsApiType } from '@backend/trpc/router';
import { useQuickDeliveryStore } from '@/src/store/quickDeliveryStore';
import { useCentralSlotStore } from '@/src/store/centralSlotStore';
import { useCentralProductStore } from '@/src/store/centralProductStore';
import { useAddToCart, useGetCart, useUpdateCartItem, useRemoveFromCart } from '@/hooks/cart-query-hooks';
import { useHideTabNav } from '@/src/hooks/useHideTabNav';
import CartIcon from '@/components/icons/CartIcon';
@ -34,7 +36,7 @@ interface SlotLayoutProps {
function CustomDrawerContent(baseUrl: string, drawerProps: DrawerContentComponentProps, slotIdParent?: number, storeIdParent?: number) {
const router = useRouter();
const pathname = usePathname();
const { data: storesData } = trpc.user.stores.getStores.useQuery();
const { data: storesData } = useStores();
const setStoreId = useSlotStore(state => state.setStoreId);
const { slotId, storeId } = useSlotStore();
@ -181,17 +183,10 @@ export function SlotLayout({ slotId, storeId, baseUrl, isForFlashDelivery }: Slo
router.replace(`${baseUrl}?slotId=${newSlotId}` as any);
};
const slotQuery = slotId
? trpc.user.slots.getSlotById.useQuery({ slotId: Number(slotId) })
: trpc.user.slots.nextMajorDelivery.useQuery();
const deliveryTime = dayjs(slotQuery.data?.deliveryTime).format('DD MMM hh:mm A');
return (
<>
<View style={tw` w-full flex-row bg-white px-4 py-2 mb-1`}>
<QuickDeliveryAddressSelector
deliveryTime={deliveryTime}
slotId={Number(slotId)}
onSlotChange={handleSlotChange}
isForFlashDelivery={isForFlashDelivery}
@ -344,22 +339,20 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
const slotId = slotIdParent;
const storeId = storeIdParent;
const storeIdNum = storeId;
// const { storeId, slotId: slotIdRaw } = useLocalSearchParams();
// const slotId = Number(slotIdRaw);
const { data: slotsData, isLoading: slotsLoading, error: slotsError } = useSlots();
const { productsById } = useCentralProductStore();
const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap);
// const storeIdNum = storeId ? Number(storeId) : undefined;
const slotQuery = trpc.user.slots.getSlotById.useQuery({ slotId: slotId! }, { enabled: !!slotId });
const productsQuery = useAllProducts();
// Find the specific slot from cached data
const slot = slotsData?.slots?.find(s => s.id === slotId);
const { addToCart = () => { } } = useAddToCart({ showSuccessAlert: false, showErrorAlert: false, refetchCart: true }, "regular") || {};
const handleAddToCart = (productId: number) => {
setIsLoadingDialogOpen(true);
const item = filteredProducts.find((p: any) => p.id === productId);
const deliveryTime = slotQuery.data?.deliveryTime ? dayjs(slotQuery.data.deliveryTime).format('ddd, DD MMM • h:mm A') : '';
const item = filteredProducts.find((p) => p.id === productId);
const deliveryTime = slot?.deliveryTime ? dayjs(slot.deliveryTime).format('ddd, DD MMM • h:mm A') : '';
addToCart(productId, 1, slotId || 0, () => {
setIsLoadingDialogOpen(false);
if (item) {
@ -368,7 +361,7 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
});
};
if (slotQuery.isLoading || (storeIdNum && productsQuery?.isLoading)) {
if (slotsLoading) {
return (
<AppContainer>
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
@ -378,7 +371,7 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
);
}
if (slotQuery.error || (storeIdNum && productsQuery?.error)) {
if (slotsError) {
return (
<AppContainer>
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
@ -390,7 +383,7 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
);
}
if (!slotQuery.data) {
if (!slot) {
return (
<AppContainer>
<View style={tw`flex-1 justify-center items-center`}>
@ -401,14 +394,16 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
);
}
// Create a Set of product IDs from slot data for O(1) lookup
const slotProductIds = new Set(slotQuery.data.products?.map((p: any) => p.id) || []);
// Get product details from central store using slot product IDs
// Filter: 1) Must exist in productsById, 2) Must not be out of stock (from slots data)
const slotProducts = slot.products
?.map(p => productsById[p.id])
?.filter((product): product is NonNullable<typeof product> => product !== null && product !== undefined)
?.filter(product => !productSlotsMap[product.id]?.isOutOfStock) || [];
const filteredProducts: any[] = storeIdNum
? productsQuery?.data?.products?.filter(p =>
p.storeId === storeIdNum && slotProductIds.has(p.id)
) || []
: slotQuery.data.products;
const filteredProducts = storeIdNum
? slotProducts.filter(p => p.storeId === storeIdNum)
: slotProducts;
return (
<View testID="slot-detail-page" style={tw`flex-1`}>
@ -426,7 +421,7 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
keyExtractor={(item, index) => index.toString()}
columnWrapperStyle={{ gap: 16, justifyContent: 'flex-start' }}
contentContainerStyle={[tw`pb-24 px-4`, { gap: 16 }]}
onRefresh={() => slotQuery.refetch()}
onRefresh={() => {}}
ListEmptyComponent={
storeIdNum ? (
<View style={tw`items-center justify-center py-10`}>
@ -491,7 +486,7 @@ export function FlashDeliveryProducts({ storeId:storeIdParent, baseUrl, onProduc
}
// Filter products to only include those eligible for flash delivery
let flashProducts: any[] = [];
let flashProducts: AllProductsApiType['products'][number][] = [];
if (storeIdNum) {
// Filter by store, flash availability, and stock status
flashProducts = productsQuery?.data?.products?.filter(p => {

View file

@ -3,20 +3,21 @@ import { useEffect } from 'react'
import { useAllProducts } from '@/src/hooks/prominent-api-hooks'
import { AllProductsApiType } from '@backend/trpc/router'
type Product = AllProductsApiType['products'][number] & {
flashPrice?: number | null
}
type Product = AllProductsApiType['products'][number]
interface CentralProductState {
products: Product[]
productsById: Record<number, Product>
refetchProducts: (() => Promise<void>) | null
setProducts: (products: Product[]) => void
clearProducts: () => void
setRefetchProducts: (refetch: () => Promise<void>) => void
}
export const useCentralProductStore = create<CentralProductState>((set) => ({
products: [],
productsById: {},
refetchProducts: null,
setProducts: (products) => {
const productsById: Record<number, Product> = {}
@ -27,15 +28,23 @@ export const useCentralProductStore = create<CentralProductState>((set) => ({
set({ products, productsById })
},
clearProducts: () => set({ products: [], productsById: {} }),
setRefetchProducts: (refetchProducts) => set({ refetchProducts }),
}))
export function useInitializeCentralProductStore() {
const { data: productsData } = useAllProducts()
const { data: productsData, refetch } = useAllProducts()
const setProducts = useCentralProductStore((state) => state.setProducts)
const setRefetchProducts = useCentralProductStore((state) => state.setRefetchProducts)
useEffect(() => {
if (productsData?.products) {
setProducts(productsData.products)
}
}, [productsData, setProducts])
useEffect(() => {
setRefetchProducts(async () => {
await refetch()
})
}, [refetch, setRefetchProducts])
}

View file

@ -15,13 +15,16 @@ interface ProductSlotInfo {
interface CentralSlotState {
slots: Slot[];
productSlotsMap: Record<number, ProductSlotInfo>;
refetchSlots: (() => Promise<void>) | null;
setSlotsData: (slots: Slot[], productAvailability: ProductAvailability[]) => void;
clearSlotsData: () => void;
setRefetchSlots: (refetch: () => Promise<void>) => void;
}
export const useCentralSlotStore = create<CentralSlotState>((set) => ({
slots: [],
productSlotsMap: {},
refetchSlots: null,
setSlotsData: (slots, productAvailability) => {
const productSlotsMap: Record<number, ProductSlotInfo> = {};
@ -46,15 +49,23 @@ export const useCentralSlotStore = create<CentralSlotState>((set) => ({
set({ slots, productSlotsMap });
},
clearSlotsData: () => set({ slots: [], productSlotsMap: {} }),
setRefetchSlots: (refetchSlots) => set({ refetchSlots }),
}));
export function useInitializeCentralSlotStore() {
const { data: slotsData } = useSlots();
const { data: slotsData, refetch } = useSlots();
const setSlotsData = useCentralSlotStore((state) => state.setSlotsData);
const setRefetchSlots = useCentralSlotStore((state) => state.setRefetchSlots);
useEffect(() => {
if (slotsData?.slots) {
setSlotsData(slotsData.slots, slotsData.productAvailability || []);
}
}, [slotsData, setSlotsData]);
useEffect(() => {
setRefetchSlots(async () => {
await refetch();
});
}, [refetch, setRefetchSlots]);
}

View file

@ -63,10 +63,10 @@ const isDevMode = Constants.executionEnvironment !== "standalone";
// const BASE_API_URL = API_URL;
// 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.1.5:4000';
// const BASE_API_URL = 'http://192.168.1.5:4000';
// let BASE_API_URL = "https://mf.freshyo.in";
// let BASE_API_URL = "https://freshyo.technocracy.ovh";
// let BASE_API_URL = 'http://192.168.100.107:4000';
let BASE_API_URL = 'http://192.168.100.107:4000';
// let BASE_API_URL = 'http://192.168.29.176:4000';
// if(isDevMode) {