Skip to content

Api connecting-Edite problems in AddListing page #58

Merged
eng-raghadsami merged 4 commits into
devfrom
ApiConnecting
Mar 6, 2026
Merged

Api connecting-Edite problems in AddListing page #58
eng-raghadsami merged 4 commits into
devfrom
ApiConnecting

Conversation

@eng-raghadsami

@eng-raghadsami eng-raghadsami commented Mar 6, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

  • New Features

    • Save listing as draft while creating—return anytime to complete it
    • Success confirmation modal displayed after listing creation with navigation options
    • Dynamic location display on listings and product details
    • Document type selection for identity verification (Identity card, Passport, Driver License)
    • Structured rejection reasons for rejected listings in My Listings
    • Archive and republish functionality for managing listings
  • Bug Fixes

    • Improved error handling and user feedback for listing operations

@vercel

vercel Bot commented Mar 6, 2026

Copy link
Copy Markdown

@coderabbitai

coderabbitai Bot commented Mar 6, 2026

Copy link
Copy Markdown

Walkthrough

This PR introduces draft-saving capability to the listing creation flow, enriches product data models with location/coordinates/seller/rejection information, refactors product detail page to use a view-model layer, and normalizes product data transformation across API endpoints with consistent mappers.

Changes

Cohort / File(s) Summary
Localization
src/locales/en.json
Added button labels for listing creation actions (goToHome, addAnother) and new successModal with confirmation messaging.
AddListing Draft Flow
src/pages/AddListingPage/AddListingPage.tsx, src/pages/AddListingPage/components/BasicDetailsStep.tsx, src/pages/AddListingPage/components/MoreDetailsStep.tsx
Introduced draft-saving with new state/handlers, centralized CATEGORY_SYNONYM_GROUPS for consistent category matching, refactored category resolution logic via useCallback, and added draft UI buttons with loading states to both step components.
Product Type Enrichment
src/types/product.ts
Extended Product interface with optional fields: description, location, locationCoordinates (ProductLocationCoordinates), attributes (ProductAttributeValue[]), rejectionReason, and seller (ProductSellerSummary); added three new exported interfaces.
Product Data Normalization
src/services/product.service.ts
Introduced comprehensive mappers (parseImages, parseAttributes, resolveLocation, resolveCoordinates, mapSeller, normalizeCondition, mapRawProduct) and new internal types to normalize raw API payloads into consistent Product models; replaced inline mapping logic in getAll/getMine/getById with centralized mapRawProduct pipeline.
Verification & Profile
src/pages/ProfilePage/ProfileDetails.tsx, src/services/verification.service.ts
Enhanced ProfileDetails with memoized verification merge logic combining stored and computed states; extended verification.service to derive identity/phone/email status from both stored verification and existing values with fallback handling.
Identity Verification Flow
src/pages/IdentityVerificationPage/IdentityVerificationSelectPage.tsx
Added DOCUMENT_OPTIONS constant for document type selection, replaced simulated outcomes with real auth store persistence via setVerification, and now records structured verification object with timestamps and status.
Listing Display & Location Mapping
src/pages/HomePage/HomePage.tsx, src/pages/SearchPage/SearchPage.tsx, src/pages/MyListingsPage/MyListingsPage.tsx
Made product location dynamic with fallback "Location not specified"; MyListingsPage now computes rejectionReason objects, calls real API endpoints (archive/republish) with error handling, and simplified status filtering query logic.
Product Detail Refactor
src/pages/ProductDetailPage/ProductDetailPage.tsx
Introduced ProductDetailViewModel pattern with toViewModelFromProduct mapper, formatters (formatConditionLabel, formatPostedAtLabel), demo/server data paths, loading/error states, and replaced all direct product property access with viewModel fields throughout UI components.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Feat/home apis #51: Overlapping changes to product data modeling (src/services/product.service.ts and src/types/product.ts) with normalized product transformation and HomePage product-to-listing mapping.
  • Api connecting #52: Directly related to AddListing draft flow; implements backend endpoints and hooks that this PR's draft-saving feature consumes.
  • fix my listing page UI #50: Modifies MyListingsPage and introduces rejectionReason field on MyListing type, directly overlapping with this PR's changes to MyListingsPage data structure and rejection handling.

