Compare commits

..

No commits in common. "3ebbf989f0bbd502bf425946ec9188f22104ea70" and "adb81a6b3d64554850c14584bb2d9e287b44c733" have entirely different histories.

12 changed files with 2565 additions and 108 deletions

View file

@ -22,7 +22,7 @@
- Prefers test plans written for a non-technical audience, using plain-language, click-by-click instructions ("tap this", "type that", "check there") rather than technical terms or API names. Confidence: 0.9 - Prefers test plans written for a non-technical audience, using plain-language, click-by-click instructions ("tap this", "type that", "check there") rather than technical terms or API names. Confidence: 0.9
- When updating or creating a document, prefers the agent first compare it against existing source documents, identify missing items or gaps, and add them in the same established format. Confidence: 0.9 - When updating or creating a document, prefers the agent first compare it against existing source documents, identify missing items or gaps, and add them in the same established format. Confidence: 0.9
- Dislikes nested headers in mobile/drawer navigation; prefers a single, shared header (e.g., the drawer header) and relies on device back buttons or gestures for returning to previous screens. Confidence: 0.9 - Dislikes nested headers in mobile/drawer navigation; prefers a single, shared header (e.g., the drawer header) and relies on device back buttons or gestures for returning to previous screens. Confidence: 0.9
- After reviewing a presented plan, prefers brief, action-oriented approval (e.g., "nice. go ahead", "go ahead and implement") before implementation proceeds, expecting the agent to autonomously execute the already-presented plan. Confidence: 0.9 - After reviewing a presented plan, prefers brief, action-oriented approval (e.g., "nice. go ahead", "go ahead and implement") before implementation proceeds, expecting the agent to autonomously execute the already-presented plan. Confidence: 0.85
- Wants feature parity maintained between the web-ui and user-ui apps (port logic/data patterns, rebuild UI per platform) — e.g., when he asks for a link in the cart page "at the coupons section" of @apps/user-ui/components/cart-page.tsx, he means mirror user-ui's coupons section (card, header, icon) in web-ui and add the requested link there; and conversely asks for features added in one app to be added to the sibling app "too" (e.g., "add the coupon redirection on the user-ui cart-page too"), expecting the agent to locate the equivalent component/route and replicate it. Confidence: 0.98 - Wants feature parity maintained between the web-ui and user-ui apps (port logic/data patterns, rebuild UI per platform) — e.g., when he asks for a link in the cart page "at the coupons section" of @apps/user-ui/components/cart-page.tsx, he means mirror user-ui's coupons section (card, header, icon) in web-ui and add the requested link there; and conversely asks for features added in one app to be added to the sibling app "too" (e.g., "add the coupon redirection on the user-ui cart-page too"), expecting the agent to locate the equivalent component/route and replicate it. Confidence: 0.98
- Wants the web-ui home page section/component order to match the reference user-ui home page exactly (e.g., "on the home page have the order same as that of @apps/user-ui/.../home/index.tsx"), including when deciding placement of sections like Explore, Stores, Slots, and All Products. Confidence: 0.7 - Wants the web-ui home page section/component order to match the reference user-ui home page exactly (e.g., "on the home page have the order same as that of @apps/user-ui/.../home/index.tsx"), including when deciding placement of sections like Explore, Stores, Slots, and All Products. Confidence: 0.7
- Wants brand/logo spots to use the single real brand logo asset (shared across apps, e.g., the Freshyo splash logo) displayed as an image, not a generic/decorative icon (e.g., a "meat piece" Meat/Beef icon) — web-ui should be on par with user-ui and show the same logo; applies to favicon, PWA icons, and every in-UI branding spot (sidebar, topbar, login, home banner). Confidence: 0.85 - Wants brand/logo spots to use the single real brand logo asset (shared across apps, e.g., the Freshyo splash logo) displayed as an image, not a generic/decorative icon (e.g., a "meat piece" Meat/Beef icon) — web-ui should be on par with user-ui and show the same logo; applies to favicon, PWA icons, and every in-UI branding spot (sidebar, topbar, login, home banner). Confidence: 0.85
@ -92,13 +92,7 @@ er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the
- When deleting a record, prefers the existing soft-delete/invalidate path over hard-deleting rows: the supported delete already sets `isInvalidated: true` (keeping history/audit and avoiding broken FK joins), and a raw hard delete should only be considered when confirmed nothing references the row (no usage records), with related rows (couponApplicableUsers/Products) removed transactionally to avoid orphans. Confidence: 0.75 - When deleting a record, prefers the existing soft-delete/invalidate path over hard-deleting rows: the supported delete already sets `isInvalidated: true` (keeping history/audit and avoiding broken FK joins), and a raw hard delete should only be considered when confirmed nothing references the row (no usage records), with related rows (couponApplicableUsers/Products) removed transactionally to avoid orphans. Confidence: 0.75
- When commissioning a dead-code audit of an app/package, expects a "close analysis" that is exhaustive across every category — dead, unreachable, and unused code: orphaned files, unreachable/unlinked routes, dormant or disabled-by-flag features, dead branches and commented-out blocks, and unused imports/exports — finding "every such code which serves no purpose" rather than only obviously-unreferenced files; findings should distinguish verified-dead items from merely dormant/judgment-call items. Confidence: 0.65 - When commissioning a dead-code audit of an app/package, expects a "close analysis" that is exhaustive across every category — dead, unreachable, and unused code: orphaned files, unreachable/unlinked routes, dormant or disabled-by-flag features, dead branches and commented-out blocks, and unused imports/exports — finding "every such code which serves no purpose" rather than only obviously-unreferenced files; findings should distinguish verified-dead items from merely dormant/judgment-call items. Confidence: 0.65
- When an analysis/audit has been produced for one app in the monorepo, expects the same analysis run for the sibling apps too — after the user-ui dead-code audit he immediately asked "now do the same for the @apps/admin-ui" — applying the identical exhaustive methodology and document style to each named app. Confidence: 0.85 - When an analysis/audit has been produced for one app in the monorepo, expects the same analysis run for the sibling apps too — after the user-ui dead-code audit he immediately asked "now do the same for the @apps/admin-ui" — applying the identical exhaustive methodology and document style to each named app. Confidence: 0.85
- For analysis documents, expects a consistent naming pattern following the established convention (`user_ui_dpsk.md``admin_ui_dpsk.md`, i.e., `{app}_dpsk.md` at the repo root) rather than an ad-hoc filename — "make another md file with same naming pattern"; the `_dpsk.md` suffix also applies to cross-cutting topic analyses (e.g., `repeat_types_dpsk.md` for the repeated-types scan). Confidence: 0.9 - For per-app analysis documents, expects a consistent naming pattern following the established convention (`user_ui_dpsk.md``admin_ui_dpsk.md`, i.e., `{app}_dpsk.md` at the repo root) rather than an ad-hoc filename — "make another md file with same naming pattern". Confidence: 0.9
- When commissioning a cross-cutting audit/analysis, expects the scan to span the entire monorepo in one pass — every app, the backend, AND the shared packages (e.g., "scan the entire @apps/user-ui/ @apps/admin-ui/ @apps/backend/ and also entire @packages/") — not just one app or one area. Confidence: 0.65
- Values type-level hygiene alongside runtime structures: wants repeated type declarations across the monorepo hunted down and classified (identical duplicates, near-identical/unifiable shapes, same-name-different-shape collisions) and documented in a report before any refactor, rather than fixing types piecemeal. Confidence: 0.6
- Requires the parallel DB-helper packages (db_helper_sqlite and db_helper_postgres) to not duplicate type definitions: they must share one canonical set of types and emit exactly the same data shape, since they represent the same business entities on different backends — stated directly: "they shouldn't duplicate types. They should emit exactly same type of data. They should share the types." Confidence: 0.9
- When the two DB-helper implementations diverge at the data-model level (sqlite is SKU-based with skuIds; the dormant postgres collapsed the SKU tier into productIds), the live/canonical model wins: the sqlite/SKU model — which packages/shared already standardizes on — is the source of truth and the dormant postgres helper is to be aligned/migrated to it (up to and including schema-level changes so even row types match), not the reverse. Confidence: 0.75
e changes. Confidence: 0.95 e changes. Confidence: 0.95
er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the agent to mirror that pattern exactly, including details like item counts and container styling. Confidence: 0.9 er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the agent to mirror that pattern exactly, including details like item counts and container styling. Confidence: 0.9
als) rather than introducing new brand palettes; approval of a redesign's layout/composition does not imply approval of color or theme changes. Confidence: 0.95 als) rather than introducing new brand palettes; approval of a redesign's layout/composition does not imply approval of color or theme changes. Confidence: 0.95
@ -109,11 +103,3 @@ er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the
y, including details like item counts and container styling. Confidence: 0.9 y, including details like item counts and container styling. Confidence: 0.9
tes; approval of a redesign's layout/composition does not imply approval of color or theme changes. Confidence: 0.95 tes; approval of a redesign's layout/composition does not imply approval of color or theme changes. Confidence: 0.95
er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the agent to mirror that pattern exactly, including details like item counts and container styling. Confidence: 0.9 er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the agent to mirror that pattern exactly, including details like item counts and container styling. Confidence: 0.9
olor or theme changes. Confidence: 0.95
roducing new brand palettes; approval of a redesign's layout/composition does not imply approval of color or theme changes. Confidence: 0.95
er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the agent to mirror that pattern exactly, including details like item counts and container styling. Confidence: 0.9
tes; approval of a redesign's layout/composition does not imply approval of color or theme changes. Confidence: 0.95
er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the agent to mirror that pattern exactly, including details like item counts and container styling. Confidence: 0.9
y, including details like item counts and container styling. Confidence: 0.9
tes; approval of a redesign's layout/composition does not imply approval of color or theme changes. Confidence: 0.95
er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the agent to mirror that pattern exactly, including details like item counts and container styling. Confidence: 0.9

