This commit is contained in:
shafi54 2026-05-20 09:29:36 +05:30
parent 01f6b88392
commit 286e6aabcb
11 changed files with 209 additions and 101 deletions

View file

@ -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()
}

View file

@ -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)
})
}

View file

@ -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);

View file

@ -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<string, string> & {
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 }))
},
}

View file

@ -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

View file

@ -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

View file

@ -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()
});
});

View file

@ -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;

View file

@ -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,
})

View file

@ -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 (
<div className="flex flex-col items-center justify-center py-10">
<Loader2 className="h-8 w-8 animate-spin text-brand-500" />
{label && (
<p className="mt-2 text-sm text-gray-500">{label}</p>
)}
</div>
)
}
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() {
</div>
{/* Stores Section */}
{stores.length > 0 && (
<div className="mb-6">
<div className="mb-4 flex items-center justify-between">
<div>
<p className="font-bold text-xl text-gray-900">
Our Stores
</p>
<p className="mt-0.5 text-xs text-gray-500">
Fresh from our locations
</p>
</div>
<div className="mb-6">
<div className="mb-4 flex items-center justify-between">
<div>
<p className="font-bold text-xl text-gray-900">
Our Stores
</p>
<p className="mt-0.5 text-xs text-gray-500">
Fresh from our locations
</p>
</div>
</div>
{storesLoading ? (
<SectionSpinner label="Loading stores..." />
) : (
<div className="flex flex-wrap gap-4">
{stores.map((store: any) => (
<div key={store.id}>
@ -228,70 +249,78 @@ function HomePage() {
</div>
))}
</div>
</div>
)}
)}
</div>
{/* Popular Items Section */}
{popularProducts.length > 0 && (
<div className="mb-6">
<div className="mb-4">
<p className="font-bold text-xl text-gray-900">
Popular Items
</p>
<p className="mt-0.5 text-sm text-gray-500">
Trending fresh picks just for you
</p>
</div>
<div
ref={popularScrollRef}
className="scrollbar-hide -mx-4 flex gap-4 overflow-x-auto px-4 pb-2"
>
{popularProducts.map((product) => (
<div key={product.id} className="w-40 shrink-0">
<ProductCard
item={product}
onPress={() => handleProductPress(product.id)}
showDeliveryInfo={false}
miniView={true}
useAddToCartDialog={true}
/>
</div>
))}
</div>
<ScrollIndicator
containerRef={popularScrollRef}
itemCount={popularProducts.length}
itemWidth={160}
/>
<div className="mb-6">
<div className="mb-4">
<p className="font-bold text-xl text-gray-900">
Popular Items
</p>
<p className="mt-0.5 text-sm text-gray-500">
Trending fresh picks just for you
</p>
</div>
)}
{productsLoading ? (
<SectionSpinner label="Loading popular items..." />
) : (
<>
<div
ref={popularScrollRef}
className="scrollbar-hide -mx-4 flex gap-4 overflow-x-auto px-4 pb-2"
>
{popularProducts.map((product) => (
<div key={product.id} className="w-40 shrink-0">
<ProductCard
item={product}
onPress={() => handleProductPress(product.id)}
showDeliveryInfo={false}
miniView={true}
useAddToCartDialog={true}
/>
</div>
))}
</div>
<ScrollIndicator
containerRef={popularScrollRef}
itemCount={popularProducts.length}
itemWidth={160}
/>
</>
)}
</div>
{/* Upcoming Delivery Slots Section */}
{sortedSlots.length > 0 && (
<div className="mb-6">
<div className="mb-4">
<p className="font-bold text-xl text-gray-900">
Upcoming Delivery Slots
</p>
<p className="mt-0.5 text-sm text-gray-500">
Plan your fresh deliveries ahead
</p>
</div>
<div
ref={slotsScrollRef}
className="scrollbar-hide -mx-4 flex gap-4 overflow-x-auto px-4 pb-2"
>
{sortedSlots.slice(0, 5).map((slot) => (
<SlotCard key={slot.id} slot={slot} />
))}
</div>
<ScrollIndicator
containerRef={slotsScrollRef}
itemCount={sortedSlots.slice(0, 5).length}
itemWidth={280}
/>
<div className="mb-6">
<div className="mb-4">
<p className="font-bold text-xl text-gray-900">
Upcoming Delivery Slots
</p>
<p className="mt-0.5 text-sm text-gray-500">
Plan your fresh deliveries ahead
</p>
</div>
)}
{slotsLoading ? (
<SectionSpinner label="Loading slots..." />
) : (
<>
<div
ref={slotsScrollRef}
className="scrollbar-hide -mx-4 flex gap-4 overflow-x-auto px-4 pb-2"
>
{sortedSlots.slice(0, 5).map((slot) => (
<SlotCard key={slot.id} slot={slot} />
))}
</div>
<ScrollIndicator
containerRef={slotsScrollRef}
itemCount={sortedSlots.slice(0, 5).length}
itemWidth={280}
/>
</>
)}
</div>
{/* All Products Section */}
<div className="rounded-t-3xl bg-white pt-4">
@ -304,23 +333,20 @@ function HomePage() {
</p>
</div>
{/* Responsive Grid for Products - 2 columns on mobile */}
<div className="grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-4">
{sortedProducts.map((product) => (
<ProductCard
key={product.id}
item={product}
onPress={() => handleProductPress(product.id)}
showDeliveryInfo={true}
miniView={false}
useAddToCartDialog={true}
/>
))}
</div>
{sortedProducts.length === 0 && (
<div className="py-8 text-center">
<p className="text-gray-500">No products available</p>
{productsLoading ? (
<SectionSpinner label="Loading products..." />
) : (
<div className="grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-4">
{sortedProducts.map((product) => (
<ProductCard
key={product.id}
item={product}
onPress={() => handleProductPress(product.id)}
showDeliveryInfo={true}
miniView={false}
useAddToCartDialog={true}
/>
))}
</div>
)}
</div>

View file

@ -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<AdminGetAl
} = input
let whereCondition: SQL<unknown> | 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))