Suggested labels

needs-review, bug, refactor

Suggested reviewers

  • RihamKatout
  • ShadiSbaih
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title mentions only 'AddListing page' but the changeset spans 13 files across multiple features including product detail page, search page, listings management, verification, and backend service refactoring. Revise the title to accurately reflect the scope: consider 'Refactor product services and UI for better data handling' or 'Connect APIs and enhance product/listing management across pages' to better represent all changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ApiConnecting

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot]
coderabbitai Bot previously requested changes Mar 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/pages/AddListingPage/AddListingPage.tsx (2)

445-458: ⚠️ Potential issue | 🟠 Major

Fail fast before pending submit when no attributes were mapped.

src/services/product.service.ts:16-24 documents attributes as required for pending listings, but this branch now turns an empty mapper result into attributes: undefined. That shifts a predictable validation failure to the backend and makes the review flow fail late. Bail out locally when no attributes were resolved instead of calling pendingMutation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/AddListingPage/AddListingPage.tsx` around lines 445 - 458, The
branch builds payloadWithAttributes from resolveListingAttributes (mappedAttrs)
and then calls pendingMutation when isPendingRoute; because attributes are
required for pending listings, add a guard before calling pendingMutation that
fails fast when mappedAttrs is empty (e.g., surface a validation
error/throw/return early) instead of turning attributes into undefined and
calling pendingMutation; update the logic around resolveListingAttributes,
mappedAttrs, payloadWithAttributes, isPendingRoute and pendingMutation to
validate mappedAttrs.length > 0 and abort the pending submit path if not.

251-265: ⚠️ Potential issue | 🟠 Major

Use a symmetric category matcher and stop falling back to an arbitrary ID.

The current synonym check only accepts backend labels containing group[0], so valid aliases like default "Audio" against a backend "Headphones" category can miss. resolveCategoryId then degrades to localCategories[0] / "1", which saves the listing under the wrong category and resolves attributes against the wrong schema. Return undefined on no-match and let callers block submit or omit categoryId for drafts instead of silently picking a fallback.

Also applies to: 343-365

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/AddListingPage/AddListingPage.tsx` around lines 251 - 265, The
category matcher currently only checks whether the input name contains a synonym
and the backend label contains group[0], and when no match is found
resolveCategoryId falls back to localCategories[0]/"1"; change findCategory to
perform a symmetric synonym match (treat any synonym in CATEGORY_SYNONYM_GROUPS
as matching if either the input name contains a group member and the candidate
label contains any group member, or vice versa), and update resolveCategoryId
(and the duplicate logic at the other occurrence around the 343-365 block) to
return undefined on no-match instead of selecting localCategories[0] so callers
can handle missing categoryId for drafts or block submission for required
categories.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/locales/en.json`:
- Around line 104-106: The translation file defines two very similar keys
("goHome" and "goToHome") which can cause confusion and duplication; choose one
canonical key (e.g., keep "goHome" or "goToHome"), remove the duplicate entry,
and update any code references to use the chosen key (search for usages of
goHome and goToHome in components/containers to replace the deprecated one);
ensure the remaining key's value matches project tone and run i18n tests/lint to
catch any leftover references.

In `@src/pages/AddListingPage/AddListingPage.tsx`:
- Around line 373-403: The draft save currently blocks on resolveCategoryId and
resolveListingAttributes; change the flow in the submit handler so you build
draftPayload opportunistically (using title, price, condition, images, etc.) and
call draftMutation.mutateAsync immediately, then attempt to enrich the payload
with categoryId and attributes only if resolveCategoryId and
resolveListingAttributes succeed; specifically, compute attributeInputs and call
resolveCategoryId/resolveListingAttributes inside try/catch (or
Promise.allSettled), and only set draftPayload.categoryId and
draftPayload.attributes from mappedAttrs when those calls succeed (leave them
undefined on failure) before calling draftMutation.mutateAsync (or call
mutateAsync first and patch later if you prefer), referencing resolveCategoryId,
resolveListingAttributes, attributeInputs, mappedAttrs, draftPayload,
CreateProductPayload, and draftMutation.mutateAsync to locate the code to
update.

In `@src/pages/AddListingPage/components/BasicDetailsStep.tsx`:
- Around line 699-717: The CTA row in BasicDetailsStep currently uses fixed
widths on the two Button components which causes overflow; update the two Button
classNames to use responsive fluid sizing by replacing w-[358px] with w-full and
adding sm:flex-1, and remove the fixed horizontal padding token px-[119px] from
the "Next" button so both buttons can shrink with the container (keep existing
justify-center, height, rounded and disabled/bg conditional logic, and handlers
onSaveDraft/onNext unchanged).

In `@src/pages/IdentityVerificationPage/IdentityVerificationSelectPage.tsx`:
- Around line 277-292: handleSubmit currently ignores fileSlots and locally
marks identity approved; instead, read front/back images from fileSlots, call
the verification upload mutation in src/services/verification.service.ts (e.g.,
the uploadIdentityDocuments/submitIdentityVerification function used by the
service at lines ~87-111), setPageStatus("submitting") and keep local
verification.state as pending (do not set identity.status = "approved" or
reviewedAt locally), await the upload response, then update setVerification with
the server response and setPageStatus based on that result; follow the
backend-sync pattern used in IdentityVerificationStatusPage (the fetch/merge
flow at lines ~19-29) so reviewedAt only comes from the backend.

In `@src/pages/ProductDetailPage/ProductDetailPage.tsx`:
- Around line 226-228: The ternary assigning viewModel uses a type assertion
(serverViewModel as ProductDetailViewModel) that is safe because of the earlier
guard that validated serverViewModel; add a brief inline comment next to this
assignment (near viewModel, isDemo, demoViewModel, serverViewModel, and
ProductDetailViewModel) stating that the prior guard guarantees serverViewModel
conforms to ProductDetailViewModel, so the assertion is intentional and safe.
- Around line 52-55: formatConditionLabel currently special-cases "like-new" and
falls back to naive capitalization for other Product["condition"] values;
replace this with an explicit mapping object inside formatConditionLabel (e.g.,
map keys for "new", "like-new", "good", "fair" and any future conditions) and
return map[condition] || a sensible default so labels are clear and
maintainable; update references to formatConditionLabel accordingly.

In `@src/services/verification.service.ts`:
- Around line 27-28: The persisted defaults ("not_started"/"not_verified") from
getVerification() are masking real verification transitions; update the logic
that assigns storedVerification (and the similar block at lines 53-66) to treat
those default sentinel values as absent so later states like "pending" or the
is*Verified flags can overwrite them: when calling getVerification(), if the
returned object has status fields equal to "not_started" or "not_verified" (or
all fields are defaults), treat it as null/undefined rather than using the
nullish coalescing (the current (getVerification() as VerificationState | null)
?? null), and ensure uploadIdentityDocument() and any code reading
storedVerification will accept and persist non-default transitions. Reference
symbols: storedVerification, getVerification(), uploadIdentityDocument(), and
the is*Verified flags.

---

Outside diff comments:
In `@src/pages/AddListingPage/AddListingPage.tsx`:
- Around line 445-458: The branch builds payloadWithAttributes from
resolveListingAttributes (mappedAttrs) and then calls pendingMutation when
isPendingRoute; because attributes are required for pending listings, add a
guard before calling pendingMutation that fails fast when mappedAttrs is empty
(e.g., surface a validation error/throw/return early) instead of turning
attributes into undefined and calling pendingMutation; update the logic around
resolveListingAttributes, mappedAttrs, payloadWithAttributes, isPendingRoute and
pendingMutation to validate mappedAttrs.length > 0 and abort the pending submit
path if not.
- Around line 251-265: The category matcher currently only checks whether the
input name contains a synonym and the backend label contains group[0], and when
no match is found resolveCategoryId falls back to localCategories[0]/"1"; change
findCategory to perform a symmetric synonym match (treat any synonym in
CATEGORY_SYNONYM_GROUPS as matching if either the input name contains a group
member and the candidate label contains any group member, or vice versa), and
update resolveCategoryId (and the duplicate logic at the other occurrence around
the 343-365 block) to return undefined on no-match instead of selecting
localCategories[0] so callers can handle missing categoryId for drafts or block
submission for required categories.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4e5e21f4-c3a1-4deb-9104-310e4b1fb8cd

📥 Commits

Reviewing files that changed from the base of the PR and between 88276c7 and 75ea58a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
📒 Files selected for processing (13)
  • src/locales/en.json
  • src/pages/AddListingPage/AddListingPage.tsx
  • src/pages/AddListingPage/components/BasicDetailsStep.tsx
  • src/pages/AddListingPage/components/MoreDetailsStep.tsx
  • src/pages/HomePage/HomePage.tsx
  • src/pages/IdentityVerificationPage/IdentityVerificationSelectPage.tsx
  • src/pages/MyListingsPage/MyListingsPage.tsx
  • src/pages/ProductDetailPage/ProductDetailPage.tsx
  • src/pages/ProfilePage/ProfileDetails.tsx
  • src/pages/SearchPage/SearchPage.tsx
  • src/services/product.service.ts
  • src/services/verification.service.ts
  • src/types/product.ts

Comment thread src/locales/en.json
Comment on lines 104 to +106
"goHome": "Go Home",
"goToHome": "Go to Home",
"addAnother": "Add another listing",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Potential duplication: "goHome" vs "goToHome".

Line 104 has "goHome": "Go Home" and line 105 adds "goToHome": "Go to Home". These are semantically similar. Consider consolidating to avoid confusion about which key to use.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/locales/en.json` around lines 104 - 106, The translation file defines two
very similar keys ("goHome" and "goToHome") which can cause confusion and
duplication; choose one canonical key (e.g., keep "goHome" or "goToHome"),
remove the duplicate entry, and update any code references to use the chosen key
(search for usages of goHome and goToHome in components/containers to replace
the deprecated one); ensure the remaining key's value matches project tone and
run i18n tests/lint to catch any leftover references.

