enh
This commit is contained in:
parent
5df040de9a
commit
2d37726c62
17 changed files with 223 additions and 203 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
ENV_MODE=PROD
|
ENV_MODE=PROD
|
||||||
# DATABASE_URL=postgresql://postgres:meatfarmer_master_password@57.128.212.174:7447/meatfarmer #technocracy
|
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=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
|
||||||
PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/
|
PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/
|
||||||
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
|
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
|
||||||
PHONE_PE_CLIENT_VERSION=1
|
PHONE_PE_CLIENT_VERSION=1
|
||||||
|
|
@ -22,7 +22,8 @@ EXPO_ACCESS_TOKEN=Asvpy8cByRh6T4ksnWScO6PLcio2n35-BwES5zK-
|
||||||
JWT_SECRET=my_meatfarmer_jwt_secret_key
|
JWT_SECRET=my_meatfarmer_jwt_secret_key
|
||||||
ASSETS_DOMAIN=https://assets.freshyo.in/
|
ASSETS_DOMAIN=https://assets.freshyo.in/
|
||||||
API_CACHE_KEY=api-cache-dev
|
API_CACHE_KEY=api-cache-dev
|
||||||
CLOUDFLARE_API_TOKEN=I8Vp4E9TX58E8qEDeH0nTFDS2d2zXNYiXvbs4Ckj
|
# CLOUDFLARE_API_TOKEN=I8Vp4E9TX58E8qEDeH0nTFDS2d2zXNYiXvbs4Ckj
|
||||||
|
CLOUDFLARE_API_TOKEN=N7jAg5X-RUj_fVfMW6zbfJ8qIYc81TSIKKlbZ6oh
|
||||||
CLOUDFLARE_ZONE_ID=edefbf750bfc3ff26ccd11e8e28dc8d7
|
CLOUDFLARE_ZONE_ID=edefbf750bfc3ff26ccd11e8e28dc8d7
|
||||||
# REDIS_URL=redis://default:redis_shafi_password@5.223.55.14:6379
|
# REDIS_URL=redis://default:redis_shafi_password@5.223.55.14:6379
|
||||||
REDIS_URL=redis://default:redis_shafi_password@57.128.212.174:6379
|
REDIS_URL=redis://default:redis_shafi_password@57.128.212.174:6379
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -6,7 +6,7 @@ import { ApiError } from "@/src/lib/api-error";
|
||||||
import { imageUploadS3, generateSignedUrlFromS3Url } from "@/src/lib/s3-client";
|
import { imageUploadS3, generateSignedUrlFromS3Url } from "@/src/lib/s3-client";
|
||||||
import { deleteS3Image } from "@/src/lib/delete-image";
|
import { deleteS3Image } from "@/src/lib/delete-image";
|
||||||
import { initializeAllStores } from '@/src/stores/store-initializer';
|
import { initializeAllStores } from '@/src/stores/store-initializer';
|
||||||
import { createStoresFile } from '@/src/lib/cloud_cache';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new product tag
|
* Create a new product tag
|
||||||
|
|
@ -66,11 +66,6 @@ export const createTag = async (req: Request, res: Response) => {
|
||||||
tag: newTag,
|
tag: newTag,
|
||||||
message: "Tag created successfully",
|
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,
|
tag: updatedTag,
|
||||||
message: "Tag updated successfully",
|
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({
|
res.status(200).json({
|
||||||
message: "Tag deleted successfully",
|
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)
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
@ -7,7 +7,7 @@ import { imageUploadS3, getOriginalUrlFromSignedUrl } from "@/src/lib/s3-client"
|
||||||
import { deleteS3Image } from "@/src/lib/delete-image";
|
import { deleteS3Image } from "@/src/lib/delete-image";
|
||||||
import type { SpecialDeal } from "@/src/db/types";
|
import type { SpecialDeal } from "@/src/db/types";
|
||||||
import { initializeAllStores } from '@/src/stores/store-initializer';
|
import { initializeAllStores } from '@/src/stores/store-initializer';
|
||||||
import { createProductsFile } from '@/src/lib/cloud_cache';
|
|
||||||
|
|
||||||
type CreateDeal = {
|
type CreateDeal = {
|
||||||
quantity: number;
|
quantity: number;
|
||||||
|
|
@ -117,11 +117,6 @@ export const createProduct = async (req: Request, res: Response) => {
|
||||||
deals: createdDeals,
|
deals: createdDeals,
|
||||||
message: "Product created successfully",
|
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,
|
product: updatedProduct,
|
||||||
message: "Product updated successfully",
|
message: "Product updated successfully",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Then regenerate products cache (fire-and-forget)
|
|
||||||
createProductsFile().catch(error => {
|
|
||||||
console.error('Failed to regenerate products cache after update:', error)
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
@ -193,6 +193,117 @@ export async function createAllStoresFiles(): Promise<string[]> {
|
||||||
return results
|
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[] }> {
|
export async function clearUrlCache(urls: string[]): Promise<{ success: boolean; errors?: string[] }> {
|
||||||
if (!cloudflareApiToken || !cloudflareZoneId) {
|
if (!cloudflareApiToken || !cloudflareZoneId) {
|
||||||
console.warn('Cloudflare credentials not configured, skipping cache clear')
|
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(', ')}`)
|
console.log(`Successfully purged ${urls.length} URLs from Cloudflare cache: ${urls.join(', ')}`)
|
||||||
return { success: true }
|
return { success: true }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
||||||
console.error(`Error clearing Cloudflare cache for URLs: ${urls.join(', ')}`, errorMessage)
|
console.error(`Error clearing Cloudflare cache for URLs: ${urls.join(', ')}`, errorMessage)
|
||||||
return { success: false, errors: [errorMessage] }
|
return { success: false, errors: [errorMessage] }
|
||||||
|
|
@ -261,4 +373,4 @@ export async function clearAllCache(): Promise<{ success: boolean; errors?: stri
|
||||||
console.error('Error clearing Cloudflare cache:', errorMessage)
|
console.error('Error clearing Cloudflare cache:', errorMessage)
|
||||||
return { success: false, errors: [errorMessage] }
|
return { success: false, errors: [errorMessage] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { initializeAllStores } from '@/src/stores/store-initializer'
|
||||||
import { initializeUserNegativityStore } from '@/src/stores/user-negativity-store'
|
import { initializeUserNegativityStore } from '@/src/stores/user-negativity-store'
|
||||||
import { startOrderHandler, startCancellationHandler, publishOrder } from '@/src/lib/post-order-handler'
|
import { startOrderHandler, startCancellationHandler, publishOrder } from '@/src/lib/post-order-handler'
|
||||||
import { deleteOrders } from '@/src/lib/delete-orders'
|
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
|
* Initialize all application services
|
||||||
|
|
@ -26,15 +26,8 @@ export const initFunc = async (): Promise<void> => {
|
||||||
startCancellationHandler(),
|
startCancellationHandler(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Create cache files in parallel after stores are initialized
|
// Create all cache files after stores are initialized
|
||||||
await Promise.all([
|
await createAllCacheFiles();
|
||||||
createProductsFile(),
|
|
||||||
createEssentialConstsFile(),
|
|
||||||
createStoresFile(),
|
|
||||||
createSlotsFile(),
|
|
||||||
createBannersFile(),
|
|
||||||
createAllStoresFiles(),
|
|
||||||
]);
|
|
||||||
console.log('Cache files created successfully');
|
console.log('Cache files created successfully');
|
||||||
|
|
||||||
console.log('Application initialization completed successfully');
|
console.log('Application initialization completed successfully');
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { initializeProducts } from '@/src/stores/product-store'
|
||||||
import { initializeProductTagStore } from '@/src/stores/product-tag-store'
|
import { initializeProductTagStore } from '@/src/stores/product-tag-store'
|
||||||
import { initializeSlotStore } from '@/src/stores/slot-store'
|
import { initializeSlotStore } from '@/src/stores/slot-store'
|
||||||
import { initializeBannerStore } from '@/src/stores/banner-store'
|
import { initializeBannerStore } from '@/src/stores/banner-store'
|
||||||
|
import { createAllCacheFiles } from '@/src/lib/cloud_cache'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize all application stores
|
* Initialize all application stores
|
||||||
|
|
@ -29,6 +30,11 @@ export const initializeAllStores = async (): Promise<void> => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
console.log('All application stores initialized successfully');
|
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) {
|
} catch (error) {
|
||||||
console.error('Application stores initialization failed:', error);
|
console.error('Application stores initialization failed:', error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { extractKeyFromPresignedUrl, generateSignedUrlFromS3Url } from '@/src/li
|
||||||
import { ApiError } from '@/src/lib/api-error';
|
import { ApiError } from '@/src/lib/api-error';
|
||||||
import { initializeAllStores } from '@/src/stores/store-initializer'
|
import { initializeAllStores } from '@/src/stores/store-initializer'
|
||||||
|
|
||||||
|
|
||||||
export const bannerRouter = router({
|
export const bannerRouter = router({
|
||||||
// Get all banners
|
// Get all banners
|
||||||
getBanners: protectedProcedure
|
getBanners: protectedProcedure
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { imageUploadS3, generateSignedUrlsFromS3Urls, getOriginalUrlFromSignedUr
|
||||||
import { deleteS3Image } from '@/src/lib/delete-image'
|
import { deleteS3Image } from '@/src/lib/delete-image'
|
||||||
import type { SpecialDeal } from '@/src/db/types'
|
import type { SpecialDeal } from '@/src/db/types'
|
||||||
import { initializeAllStores } from '@/src/stores/store-initializer'
|
import { initializeAllStores } from '@/src/stores/store-initializer'
|
||||||
import { createProductsFile } from '@/src/lib/cloud_cache'
|
|
||||||
|
|
||||||
type CreateDeal = {
|
type CreateDeal = {
|
||||||
quantity: number;
|
quantity: number;
|
||||||
|
|
@ -105,11 +105,6 @@ export const productRouter = router({
|
||||||
// Reinitialize stores to reflect changes
|
// Reinitialize stores to reflect changes
|
||||||
await initializeAllStores();
|
await initializeAllStores();
|
||||||
|
|
||||||
// Regenerate products cache (fire-and-forget)
|
|
||||||
createProductsFile().catch(error => {
|
|
||||||
console.error('Failed to regenerate products cache after delete:', error)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: "Product deleted successfully",
|
message: "Product deleted successfully",
|
||||||
};
|
};
|
||||||
|
|
@ -197,11 +192,6 @@ export const productRouter = router({
|
||||||
// Reinitialize stores to reflect changes
|
// Reinitialize stores to reflect changes
|
||||||
await initializeAllStores();
|
await initializeAllStores();
|
||||||
|
|
||||||
// Regenerate products cache (fire-and-forget)
|
|
||||||
createProductsFile().catch(error => {
|
|
||||||
console.error('Failed to regenerate products cache after slot products update:', error)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: "Slot products updated successfully",
|
message: "Slot products updated successfully",
|
||||||
added: productsToAdd.length,
|
added: productsToAdd.length,
|
||||||
|
|
@ -404,11 +394,6 @@ export const productRouter = router({
|
||||||
// Reinitialize stores to reflect changes
|
// Reinitialize stores to reflect changes
|
||||||
await initializeAllStores();
|
await initializeAllStores();
|
||||||
|
|
||||||
// Regenerate products cache (fire-and-forget)
|
|
||||||
createProductsFile().catch(error => {
|
|
||||||
console.error('Failed to regenerate products cache after group creation:', error)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
group: newGroup,
|
group: newGroup,
|
||||||
message: 'Group created successfully',
|
message: 'Group created successfully',
|
||||||
|
|
@ -457,11 +442,6 @@ export const productRouter = router({
|
||||||
// Reinitialize stores to reflect changes
|
// Reinitialize stores to reflect changes
|
||||||
await initializeAllStores();
|
await initializeAllStores();
|
||||||
|
|
||||||
// Regenerate products cache (fire-and-forget)
|
|
||||||
createProductsFile().catch(error => {
|
|
||||||
console.error('Failed to regenerate products cache after group update:', error)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
group: updatedGroup,
|
group: updatedGroup,
|
||||||
message: 'Group updated successfully',
|
message: 'Group updated successfully',
|
||||||
|
|
@ -491,12 +471,6 @@ export const productRouter = router({
|
||||||
// Reinitialize stores to reflect changes
|
// Reinitialize stores to reflect changes
|
||||||
await initializeAllStores();
|
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 {
|
return {
|
||||||
message: 'Group deleted successfully',
|
message: 'Group deleted successfully',
|
||||||
};
|
};
|
||||||
|
|
@ -550,17 +524,12 @@ export const productRouter = router({
|
||||||
|
|
||||||
await Promise.all(updatePromises);
|
await Promise.all(updatePromises);
|
||||||
|
|
||||||
// Reinitialize stores to reflect changes
|
// Reinitialize stores to reflect changes
|
||||||
await initializeAllStores();
|
await initializeAllStores();
|
||||||
|
|
||||||
// Regenerate products cache (fire-and-forget)
|
return {
|
||||||
createProductsFile().catch(error => {
|
message: `Updated prices for ${updates.length} product(s)`,
|
||||||
console.error('Failed to regenerate products cache after price update:', error)
|
updatedCount: updates.length,
|
||||||
})
|
};
|
||||||
|
}),
|
||||||
return {
|
});
|
||||||
message: `Updated prices for ${updates.length} product(s)`,
|
|
||||||
updatedCount: updates.length,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { appUrl } from "@/src/lib/env-exporter"
|
||||||
import redisClient from "@/src/lib/redis-client"
|
import redisClient from "@/src/lib/redis-client"
|
||||||
import { getSlotSequenceKey } from "@/src/lib/redisKeyGetters"
|
import { getSlotSequenceKey } from "@/src/lib/redisKeyGetters"
|
||||||
import { initializeAllStores } from '@/src/stores/store-initializer'
|
import { initializeAllStores } from '@/src/stores/store-initializer'
|
||||||
import { createSlotsFile } from '@/src/lib/cloud_cache'
|
|
||||||
|
|
||||||
interface CachedDeliverySequence {
|
interface CachedDeliverySequence {
|
||||||
[userId: string]: number[];
|
[userId: string]: number[];
|
||||||
|
|
@ -218,11 +218,6 @@ export const slotsRouter = router({
|
||||||
// Reinitialize stores to reflect changes
|
// Reinitialize stores to reflect changes
|
||||||
await initializeAllStores();
|
await initializeAllStores();
|
||||||
|
|
||||||
// Regenerate slots cache (fire-and-forget)
|
|
||||||
createSlotsFile().catch(error => {
|
|
||||||
console.error('Failed to regenerate slots cache after slot products update:', error)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: "Slot products updated successfully",
|
message: "Slot products updated successfully",
|
||||||
added: productsToAdd.length,
|
added: productsToAdd.length,
|
||||||
|
|
@ -306,11 +301,6 @@ export const slotsRouter = router({
|
||||||
// Reinitialize stores to reflect changes (outside transaction)
|
// Reinitialize stores to reflect changes (outside transaction)
|
||||||
await initializeAllStores();
|
await initializeAllStores();
|
||||||
|
|
||||||
// Regenerate slots cache (fire-and-forget)
|
|
||||||
createSlotsFile().catch(error => {
|
|
||||||
console.error('Failed to regenerate slots cache after slot creation:', error)
|
|
||||||
})
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
@ -470,11 +460,6 @@ export const slotsRouter = router({
|
||||||
// Reinitialize stores to reflect changes (outside transaction)
|
// Reinitialize stores to reflect changes (outside transaction)
|
||||||
await initializeAllStores();
|
await initializeAllStores();
|
||||||
|
|
||||||
// Regenerate slots cache (fire-and-forget)
|
|
||||||
createSlotsFile().catch(error => {
|
|
||||||
console.error('Failed to regenerate slots cache after slot update:', error)
|
|
||||||
})
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
|
|
@ -505,11 +490,6 @@ export const slotsRouter = router({
|
||||||
// Reinitialize stores to reflect changes
|
// Reinitialize stores to reflect changes
|
||||||
await initializeAllStores();
|
await initializeAllStores();
|
||||||
|
|
||||||
// Regenerate slots cache (fire-and-forget)
|
|
||||||
createSlotsFile().catch(error => {
|
|
||||||
console.error('Failed to regenerate slots cache after slot deletion:', error)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: "Slot deleted successfully",
|
message: "Slot deleted successfully",
|
||||||
};
|
};
|
||||||
|
|
@ -590,11 +570,6 @@ export const slotsRouter = router({
|
||||||
console.warn('Redis cache write failed:', cacheError);
|
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 {
|
return {
|
||||||
slot: updatedSlot,
|
slot: updatedSlot,
|
||||||
message: "Delivery sequence updated successfully",
|
message: "Delivery sequence updated successfully",
|
||||||
|
|
@ -626,11 +601,6 @@ export const slotsRouter = router({
|
||||||
// Reinitialize stores to reflect changes
|
// Reinitialize stores to reflect changes
|
||||||
await initializeAllStores();
|
await initializeAllStores();
|
||||||
|
|
||||||
// Regenerate slots cache (fire-and-forget)
|
|
||||||
createSlotsFile().catch(error => {
|
|
||||||
console.error('Failed to regenerate slots cache after slot capacity update:', error)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
slot: updatedSlot,
|
slot: updatedSlot,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { ApiError } from '@/src/lib/api-error'
|
||||||
import { extractKeyFromPresignedUrl, deleteImageUtil, generateSignedUrlFromS3Url } from '@/src/lib/s3-client'
|
import { extractKeyFromPresignedUrl, deleteImageUtil, generateSignedUrlFromS3Url } from '@/src/lib/s3-client'
|
||||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||||
import { initializeAllStores } from '@/src/stores/store-initializer'
|
import { initializeAllStores } from '@/src/stores/store-initializer'
|
||||||
import { createStoresFile } from '@/src/lib/cloud_cache'
|
|
||||||
|
|
||||||
export const storeRouter = router({
|
export const storeRouter = router({
|
||||||
getStores: protectedProcedure
|
getStores: protectedProcedure
|
||||||
|
|
@ -88,11 +88,6 @@ export const storeRouter = router({
|
||||||
// Reinitialize stores to reflect changes
|
// Reinitialize stores to reflect changes
|
||||||
await initializeAllStores();
|
await initializeAllStores();
|
||||||
|
|
||||||
// Regenerate stores cache (fire-and-forget)
|
|
||||||
createStoresFile().catch(error => {
|
|
||||||
console.error('Failed to regenerate stores cache after store creation:', error)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
store: newStore,
|
store: newStore,
|
||||||
message: "Store created successfully",
|
message: "Store created successfully",
|
||||||
|
|
@ -169,19 +164,14 @@ export const storeRouter = router({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reinitialize stores to reflect changes
|
// Reinitialize stores to reflect changes
|
||||||
await initializeAllStores();
|
await initializeAllStores();
|
||||||
|
|
||||||
// Regenerate stores cache (fire-and-forget)
|
return {
|
||||||
createStoresFile().catch(error => {
|
store: updatedStore,
|
||||||
console.error('Failed to regenerate stores cache after store update:', error)
|
message: "Store updated successfully",
|
||||||
})
|
};
|
||||||
|
}),
|
||||||
return {
|
|
||||||
store: updatedStore,
|
|
||||||
message: "Store updated successfully",
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
|
|
||||||
deleteStore: protectedProcedure
|
deleteStore: protectedProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
|
|
@ -212,14 +202,9 @@ export const storeRouter = router({
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reinitialize stores to reflect changes (outside transaction)
|
// Reinitialize stores to reflect changes (outside transaction)
|
||||||
await initializeAllStores();
|
await initializeAllStores();
|
||||||
|
|
||||||
// Regenerate stores cache (fire-and-forget)
|
return result;
|
||||||
createStoresFile().catch(error => {
|
}),
|
||||||
console.error('Failed to regenerate stores cache after store deletion:', error)
|
});
|
||||||
})
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
productInfo,
|
productInfo,
|
||||||
units,
|
units,
|
||||||
} from "@/src/db/schema";
|
} 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 { getAllSlots as getAllSlotsFromCache, getSlotById as getSlotByIdFromCache } from "@/src/stores/slot-store";
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
|
@ -84,26 +84,6 @@ export const slotsRouter = router({
|
||||||
return response;
|
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
|
getSlotById: publicProcedure
|
||||||
.input(z.object({ slotId: z.number() }))
|
.input(z.object({ slotId: z.number() }))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import { trpc } from "@/src/trpc-client";
|
||||||
import { useAllProducts, useStores, useSlots, useGetEssentialConsts } from "@/src/hooks/prominent-api-hooks";
|
import { useAllProducts, useStores, useSlots, useGetEssentialConsts } from "@/src/hooks/prominent-api-hooks";
|
||||||
import { useProductSlotIdentifier } from "@/hooks/useProductSlotIdentifier";
|
import { useProductSlotIdentifier } from "@/hooks/useProductSlotIdentifier";
|
||||||
import { useCentralSlotStore } from "@/src/store/centralSlotStore";
|
import { useCentralSlotStore } from "@/src/store/centralSlotStore";
|
||||||
|
import { useCentralProductStore } from "@/src/store/centralProductStore";
|
||||||
import FloatingCartBar from "@/components/floating-cart-bar";
|
import FloatingCartBar from "@/components/floating-cart-bar";
|
||||||
import BannerCarousel from "@/components/BannerCarousel";
|
import BannerCarousel from "@/components/BannerCarousel";
|
||||||
import { useUserDetails } from "@/src/contexts/AuthContext";
|
import { useUserDetails } from "@/src/contexts/AuthContext";
|
||||||
|
|
@ -369,19 +370,20 @@ export default function Dashboard() {
|
||||||
const { backgroundColor } = useStatusBarStore();
|
const { backgroundColor } = useStatusBarStore();
|
||||||
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 refetchSlotsFromStore = useCentralSlotStore((state) => state.refetchSlots);
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: productsData,
|
data: productsData,
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
refetch,
|
|
||||||
} = useAllProducts();
|
} = useAllProducts();
|
||||||
|
|
||||||
const { data: essentialConsts, isLoading: isLoadingConsts, error: constsError, refetch: refetchConsts } = useGetEssentialConsts();
|
const { data: essentialConsts, isLoading: isLoadingConsts, error: constsError, refetch: refetchConsts } = useGetEssentialConsts();
|
||||||
|
|
||||||
const { data: storesData, refetch: refetchStores } = useStores();
|
const { data: storesData, refetch: refetchStores } = useStores();
|
||||||
const { data: slotsData, refetch: refetchSlots } = useSlots();
|
const { data: slotsData } = useSlots();
|
||||||
|
|
||||||
const products = productsData?.products || [];
|
const products = productsData?.products || [];
|
||||||
|
|
||||||
|
|
@ -440,11 +442,22 @@ export default function Dashboard() {
|
||||||
const handleRefresh = useCallback(async () => {
|
const handleRefresh = useCallback(async () => {
|
||||||
setIsRefreshing(true);
|
setIsRefreshing(true);
|
||||||
try {
|
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 {
|
} finally {
|
||||||
setIsRefreshing(false);
|
setIsRefreshing(false);
|
||||||
}
|
}
|
||||||
}, [refetch, refetchStores, refetchSlots, refetchConsts]);
|
}, [refetchProducts, refetchSlotsFromStore, refetchStores, refetchConsts]);
|
||||||
|
|
||||||
useManualRefresh(() => {
|
useManualRefresh(() => {
|
||||||
handleRefresh();
|
handleRefresh();
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,11 @@ import { useRouter, usePathname } from 'expo-router';
|
||||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||||
import { tw, theme, MyText, MyTouchableOpacity, MyFlatList, AppContainer, MiniQuantifier } from 'common-ui';
|
import { tw, theme, MyText, MyTouchableOpacity, MyFlatList, AppContainer, MiniQuantifier } from 'common-ui';
|
||||||
import { trpc } from '@/src/trpc-client';
|
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 { useQuickDeliveryStore } from '@/src/store/quickDeliveryStore';
|
||||||
import { useCentralSlotStore } from '@/src/store/centralSlotStore';
|
import { useCentralSlotStore } from '@/src/store/centralSlotStore';
|
||||||
|
import { useCentralProductStore } from '@/src/store/centralProductStore';
|
||||||
import { useAddToCart, useGetCart, useUpdateCartItem, useRemoveFromCart } from '@/hooks/cart-query-hooks';
|
import { useAddToCart, useGetCart, useUpdateCartItem, useRemoveFromCart } from '@/hooks/cart-query-hooks';
|
||||||
import { useHideTabNav } from '@/src/hooks/useHideTabNav';
|
import { useHideTabNav } from '@/src/hooks/useHideTabNav';
|
||||||
import CartIcon from '@/components/icons/CartIcon';
|
import CartIcon from '@/components/icons/CartIcon';
|
||||||
|
|
@ -34,7 +36,7 @@ interface SlotLayoutProps {
|
||||||
function CustomDrawerContent(baseUrl: string, drawerProps: DrawerContentComponentProps, slotIdParent?: number, storeIdParent?: number) {
|
function CustomDrawerContent(baseUrl: string, drawerProps: DrawerContentComponentProps, slotIdParent?: number, storeIdParent?: number) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const { data: storesData } = trpc.user.stores.getStores.useQuery();
|
const { data: storesData } = useStores();
|
||||||
const setStoreId = useSlotStore(state => state.setStoreId);
|
const setStoreId = useSlotStore(state => state.setStoreId);
|
||||||
|
|
||||||
const { slotId, storeId } = useSlotStore();
|
const { slotId, storeId } = useSlotStore();
|
||||||
|
|
@ -181,17 +183,10 @@ export function SlotLayout({ slotId, storeId, baseUrl, isForFlashDelivery }: Slo
|
||||||
router.replace(`${baseUrl}?slotId=${newSlotId}` as any);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<View style={tw` w-full flex-row bg-white px-4 py-2 mb-1`}>
|
<View style={tw` w-full flex-row bg-white px-4 py-2 mb-1`}>
|
||||||
<QuickDeliveryAddressSelector
|
<QuickDeliveryAddressSelector
|
||||||
deliveryTime={deliveryTime}
|
|
||||||
slotId={Number(slotId)}
|
slotId={Number(slotId)}
|
||||||
onSlotChange={handleSlotChange}
|
onSlotChange={handleSlotChange}
|
||||||
isForFlashDelivery={isForFlashDelivery}
|
isForFlashDelivery={isForFlashDelivery}
|
||||||
|
|
@ -344,22 +339,20 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
|
||||||
const slotId = slotIdParent;
|
const slotId = slotIdParent;
|
||||||
const storeId = storeIdParent;
|
const storeId = storeIdParent;
|
||||||
const storeIdNum = storeId;
|
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;
|
// Find the specific slot from cached data
|
||||||
|
const slot = slotsData?.slots?.find(s => s.id === slotId);
|
||||||
const slotQuery = trpc.user.slots.getSlotById.useQuery({ slotId: slotId! }, { enabled: !!slotId });
|
|
||||||
|
|
||||||
const productsQuery = useAllProducts();
|
|
||||||
|
|
||||||
const { addToCart = () => { } } = useAddToCart({ showSuccessAlert: false, showErrorAlert: false, refetchCart: true }, "regular") || {};
|
const { addToCart = () => { } } = useAddToCart({ showSuccessAlert: false, showErrorAlert: false, refetchCart: true }, "regular") || {};
|
||||||
|
|
||||||
const handleAddToCart = (productId: number) => {
|
const handleAddToCart = (productId: number) => {
|
||||||
setIsLoadingDialogOpen(true);
|
setIsLoadingDialogOpen(true);
|
||||||
const item = filteredProducts.find((p: any) => p.id === productId);
|
const item = filteredProducts.find((p) => p.id === productId);
|
||||||
const deliveryTime = slotQuery.data?.deliveryTime ? dayjs(slotQuery.data.deliveryTime).format('ddd, DD MMM • h:mm A') : '';
|
const deliveryTime = slot?.deliveryTime ? dayjs(slot.deliveryTime).format('ddd, DD MMM • h:mm A') : '';
|
||||||
addToCart(productId, 1, slotId || 0, () => {
|
addToCart(productId, 1, slotId || 0, () => {
|
||||||
setIsLoadingDialogOpen(false);
|
setIsLoadingDialogOpen(false);
|
||||||
if (item) {
|
if (item) {
|
||||||
|
|
@ -368,7 +361,7 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (slotQuery.isLoading || (storeIdNum && productsQuery?.isLoading)) {
|
if (slotsLoading) {
|
||||||
return (
|
return (
|
||||||
<AppContainer>
|
<AppContainer>
|
||||||
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
<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 (
|
return (
|
||||||
<AppContainer>
|
<AppContainer>
|
||||||
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
<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 (
|
return (
|
||||||
<AppContainer>
|
<AppContainer>
|
||||||
<View style={tw`flex-1 justify-center items-center`}>
|
<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
|
// Get product details from central store using slot product IDs
|
||||||
const slotProductIds = new Set(slotQuery.data.products?.map((p: any) => p.id) || []);
|
// 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
|
const filteredProducts = storeIdNum
|
||||||
? productsQuery?.data?.products?.filter(p =>
|
? slotProducts.filter(p => p.storeId === storeIdNum)
|
||||||
p.storeId === storeIdNum && slotProductIds.has(p.id)
|
: slotProducts;
|
||||||
) || []
|
|
||||||
: slotQuery.data.products;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View testID="slot-detail-page" style={tw`flex-1`}>
|
<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()}
|
keyExtractor={(item, index) => index.toString()}
|
||||||
columnWrapperStyle={{ gap: 16, justifyContent: 'flex-start' }}
|
columnWrapperStyle={{ gap: 16, justifyContent: 'flex-start' }}
|
||||||
contentContainerStyle={[tw`pb-24 px-4`, { gap: 16 }]}
|
contentContainerStyle={[tw`pb-24 px-4`, { gap: 16 }]}
|
||||||
onRefresh={() => slotQuery.refetch()}
|
onRefresh={() => {}}
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
storeIdNum ? (
|
storeIdNum ? (
|
||||||
<View style={tw`items-center justify-center py-10`}>
|
<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
|
// Filter products to only include those eligible for flash delivery
|
||||||
let flashProducts: any[] = [];
|
let flashProducts: AllProductsApiType['products'][number][] = [];
|
||||||
if (storeIdNum) {
|
if (storeIdNum) {
|
||||||
// Filter by store, flash availability, and stock status
|
// Filter by store, flash availability, and stock status
|
||||||
flashProducts = productsQuery?.data?.products?.filter(p => {
|
flashProducts = productsQuery?.data?.products?.filter(p => {
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,21 @@ import { useEffect } from 'react'
|
||||||
import { useAllProducts } from '@/src/hooks/prominent-api-hooks'
|
import { useAllProducts } from '@/src/hooks/prominent-api-hooks'
|
||||||
import { AllProductsApiType } from '@backend/trpc/router'
|
import { AllProductsApiType } from '@backend/trpc/router'
|
||||||
|
|
||||||
type Product = AllProductsApiType['products'][number] & {
|
type Product = AllProductsApiType['products'][number]
|
||||||
flashPrice?: number | null
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CentralProductState {
|
interface CentralProductState {
|
||||||
products: Product[]
|
products: Product[]
|
||||||
productsById: Record<number, Product>
|
productsById: Record<number, Product>
|
||||||
|
refetchProducts: (() => Promise<void>) | null
|
||||||
setProducts: (products: Product[]) => void
|
setProducts: (products: Product[]) => void
|
||||||
clearProducts: () => void
|
clearProducts: () => void
|
||||||
|
setRefetchProducts: (refetch: () => Promise<void>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCentralProductStore = create<CentralProductState>((set) => ({
|
export const useCentralProductStore = create<CentralProductState>((set) => ({
|
||||||
products: [],
|
products: [],
|
||||||
productsById: {},
|
productsById: {},
|
||||||
|
refetchProducts: null,
|
||||||
setProducts: (products) => {
|
setProducts: (products) => {
|
||||||
const productsById: Record<number, Product> = {}
|
const productsById: Record<number, Product> = {}
|
||||||
|
|
||||||
|
|
@ -27,15 +28,23 @@ export const useCentralProductStore = create<CentralProductState>((set) => ({
|
||||||
set({ products, productsById })
|
set({ products, productsById })
|
||||||
},
|
},
|
||||||
clearProducts: () => set({ products: [], productsById: {} }),
|
clearProducts: () => set({ products: [], productsById: {} }),
|
||||||
|
setRefetchProducts: (refetchProducts) => set({ refetchProducts }),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export function useInitializeCentralProductStore() {
|
export function useInitializeCentralProductStore() {
|
||||||
const { data: productsData } = useAllProducts()
|
const { data: productsData, refetch } = useAllProducts()
|
||||||
const setProducts = useCentralProductStore((state) => state.setProducts)
|
const setProducts = useCentralProductStore((state) => state.setProducts)
|
||||||
|
const setRefetchProducts = useCentralProductStore((state) => state.setRefetchProducts)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (productsData?.products) {
|
if (productsData?.products) {
|
||||||
setProducts(productsData.products)
|
setProducts(productsData.products)
|
||||||
}
|
}
|
||||||
}, [productsData, setProducts])
|
}, [productsData, setProducts])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setRefetchProducts(async () => {
|
||||||
|
await refetch()
|
||||||
|
})
|
||||||
|
}, [refetch, setRefetchProducts])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,16 @@ interface ProductSlotInfo {
|
||||||
interface CentralSlotState {
|
interface CentralSlotState {
|
||||||
slots: Slot[];
|
slots: Slot[];
|
||||||
productSlotsMap: Record<number, ProductSlotInfo>;
|
productSlotsMap: Record<number, ProductSlotInfo>;
|
||||||
|
refetchSlots: (() => Promise<void>) | null;
|
||||||
setSlotsData: (slots: Slot[], productAvailability: ProductAvailability[]) => void;
|
setSlotsData: (slots: Slot[], productAvailability: ProductAvailability[]) => void;
|
||||||
clearSlotsData: () => void;
|
clearSlotsData: () => void;
|
||||||
|
setRefetchSlots: (refetch: () => Promise<void>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCentralSlotStore = create<CentralSlotState>((set) => ({
|
export const useCentralSlotStore = create<CentralSlotState>((set) => ({
|
||||||
slots: [],
|
slots: [],
|
||||||
productSlotsMap: {},
|
productSlotsMap: {},
|
||||||
|
refetchSlots: null,
|
||||||
setSlotsData: (slots, productAvailability) => {
|
setSlotsData: (slots, productAvailability) => {
|
||||||
const productSlotsMap: Record<number, ProductSlotInfo> = {};
|
const productSlotsMap: Record<number, ProductSlotInfo> = {};
|
||||||
|
|
||||||
|
|
@ -46,15 +49,23 @@ export const useCentralSlotStore = create<CentralSlotState>((set) => ({
|
||||||
set({ slots, productSlotsMap });
|
set({ slots, productSlotsMap });
|
||||||
},
|
},
|
||||||
clearSlotsData: () => set({ slots: [], productSlotsMap: {} }),
|
clearSlotsData: () => set({ slots: [], productSlotsMap: {} }),
|
||||||
|
setRefetchSlots: (refetchSlots) => set({ refetchSlots }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export function useInitializeCentralSlotStore() {
|
export function useInitializeCentralSlotStore() {
|
||||||
const { data: slotsData } = useSlots();
|
const { data: slotsData, refetch } = useSlots();
|
||||||
const setSlotsData = useCentralSlotStore((state) => state.setSlotsData);
|
const setSlotsData = useCentralSlotStore((state) => state.setSlotsData);
|
||||||
|
const setRefetchSlots = useCentralSlotStore((state) => state.setRefetchSlots);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (slotsData?.slots) {
|
if (slotsData?.slots) {
|
||||||
setSlotsData(slotsData.slots, slotsData.productAvailability || []);
|
setSlotsData(slotsData.slots, slotsData.productAvailability || []);
|
||||||
}
|
}
|
||||||
}, [slotsData, setSlotsData]);
|
}, [slotsData, setSlotsData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setRefetchSlots(async () => {
|
||||||
|
await refetch();
|
||||||
|
});
|
||||||
|
}, [refetch, setRefetchSlots]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,10 @@ const isDevMode = Constants.executionEnvironment !== "standalone";
|
||||||
// const BASE_API_URL = API_URL;
|
// const BASE_API_URL = API_URL;
|
||||||
// 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.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://mf.freshyo.in";
|
||||||
// let BASE_API_URL = "https://freshyo.technocracy.ovh";
|
// 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';
|
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
||||||
|
|
||||||
// if(isDevMode) {
|
// if(isDevMode) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue