enh
This commit is contained in:
parent
01f6b88392
commit
286e6aabcb
11 changed files with 209 additions and 101 deletions
30
apps/backend/src/lib/flash-delivery-cron.ts
Normal file
30
apps/backend/src/lib/flash-delivery-cron.ts
Normal 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()
|
||||
}
|
||||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 }))
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue