Compare commits
2 commits
adb81a6b3d
...
3ebbf989f0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ebbf989f0 | ||
|
|
be71f8eaa1 |
12 changed files with 108 additions and 2565 deletions
|
|
@ -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
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
- 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 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,7 +92,13 @@ 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 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
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
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
|
||||
|
|
@ -103,3 +109,11 @@ 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
|
||||
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -4,18 +4,6 @@ import { getStaffUserById, isUserSuspended } from '@/src/dbService';
|
|||
import { ApiError } from '@/src/lib/api-error';
|
||||
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) => {
|
||||
try {
|
||||
const authHeader = c.req.header('authorization');
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ routes = [
|
|||
|
||||
[[d1_databases]]
|
||||
binding = "DB"
|
||||
database_name = "freshyo-backend-dev"
|
||||
database_id = "b2528bdb-0ec0-478e-8765-b118918153c0"
|
||||
#database_name = "freshyo-dev"
|
||||
#database_id = "3cad440f-dc14-4baa-ab05-04eb08e206de"
|
||||
#database_name = "freshyo-backend-dev"
|
||||
#database_id = "b2528bdb-0ec0-478e-8765-b118918153c0"
|
||||
database_name = "freshyo-dev"
|
||||
database_id = "3cad440f-dc14-4baa-ab05-04eb08e206de"
|
||||
migrations_dir="../../packages/db_helper_sqlite/drizzle"
|
||||
migrations_pattern="migration.sql"
|
||||
[durable_objects]
|
||||
|
|
|
|||
|
|
@ -604,3 +604,91 @@ KEPT (per instruction / verification): §4 routes & affordances (rebalance-order
|
|||
Verification:
|
||||
- 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.
|
||||
|
||||
[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.
|
||||
|
|
|
|||
|
|
@ -21,12 +21,6 @@ export interface Coupon {
|
|||
createdBy: number;
|
||||
}
|
||||
|
||||
export interface ReservedCoupon extends Coupon {
|
||||
secretCode: string;
|
||||
redeemedUserId: number | null;
|
||||
redeemedAt: Date | null;
|
||||
}
|
||||
|
||||
export interface CouponValidationResult {
|
||||
valid: boolean;
|
||||
message?: string;
|
||||
|
|
|
|||
|
|
@ -336,21 +336,6 @@ export interface UserSlotAvailability {
|
|||
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 {
|
||||
id: number;
|
||||
deliveryTime: Date;
|
||||
|
|
|
|||
|
|
@ -1,275 +1,3 @@
|
|||
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 {
|
||||
couponCodes: string[];
|
||||
discountPercent?: number;
|
||||
|
|
@ -286,48 +14,3 @@ export interface CreateCouponPayload {
|
|||
applicableProducts?: number[];
|
||||
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';
|
||||
|
|
@ -2,11 +2,6 @@ import React from "react";
|
|||
import * as DocumentPicker from "expo-document-picker";
|
||||
import * as ImagePicker from "expo-image-picker";
|
||||
|
||||
interface BaseProps {
|
||||
label?: string;
|
||||
multiple: boolean;
|
||||
}
|
||||
|
||||
// interface Props {
|
||||
// setFile: (file: DocumentPicker.DocumentPickerAsset | null) => void;
|
||||
// label?: string;
|
||||
|
|
@ -19,22 +14,6 @@ type Props = {
|
|||
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) {
|
||||
// const { setFile } = props;
|
||||
const handlePickFile = async () => {
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
// 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
1489
test-plan-neo.md
File diff suppressed because it is too large
Load diff
145
top_queries.md
145
top_queries.md
|
|
@ -1,145 +0,0 @@
|
|||
# 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
511
web-user-ui.md
|
|
@ -1,511 +0,0 @@
|
|||
# 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
|
||||
Loading…
Add table
Reference in a new issue