Comment on lines +373 to +403
const currentValues = getValues();
const categoryId = await resolveCategoryId(currentValues.category ?? "");
const images = photos.map((p) => p.file);
const resolvedLocation =
currentValues.location?.trim() ||
[locationValue.city, locationValue.country].filter(Boolean).join(", ");
const attributeInputs: AttributeValueMap = {
brand: currentValues.brand,
model: currentValues.model,
storage: currentValues.storage,
battery: currentValues.batteryHealth,
description:
typeof currentValues.description === "string"
? currentValues.description
: undefined,
location: resolvedLocation,
};
const { attributes: mappedAttrs } = await resolveListingAttributes(
categoryId,
attributeInputs,
);
const draftPayload: CreateProductPayload = {
title: currentValues.title?.trim() || "Untitled listing",
categoryId,
condition: normalizeCondition(currentValues.condition || "good"),
price: Number(currentValues.price) || 0,
isNegotiable: Boolean(currentValues.isNegotiable),
images: images.length ? images : undefined,
attributes: mappedAttrs.length ? mappedAttrs : undefined,
};
await draftMutation.mutateAsync(draftPayload);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't make draft creation depend on category metadata APIs.

src/services/product.service.ts:260-304 already serializes drafts without categoryId or attributes, but this flow always awaits resolveCategoryId and resolveListingAttributes before sending anything. A transient categories/category-detail failure now blocks saving an otherwise valid partial draft. Build the draft payload opportunistically and only enrich it with category/attributes when those lookups succeed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/AddListingPage/AddListingPage.tsx` around lines 373 - 403, The
draft save currently blocks on resolveCategoryId and resolveListingAttributes;
change the flow in the submit handler so you build draftPayload
opportunistically (using title, price, condition, images, etc.) and call
draftMutation.mutateAsync immediately, then attempt to enrich the payload with
categoryId and attributes only if resolveCategoryId and resolveListingAttributes
succeed; specifically, compute attributeInputs and call
resolveCategoryId/resolveListingAttributes inside try/catch (or
Promise.allSettled), and only set draftPayload.categoryId and
draftPayload.attributes from mappedAttrs when those calls succeed (leave them
undefined on failure) before calling draftMutation.mutateAsync (or call
mutateAsync first and patch later if you prefer), referencing resolveCategoryId,
resolveListingAttributes, attributeInputs, mappedAttrs, draftPayload,
CreateProductPayload, and draftMutation.mutateAsync to locate the code to
update.

Comment on lines +699 to +717
<div className="flex w-full flex-col items-center justify-center gap-3 sm:flex-row">
<Button
type="button"
onClick={onSaveDraft}
disabled={isSavingDraft}
className="flex h-14 w-[358px] items-center justify-center rounded-xl border border-[#e4e4e4] bg-white px-10 py-4 text-[#212121] hover:bg-neutral-5 disabled:cursor-not-allowed disabled:opacity-60"
>
<Text className="font-['Poppins'] text-base font-medium leading-normal">
{isSavingDraft ? "Saving draft..." : "Save as draft"}
</Text>
</div>
</Button>
</Button>
<Button
type="button"
onClick={onNext}
disabled={isNextDisabled}
className={`flex h-14 w-[358px] items-center justify-center rounded-xl px-[119px] py-4 ${
isNextDisabled ? "cursor-not-allowed bg-[#e4e4e4]" : "bg-[#2563eb]"
}`}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make the new CTA row fluid instead of fixed-width.

At sm this switches to a horizontal layout, but the two w-[358px] buttons need ~728px before gap/padding. Inside the current Add Listing form that overflows on common viewport widths, so one action can end up off-screen. Use w-full + sm:flex-1 and drop the fixed px-[119px] so the row can shrink with its container.

📐 Suggested fix
-      <div className="flex w-full flex-col items-center justify-center gap-3 sm:flex-row">
+      <div className="flex w-full flex-col gap-3 sm:flex-row">
         <Button
           type="button"
           onClick={onSaveDraft}
           disabled={isSavingDraft}
-          className="flex h-14 w-[358px] items-center justify-center rounded-xl border border-[`#e4e4e4`] bg-white px-10 py-4 text-[`#212121`] hover:bg-neutral-5 disabled:cursor-not-allowed disabled:opacity-60"
+          className="flex h-14 w-full items-center justify-center rounded-xl border border-[`#e4e4e4`] bg-white px-6 py-4 text-[`#212121`] hover:bg-neutral-5 disabled:cursor-not-allowed disabled:opacity-60 sm:flex-1"
         >
