Skip to content

Fix wholesale sign-in redirect loop and nested anchor markup - #200

Merged
damianlegawiec merged 12 commits into
mainfrom
claude/wholesale-pricing-redirect-loop-xg9pzk
Jul 28, 2026
Merged

Fix wholesale sign-in redirect loop and nested anchor markup#200
damianlegawiec merged 12 commits into
mainfrom
claude/wholesale-pricing-redirect-loop-xg9pzk

Conversation

@damianlegawiec

@damianlegawiec damianlegawiec commented Jul 25, 2026

Copy link
Copy Markdown
Member

Summary

Fixes two defects in the wholesale portal: sign-in affordances that looped back to the catalog instead of reaching a sign-in form, and a nested-anchor markup violation that broke hydration on prices-hidden product cards.

Key Changes

Sign-in destination and redirect safety

  • New page wholesale/sign-in/page.tsx: a dedicated sign-in destination. On a prices_hidden channel the catalog root renders for guests, so "sign in" links pointing there landed the buyer straight back on the catalog — and with ?redirect= re-appended on every click, looped. An already-authenticated buyer is bounced into the portal.
  • wholesaleSignInHref() in lib/wholesale.ts owns the portal's sign-in path and the return-target rule in one place, including dropping a stale redirect from an earlier round trip so redirects can't nest inside redirects.
  • resolveLocalPath() in lib/utils/path.ts is now the single open-redirect guard behind every post-login return target. resolveAccountRedirect is re-expressed on top of it rather than duplicating the mechanism, so hardening (//, \, encoded separators) lives in one place. A leading-slash check alone is insufficient because the URL parser treats a backslash as a slash for http(s).

Markup and accessibility

  • ProductCard uses a stretched-link pattern (after:absolute after:inset-0 on the product name) instead of wrapping the card in an anchor, so HiddenPricePrompt can render its own link without nesting anchors — the invalid nesting surfaced as a React hydration error.
  • HiddenPricePrompt owns the stacking that keeps it clickable above the card's overlay.

Cart and input behaviour

  • Remove buttons on both cart surfaces are disabled while an update is in flight, matching the quantity controls; concurrent mutations could otherwise race and leave stale cart contents.
  • QuantityPicker: Escape cancels the edit without also dismissing a surrounding dialog (the cart drawer).

Testing

Unit coverage for the redirect helpers (path.test.ts, wholesale.test.ts) — local-path resolution, off-origin and encoded-separator rejection, return-target nesting, and sign-in href construction.

An E2E suite for this flow was written and then removed from this PR: its buyer sign-in spec proved unreliable, failing on a tree byte-identical to one that passed. The cause is a race in the post-login navigation (a client push, a router refresh, and the sign-in page's server redirect all competing), which is a real user-facing issue worth fixing on its own terms — tracked separately rather than gating this change.

Follow-ups

  • Post-login navigation race described above.
  • Wholesale ?redirect= targets are same-origin-only but unscoped within the origin; scoping them to the portal is deferred until the navigation race is resolved, since it changes the same code path.

https://claude.ai/code/session_018ud2Fsfy5yZPL8ZDnVowQY

@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
storefront Ready Ready Preview, Comment Jul 28, 2026 12:04pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The change adds wholesale sign-in and redirect handling, seeds a gated wholesale channel for E2E tests, improves product and cart interaction behavior, and introduces Playwright coverage for browsing, authentication, ordering, cart, and application flows.

Changes

Wholesale portal flow

Layer / File(s) Summary
Safe redirects and wholesale sign-in flow
src/lib/utils/path.ts, src/lib/utils/account-redirect.ts, src/lib/wholesale.ts, src/lib/**/__tests__/*
Shared utilities validate local redirect paths, constrain account destinations, and generate wholesale sign-in URLs with safe encoded redirects.
Wholesale sign-in route and portal entry points
src/app/[country]/[locale]/(wholesale)/wholesale/**
The sign-in route renders the guest wall or redirects authenticated customers, while wholesale links preserve valid return targets.
Wholesale channel bootstrap and E2E setup
scripts/e2e/bootstrap-spree.sh
The E2E bootstrap configures the wholesale channel with hidden prices and writes its channel code to .env.e2e.
Wholesale product and cart interaction behavior
src/components/products/*, src/components/ui/quantity-picker.tsx, src/app/.../cart/*
Product cards use stretched links, price prompts remain clickable, and cart controls are disabled during updates.
Wholesale end-to-end validation
e2e/helpers.ts, e2e/checkout.spec.ts, e2e/wholesale.spec.ts
Playwright helpers and tests cover hydration, URL navigation, wholesale access gates, buyer ordering, cart behavior, and applications.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Guest
  participant WholesaleCatalog
  participant WholesaleSignInPage
  participant WholesaleSignInWall
  participant WholesaleCart
  Guest->>WholesaleCatalog: Open hidden-price catalog
  WholesaleCatalog->>WholesaleSignInPage: Navigate with redirect target
  WholesaleSignInPage->>WholesaleSignInWall: Render sign-in wall
  Guest->>WholesaleSignInWall: Submit credentials
  WholesaleSignInPage-->>Guest: Redirect to original product path
  Guest->>WholesaleCart: Add product to wholesale cart
  WholesaleCart-->>Guest: Show product in cart drawer
Loading

Possibly related PRs

Poem

A bunny hops through sign-in light,
Safe redirects point paths just right.
Hidden prices guard the store,
Cart buttons wait while updates pour.
Tests chase hydration bright—
Wholesale blooms from left to right!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 81.25% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main changes: wholesale sign-in redirect handling and nested anchor markup fixes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/wholesale-pricing-redirect-loop-xg9pzk

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
e2e/helpers.ts (1)

16-27: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

RegExp#test is stateful for global-flag regexes.

url.test(page.url()) on line 22 will silently misbehave if a caller ever passes a regex with the g flag — .test() advances lastIndex on that instance, so repeated calls (as happens across toPass() retries) alternate matches. No current caller in wholesale.spec.ts uses a global regex, but as a shared, exported helper this is an easy trap for future test authors, and the resulting flakiness would be hard to trace back to this line.

🛡️ Proposed defensive fix
   await expect(async () => {
-    if (!url.test(page.url())) {
+    url.lastIndex = 0;
+    if (!url.test(page.url())) {
       await locator.click({ timeout: ATTEMPT_TIMEOUT });
     }
     await page.waitForURL(url, { timeout: ATTEMPT_TIMEOUT });
   }).toPass({ timeout: NAVIGATION_TIMEOUT });

Please confirm this matches your understanding of RegExp.prototype.test semantics with the g flag.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/helpers.ts` around lines 16 - 27, Update clickUntilUrl to avoid stateful
RegExp.test behavior when checking page.url() across retries, resetting or
otherwise neutralizing the regex lastIndex before each test so global-flag
patterns produce consistent results. Preserve the existing click and waitForURL
retry flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@e2e/wholesale.spec.ts`:
- Around line 168-178: Update the add-to-cart retry block to track whether
addToCart has already been clicked, rather than using drawer.isVisible() as the
click guard. Click only once across toPass retries, then continue polling for
the drawer line item until it becomes visible, preserving the existing timeouts
and assertion behavior.

In `@scripts/e2e/bootstrap-spree.sh`:
- Around line 119-125: Update the channel setup around find_or_create_by! so
preferred_guest_checkout is unconditionally set to false after lookup or
creation, matching the existing preferred_storefront_access reassertion. Keep
the creation block’s name initialization and ensure the corrected value is
persisted before the success message.

In `@src/app/`[country]/[locale]/(storefront)/cart/page.tsx:
- Around line 135-138: Guard all cart mutations during in-flight updates by
adding disabled={updating} to the remove button in
src/app/[country]/[locale]/(storefront)/cart/page.tsx lines 135-138 and
src/app/[country]/[locale]/(wholesale)/wholesale/cart/WholesaleCartView.tsx
lines 123-128; leave the existing quantity-control handling unchanged.

---

Nitpick comments:
In `@e2e/helpers.ts`:
- Around line 16-27: Update clickUntilUrl to avoid stateful RegExp.test behavior
when checking page.url() across retries, resetting or otherwise neutralizing the
regex lastIndex before each test so global-flag patterns produce consistent
results. Preserve the existing click and waitForURL retry flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cc62c08b-a67d-41f9-bd06-9ed7704d75d7

📥 Commits

Reviewing files that changed from the base of the PR and between a3d8454 and 64b0e81.

📒 Files selected for processing (15)
  • e2e/helpers.ts
  • e2e/wholesale.spec.ts
  • scripts/e2e/bootstrap-spree.sh
  • src/app/[country]/[locale]/(storefront)/account/page.tsx
  • src/app/[country]/[locale]/(storefront)/cart/page.tsx
  • src/app/[country]/[locale]/(wholesale)/wholesale/_components/WholesaleGuestBrowse.tsx
  • src/app/[country]/[locale]/(wholesale)/wholesale/_components/WholesaleHeader.tsx
  • src/app/[country]/[locale]/(wholesale)/wholesale/_components/WholesaleSignInWall.tsx
  • src/app/[country]/[locale]/(wholesale)/wholesale/apply/page.tsx
  • src/app/[country]/[locale]/(wholesale)/wholesale/cart/WholesaleCartView.tsx
  • src/app/[country]/[locale]/(wholesale)/wholesale/sign-in/page.tsx
  • src/components/products/HiddenPricePrompt.tsx
  • src/components/products/ProductCard.tsx
  • src/components/ui/quantity-picker.tsx
  • src/lib/utils/path.ts
💤 Files with no reviewable changes (1)
  • src/components/products/HiddenPricePrompt.tsx

Comment thread e2e/wholesale.spec.ts Outdated
Comment thread scripts/e2e/bootstrap-spree.sh Outdated
Comment thread src/app/[country]/[locale]/(storefront)/cart/page.tsx
@damianlegawiec
damianlegawiec force-pushed the claude/wholesale-pricing-redirect-loop-xg9pzk branch from 64b0e81 to 554a2b9 Compare July 27, 2026 10:21
@cursor

cursor Bot commented Jul 27, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
e2e/checkout.spec.ts (1)

2-2: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use an absolute alias import for the E2E helper.

This new TypeScript import uses ./helpers, contrary to the repository rule requiring @/ imports. Confirm that the alias covers e2e/; otherwise document an E2E exception.

As per coding guidelines, TypeScript files should use absolute @/ imports.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/checkout.spec.ts` at line 2, Update the import of clickUntilUrl in
checkout.spec.ts to use the repository’s absolute `@/` alias instead of ./helpers,
and verify that the alias resolves the e2e/ directory; if it does not, document
the required E2E import exception.

Source: Coding guidelines

src/lib/utils/account-redirect.ts (1)

34-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding unit tests for account-redirect.ts.

The rebase logic (prefix slicing, re-validation against the new base path) is non-trivial and security-relevant (it gates the login return target), yet unlike path.ts and wholesale.ts in this PR, this file has no companion test suite in this batch. A test file mirroring src/lib/utils/__tests__/path.test.ts covering resolveAccountRedirect, buildAccountLoginHref, and rebaseAccountRedirect/rebaseAccountRedirectSearch would be valuable given how easy it is to introduce a subtle regression in this kind of path-rebasing logic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/utils/account-redirect.ts` around lines 34 - 74, Add a companion unit
test suite for account-redirect.ts covering resolveAccountRedirect,
buildAccountLoginHref, and rebaseAccountRedirect/rebaseAccountRedirectSearch.
Include cases for valid and invalid targets, account-root handling, prefix
rebasing while preserving suffixes, queries, and hashes, and re-validation
against the next base path, following the conventions of the existing path and
wholesale utility tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/utils/path.ts`:
- Around line 53-63: The wholesale sign-in redirect flow must not pass arbitrary
same-origin paths through safeRedirectPath. Update WholesaleSignInPage and
WholesaleSignInWall to use a wholesale-scoped redirect resolver with an
appropriate fallback, or remove the redirect query from the authenticated path,
while preserving only destinations permitted for wholesale buyers and leaving
resolveAccountRedirect behavior unchanged.

---

Nitpick comments:
In `@e2e/checkout.spec.ts`:
- Line 2: Update the import of clickUntilUrl in checkout.spec.ts to use the
repository’s absolute `@/` alias instead of ./helpers, and verify that the alias
resolves the e2e/ directory; if it does not, document the required E2E import
exception.

In `@src/lib/utils/account-redirect.ts`:
- Around line 34-74: Add a companion unit test suite for account-redirect.ts
covering resolveAccountRedirect, buildAccountLoginHref, and
rebaseAccountRedirect/rebaseAccountRedirectSearch. Include cases for valid and
invalid targets, account-root handling, prefix rebasing while preserving
suffixes, queries, and hashes, and re-validation against the next base path,
following the conventions of the existing path and wholesale utility tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7dd5ea93-8712-4ecf-9e62-c25fcf444791

📥 Commits

Reviewing files that changed from the base of the PR and between 64b0e81 and 51772ab.

📒 Files selected for processing (19)
  • e2e/checkout.spec.ts
  • e2e/helpers.ts
  • e2e/wholesale.spec.ts
  • scripts/e2e/bootstrap-spree.sh
  • src/app/[country]/[locale]/(storefront)/cart/page.tsx
  • src/app/[country]/[locale]/(wholesale)/wholesale/_components/WholesaleGuestBrowse.tsx
  • src/app/[country]/[locale]/(wholesale)/wholesale/_components/WholesaleHeader.tsx
  • src/app/[country]/[locale]/(wholesale)/wholesale/_components/WholesaleSignInWall.tsx
  • src/app/[country]/[locale]/(wholesale)/wholesale/apply/page.tsx
  • src/app/[country]/[locale]/(wholesale)/wholesale/cart/WholesaleCartView.tsx
  • src/app/[country]/[locale]/(wholesale)/wholesale/sign-in/page.tsx
  • src/components/products/HiddenPricePrompt.tsx
  • src/components/products/ProductCard.tsx
  • src/components/ui/quantity-picker.tsx
  • src/lib/__tests__/wholesale.test.ts
  • src/lib/utils/__tests__/path.test.ts
  • src/lib/utils/account-redirect.ts
  • src/lib/utils/path.ts
  • src/lib/wholesale.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/app/[country]/[locale]/(wholesale)/wholesale/apply/page.tsx
  • e2e/helpers.ts
  • src/app/[country]/[locale]/(wholesale)/wholesale/cart/WholesaleCartView.tsx
  • src/app/[country]/[locale]/(wholesale)/wholesale/_components/WholesaleHeader.tsx
  • scripts/e2e/bootstrap-spree.sh
  • src/app/[country]/[locale]/(storefront)/cart/page.tsx
  • src/app/[country]/[locale]/(wholesale)/wholesale/_components/WholesaleSignInWall.tsx
  • src/app/[country]/[locale]/(wholesale)/wholesale/sign-in/page.tsx
  • src/components/ui/quantity-picker.tsx
  • e2e/wholesale.spec.ts

Comment thread src/lib/utils/path.ts
@cursor

cursor Bot commented Jul 27, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

claude added 12 commits July 28, 2026 12:02
On a prices_hidden channel the guest catalog's "Sign in for pricing" and
"Sign in to order" links pointed at the catalog root, which just renders
the guest view again — every click bounced back to the same page and
nested the previous ?redirect= param inside a new one, so a guest could
never reach a sign-in form. Add a dedicated /wholesale/sign-in page that
always shows the sign-in wall (with the apply-for-access link) and
honours the existing ?redirect= contract, and point every guest sign-in
affordance (price prompts, header, apply-page footer) at it. The guest
view also drops any stale redirect param when building its return URL so
redirects can never nest again.

Also restructure ProductCard as a stretched link so the hidden-price
prompt's link no longer renders inside the card's link — nested anchors
are invalid HTML and broke hydration on the guest catalog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ud2Fsfy5yZPL8ZDnVowQY
Covers the prices-hidden guest experience end to end: catalog browse with
sign-in-for-pricing prompts (including a regression listener for the
nested-anchor hydration error), the dedicated sign-in page and its
?redirect= return contract (including the redirect-nesting regression),
the sign-in wall on ordering surfaces, the apply-and-under-review flow,
and the approved buyer's sign-in, return-to-PDP, add-to-cart round trip.

The e2e bootstrap now flips the seeded wholesale channel to prices_hidden
and enables the portal via SPREE_WHOLESALE_CHANNEL — that posture
exercises strictly more portal UI than the login_required default, while
ordering surfaces wall guests off under either posture.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ud2Fsfy5yZPL8ZDnVowQY
Sanitize every post-login return target through one shared helper. The
previous leading-slash check admitted values like "/\evil.com", which the
URL parser resolves off-site, so a crafted link could turn sign-in into an
open redirect; the helper also tolerates repeated query keys, which arrive
as an array and previously crashed the wholesale sign-in page for
authenticated buyers. Applied to the wholesale sign-in page, the wholesale
wall, and the account sign-in.

The nested-anchor regression test asserted before React had hydrated, so
it passed even with the bug present. It now waits for the client to take
over and also asserts the rendered tree contains no nested links.

Also: keep Escape inside the quantity input from dismissing the cart
drawer, disable the picker on both full-page carts while a cart update is
in flight so a typed quantity can't be clobbered by a stale +/- click,
scope the stretched-link elevation to the card that owns the overlay, and
share the e2e click-then-navigate retry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ud2Fsfy5yZPL8ZDnVowQY
A soft navigation here leaves the previous route mounted for a while, so
whole-page locators pick up content the test is not talking about: the
sign-in wall's email field collided with the apply form's, and the
catalog's per-card sign-in prompts were counted while asserting a product
page was unlocked.

Load the apply page directly (the wall's link to it is already covered)
and assert only the product page's own ordering affordance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ud2Fsfy5yZPL8ZDnVowQY
The apply spec failed in CI with a bare "element not found", which says
nothing about why registration did not complete — the reason only ever
appears in the form's own alert. Surface that text instead.

Registration is rate limited to 3 per IP per minute, so retrying this spec
replays into a 429 and buries the original cause; it now runs once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ud2Fsfy5yZPL8ZDnVowQY
Disable the remove button while a cart update is in flight, matching the
quantity control — concurrent mutations each write cart state, so a remove
during another update could land on top of it.

Click add-to-cart once rather than re-clicking whenever the drawer is not
yet visible: the drawer can render behind a click that already succeeded,
which would add the item twice.

Reassert the wholesale channel's guest-checkout setting on every bootstrap
run, not only when the channel is created.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ud2Fsfy5yZPL8ZDnVowQY
CI showed registration succeeding while the post-submit confirmation card
never appeared and no error was reported, so the card is not a reliable
signal: it is local state on a form that the registration's own refresh
remounts.

Assert instead that the portal gates the new applicant as pending, which
only holds if the account was created, and keep reporting the form's alert
text when the API refuses the application.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ud2Fsfy5yZPL8ZDnVowQY
Both the apply form and the sign-in wall use controlled inputs with a
React submit handler, so any interaction before hydration is lost: the
typed values never reach React state and the click falls through to a
native form submit that reloads the page. On slower CI runners that made
the application spec fail with no account created and no error to report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ud2Fsfy5yZPL8ZDnVowQY
The wholesale portal shipped its own open-redirect guard alongside the
account one, so any future hardening had to be found and applied twice.
Both now resolve through a single validator, and the portal has one owner
for its sign-in destination instead of three call sites repeating it —
including the rule that a stale return target must not nest. Both are
covered by unit tests they previously lacked.

Also folds the click-until-navigated retry into the shared e2e helper the
checkout suite already had inline, and separates its click and navigation
budgets so a slow first route compile is waited out rather than clicked a
second time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ud2Fsfy5yZPL8ZDnVowQY
Buyers only reach the wholesale sign-in from portal surfaces, so a return
target pointing anywhere else is a crafted link rather than a real one.
The portal now narrows its `?redirect=` targets the way account sign-in
already narrows its own, and the permissive same-origin resolver is gone
rather than left available to the next caller. Also stops an
already-authenticated buyer bouncing off the sign-in page forever when
the return target is the sign-in page itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ud2Fsfy5yZPL8ZDnVowQY
The buyer sign-in spec proved unreliable: it fails on a tree byte-identical
to one that passed, because the post-login navigation races between a client
push, a router refresh, and the sign-in page's own server redirect. The race
is worth fixing on its own terms, but not as a gate on shipping the portal
fixes. Restores the checkout spec and the E2E bootstrap to their prior state,
leaving this change to the feature code and its unit tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ud2Fsfy5yZPL8ZDnVowQY
@damianlegawiec
damianlegawiec force-pushed the claude/wholesale-pricing-redirect-loop-xg9pzk branch from 758440c to e8b9801 Compare July 28, 2026 12:03
@damianlegawiec damianlegawiec changed the title Add wholesale portal E2E tests and fix nested anchor markup Fix wholesale sign-in redirect loop and nested anchor markup Jul 28, 2026
@damianlegawiec
damianlegawiec merged commit 4e3172e into main Jul 28, 2026
7 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