# Handoff Project: `ina-trading-web` Current branch: `main` Latest verified commit: `f090ba7` ## Summary This codebase has recent updates around auth/onboarding, help/privacy pages, dashboard search, product creation/edit/review/detail, admin review/detail, stock/price editing, seller in-review listing, backend request logging, expired-session redirects, product keyword limits, sanitized backend error display, AddProductRequest validation, and 10 MB upload limits. The latest build was verified successfully with: ```bash npm run build ``` Latest TypeScript verification after the validation/upload changes: ```bash npx tsc --noEmit ``` ## Latest Codex Changes After `76fb4f3` ### Product create preorder and warranty submit rules Files: - `src/app/(dashboard)/products/new/details/page.tsx` - `src/app/(dashboard)/products/new/specifications/page.tsx` - `src/app/(dashboard)/products/new/review/page.tsx` - `src/lib/product-draft.tsx` - `src/lib/use-product-submit.ts` - `src/lib/product-request-validation.ts` Behavior: - Pre-order day placeholder is now `0`, not `14`, so an empty/unfilled value is visually obvious. - If pre-order is checked, the Details step blocks Next unless `preOrderDay` is a valid number greater than or equal to 1. - Warranty now has an On/Off switch in the Specifications step. - When warranty is On: - warranty type is required - warranty duration must be greater than or equal to 1 - the review page shows warranty information - submit payload sends the filled `warrantyInformation` object - When warranty is Off: - warranty inputs are disabled and dimmed - the review page hides warranty information - submit payload still sends `warrantyInformation` as an object required by the backend, with: ```json { "type": null, "duration": null, "durationType": "MONTH" } ``` - Frontend payload validation treats the above null-valued warranty object as warranty Off, so it does not block submit with warranty type/duration errors. - Local test server was rebuilt and restarted against `https://api-dev.inatrading.co.id` in `.env.local`; `.env.local` is not committed. ### AddProductRequest validation Files: - `src/lib/product-request-validation.ts` - `src/lib/use-product-submit.ts` - `src/app/(dashboard)/products/[productId]/edit/page.tsx` - `src/app/(dashboard)/products/new/details/page.tsx` - `src/app/(dashboard)/products/new/pricing/page.tsx` - `src/app/(dashboard)/products/new/specifications/page.tsx` - `src/lib/product-draft.tsx` Behavior: - Added a shared validator for create/edit product payloads before requests are sent to backend. - Validates the backend `AddProductRequest` limits for: - basic product fields - image/file IDs - keywords max 3 and features max 5 - model fields, currencies, SKU, package fields - numeric weight/dimension/package fields max 9 digits plus 1 decimal - warehouse IDs max 50 and stock minimal 1 - measurement fields, measurement warehouses, and measurement stock - product/category information param name/value limits - compliance information limits - warranty type max 50 and duration minimal 1 when warranty is filled - Existing wizard drafts normalize keywords to max 3 and features to max 5. - Create-product UI now shows visible validation-limit panels on: - Details - Pricing - Specifications - Relevant inputs also received direct hints/limits such as max product name length, keyword/feature limits, pre-order minimum, warranty minimum, and upload limit text. ### Upload file size limit Files: - `src/lib/upload-limits.ts` - `src/app/api/upload/route.ts` - `src/components/upload-field.tsx` - `src/app/(dashboard)/products/new/specifications/page.tsx` - `src/app/(dashboard)/products/[productId]/edit/page.tsx` - `src/app/(onboarding)/onboarding/business/page.tsx` Behavior: - Uploads are limited to 10 MB per file. - Client-side checks reject oversized files before upload in shared upload fields, product create/edit document uploads, and onboarding legal document upload. - Server-side `/api/upload` rejects oversized files with HTTP `413` and a safe `responseDesc`. - User-facing helper text now mentions the 10 MB limit for document uploads. ## Previous Codex Changes After `aa406f5` ### Backend URL Local `.env.local` now points to: ```bash NEXT_PUBLIC_API_URL=https://api.inatrading.co.id ``` The previous local value was `https://be.inatrading.co.id`. ### Expired session / access denied handling Files: - `src/components/auth-session-guard.tsx` - `src/app/layout.tsx` Behavior: - A global client guard wraps local `/api/*` fetch calls, excluding `/api/auth/*`. - If a response is `401`, `403`, or contains auth-style error text such as: - `access denied` - `unauthorized` - `session expired` - `token expired` - `invalid token` - It clears `token` and `role` from `localStorage` and `sessionStorage`, then redirects to `/login`. ### Product create keyword limit Files: - `src/app/(dashboard)/products/new/details/page.tsx` - `src/lib/product-draft.tsx` - `src/lib/use-product-submit.ts` - `src/lib/translations/id.ts` - `src/lib/translations/en.ts` Behavior: - Create-product search keywords are limited to 3. - The keyword input and add button are disabled after 3 keywords. - Existing saved wizard drafts are normalized to a maximum of 3 keywords when loaded from `sessionStorage`. - Submit payload uses only the first 3 keywords as a backend guard. ### Sanitized backend error display Files: - `src/lib/error-message.ts` - `src/lib/use-product-submit.ts` - `src/components/upload-field.tsx` - `src/app/(dashboard)/products/new/review/page.tsx` - `src/app/(dashboard)/products/[productId]/edit/page.tsx` - plus updated callers across dashboard, admin, settings, onboarding, upload, news, places, categories, and warehouse forms Behavior: - Raw JSON request/response error logs are no longer shown to users. - `Copy Error Log` UI was removed from create-product review and edit-product save errors. - `getBackendErrorMessage()` extracts a user-safe message from: - `responseDesc` - `message` - `error` - `details` - nested `data` - JSON-looking strings are parsed for a useful message; if none is found, the UI falls back to the local generic error. - Long text messages are capped to avoid large raw dumps in the UI. ### Verification notes - `npx tsc --noEmit` passed after the latest changes. - `npm run lint` still has pre-existing unrelated errors in: - `src/app/(dashboard)/dashboard/page.tsx` - `src/app/(dashboard)/layout.tsx` - Local dev server was running at `http://localhost:3000`. ## Current Local Changes After `7e6446b` These are the important local changes made after the last recorded commit. The latest local build was verified successfully with `npm run build`, and the production local server was restarted on `http://localhost:3000`. ### Admin review compare flow File: - `src/app/admin/review/[productId]/page.tsx` Behavior: - Product update review now uses the compare API as the source of truth: - frontend proxy: `GET /api/admin/review/{productId}/compare` - backend: `GET /api/v1.0/product/compare/{productId}` - The compare payload is parsed from rows like: - `productImages[id=...].image` - `productFiles[id=...].fileId` - `productModels[sku=...].price` - `categoryInformations[paramName=Design].paramValue` - `productFeatures[3]` - nested objects such as `complianceInformation.safetyWarning` - The page reconstructs: - `oldProduct` from `oldValue` - `newProduct` from `newValue` - The compare layout is now section-paired, not product-paired: - `Gambar Produk (Diajukan)` - `Gambar Produk (Live Saat Ini)` - `Detail Produk (Diajukan)` - `Detail Produk (Live Saat Ini)` - and so on for features, keywords, info, files, models, compliance, warranty - Empty section pairs are hidden. If both proposed and live sections have no displayable content, nothing is rendered. - `Status` is not shown in the compare `Detail Produk` section and no longer marks the section as updated. - Compare image thumbnails are rendered as small square thumbnails with `object-contain`, so images are visible in full instead of cropped into wide banners. - Approve/reject for update reviews uses the resolved review/action id from compare, when available. Reference test data: - `/Users/wirabasalamah/Downloads/json compare.txt` - Example compare API: - `/api/v1.0/product/compare/ba70a8e3-2342-4cd3-a7c7-a0f1b58689ab` ### Image URL handling Files: - `src/lib/image-url.ts` - `src/components/product-variant-showcase.tsx` - `src/app/(dashboard)/products/[productId]/detail/page.tsx` - `src/app/admin/products/[productId]/page.tsx` - `src/app/admin/review/[productId]/page.tsx` - `src/app/admin/review/page.tsx` - `src/app/admin/news/new/page.tsx` - `src/app/admin/news/[newsId]/edit/page.tsx` - `src/app/admin/places/PlaceForm.tsx` Behavior: - Added shared image URL helper: - if backend already provides an image URL, use it as-is - otherwise build a fallback URL from `imageId` - default fallback uses `/api/v1.0/file/image/tmp/{imageId}` - Seller/admin product details and admin review now use backend `image` fields before building URLs. - Product variant showcase now supports `image` on product images and model images. ### Product create/edit thumbnail persistence Files: - `src/app/(dashboard)/products/new/details/page.tsx` - `src/app/(dashboard)/products/new/pricing/page.tsx` - `src/app/(dashboard)/products/new/review/page.tsx` - `src/app/api/file/image/[...path]/route.ts` Behavior: - Added local API proxy for images: - `GET /api/file/image/{path}` - forwards to backend `/api/v1.0/file/image/{path}` with auth headers - Create product step 2 and step 3 reload thumbnails from saved file ids when navigating forward/back. - Create product final review shows real thumbnails for visual identity and model images. - Review page was made full width and now displays model image and measurement details more completely. ### Product create submit debug logging File: - `src/app/api/products/create/route.ts` Behavior: - When `DEBUG_BACKEND_PROXY=true`, create product submit writes `product-create-submit-log.json`. - The log captures: - local endpoint - backend endpoint - redacted headers - full request payload - backend response Current known finding from latest capture: - Payload sent top-level `imageId` and separate `productImages`. - Backend review detail returned top-level `imageId` from `productImages[0]`, not from request top-level `imageId`. - Payload sent `productFiles`, but review detail returned `productFiles: []` in the checked response. ### Dashboard seller metrics File: - `src/app/api/dashboard/seller/route.ts` Behavior: - Fixed parsing of scalar metrics from backend payloads where `data` is an object like `{ total: 96 }`. - Verified for Pendopo account that dashboard total products can resolve to non-zero. ### Admin product list/detail Files: - `src/app/admin/products/page.tsx` - `src/app/admin/products/[productId]/page.tsx` - `src/app/api/admin/products/route.ts` Behavior: - Admin product list now renders product thumbnails in `All Product` and `Deleted` tabs. - Thumbnail uses the shared image URL helper. - Admin product detail now resolves main category by looking up categories/subcategories when backend omits `subCategory.category.name`. - Admin All Product endpoint was tested with: - `GET /api/v1.0/admin/product?page=1&size=20` - Backend returned: - HTTP `400` - body `{"responseCode":"000001","data":null,"responseDesc":"Bad Request"}` - Current proxy behavior: - tries `/api/v1.0/admin/product` - if non-deleted tab and the admin endpoint fails, falls back to `/api/v1.0/product` - Deleted endpoint remains: - `GET /api/v1.0/admin/deleted/product?page={page}&size={size}` - Server log showed request reached backend with admin token and failed in about 3 ms, suggesting request validation/contract mismatch rather than auth or DB latency. ### Seller profile save flow Files: - `src/app/(dashboard)/settings/page.tsx` - `src/app/api/seller/profile/route.ts` Behavior: - Seller profile edit saves via: - frontend: `PUT /api/seller/profile` - backend: `PUT /api/v1.0/seller/store` - Payload: - `storeName` - `storeBiography` - optional `imageId` when seller avatar was uploaded - optional `storeImageId` when store image was uploaded Example payload: ```json { "storeName": "...", "storeBiography": "...", "imageId": "optional-file-id", "storeImageId": "optional-file-id" } ``` ## Recent Commits - `7e6446b` `Refine seller onboarding and product review flows` - `e9a0cd0` `Update product review, measurement, and backend logging flows` - `b266047` `Fix TypeScript build errors in product detail and admin review` ## Main Changes ### 1. Auth, help, and onboarding Files: - `next.config.ts` - `src/app/(onboarding)/layout.tsx` - `src/app/(auth)/login/page.tsx` - `src/app/(auth)/register/verify/page.tsx` - `src/app/(auth)/forgot-password/page.tsx` - `src/app/(auth)/account-not-found/page.tsx` - `src/app/help/page.tsx` - `src/app/privacy/page.tsx` - `src/app/terms/page.tsx` Behavior: - Seller onboarding is enabled again - Seller login redirects incomplete seller profiles to onboarding - Register verify flow redirects seller to onboarding instead of dashboard - Forgot password support link opens default mail app to `admin@inatrading.co.id` - Forgot password `?` button links to public `/help` - Account-not-found `Bantuan`, `Kebijakan Privasi`, and `Syarat & Ketentuan` link to public pages - Login `Remember me` now stores email and password in localStorage ### 2. Onboarding seller final submit flow Files: - `src/app/(onboarding)/onboarding/business/page.tsx` - `src/app/(onboarding)/onboarding/store-detail/page.tsx` - `src/app/api/seller/route.ts` - `src/app/api/seller/store/route.ts` - `src/app/api/warehouses/route.ts` Behavior: - Plan step was removed from seller onboarding UI flow - `Warehouse Type` is hidden in UI and forced to `INA` - Final onboarding submit is not a single endpoint. It calls, in order: 1. `POST /api/seller` -> host `POST /api/v1.0/seller` 2. `PUT /api/seller/store` -> host `PUT /api/v1.0/seller/store` 3. `GET /api/products/warehouses?page=1&size=100` 4. `PUT /api/warehouses/{id}` or `POST /api/warehouses` - Frontend tries to reuse backend autogenerated warehouse if found, to avoid duplicate warehouse creation ### 3. Backend proxy logging Files: - `src/lib/backend-fetch-logger.ts` - `src/instrumentation.ts` - `.env.local` uses `DEBUG_BACKEND_PROXY=true` locally Behavior: - Logs server-side `fetch` requests to backend - Logs request method, URL, headers, body preview - Logs response status, duration, headers, body preview - Redacts authorization header Use this when tracing frontend-to-backend failures in local dev. ## 4. Product measurement mode on add product Files: - `src/app/(dashboard)/products/new/pricing/page.tsx` - `src/lib/use-product-submit.ts` - `src/app/(dashboard)/products/new/review/page.tsx` Behavior: - Each model has a `Product Measurement` switch - When enabled: - nested measurements are shown - model-level price, currency, weight, dimensions, promotion, packaging, and warehouse stock are disabled - at least 1 measurement row is required - Submit payload uses `model.hasMeasurements` - When measurement mode is active, model-level numeric fields are zeroed and model warehouses are omitted ## 5. Product measurement mode on edit product File: - `src/app/(dashboard)/products/[productId]/edit/page.tsx` Behavior: - Matches add-product measurement behavior - Uses explicit `hasMeasurements` state instead of guessing from array length - Disables model-level fields when measurement mode is on ## 6. Seller product review page improvements File: - `src/app/(dashboard)/products/new/review/page.tsx` Behavior: - Review now renders actual product thumbnails using backend file URLs - Previously it only showed image indicators, which was confusing - Review also reflects measurement-based pricing correctly ## 7. Product list page improvements File: - `src/app/(dashboard)/products/page.tsx` - `src/lib/product-variants.ts` Behavior: - Edit stock/price modal supports: - choosing model - choosing measurement if present - choosing warehouse - Warehouse labels use warehouse names from master warehouse API, not raw UUIDs - Product row status badges now show explicit state like active, draft, review, unpublished, deleted, rejected - Seller `In Review` listing now uses host endpoint `/api/v1.0/seller/product/review` - If list API returns `minPrice = 0` and `maxPrice = 0`, frontend hydrates that row by fetching product detail and calculating effective price from model/measurement data Important: - This fallback exists because some list rows from backend do not include valid min/max price for measurement-based products - Example verified case: product `Pembersih Mobil` returned `0/0` in list but `IDR 50.000` in detail measurement ## 8. Product detail page improvements File: - `src/app/(dashboard)/products/[productId]/detail/page.tsx` - `src/components/product-variant-showcase.tsx` - `src/lib/product-variants.ts` Behavior: - Main category now resolves even when backend omits `subCategory.category.name` - Warehouse display now uses warehouse master names instead of UUID slices - TypeScript nullability issue for category resolution was fixed here - Seller detail, admin detail, and admin review now share the same variant presentation logic - Effective price rules: - no measurement: use model price - with measurement: use measurement price - multiple measurements: show min-max from measurements - multiple models: show min-max from effective model prices - Variant showcase now supports: - product-level summary price/weight/dimension - model selector - measurement selector - selected variant detail - model image thumbnail switching ## 9. Admin review page improvements File: - `src/app/admin/review/[productId]/page.tsx` Behavior: - Review supports model + measurement structures - New product review and compare review now handle measurement-driven price/weight/dimension/promo/stock - Compare view highlights updated sections using backend compare response and `isUpdate` - Product images are rendered using main image + gallery images - TypeScript issue around `row.field` nullability was fixed here - Single-product review view now follows admin product detail layout more closely - Compare view stays separate only for product update review - Approve/reject action flow: - UI `POST /api/admin/review/{productId}` - new create accept -> host `POST /api/v1.0/product/accept/{productId}` with `{ "state": "PUBLISHED" }` - update accept -> host `PUT /api/v1.0/product/accept/{productId}` with `{ "state": "PUBLISHED" }` - create reject -> host `POST /api/v1.0/product/reject/{productId}` - update reject -> host `PUT /api/v1.0/product/reject/{productId}` - Review approve does not resubmit full product payload. It only submits action and state. ## 10. Dashboard shell and search Files: - `src/app/(dashboard)/layout.tsx` - `src/app/(dashboard)/dashboard/page.tsx` Behavior: - Bell and message icons are intentionally grayed/disabled because no backend function exists yet - Dashboard search now resolves query results across dashboard content instead of behaving like a dead input ## 11. Sidebar submenu reliability Files: - `src/components/product-submenu-nav.tsx` - `src/components/admin-product-submenu-nav.tsx` Behavior: - Click behavior was hardened because submenu items were sometimes not navigating - Navigation is explicit via `router.push(...)` ## Translation Files Touched - `src/lib/translations/id.ts` - `src/lib/translations/en.ts` These include labels added for statuses and stock/price modal UI. ## Important Findings ### Product image inconsistency on `Pembersih Mobil` Verified seller account: - `Pendopo@Pendopo.com` - password: `password` Product: - `Pembersih Mobil` - ID: `92a8fae0-d067-4c90-be24-da31012aeaf9` Verified backend behavior: - list endpoint row returned `minPrice: 0`, `maxPrice: 0` - detail endpoint returned one measurement priced at `IDR 50.000` - detail endpoint currently returns: - `imageId: null` - `productImages: []` - `model.imageId: null` Implication: - product images are currently absent in backend detail payload for this product - if user reports image visible in older browser tab, suspect stale tab state before assuming frontend regression ### Admin review image issue For at least one reviewed product, backend review payload returned: - `image: null` - `imageId: null` - `productImages: []` In that case frontend cannot render images. If an image is missing in review, verify backend review payload before debugging frontend. ### Edit product empty main image behavior In edit product submit flow, empty `imageId` is currently sent as `undefined`, which means the field is omitted from JSON payload, not sent as `null` and not sent as empty string. If backend requires explicit image removal via `null`, this behavior will need to be changed. ### Seller in-review API note Postman latest collection contains: - seller review listing: `/api/v1.0/seller/product/review` - generic review detail: `/api/v1.0/product/review/{productId}` Current app matches this for listing and still uses generic review detail endpoint for product review detail. ## Local Dev Notes Run local dev: ```bash npm run dev ``` Build verification: ```bash npm run build ``` ## Dev Server Update Guide Based on current deployment notes, dev server update flow is: ```bash sudo -iu inadev cd ~/apps/ina-trading-web git fetch origin git checkout main git pull --ff-only origin main npm ci npm run build pm2 restart ina-trading-dev pm2 save sudo -iu inadev pm2 status ``` Current expected deployed commit after latest push: ```bash 2f64282 ``` Verify on server: ```bash git rev-parse HEAD ``` Optional logs: ```bash sudo -iu inadev pm2 logs ina-trading-dev --lines 100 ``` ## Suggested Next Checks - Verify whether backend expects `null` when main image is intentionally removed during edit - Verify seller and admin review payload consistency for image fields - Verify warehouse master API always contains all warehouse IDs referenced in product payloads - Verify whether seller in-review detail also has a seller-specific detail endpoint in backend/Postman, to align with seller-specific listing endpoint - Investigate when product images were lost for `Pembersih Mobil` and whether edit/update flow can preserve old image ids more defensively - If submenu click issue still appears, inspect layout overlays with browser tooling - Consider adding field-level compare highlighting, not only section-level highlighting, in admin review compare page ## Files Most Likely To Be Relevant Next - `src/app/(dashboard)/products/new/pricing/page.tsx` - `src/app/(dashboard)/products/[productId]/edit/page.tsx` - `src/app/(dashboard)/products/new/review/page.tsx` - `src/app/(dashboard)/products/[productId]/detail/page.tsx` - `src/app/(dashboard)/products/page.tsx` - `src/app/admin/review/[productId]/page.tsx` - `src/components/product-variant-showcase.tsx` - `src/lib/product-variants.ts` - `src/lib/use-product-submit.ts` - `src/lib/backend-fetch-logger.ts`