@@
-          className={`flex h-14 w-[358px] items-center justify-center rounded-xl px-[119px] py-4 ${
+          className={`flex h-14 w-full items-center justify-center rounded-xl px-6 py-4 sm:flex-1 ${
             isNextDisabled ? "cursor-not-allowed bg-[`#e4e4e4`]" : "bg-[`#2563eb`]"
           }`}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="flex w-full flex-col items-center justify-center gap-3 sm:flex-row">
<Button
type="button"
onClick={onSaveDraft}
disabled={isSavingDraft}
className="flex h-14 w-[358px] items-center justify-center rounded-xl border border-[#e4e4e4] bg-white px-10 py-4 text-[#212121] hover:bg-neutral-5 disabled:cursor-not-allowed disabled:opacity-60"
>
<Text className="font-['Poppins'] text-base font-medium leading-normal">
{isSavingDraft ? "Saving draft..." : "Save as draft"}
</Text>
</div>
</Button>
</Button>
<Button
type="button"
onClick={onNext}
disabled={isNextDisabled}
className={`flex h-14 w-[358px] items-center justify-center rounded-xl px-[119px] py-4 ${
isNextDisabled ? "cursor-not-allowed bg-[#e4e4e4]" : "bg-[#2563eb]"
}`}
>
<div className="flex w-full flex-col gap-3 sm:flex-row">
<Button
type="button"
onClick={onSaveDraft}
disabled={isSavingDraft}
className="flex h-14 w-full items-center justify-center rounded-xl border border-[`#e4e4e4`] bg-white px-6 py-4 text-[`#212121`] hover:bg-neutral-5 disabled:cursor-not-allowed disabled:opacity-60 sm:flex-1"
>
<Text className="font-['Poppins'] text-base font-medium leading-normal">
{isSavingDraft ? "Saving draft..." : "Save as draft"}
</Text>
</Button>
<Button
type="button"
onClick={onNext}
disabled={isNextDisabled}
className={`flex h-14 w-full items-center justify-center rounded-xl px-6 py-4 sm:flex-1 ${
isNextDisabled ? "cursor-not-allowed bg-[`#e4e4e4`]" : "bg-[`#2563eb`]"
}`}
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/AddListingPage/components/BasicDetailsStep.tsx` around lines 699 -
717, The CTA row in BasicDetailsStep currently uses fixed widths on the two
Button components which causes overflow; update the two Button classNames to use
responsive fluid sizing by replacing w-[358px] with w-full and adding sm:flex-1,
and remove the fixed horizontal padding token px-[119px] from the "Next" button
so both buttons can shrink with the container (keep existing justify-center,
height, rounded and disabled/bg conditional logic, and handlers
onSaveDraft/onNext unchanged).

Comment on lines 277 to +292
const handleSubmit = () => {
setPageStatus("submitting");
// Simulate network request
const submissionTimestamp = new Date().toISOString();

// Simulate a short review process before marking identity as verified locally
setTimeout(() => {
// Randomly succeed or fail
const isSuccess = Math.random() > 0.5;
setPageStatus(isSuccess ? "success" : "failed");
}, 3000);
setVerification({
identity: {
status: "approved",
type: docType,
submittedAt: submissionTimestamp,
reviewedAt: submissionTimestamp,
},
});
setPageStatus("success");
}, 1500);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

