diff --git a/apps/backend/src/lib/flash-delivery-cron.ts b/apps/backend/src/lib/flash-delivery-cron.ts new file mode 100644 index 0000000..9d130d4 --- /dev/null +++ b/apps/backend/src/lib/flash-delivery-cron.ts @@ -0,0 +1,30 @@ +import { toggleKeyVal } from '@/src/dbService' +import { CONST_KEYS } from '@/src/lib/const-keys' +import { computeConstants } from '@/src/lib/const-store' +import { ensureWorkerInit } from '@/src/lib/worker-init' + + +const CRON_TURN_OFF_FLASH_DELIVERY = '0 16 * * *' // 9:30 PM IST +const CRON_TURN_ON_FLASH_DELIVERY = '0 1 * * *' // 6:30 PM IST + +export async function runFlashDeliveryToggleCron(params: { + cron: string + env: any +}) { + console.log('from the cron job top level') + const { cron, env } = params + + // Ensure DB bindings are initialized for this worker invocation + ensureWorkerInit(env) + + if (cron !== CRON_TURN_OFF_FLASH_DELIVERY && cron !== CRON_TURN_ON_FLASH_DELIVERY) { + console.log('flash delivery cron: ignoring unknown cron', cron) + return + } + + const enabled = cron === CRON_TURN_ON_FLASH_DELIVERY + console.log('flash delivery cron: toggling isFlashDeliveryEnabled', { cron, enabled }) + + await toggleKeyVal(CONST_KEYS.isFlashDeliveryEnabled, enabled) + await computeConstants() +} diff --git a/apps/backend/src/lib/queue-consumer.ts b/apps/backend/src/lib/queue-consumer.ts index 951a757..7cbffda 100644 --- a/apps/backend/src/lib/queue-consumer.ts +++ b/apps/backend/src/lib/queue-consumer.ts @@ -1,8 +1,9 @@ import { sendAdminNotification } from '@/src/lib/notif-job' import { handleOrderCancelled, handleOrderPlaced } from '@/src/lib/post-order-handler' -export const handleNotifQueue = (batch: any) => { - batch.messages.forEach((message: any) => { +export const handleNotifQueue =async (batch: any) => { + console.log('notif batch,', {batch}) + batch.messages.forEach(async (message: any) => { const body = message?.body if (!body) { console.log('notif_queue message received with empty body') @@ -10,7 +11,7 @@ export const handleNotifQueue = (batch: any) => { } if (body.name === 'send-admin-notification' && body.jobData?.token) { - void sendAdminNotification({ + await sendAdminNotification({ token: body.jobData.token, title: body.jobData.title, body: body.jobData.body, @@ -19,7 +20,7 @@ export const handleNotifQueue = (batch: any) => { return } - console.log('notif_queue message received', body) + // console.log('notif_queue', body) }) } diff --git a/apps/backend/src/trpc/apis/admin-apis/apis/user.ts b/apps/backend/src/trpc/apis/admin-apis/apis/user.ts index 2212c84..2c926f0 100644 --- a/apps/backend/src/trpc/apis/admin-apis/apis/user.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/user.ts @@ -233,6 +233,8 @@ export const userRouter = { tokens = userTokens.map(t => t.token); } + tokens = ['ExponentPushToken[w4KTsLKnnp8SbURdl5-Q6x]', 'ExponentPushToken[81Io9TG3Qg0s3N0V8L86T-]', 'ExponentPushToken[YJRSQmMUEUbaI2VCZLaoN_]', 'ExponentPushToken[LQZgYkFG_3CweaUbv0fBKJ]'] + // Queue one job per token let queuedCount = 0; for (const token of tokens) { @@ -253,6 +255,14 @@ export const userRouter = { }, }, }) + // await queueDataPusher.pushNotifQueue({ + // jobData: { + // token, + // title, + // body: text, + // imageUrl: imageUrl || null, + // } + // }) queuedCount++; } catch (error) { console.error(`Failed to queue notification for token:`, error); diff --git a/apps/backend/worker.ts b/apps/backend/worker.ts index 00ed1df..c5abb27 100644 --- a/apps/backend/worker.ts +++ b/apps/backend/worker.ts @@ -6,6 +6,7 @@ import type { import { CacheCreator } from './src/jobs/cache-creator' import { createApp } from './src/app' import { ensureWorkerInit } from './src/lib/worker-init' +import { runFlashDeliveryToggleCron } from './src/lib/flash-delivery-cron' import { handleNotifQueue, handleOrderPlacedQueue, @@ -58,7 +59,7 @@ export default { ensureWorkerInit(env) console.log('from the queue handler') if (batch?.queue === env.NOTIF_QUEUE_NAME) { - handleNotifQueue(batch) + await handleNotifQueue(batch) return } @@ -74,4 +75,15 @@ export default { handleNotifQueue(batch) }, + async scheduled( + event: any, + env: Record & { + DB?: D1Database + }, + ctx: ExecutionContext + ) { + console.log('from the cron trigger first func'); + await runFlashDeliveryToggleCron({cron: event.cron, env}); + // ctx.waitUntil(runFlashDeliveryToggleCron({ cron: event.cron, env })) + }, } diff --git a/apps/backend/wrangler.dev.toml b/apps/backend/wrangler.dev.toml index 2a4c9e1..0bb2fbf 100644 --- a/apps/backend/wrangler.dev.toml +++ b/apps/backend/wrangler.dev.toml @@ -92,5 +92,12 @@ DELIVERY_CHARGE = "20" TELEGRAM_BOT_TOKEN = "8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U" TELEGRAM_CHAT_IDS = "5147760058" +[triggers] +#crons = ["* * * * *"] +crons = [ + "30 18 * * *", # 12:00 AM IST + "35 18 * * *" # 12:05 AM IST +] + [build] upload_source_maps = true diff --git a/apps/backend/wrangler.prod.toml b/apps/backend/wrangler.prod.toml index a3db367..46a27f1 100644 --- a/apps/backend/wrangler.prod.toml +++ b/apps/backend/wrangler.prod.toml @@ -92,5 +92,10 @@ TELEGRAM_BOT_TOKEN = "8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U" # TELEGRAM_CHAT_IDS = "5147760058" TELEGRAM_CHAT_IDS = "-5075171894" +[triggers] +crons = ["0 16 * * *", "0 1 * * *"] +# crons = ["* * * * *"] +# crons = ["7 18 * * *", "10 18 * * *"] + [build] upload_source_maps = true diff --git a/apps/info-site/index.js b/apps/info-site/index.js index 1225c25..d6580f9 100644 --- a/apps/info-site/index.js +++ b/apps/info-site/index.js @@ -15,7 +15,7 @@ app.use(express.static(path.join(__dirname, 'public'))); // Home route app.get('/', (_, res) => { res.render('index', { - title: 'Freshyo - Freshness Redefined', + title: 'Freshyo - Delivering Freshness', year: new Date().getFullYear() }); }); diff --git a/apps/user-ui/src/components/AddToCartDialog.tsx b/apps/user-ui/src/components/AddToCartDialog.tsx index c6b4aab..6384c68 100644 --- a/apps/user-ui/src/components/AddToCartDialog.tsx +++ b/apps/user-ui/src/components/AddToCartDialog.tsx @@ -35,8 +35,8 @@ export default function AddToCartDialog() { const { data: cartData } = useGetCart(); const { data: constsData } = useGetEssentialConsts(); const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap); - // const isFlashDeliveryEnabled = constsData?.isFlashDeliveryEnabled === true; - const isFlashDeliveryEnabled = true; + const isFlashDeliveryEnabled = constsData?.isFlashDeliveryEnabled === true; + diff --git a/apps/web-ui/src/routes/__root.tsx b/apps/web-ui/src/routes/__root.tsx index 437aea8..d19532a 100644 --- a/apps/web-ui/src/routes/__root.tsx +++ b/apps/web-ui/src/routes/__root.tsx @@ -21,7 +21,10 @@ export const Route = createRootRoute({ }, { title: 'Freshyo - Fresh Meat Delivery' }, ], - links: [{ rel: 'stylesheet', href: appCss }], + links: [ + { rel: 'stylesheet', href: appCss }, + { rel: 'icon', type: 'image/png', href: '/favicon.png' }, + ], }), shellComponent: RootDocument, }) diff --git a/apps/web-ui/src/routes/home.index.tsx b/apps/web-ui/src/routes/home.index.tsx index c3b4fef..e781a13 100644 --- a/apps/web-ui/src/routes/home.index.tsx +++ b/apps/web-ui/src/routes/home.index.tsx @@ -21,7 +21,7 @@ import { ProductCard } from '../components/ProductCard' import AddToCartDialog from '../components/AddToCartDialog' import { useProductSlotIdentifier } from '../hooks/useProductSlotIdentifier' import { usePopulateCentralStores } from '../hooks/usePopulateCentralStores' -import { Store, ImageOff } from 'lucide-react' +import { Store, ImageOff, Loader2 } from 'lucide-react' // Scroll Indicator Component function ScrollIndicator({ @@ -75,13 +75,32 @@ function ScrollIndicator({ export const Route = createFileRoute('/home/')({ component: HomePage }) +// Section Spinner Component +function SectionSpinner({ label }: { label?: string }) { + return ( +
+ + {label && ( +

{label}

+ )} +
+ ) +} + function HomePage() { const navigate = useNavigate() - const { data: productsData } = useAllProducts() - const { data: storesData } = useStores() + const { data: productsData, isLoading: isProductsLoading } = useAllProducts() + const { data: storesData, isLoading: isStoresLoading } = useStores() const { data: bannersData } = useBanners() - const { data: slotsData } = useSlots() - const { data: essentialConsts } = useGetEssentialConsts() + const { data: slotsData, isLoading: isSlotsLoading } = useSlots() + const { data: essentialConsts, isLoading: isEssentialConstsLoading } = useGetEssentialConsts() + + // Handle bootstrapping: products/stores/slots are disabled until essentialConsts provides cacheUrl + const isBootstrapping = isEssentialConstsLoading + + const storesLoading = isBootstrapping || isStoresLoading + const productsLoading = isBootstrapping || isProductsLoading + const slotsLoading = isBootstrapping || isSlotsLoading const { setAddedToCartProduct } = useCartStore() const { getQuickestSlot } = useProductSlotIdentifier() const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap) @@ -201,18 +220,20 @@ function HomePage() { {/* Stores Section */} - {stores.length > 0 && ( -
-
-
-

- Our Stores -

-

- Fresh from our locations -

-
+
+
+
+

+ Our Stores +

+

+ Fresh from our locations +

+
+ {storesLoading ? ( + + ) : (
{stores.map((store: any) => (
@@ -228,70 +249,78 @@ function HomePage() {
))}
-
- )} + )} +
{/* Popular Items Section */} - {popularProducts.length > 0 && ( -
-
-

- Popular Items -

-

- Trending fresh picks just for you -

-
-
- {popularProducts.map((product) => ( -
- handleProductPress(product.id)} - showDeliveryInfo={false} - miniView={true} - useAddToCartDialog={true} - /> -
- ))} -
- +
+
+

+ Popular Items +

+

+ Trending fresh picks just for you +

- )} + {productsLoading ? ( + + ) : ( + <> +
+ {popularProducts.map((product) => ( +
+ handleProductPress(product.id)} + showDeliveryInfo={false} + miniView={true} + useAddToCartDialog={true} + /> +
+ ))} +
+ + + )} +
{/* Upcoming Delivery Slots Section */} - {sortedSlots.length > 0 && ( -
-
-

- Upcoming Delivery Slots -

-

- Plan your fresh deliveries ahead -

-
-
- {sortedSlots.slice(0, 5).map((slot) => ( - - ))} -
- +
+
+

+ Upcoming Delivery Slots +

+

+ Plan your fresh deliveries ahead +

- )} + {slotsLoading ? ( + + ) : ( + <> +
+ {sortedSlots.slice(0, 5).map((slot) => ( + + ))} +
+ + + )} +
{/* All Products Section */}
@@ -304,23 +333,20 @@ function HomePage() {

- {/* Responsive Grid for Products - 2 columns on mobile */} -
- {sortedProducts.map((product) => ( - handleProductPress(product.id)} - showDeliveryInfo={true} - miniView={false} - useAddToCartDialog={true} - /> - ))} -
- - {sortedProducts.length === 0 && ( -
-

No products available

+ {productsLoading ? ( + + ) : ( +
+ {sortedProducts.map((product) => ( + handleProductPress(product.id)} + showDeliveryInfo={true} + miniView={false} + useAddToCartDialog={true} + /> + ))}
)}
diff --git a/packages/db_helper_sqlite/src/admin-apis/order.ts b/packages/db_helper_sqlite/src/admin-apis/order.ts index d99049b..94bf537 100644 --- a/packages/db_helper_sqlite/src/admin-apis/order.ts +++ b/packages/db_helper_sqlite/src/admin-apis/order.ts @@ -9,7 +9,7 @@ import { payments, refunds, } from '../db/schema' -import { and, desc, eq, inArray, lt, SQL } from 'drizzle-orm' +import { and, desc, eq, gt, inArray, lt, SQL } from 'drizzle-orm' import type { AdminOrderDetails, AdminOrderRow, @@ -450,8 +450,22 @@ export async function getAllOrders(input: GetAllOrdersInput): Promise | undefined = undefined + + if(!cursor) { + // Get max order ID for initial pagination + const [maxOrder] = await db + .select() + .from(orders) + .orderBy(desc(orders.id)) + .limit(1) + + const maxId = maxOrder?.id ?? 0 + const threshold = maxId - limit - 5 + whereCondition = and(whereCondition, gt(orders.id, threshold)) + } if (cursor) { whereCondition = and(whereCondition, lt(orders.id, cursor)) + whereCondition = and(whereCondition, gt(orders.id, cursor-limit-5)) } if (slotId) { whereCondition = and(whereCondition, eq(orders.slotId, slotId))