View file

@ -4,6 +4,18 @@ import { getStaffUserById, isUserSuspended } from '@/src/dbService';
import { ApiError } from '@/src/lib/api-error'; import { ApiError } from '@/src/lib/api-error';
import { getEncodedJwtSecret } from '@/src/lib/env-exporter'; import { getEncodedJwtSecret } from '@/src/lib/env-exporter';
interface UserContext {
userId: number;
name?: string;
email?: string;
mobile?: string;
}
interface StaffContext {
id: number;
name: string;
}
export const authenticateUser = async (c: Context, next: Next) => { export const authenticateUser = async (c: Context, next: Next) => {
try { try {
const authHeader = c.req.header('authorization'); const authHeader = c.req.header('authorization');

View file

@ -8,10 +8,10 @@ routes = [
[[d1_databases]] [[d1_databases]]
binding = "DB" binding = "DB"
#database_name = "freshyo-backend-dev" database_name = "freshyo-backend-dev"
#database_id = "b2528bdb-0ec0-478e-8765-b118918153c0" database_id = "b2528bdb-0ec0-478e-8765-b118918153c0"
database_name = "freshyo-dev" #database_name = "freshyo-dev"
database_id = "3cad440f-dc14-4baa-ab05-04eb08e206de" #database_id = "3cad440f-dc14-4baa-ab05-04eb08e206de"
migrations_dir="../../packages/db_helper_sqlite/drizzle" migrations_dir="../../packages/db_helper_sqlite/drizzle"
migrations_pattern="migration.sql" migrations_pattern="migration.sql"
[durable_objects] [durable_objects]

View file

@ -604,91 +604,3 @@ KEPT (per instruction / verification): §4 routes & affordances (rebalance-order
Verification: Verification:
- grep across apps/admin-ui: zero remaining references to any deleted file/symbol (only generated .expo cache regenerated clean). - grep across apps/admin-ui: zero remaining references to any deleted file/symbol (only generated .expo cache regenerated clean).
- tsc --noEmit: no errors in any file I edited. Remaining 19 admin-ui-local errors + backend errors are pre-existing (in untouched files: complaints, coupons/edit, customize-app, products, user-management, delivery-sequences lines 343/398 implicit-any, ProductGroupForm, ProductsSelector, ProductForm) — verified none are in modified regions. - tsc --noEmit: no errors in any file I edited. Remaining 19 admin-ui-local errors + backend errors are pre-existing (in untouched files: complaints, coupons/edit, customize-app, products, user-management, delivery-sequences lines 343/398 implicit-any, ProductGroupForm, ProductsSelector, ProductForm) — verified none are in modified regions.
[2026-09-02 23:20:00] CREATED analysis script: scripts/dead-code/repeat-types.js (read-only type-declaration scanner, outputs scripts/dead-code/repeat-types-raw.json)
[2026-09-03 00:10:00] EXECUTE repeat_types_dpsk.md cleanup — remove legacy doctor/hospital/token types (not relevant to meat-farmer app).
PLANNED CHANGES:
=== packages/ui/shared-types.ts ===
- DELETE all legacy doctor/hospital/token types (zero external references — verified by grep across apps + packages):
Person, User (hospital-era), Hospital, DoctorSpecialization, DoctorInfo, Doctor, DoctorDetails,
DashboardDoctor, TokenDoctor, UpcomingToken, PastToken, UpcomingAppointment, MyTokensResponse,
PastTokensResponse, BookTokenPayload, BookTokenResponse, DoctorAvailability,
DoctorAvailabilityWithDate, DoctorAvailabilityResponse, DoctorAvailabilityPayload, token_user,
PatientHistoryToken, PatientHistory, GetHospitalPatientHistoryResponse, PatientHistoryFilters
- DELETE shadowed meat-farmer duplicates that collide with packages/shared (also zero importers):
Order, Slot, Coupon (canonical copies live in packages/shared/types/{user.ts, admin.ts})
- KEEP: CreateCouponPayload (only live export — imported by apps/admin-ui coupons via 'common-ui/shared-types')
- REMOVE trailing `export * from './token-types'`
=== packages/ui/token-types.ts ===
- DELETE entire file (DoctorTokenSummary, HospitalTodaysTokensResponse, DoctorTodayToken, DoctorTodaysTokensResponse — only re-exported by shared-types.ts:333, no external consumers)
[2026-09-03 00:15:00] COMPLETED legacy doctor/hospital/token type removal.
- packages/ui/shared-types.ts: rewritten to contain ONLY CreateCouponPayload (the single live export). Removed 28 legacy doctor/hospital/token types + 3 shadowed meat-farmer duplicates (Order, Slot, Coupon — canonical copies live in packages/shared) + trailing `export * from './token-types'`.
- packages/ui/token-types.ts: deleted (4 token-summary types, zero external consumers).
Verification:
- grep across apps + packages: zero references to any removed legacy type name or the token-types module.
- CreateCouponPayload intact; its only importers (admin-ui coupons/edit/[id].tsx + src/components/CouponForm.tsx) unchanged.
- Pre-existing tsc error in coupons/edit/[id].tsx:92 (`skuIds` not in CreateCouponPayload) untouched and unrelated (predates this change; CreateCouponPayload body preserved byte-for-byte).
[2026-09-03 20:03:29] DELETE unused types (no consumers repo-wide; scanner: scripts/dead-code/unused-types.js; ambient .d.ts declarations excluded by design).
NOTE: date rollover — this entry and deletions performed 2026-09-03.
=== apps/backend/src/middleware/auth.middleware.ts ===
- removed interface UserContext (lines 7-12):
interface UserContext {
userId: number;
name?: string;
email?: string;
mobile?: string;
}
- removed interface StaffContext (lines 14-17):
interface StaffContext {
id: number;
name: string;
}
(file types context vars via hono's ContextVariableMap augmentation in types/hono.d.ts — those ambient declarations KEPT, they are consumed by hono's type system, not by name)
=== packages/shared/types/coupon.types.ts ===
- removed interface ReservedCoupon (lines 21-25):
export interface ReservedCoupon extends Coupon {
secretCode: string;
redeemedUserId: number | null;
redeemedAt: Date | null;
}
=== packages/shared/types/user.ts ===
- removed interface UserAvailabilityEntry (lines 339-345) — transitively dead (only consumer was UserAvailabilityResponse):
export interface UserAvailabilityEntry {
id: number;
price: string;
marketPrice: string | null;
flashPrice: string | null;
isFlashAvailable: boolean;
isOutOfStock: boolean;
isSuspended: boolean;
}
- removed interface UserAvailabilityResponse (lines 347-350):
export interface UserAvailabilityResponse {
availability: UserAvailabilityEntry[];
count: number;
}
=== packages/ui/src/components/use-pick-image.tsx ===
- removed interface BaseProps (lines 5-8):
interface BaseProps {
label?: string;
multiple: boolean;
}
- removed interface ImageInfo (lines 22-36) — 15-field expo ImagePicker asset mirror, never referenced (picker assets are spread as any)
(live hook + its Props type kept; file has 7 consumers)
[2026-09-03 20:06:30] COMPLETED unused-type deletion: 7 types removed across 4 files.
- auth.middleware.ts: UserContext, StaffContext (kept hono.d.ts ContextVariableMap — ambient module augmentation, consumed by hono's type system)
- shared/coupon.types.ts: ReservedCoupon
- shared/user.ts: UserAvailabilityResponse + UserAvailabilityEntry (transitively dead — only consumer was the removed response type)
- ui/use-pick-image.tsx: BaseProps, ImageInfo
Verification: zero leftover references repo-wide; tsc baselines unchanged — backend 11, packages/ui 924, user-ui 106, admin-ui 122 (all pre-existing).
Scanner limitation noted: same-name declarations across files (e.g. Props, Order) can mask per-copy deadness via word-boundary matching; those require import-graph resolution and were not touched.

View file

@ -21,6 +21,12 @@ export interface Coupon {
createdBy: number; createdBy: number;
} }
export interface ReservedCoupon extends Coupon {
secretCode: string;
redeemedUserId: number | null;
redeemedAt: Date | null;
}
export interface CouponValidationResult { export interface CouponValidationResult {
valid: boolean; valid: boolean;
message?: string; message?: string;

View file

@ -336,6 +336,21 @@ export interface UserSlotAvailability {
isOutOfStock: boolean; isOutOfStock: boolean;
} }
export interface UserAvailabilityEntry {
id: number;
price: string;
marketPrice: string | null;
flashPrice: string | null;
isFlashAvailable: boolean;
isOutOfStock: boolean;
isSuspended: boolean;
}
export interface UserAvailabilityResponse {
availability: UserAvailabilityEntry[];
count: number;
}
export interface UserDeliverySlot { export interface UserDeliverySlot {
id: number; id: number;
deliveryTime: Date; deliveryTime: Date;

View file

@ -1,3 +1,275 @@
export type Person = {
name: string;
age: number;
email: string;
}
export interface User {
id: number;
name?: string | null;
email: string;
mobile: string;
profilePicUrl?: string;
address?: string;
joinDate?: Date;
specializations: DoctorSpecialization[] | null;
qualifications?: string;
hospital?: string;
hospitalId?: number;
consultationFee?: string;
}
export interface Hospital {
id: number;
name: string;
description?: string;
address: string;
adminId?: number;
adminName?: string;
hospitalImages?: string[]
}
export interface DoctorSpecialization {
id: number;
name: string | null;
description: string | null;
}
export interface DoctorInfo {
id: number;
qualifications: string | null;
dailyTokenCount: number;
consultationFee: number | string;
}
export interface Doctor {
id: number;
name: string;
username?: string | null;
email?: string | null;
mobile?: string;
doctorInfo?: DoctorInfo;
specializations?: DoctorSpecialization[] | null;
qualifications: string | null;
}
export interface DoctorDetails {
id: number;
name: string;
username?: string | null;
email?: string | null;
mobile?: string;
profilePicUrl?: string | null;
address?: string | null;
joinDate?: string | null;
role: string;
roles: string[];
doctorId: number;
qualifications?: string | null;
dailyTokenCount: number;
consultationFee: number;
hospital?: string;
hospitalId?: number;
description?: string | null;
yearsOfExperience?: number | null;
specializations: {
id: number;
name: string;
description: string | null;
}[];
isConsultationsPaused: boolean;
pauseReason: string | null;
}
// Define local DashboardDoctor type based on the API response structure
export interface DashboardDoctor {
availableTokens: number;
consultationFee: string;
consultationsDone: number;
currentConsultationNumber: number;
id: number;
isAvailable: boolean;
name: string;
profilePicUrl?: string | null;
qualifications: string | null;
tokensIssuedToday: number;
totalTokenCount: number;
}
// Token types
export interface TokenDoctor {
id: number;
name: string;
mobile: string;
profilePicUrl: string | null;
}
export interface UpcomingToken {
id: number;
tokenDate: string;
queueNumber: number;
description: string | null;
createdAt: string;
doctor: TokenDoctor;
currentConsultationNumber?: number; // Current consultation number for the doctor
}
export interface PastToken {
id: number;
tokenDate: string;
queueNumber: number;
description: string | null;
createdAt: string;
doctor: TokenDoctor;
status?: 'COMPLETED' | 'MISSED' | 'CANCELLED';
consultationNotes?: string | null;
}
export interface UpcomingAppointment {
id: number;
doctorName: string;
doctorImageUrl?: string;
date: string;
hospital: string;
queueNumber: number;
status: string;
}
export interface MyTokensResponse {
message: string;
tokens: UpcomingToken[];
}
export interface PastTokensResponse {
message: string;
tokens: PastToken[];
}
export interface BookTokenPayload {
doctorId: number;
userId: number;
tokenDate: string;
description?: string;
}
export interface BookTokenResponse {
message: string;
token: {
id: number;
doctorId: number;
userId: number;
tokenDate: string;
queueNumber: number;
description: string | null;
createdAt: string;
};
}
// Doctor availability types
export interface DoctorAvailability {
id: number;
doctorId: number;
date: string;
totalTokenCount: number;
filledTokenCount: number;
consultationsDone: number;
isStopped: boolean;
availableTokens: number;
isPaused: boolean;
isLeave: boolean;
pauseReason: string | null;
}
export interface DoctorAvailabilityWithDate {
date: string;
availability: DoctorAvailability | null;
}
export interface DoctorAvailabilityResponse {
message: string;
doctorId: number;
availabilities: DoctorAvailabilityWithDate[];
}
export interface DoctorAvailabilityPayload {
doctorId: number;
date: string;
tokenCount: number;
isStopped?: boolean;
filledTokenCount?: number;
consultationsDone?: number;
}
export interface token_user {
id: number;
name: string;
mobile: string;
age: number;
gender: string;
}
export interface Order {
id: string;
orderId: string;
readableId: string;
customerName: string;
address: string;
totalAmount: number;
items: {
name: string;
quantity: number;
price: number;
amount: number;
unit: string;
isPackaged: boolean;
isPackageVerified: boolean;
}[];
deliveryTime: string;
status: 'pending' | 'delivered' | 'cancelled';
isPackaged: boolean;
isDelivered: boolean;
isCod: boolean;
paymentMode: string;
paymentStatus: string;
slotId: number;
}
export interface Slot {
id: number;
deliveryTime: string;
freezeTime: string;
isActive: boolean;
deliverySequence?: number[];
}
export interface Coupon {
id: number;
couponCode: string;
isUserBased: boolean;
discountPercent: string | null;
flatDiscount: string | null;
minOrder: string | null;
targetUser: {
id: number;
name: string | null;
mobile: string;
} | null;
productIds: number[] | null;
createdBy: number;
creator: {
id: number;
name: string | null;
} | null;
maxValue: string | null;
isApplyForAll: boolean;
validTill: Date | null;
maxLimitForUser: number | null;
isInvalidated: boolean;
createdAt: Date;
}
export interface CreateCouponPayload { export interface CreateCouponPayload {
couponCodes: string[]; couponCodes: string[];
discountPercent?: number; discountPercent?: number;
@ -14,3 +286,48 @@ export interface CreateCouponPayload {
applicableProducts?: number[]; applicableProducts?: number[];
exclusiveApply?: boolean; exclusiveApply?: boolean;
} }
// Patient History Types
export interface PatientHistoryToken {
id: number;
queueNum: number;
tokenDate: string;
doctorName: string;
status: string;
description: string;
consultationNotes?: string;
}
export interface PatientHistory {
id: number;
name: string;
mobile: string;
age: number;
gender: string;
totalTokens: number;
completedTokens: number;
upcomingTokens: number;
firstVisitDate: string;
lastVisitDate: string;
tokens: PatientHistoryToken[];
}
export interface GetHospitalPatientHistoryResponse {
message: string;
patients: PatientHistory[];
totalCount: number;
page: number;
limit: number;
}
export interface PatientHistoryFilters {
patientIds?: string[];
doctorIds?: string[];
statuses?: string[];
startDate?: string | null;
endDate?: string | null;
}
// Export the types from token-types.ts
export * from './token-types';

View file

@ -2,6 +2,11 @@ import React from "react";
import * as DocumentPicker from "expo-document-picker"; import * as DocumentPicker from "expo-document-picker";
import * as ImagePicker from "expo-image-picker"; import * as ImagePicker from "expo-image-picker";
interface BaseProps {
label?: string;
multiple: boolean;
}
// interface Props { // interface Props {
// setFile: (file: DocumentPicker.DocumentPickerAsset | null) => void; // setFile: (file: DocumentPicker.DocumentPickerAsset | null) => void;
// label?: string; // label?: string;
@ -14,6 +19,22 @@ type Props = {
multiple: boolean; multiple: boolean;
}; };
interface ImageInfo {
assetId?: string | null;
base64?: string | null;
duration?: number | null;
exif?: Record<string, any> | null;
fileName?: string | null;
fileSize?: number;
height?: number;
mimeType?: string | null;
name?: string | null;
rotation?: number | null;
type?: string;
uri: string;
width?: number;
}
function usePickImage({ setFile, multiple = false }: Props) { function usePickImage({ setFile, multiple = false }: Props) {
// const { setFile } = props; // const { setFile } = props;
const handlePickFile = async () => { const handlePickFile = async () => {

43
packages/ui/token-types.ts Executable file
View file

@ -0,0 +1,43 @@
// Adding the types for hospital today's tokens and doctor today's tokens
export interface DoctorTokenSummary {
id: number;
name: string;
specializations?: string[];
totalTokens: number;
completedTokens: number;
inProgressTokens: number;
upcomingTokens: number;
currentTokenNumber?: number | null;
tokens?: DoctorTodayToken[]; // Added individual tokens
}
export interface HospitalTodaysTokensResponse {
message: string;
hospitalId: number;
hospitalName: string;
date: string;
doctors: DoctorTokenSummary[];
}
export interface DoctorTodayToken {
id: number;
queueNumber: number;
patientId: number | null;
patientName: string;
patientMobile?: string;
description: string | null;
status: 'UPCOMING' | 'IN_PROGRESS' | 'COMPLETED' | 'MISSED' | 'CANCELLED'; // Made optional
// status: string; // 'UPCOMING' | 'IN_PROGRESS' | 'COMPLETED' | 'MISSED' | 'CANCELLED'; // Made optional
consultationNotes?: string | null;
}
export interface DoctorTodaysTokensResponse {
message: string;
doctorId: number;
doctorName: string;
date: string;
currentTokenNumber?: number | null;
totalTokens: number;
completedTokens: number;
tokens: DoctorTodayToken[];
}

1489
test-plan-neo.md Normal file

File diff suppressed because it is too large Load diff

145
top_queries.md Normal file
View file

@ -0,0 +1,145 @@
# Top Full-Scan Queries
## 1. Orders with all relations (no filters)
Source: `packages/db_helper_sqlite/src/admin-apis/order.ts:441-499``getAllOrders()`
```sql
select
"id",
"user_id",
"address_id",
"slot_id",
"is_cod",
"is_online_payment",
"payment_info_id",
"total_amount",
"delivery_charge",
"readable_id",
"admin_notes",
"user_notes",
"order_group_id",
"order_group_proportion",
"is_flash_delivery",
"created_at",
(select json_array("id","name","email","mobile","created_at") from (select * from "users" where "users"."id" = "orders"."user_id" limit 1)) as "user",
(select json_array("id","user_id","name","phone","address_line1","address_line2","city","state","pincode","is_default","latitude","longitude","google_maps_url","admin_latitude","admin_longitude","zone_id","created_at") from (select * from "addresses" where "addresses"."id" = "orders"."address_id" limit 1)) as "address",
(select json_array("id","delivery_time","freeze_time","is_active","is_flash","is_capacity_full","delivery_sequence","group_ids") from (select * from "delivery_slot_info" where "delivery_slot_info"."id" = "orders"."slot_id" limit 1)) as "slot",
(select coalesce(json_group_array(json_array("id","order_id","product_id","quantity","price","discounted_price","is_packaged","is_package_verified",(select json_array("id","name","short_description","long_description","unit_id","price","market_price","images","is_out_of_stock","is_suspended","is_flash_available","flash_price","created_at","increment_step","product_quantity","store_id",(select json_array("id","short_notation","full_name") from (select * from "units" where "units"."id" = "orders_orderItems_product"."unit_id" limit 1))) from (select * from "product_info" where "product_info"."id" = "orders_orderItems"."product_id" limit 1)))),json_array()) from "order_items" where "order_items"."order_id" = "orders"."id") as "orderItems",
(select coalesce(json_group_array(json_array("id","order_time","user_id","order_id","is_packaged","is_delivered","is_cancelled","cancel_reason","is_cancelled_by_admin","payment_state","cancellation_user_notes","cancellation_admin_notes","cancellation_reviewed","cancellation_reviewed_at","refund_coupon_id")),json_array()) from "order_status" where "order_status"."order_id" = "orders"."id") as "orderStatus"
from "orders"
where "orders"."id" = "orders"."id"
order by "orders"."created_at" desc
limit ?
```
**Why it's bad**: `where "orders"."id" = "orders"."id"` is an always-true condition — it's a no-op placeholder. When no cursor/slot/filters are provided, this scans ALL orders with 6 levels of nested correlated subqueries, then filters out COD/pending payments in-memory after fetching everything.
---
## 2. Active slots (no date filter)
Source: `packages/db_helper_sqlite/src/admin-apis/slots.ts:32-47``getActiveSlotsWithProducts()`
```sql
select
"id",
"delivery_time",
"freeze_time",
"is_active",
"is_flash",
"is_capacity_full",
"delivery_sequence",
"group_ids",
(select coalesce(json_group_array(json_array("product_id","slot_id",(select json_array("id","name","images") from (select * from "product_info" where "product_info"."id" = "deliverySlotInfo_productSlots"."product_id" limit 1)))),json_array()) from "product_slots" where "product_slots"."slot_id" = "deliverySlotInfo"."id") as "productSlots"
from "delivery_slot_info"
where "deliverySlotInfo"."is_active" = ?
order by "deliverySlotInfo"."delivery_time" desc
```
**Why it's bad**: Only filters `is_active = true`, no `deliveryTime > now` check. Returns past expired slots. Correlated subquery per slot for products.
---
## 3. All slots with products for cache (deep JOINs on every slot)
Source: `packages/db_helper_sqlite/src/stores/store-helpers.ts:236-258``getAllSlotsWithProductsForCache()`
```sql
select
"id",
"delivery_time",
"freeze_time",
"is_active",
"is_flash",
"is_capacity_full",
"delivery_sequence",
"group_ids",
(select coalesce(json_group_array(json_array("product_id","slot_id",(select json_array("id","name","short_description","long_description","unit_id","price","market_price","images","is_out_of_stock","is_suspended","is_flash_available","flash_price","created_at","increment_step","product_quantity","store_id",(select json_array("id","short_notation","full_name") from (select * from "units" where "units"."id" = "deliverySlotInfo_productSlots_product"."unit_id" limit 1)),(select json_array("id","name","description","image_url","created_at","owner") from (select * from "store_info" where "store_info"."id" = "deliverySlotInfo_productSlots_product"."store_id" limit 1))) from (select * from "product_info" where "product_info"."id" = "deliverySlotInfo_productSlots"."product_id" limit 1)))),json_array()) from "product_slots" where "product_slots"."slot_id" = "deliverySlotInfo"."id") as "productSlots"
from "delivery_slot_info"
where ("deliverySlotInfo"."is_active" = ? and "deliverySlotInfo"."delivery_time" > ?)
order by "deliverySlotInfo"."delivery_time" asc
```
**Why it's bad**: Called on every slot lookup (`getSlotById`, `getProductSlots`, `getAllProductsSlots`, `getMultipleProductsSlots`) in `slot-store.ts`. Despite filtering for future active slots, it loads ALL of them with deep nested subqueries (product → unit + store) per product per slot, even when only one slot's data is needed.
---
## 4. Vendor orders (no WHERE at all)
Source: `packages/db_helper_sqlite/src/admin-apis/vendor-snippets.ts:171-187``getVendorOrders()`
```sql
select ... from "orders"
left join "users" ...
left join "order_items" ...
left join "product_info" ...
left join "units" ...
order by "orders"."created_at" desc
```
**Why it's bad**: Absolutely no WHERE clause. Fetches every single order with deep JOINs on every call.
---
## 5. Product availability (reads ALL products)
Source: `packages/db_helper_sqlite/src/user-apis/slots.ts:29-46``getProductAvailability()`
```sql
select "id", "name", "is_out_of_stock", "is_flash_available"
from "product_info"
where "product_info"."is_suspended" = ?
```
**Why it's bad**: Called on every `getSlotsWithProducts` page load. Reads every non-suspended product even though only products in active slots are relevant.
---
## 6. searchUsers (no search = all users, no limit)
Source: `packages/db_helper_sqlite/src/admin-apis/user.ts:197-216``searchUsers()`
```sql
select "id", "name", "mobile" from "users"
-- no WHERE, no LIMIT
```
**Why it's bad**: When called without a search term, returns the entire `users` table with no limit.
---
## 7. All cache initialization helpers
Source: `packages/db_helper_sqlite/src/stores/store-helpers.ts`
| Function | Line | Query |
|----------|------|-------|
| `getAllProductsForCache` | :86 | `select ... from product_info inner join units` — no WHERE |
| `getAllStoresForCache` | :115 | `select ... from store_info` — no WHERE |
| `getAllProductTagsForCache` | :159 | `select ... from product_tags inner join product_tag_info` — no WHERE |
| `getAllTagsForCache` | :187 | `select ... from product_tag_info` — no WHERE |
| `getAllTagProductMappings` | :200 | `select ... from product_tags` — no WHERE |
| `getAllUserNegativityScores` | :269 | `select ... from user_incidents group by user_id` — no WHERE |
**Why it's bad**: Individually these are meant for cache warming, but they're also called on every cache read miss in `product-store.ts`, doing 5 full table scans per product detail page view.

511
web-user-ui.md Normal file
View file

@ -0,0 +1,511 @@
# Web User UI — TanStack Start App
## Overview
Create `apps/web-ui` — a server-rendered web app using TanStack Start, Tailwind CSS, shadcn/ui, and tRPC. It mirrors `apps/user-ui` functionality (auth, products, stores, 1hr delivery, cart, checkout, orders, addresses, coupons, complaints, profile) excluding mobile-only features (notifications, location, haptics, hardware back, status bar) and payment (COD only).
Create `packages/web-components` — a shared web component library with shadcn primitives and domain components used by the web-ui app.
---
## Phase 1: Initialize `packages/web-components`
### 1.1 Create the package
```
packages/web-components/
├── package.json # name: "web-components", private: true
├── tsconfig.json
├── tailwind.config.ts
├── postcss.config.js
├── globals.css # Tailwind base + CSS variables from shadcn
├── components.json # shadcn config
├── src/
│ ├── index.ts # barrel export
│ ├── lib/
│ │ ├── utils.ts # cn() helper (clsx + tailwind-merge)
│ │ └── constants.ts # shared constants
│ ├── components/
│ │ └── ui/ # shadcn primitives (added via CLI)
│ ├── hooks/
│ └── services/
```
### 1.2 Steps
1. Create `packages/web-components/package.json`:
- name: `web-components`
- main: `./src/index.ts`
- types: `./src/index.ts`
- private: true
- peerDependencies: `react`, `react-dom`, `tailwindcss`, `class-variance-authority`, `clsx`, `tailwind-merge`, `lucide-react`
- devDependencies: `typescript`, `@types/react`, `tailwindcss`, `postcss`, `autoprefixer`
2. Create `tsconfig.json`:
- jsx: `react-jsx`
- strict: true
- paths: `@/*` -> `./src/*`
3. Initialize Tailwind:
- `npx tailwindcss init` in the package
- Configure `content` paths to scan `./src/**/*.{ts,tsx}`
4. Initialize shadcn:
- Run `npx shadcn@latest init` inside `packages/web-components`
- Config values:
- style: default
- base color: neutral / slate
- CSS variables: yes
- globals.css path: `./globals.css`
- components path: `./src/components/ui`
- utils path: `./src/lib/utils.ts`
- tailwind config: `./tailwind.config.ts`
5. Install shadcn UI primitives via CLI:
- `npx shadcn@latest add button -p .`
- `npx shadcn@latest add input -p .`
- `npx shadcn@latest add dialog -p .`
- `npx shadcn@latest add card -p .`
- `npx shadcn@latest add badge -p .`
- `npx shadcn@latest add checkbox -p .`
- `npx shadcn@latest add sheet -p .`
- `npx shadcn@latest add separator -p .`
- `npx shadcn@latest add scroll-area -p .`
- `npx shadcn@latest add select -p .`
- `npx shadcn@latest add tabs -p .`
- `npx shadcn@latest add alert-dialog -p .`
- `npx shadcn@latest add avatar -p .`
6. Build domain components in `packages/web-components/src/components/`:
| Component | Web Implementation | Mirrors from `common-ui` |
|-----------|-------------------|--------------------------|
| `my-text.tsx` | `<p>`/`<span>` with Tailwind + variant props | text.tsx |
| `my-button.tsx` | Wraps shadcn `Button` with color variants | button.tsx |
| `my-text-input.tsx` | Wraps shadcn `Input` with label + error | textinput.tsx |
| `my-touchable-opacity.tsx` | `<button>` with hover/active/ripple | touchable-opacity.tsx |
| `loading-dialog.tsx` | shadcn `Dialog` with spinner | loading-dialog.tsx |
| `bottom-dialog.tsx` | shadcn `Sheet` from bottom | dialog.tsx |
| `confirmation-dialog.tsx` | shadcn `AlertDialog` | dialog.tsx |
| `checkbox.tsx` | Wraps shadcn `Checkbox` | checkbox.tsx |
| `search-bar.tsx` | Input with search icon + debounced onChange | search-bar.tsx |
| `data-table.tsx` | HTML `<table>` with Tailwind styling | data-table.tsx |
| `quantifier.tsx` | Quantity +/- stepper control | quantifier.tsx |
| `mini-quantifier.tsx` | Compact quantity stepper | mini-quantifier.tsx |
| `image-viewer.tsx` | Full-screen image modal | image-viewer.tsx |
| `image-uploader.tsx` | File input → preview grid | ImageUploader.tsx |
| `image-uploader-neo.tsx` | File input with usePickImage pattern | ImageUploaderNeo.tsx |
| `image-carousel.tsx` | Horizontal scroll with dot indicators | ImageCarousel.tsx |
| `profile-image.tsx` | Avatar with fallback initials | profile-image.tsx |
| `app-container.tsx` | Max-width centered column layout (scrollable div, no KeyboardAwareScrollView) | app-container.tsx |
| `flat-list.tsx` | CSS grid / flexbox layout | flat-list.tsx |
| `dropdown.tsx` | shadcn `Select` | dropdown.tsx |
| `date-picker.tsx` | Native `<input type="date">` or shadcn popover | date-picker.tsx |
| `google-sign-in.tsx` | OAuth PKCE via expo-auth-session equivalent (web flow) | google-sign-in.tsx |
7. Copy hooks from `packages/ui/hooks/` to `packages/web-components/src/hooks/`:
- `theme-context.tsx` — adapt to CSS variable-based theming (remove react-native dependency)
- `usePagination.tsx` — strip react-native imports, use pure JS
- `useIsDevMode.ts` — use `window.location.hostname` instead of Platform
- `useFocusCallback.ts` — use IntersectionObserver or TanStack hooks
8. Copy services from `packages/ui/src/services/` to `packages/web-components/src/services/`:
- `StorageService.ts` — localStorage only (remove SecureStore branch + Platform check)
- `StorageServiceCasual.ts` — adapt to localStorage only
9. Copy lib files from `packages/ui/src/lib/` as needed:
- `constants.ts` — shared string constants (export same values)
- `theme-colors.ts` — adapt to CSS variable names
- `refresh-context.tsx` — strip react-native imports
---
## Phase 2: Initialize `apps/web-ui`
### 2.1 Bootstrap with TanStack Start
```bash
cd apps
mkdir web-ui && cd web-ui
npm create tanstack-app@latest . -- --start --tailwind --typescript
```
This creates:
- File-based routing in `app/routes/`
- SSR enabled via Vinxi
- Tailwind CSS configured
- TypeScript
- TanStack Router with file conventions
### 2.2 Add additional dependencies
```bash
npm install @trpc/client @trpc/react-query @trpc/server @tanstack/react-query zustand axios dayjs formik yup fuse.js jwt-decode clsx tailwind-merge class-variance-authority lucide-react
npm install -D @types/react
```
### 2.3 Configure path aliases in `tsconfig.json`
```json
{
"compilerOptions": {
"paths": {
"@/*": ["./app/*"],
"web-components": ["../../packages/web-components/src"],
"web-components/*": ["../../packages/web-components/src/*"],
"@packages/shared": ["../../packages/shared"],
"@packages/shared/*": ["../../packages/shared/*"],
"@backend/*": ["../../apps/backend/src/*"]
}
}
}
```
### 2.4 Configure workspace in root `package.json`
Add `"apps/web-ui"` to the `workspaces` array.
### 2.5 Add to `turbo.json`
```json
"web-ui#dev": { "cache": false, "dependsOn": ["^build"] },
"web-ui#build": { "dependsOn": ["^build"], "outputs": ["dist/**"] }
```
### 2.6 Add workspace script to root `package.json`
```json
"web-ui": "bun run --filter web-ui",
"web-ui:dev": "bun run web-ui dev",
"web-ui:build": "bun run web-ui build"
```
---
## Phase 3: Build Core Infrastructure
### 3.1 tRPC Client (`app/lib/trpc-client.ts`)
- Import `AppRouter` type from `@backend/trpc/router` (via path alias)
- `createTRPCReact<AppRouter>()`
- `httpBatchLink` pointing to backend URL (same as user-ui: `BASE_API_URL + '/api/trpc'`)
- Auth token injection: read from localStorage via `StorageService.getAuthToken()`
- No superjson (match user-ui's transport)
### 3.2 Auth Context (`app/lib/auth-context.tsx`)
Same flow as `user-ui/src/contexts/AuthContext.tsx`:
- On mount: check localStorage for stored JWT token
- If found: set `isAuthenticated=true`, fetch user data via `trpc.user.user.getSelfData`
- Provide: `login`, `loginWithToken`, `register`, `logout`, `updateUser`, `updateUserDetails`
- Post-login redirect via stored redirect URL in localStorage
- JWT token management: `getAuthToken()`, `saveAuthToken()`, `deleteAuthToken()` using localStorage
### 3.3 Zustand Stores (`app/lib/stores/`)
Copy and adapt from `user-ui/src/store/`:
| Store | File | Adaptations for Web |
|-------|------|---------------------|
| `addressStore` | `addressStore.ts` | None |
| `appStore` | `appStore.ts` | None |
| `cartStore` | `cartStore.ts` | None |
| `centralProductStore` | `centralProductStore.ts` | Init via loader instead of effect hook |
| `centralSlotStore` | `centralSlotStore.ts` | Init via loader instead of effect hook |
| `navigationStore` | `navigationStore.ts` | None |
| `quickDeliveryStore` | `quickDeliveryStore.ts` | None |
| `flashCartStore` | `flashCartStore.ts` | None |
| `flashNavigationStore` | `flashNavigationStore.ts` | None |
| `slotStore` | `slotStore.ts` | None |
| `storeHeaderStore` | `storeHeaderStore.ts` | None |
### 3.4 Query Client (`app/lib/queryClient.ts`)
Standard `QueryClient` from `@tanstack/react-query` with same defaults as user-ui.
### 3.5 API Hooks (`app/lib/prominent-api-hooks.ts`)
Copy from `user-ui/src/hooks/prominent-api-hooks.ts`:
- `useGetEssentialConsts()` — tRPC query with 60s refetch
- `useAllProducts()` — Axios get cached products JSON
- `useStores()` — Axios get cached stores JSON
- `useSlots()` — Axios get cached slots JSON
- `useBanners()` — Axios get cached banners JSON
- `useStoreWithProducts(storeId)` — Axios get store-specific JSON
The `BASE_API_URL` pointed to same backend URL.
### 3.6 Cart Query Hooks (`app/lib/cart-query-hooks.ts`)
Copy from `user-ui/hooks/cart-query-hooks.tsx`:
- `useGetCart(cartType)` — localStorage-based cart
- `useAddToCart(cartType)` — add item, invalidate query
- `useUpdateCartItem(cartType)` — update quantity
- `useRemoveFromCart(cartType)` — remove item
- `clearLocalCart(cartType)` — clear all
### 3.7 Other Hooks (`app/lib/hooks/`)
| Hook | Source | Notes |
|------|--------|-------|
| `useJWT.ts` | `user-ui/hooks/useJWT.ts` | Adapt to localStorage instead of SecureStore |
| `useCurrentUserId.ts` | `user-ui/hooks/useCurrentUserId.ts` | Decode JWT, same logic |
| `useUploadToObjectStore.ts` | `user-ui/hooks/useUploadToObjectStore.ts` | Same presigned URL upload logic |
| `useHideTabNav.ts` | `user-ui/src/hooks/useHideTabNav.ts` | Adapt to web scroll-based hiding |
### 3.8 Shared Types
Import types from `@packages/shared` directly (already in monorepo).
---
## Phase 4: Build Routes
### 4.1 Root Layout (`app/routes/__root.tsx`)
Provider hierarchy:
```
<QueryClientProvider client={queryClient}>
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<AuthProvider>
<RefreshProvider queryClient={queryClient}>
<CentralStoreInitializer>
<Outlet />
</CentralStoreInitializer>
</RefreshProvider>
</AuthProvider>
<Toast /> ← react-hot-toast or sonner (toast library)
</trpc.Provider>
</QueryClientProvider>
```
Removed compared to user-ui's root layout:
- ❌ MyStatusBar — not needed on web
- ❌ SafeAreaProvider/SafeAreaView — use CSS safe area env()
- ❌ UpdateChecker — not relevant for web
- ❌ HealthTestWrapper — version enforcement not needed
- ❌ WebViewWrapper — no webview overlay
- ❌ FirstUserWrapper — no first-time user flow
- ❌ NotificationProvider + NotifChecker — no push notifications
- ❌ PaperProvider — using shadcn instead
- ❌ LocationTestWrapper — no location checks
- ❌ BackHandlerWrapper — no hardware back button
- ❌ ThemeProvider (react-navigation) — use Tailwind dark mode instead
### 4.2 Route Map (Flattened)
| Route | Source (user-ui) | Description |
|-------|------------------|-------------|
| `/` | `app/index.tsx` | Redirect to `/home` |
| `/login` | `app/(auth)/login.tsx` | 3-step OTP/password login |
| `/register` | `app/(auth)/register.tsx` | Registration form |
| `/home` | `app/(drawer)/(tabs)/home/index.tsx` | Dashboard: stores carousel, banners, products grid, search bar, floating cart bar |
| `/home/cart` | `app/(drawer)/(tabs)/home/cart.tsx` | Cart page |
| `/home/checkout` | `app/(drawer)/(tabs)/home/checkout.tsx` | COD-only checkout with address selector, slot picker, order summary |
| `/home/order-success` | `app/(drawer)/(tabs)/home/order-success.tsx` | Order placed success screen |
| `/home/product/$id` | `app/(drawer)/(tabs)/home/product-detail/[id].tsx` | Product detail with reviews |
| `/home/search` | `app/(drawer)/(tabs)/home/search-results/index.tsx` | Live search with Fuse.js |
| `/flash` | `app/(drawer)/(tabs)/flash-delivery/(products)/index.tsx` | 1hr delivery product listing |
| `/flash/cart` | `app/(drawer)/(tabs)/flash-delivery/(cart)/cart.tsx` | Flash delivery cart |
| `/flash/checkout` | `app/(drawer)/(tabs)/flash-delivery/checkout.tsx` | Flash delivery checkout (COD only) |
| `/flash/order-success` | `app/(drawer)/(tabs)/flash-delivery/order-success.tsx` | Flash delivery success |
| `/flash/product/$id` | `app/(drawer)/(tabs)/flash-delivery/product-detail/[id].tsx` | Flash product detail |
| `/stores` | `app/(drawer)/(tabs)/stores/index.tsx` | Store listing |
| `/stores/$storeId` | `app/(drawer)/(tabs)/stores/store-detail/[id].tsx` | Store products with tag filters |
| `/stores/$storeId/product/$productId` | `app/(drawer)/(tabs)/stores/store-detail/product-detail/[id].tsx` | Store product detail |
| `/me` | `app/(drawer)/(tabs)/me/index.tsx` | Account menu (orders, addresses, coupons, complaints, profile, about) |
| `/me/orders` | `app/(drawer)/(tabs)/me/my-orders/index.tsx` | Order list with infinite scroll |
| `/me/orders/$id` | `app/(drawer)/(tabs)/me/my-orders/[id].tsx` | Order detail with cancel/notes/complaint |
| `/me/addresses` | `app/(drawer)/(tabs)/me/addresses/index.tsx` | Address CRUD |
| `/me/coupons` | `app/(drawer)/(tabs)/me/coupons/index.tsx` | Coupon list + redeem |
| `/me/complaints` | `app/(drawer)/(tabs)/me/complaints/index.tsx` | Complaint list + raise |
| `/me/edit-profile` | `app/(drawer)/(tabs)/me/edit-profile/index.tsx` | Edit profile |
| `/me/about` | `app/(drawer)/(tabs)/me/about/index.tsx` | About page |
| `/me/terms` | `app/(drawer)/(tabs)/me/terms/index.tsx` | Terms & Conditions |
### 4.3 Navigation Layout (`app/components/nav-layout.tsx`)
Bottom navigation bar:
- Home, Stores, Me tabs
- 1hr Delivery as a prominent center button (matching mobile's FAB style)
- Active state highlighting
- Responsive: bottom bar on mobile, sidebar on desktop (optional enhancement)
Wraps all /home, /flash, /stores, /me routes.
### 4.4 Page Components (common-ui → web-components migration)
Rebuild in `app/components/` using web-components:
| Component | Replaces from user-ui |
|-----------|-----------------------|
| `ProductCard.tsx` | Card with image, name, price, add-to-cart |
| `CartPage.tsx` | Cart items with quantifiers, totals, proceed to checkout |
| `CheckoutPage.tsx` | Address selector, slot picker, order summary, place order button (COD only) |
| `CheckoutAddressSelector.tsx` | Address selection in checkout |
| `AddressForm.tsx` | Formik + yup address form |
| `ProductDetail.tsx` | Full product detail with reviews |
| `SlotProducts.tsx` | Slot-specific product grid |
| `StoreProducts.tsx` | Store product grid with tag filters |
| `OrderMenu.tsx` | Order actions (cancel, notes, complaint) |
| `BannerCarousel.tsx` | Image banners carousel |
| `FloatingCartBar.tsx` | Sticky bottom cart summary bar |
| `TermsAndConditionsContent.tsx` | Static terms content |
| `RegistrationForm.tsx` | Registration form fields |
| `NextOrderGlimpse.tsx` | Next upcoming order summary |
| `TabLayoutWrapper.tsx` | Wrapper for screen with optional tabs |
| `FlashDeliveryProducts.tsx` | Flash-eligible products list |
| `UserAddressHeader.tsx` | Collapsed address display |
### 4.5 Payment — Omitted Entirely
- No Razorpay integration
- No PhonePe integration
- Checkout defaults to COD without a payment method selector
- Order status: placed directly without payment flow
- `placeOrder` mutation sends `paymentMethod: 'cod'` always
---
## Phase 5: Wrappers Removed (vs user-ui)
| user-ui Wrapper | Reason Removed |
|----------------|---------------|
| ThemeProvider (react-navigation) | Using Tailwind CSS variables + dark mode |
| SafeAreaProvider / SafeAreaView | Handled by CSS `env(safe-area-inset-*)` |
| MyStatusBar | No status bar on web |
| UpdateChecker (expo-updates) | Not applicable to web |
| HealthTestWrapper | Version enforcement not needed |
| WebViewWrapper | No webview overlay needed |
| FirstUserWrapper | No first-time user flow needed |
| NotificationProvider | No push notifications on web |
| NotifChecker | No push token registration |
| PaperProvider | Using shadcn/ui instead |
| LocationTestWrapper | No location checks |
| BackHandlerWrapper | No hardware back button on web |
| CentralStoreInitializer | **KEPT** — needed for product/slot store hydration |
---
## Phase 6: tRPC Integration
### 6.1 Client Setup
Direct `httpBatchLink` to the existing backend URL. No proxy needed — the backend CORS config already allows web origins (currently allows `localhost:5174` and `ui.freshyo.in`; add web-ui dev URL).
```typescript
const trpcClient = trpc.createClient({
links: [
httpBatchLink({
url: `${BASE_API_URL}/api/trpc`,
headers: async () => {
const token = await getAuthToken()
return token ? { Authorization: `Bearer ${token}` } : {}
},
}),
],
})
```
### 6.2 Used Procedures (same as user-ui)
| Category | Procedures |
|----------|-----------|
| Auth | `trpc.user.auth.login`, `.sendOtp`, `.verifyOtp`, `.register`, `.updateProfile`, `.deleteAccount` |
| User | `trpc.user.user.getSelfData`, `.checkProfileComplete` |
| Address | `trpc.user.address.getUserAddresses`, `.getDefaultAddress`, `.createAddress`, `.updateAddress`, `.deleteAddress` |
| Order | `trpc.user.order.getOrders`, `.getOrderById`, `.getRecentlyOrderedProducts`, `.placeOrder`, `.cancelOrder`, `.updateUserNotes` |
| Product | `trpc.user.product.getProductDetails`, `.getProductReviews`, `.createReview` |
| Coupon | `trpc.user.coupon.getEligible`, `.getMyCoupons`, `.redeemReservedCoupon` |
| Complaint | `trpc.user.complaint.getAll`, `.raise` |
| Cart | `trpc.user.cart.getCartSlots` |
| File | `trpc.user.fileUpload.generateUploadUrls`, `trpc.common.generateUploadUrls` |
| Common | `trpc.common.healthCheck`, `.essentialConsts`, `.getStoresSummary` |
### 6.3 Caching Strategy
Same as user-ui:
- Products, stores, slots, banners: fetched via Axios from cached JSON files on CDN/assets domain
- tRPC used for dynamic data (auth, orders, addresses, etc.)
---
## Phase 7: Implementation Order
| Step | Task | Depends On |
|------|------|-----------|
| 1 | Create `packages/web-components/` structure, package.json, tsconfig | — |
| 2 | Initialize Tailwind + shadcn CLI in web-components | 1 |
| 3 | Install shadcn primitives (button, input, dialog, etc.) | 2 |
| 4 | Build domain components in web-components | 3 |
| 5 | Copy hooks + services to web-components | 1 |
| 6 | Bootstrap `apps/web-ui` with TanStack Start | — |
| 7 | Add dependencies, configure tsconfig, workspace, turbo | 6 |
| 8 | Build tRPC client, auth context, zustand stores | 7 |
| 9 | Build query client, API hooks, cart hooks | 7 |
| 10 | Build root layout with all providers | 8 |
| 11 | Build login + register routes | 8, 9 |
| 12 | Build home dashboard route | 9, 4 |
| 13 | Build 1hr delivery (flash) routes | 9, 4 |
| 14 | Build stores routes | 9, 4 |
| 15 | Build cart + checkout (COD only) | 9, 4 |
| 16 | Build me section (orders, addresses, coupons, complaints, profile) | 8, 9 |
| 17 | Build about + terms routes | 8 |
| 18 | Add navigation layout (bottom bar) | 10 |
| 19 | Test end-to-end with existing backend | all |
---
## Files That Don't Need Migration
| user-ui File | Reason Not Needed |
|-------------|------------------|
| `services/notif-service/` | Push notifications not supported on web |
| `services/toaster.ts` | Replace with sonner/react-hot-toast |
| `src/components/LocationAttacher.tsx` | No location attach on web |
| `components/LocationTestWrapper.tsx` | No location checks |
| `components/HealthTestWrapper.tsx` | No version enforcement |
| `components/WebViewWrapper.tsx` | No webview overlay |
| `components/UpdateChecker.tsx` | No expo-updates |
| `components/BackHandler.tsx` | No hardware back |
| `components/FirstUserWrapper.tsx` | No first-time user splash |
| `app/api/auth/` | OAuth handled differently on web (no expo-router API routes needed) |
| `src/components/MyStatusBar.tsx` | No status bar concepts on web |
| `eas.json`, `app.json`, `expo-env.d.ts` | Expo-specific config files |
| `metro.config.js` | Metro not used; TanStack uses Vite |
| `assets/` | Static assets handled differently in web builds |
| `google-services.json` | Android-specific |
---
## State Flow Notes
### Auth Flow (same as user-ui)
1. App mounts → `AuthProvider.init()` checks localStorage for JWT
2. If token found: fetch user data via `trpc.user.user.getSelfData`
3. Login: OTP or password → receive token → store in localStorage → set auth state
4. Registration: collect details → register via tRPC → auto-login
5. Logout: clear localStorage → reset auth state → redirect to `/login`
### Cart Flow (same as user-ui)
- Cart stored in localStorage (`StorageServiceCasual`)
- Uses TanStack Query with key `local-cart-{cartType}`
- Add/update/remove operations invalidate query to trigger re-render
- No server-side cart — purely local
### Checkout Flow (COD only, no payment)
1. User selects address (from saved addresses or adds new)
2. User selects delivery slot per store
3. User reviews order summary (items, total, delivery charge)
4. User clicks "Place Order" → `trpc.user.order.placeOrder` with `paymentMethod: 'cod'`
5. On success → clear cart → navigate to order-success page with order ID
### Product/Slot Caching (same as user-ui)
- Products, stores, slots cached JSON fetched via Axios from CDN
- `centralProductStore` keeps all products in memory with `productsById` map
- `centralSlotStore` keeps slots with per-product availability
- Refetch functions stored in stores, triggered by pull-to-refresh