handleSubmit() never uploads the documents and self-approves the user.

This block ignores fileSlots, waits 1.5s, then persists identity.status = "approved" and reviewedAt locally. That bypasses the real upload path in src/services/verification.service.ts Lines 87-111 and the backend-sync pattern in src/pages/IdentityVerificationPage/IdentityVerificationStatusPage.tsx Lines 19-29, so the UI can show a verified identity without any server review.

🛠️ Wire submission to the upload mutation and keep the local state pending
-  const handleSubmit = () => {
+  const handleSubmit = async () => {
     setPageStatus("submitting");
     const submissionTimestamp = new Date().toISOString();
-
-    // Simulate a short review process before marking identity as verified locally
-    setTimeout(() => {
+    try {
+      await uploadIdentityDocument.mutateAsync({
+        type: docType,
+        frontImage,
+        backImage,
+      });
       setVerification({
         identity: {
-          status: "approved",
+          status: "pending",
           type: docType,
           submittedAt: submissionTimestamp,
-          reviewedAt: submissionTimestamp,
         },
       });
       setPageStatus("success");
-    }, 1500);
+    } catch {
+      setPageStatus("failed");
+    }
   };

frontImage / backImage should come from the selected slots, and reviewedAt should only be populated from the backend once the review actually completes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/IdentityVerificationPage/IdentityVerificationSelectPage.tsx` around
lines 277 - 292, handleSubmit currently ignores fileSlots and locally marks
identity approved; instead, read front/back images from fileSlots, call the
verification upload mutation in src/services/verification.service.ts (e.g., the
uploadIdentityDocuments/submitIdentityVerification function used by the service
at lines ~87-111), setPageStatus("submitting") and keep local verification.state
as pending (do not set identity.status = "approved" or reviewedAt locally),
await the upload response, then update setVerification with the server response
and setPageStatus based on that result; follow the backend-sync pattern used in
IdentityVerificationStatusPage (the fetch/merge flow at lines ~19-29) so
reviewedAt only comes from the backend.

Comment on lines +52 to +55
const formatConditionLabel = (condition: Product["condition"]): string => {
if (condition === "like-new") return "Like New";
return condition.charAt(0).toUpperCase() + condition.slice(1);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Condition label formatting may not handle all valid conditions correctly.

The formatConditionLabel function handles "like-new" explicitly but uses a generic capitalize for others. Given that Product["condition"] is "new" | "like-new" | "good" | "fair", this works correctly. However, if other conditions are added in the future, consider using an explicit mapping for clarity and maintainability.

♻️ Optional: Use explicit mapping for all conditions
 const formatConditionLabel = (condition: Product["condition"]): string => {
-  if (condition === "like-new") return "Like New";
-  return condition.charAt(0).toUpperCase() + condition.slice(1);
+  const labels: Record<Product["condition"], string> = {
+    "new": "New",
+    "like-new": "Like New",
+    "good": "Good",
+    "fair": "Fair",
+  };
+  return labels[condition] ?? condition;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const formatConditionLabel = (condition: Product["condition"]): string => {
if (condition === "like-new") return "Like New";
return condition.charAt(0).toUpperCase() + condition.slice(1);
};
const formatConditionLabel = (condition: Product["condition"]): string => {
const labels: Record<Product["condition"], string> = {
"new": "New",
"like-new": "Like New",
"good": "Good",
"fair": "Fair",
};
return labels[condition] ?? condition;
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/ProductDetailPage/ProductDetailPage.tsx` around lines 52 - 55,
formatConditionLabel currently special-cases "like-new" and falls back to naive
capitalization for other Product["condition"] values; replace this with an
explicit mapping object inside formatConditionLabel (e.g., map keys for "new",
"like-new", "good", "fair" and any future conditions) and return map[condition]
|| a sensible default so labels are clear and maintainable; update references to
formatConditionLabel accordingly.

Comment on lines +226 to +228
const viewModel: ProductDetailViewModel = isDemo
? demoViewModel
: (serverViewModel as ProductDetailViewModel);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Type assertion is safe but could be clearer.

The assertion serverViewModel as ProductDetailViewModel is safe due to the guard on line 216. Consider adding a brief comment explaining why the assertion is valid to aid future readers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/ProductDetailPage/ProductDetailPage.tsx` around lines 226 - 228,
The ternary assigning viewModel uses a type assertion (serverViewModel as
ProductDetailViewModel) that is safe because of the earlier guard that validated
serverViewModel; add a brief inline comment next to this assignment (near
viewModel, isDemo, demoViewModel, serverViewModel, and ProductDetailViewModel)
stating that the prior guard guarantees serverViewModel conforms to
ProductDetailViewModel, so the assertion is intentional and safe.

Comment on lines +27 to +28
const storedVerification =
(getVerification() as VerificationState | null) ?? null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stored default statuses are masking later verification transitions.

src/lib/storage.ts Lines 18-25 persists these values in localStorage, and the ?? checks here treat "not_started" / "not_verified" as authoritative. After the initial profile sync writes those defaults, uploadIdentityDocument() can no longer surface pending, and later is*Verified user flags are ignored until something else rewrites storage.

🛠️ Suggested precedence fix
-    const resolvedIdentityStatus =
-      identityFromStore?.status ??
-      (isIdentityVerified
-        ? "approved"
-        : pendingIdentityFlag
-          ? "pending"
-          : "not_started");
+    const resolvedIdentityStatus =
+      isIdentityVerified
+        ? "approved"
+        : identityFromStore?.status && identityFromStore.status !== "not_started"
+          ? identityFromStore.status
+          : pendingIdentityFlag
+            ? "pending"
+            : "not_started";

-    const resolvedPhoneStatus =
-      phoneFromStore?.status ??
-      (isPhoneVerified ? "verified" : "not_verified");
+    const resolvedPhoneStatus =
+      isPhoneVerified || phoneFromStore?.status === "verified"
+        ? "verified"
+        : phoneFromStore?.status === "pending"
+          ? "pending"
+          : "not_verified";
-    const resolvedEmailStatus =
-      emailFromStore?.status ??
-      (isEmailVerified ? "verified" : "not_verified");
+    const resolvedEmailStatus =
+      isEmailVerified || emailFromStore?.status === "verified"
+        ? "verified"
+        : emailFromStore?.status === "pending"
+          ? "pending"
+          : "not_verified";

Also applies to: 53-66

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/verification.service.ts` around lines 27 - 28, The persisted
defaults ("not_started"/"not_verified") from getVerification() are masking real
verification transitions; update the logic that assigns storedVerification (and
the similar block at lines 53-66) to treat those default sentinel values as
absent so later states like "pending" or the is*Verified flags can overwrite
them: when calling getVerification(), if the returned object has status fields
equal to "not_started" or "not_verified" (or all fields are defaults), treat it
as null/undefined rather than using the nullish coalescing (the current
(getVerification() as VerificationState | null) ?? null), and ensure
uploadIdentityDocument() and any code reading storedVerification will accept and
persist non-default transitions. Reference symbols: storedVerification,
getVerification(), uploadIdentityDocument(), and the is*Verified flags.

@eng-raghadsami
eng-raghadsami merged commit 3752b81 into dev Mar 6, 2026
11 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants