freshyo/web-user-ui.md
2026-05-10 11:06:57 +05:30

22 KiB

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

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

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

{
  "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

"web-ui#dev": { "cache": false, "dependsOn": ["^build"] },
"web-ui#build": { "dependsOn": ["^build"], "outputs": ["dist/**"] }

2.6 Add workspace script to root package.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).

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