Skip to content

Add store policies pages and consent checkboxes#85

Open
Cichorek wants to merge 8 commits intomainfrom
feature/policies-consent
Open

Add store policies pages and consent checkboxes#85
Cichorek wants to merge 8 commits intomainfrom
feature/policies-consent

Conversation

@Cichorek
Copy link
Copy Markdown
Contributor

@Cichorek Cichorek commented Mar 26, 2026

Summary

  • Add policy detail pages (/policies/[slug]) that fetch content from Spree API
  • Add consent checkbox to registration (Privacy Policy & Terms of Service)
  • Add consent checkbox to checkout for guests only — authenticated users accepted at registration
  • Hardcode policy config in constants (POLICY_LINKS, CONSENT_POLICIES) — no dynamic API fetching for consent/footer
  • Show all 4 default policies (Shipping, Privacy, Returns, Terms of Service) in footer
  • Red border + scroll-to-error on validation failure
  • Compatible with @spree/next 0.19.0 (getPolicy removed — uses getClient().policies.get())

Closes #60

Test plan

  • Visit /us/en/policies/terms-of-service — policy content renders from Spree API
  • Visit /us/en/account/register — consent checkbox shows "I agree to the Privacy Policy and Terms of Service"
  • Try submitting without checking — red error + scroll to checkbox
  • Check checkbox and submit — registration works
  • Visit checkout as guest — consent checkbox appears
  • Visit checkout as authenticated user — no consent checkbox
  • Footer shows all 4 policy links on storefront and checkout
  • Policy links include localized basePath (e.g. /us/en/policies/terms-of-service)

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a Store Policies feature: dynamic policy pages fetched from the Spree API, a reusable PolicyConsent checkbox used in registration and guest checkout to enforce consent, policy links in footers, policy constants, and a server data module to fetch individual policies.

Changes

Cohort / File(s) Summary
Documentation
README.md
Documented new Store Policies capability and new route/data module location.
Policy Page Route
src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx
New dynamic policy page with metadata generation, policy fetch, 404 handling, and HTML/text rendering.
Policy Data
src/lib/data/policies.ts, src/lib/data/index.ts
New server-only getPolicy(slug) (re-exported in data index) that calls Spree client and returns `Policy
Policy Constants
src/lib/constants/policies.ts
Added POLICY_LINKS (all policies) and CONSENT_POLICIES (Privacy + Terms) for navigation and consent UI.
Policy UI Component
src/components/policy/PolicyConsent.tsx
New controlled client component rendering consent checkbox with policy links, error styling, and fixed id/htmlFor.
Registration Form
src/app/[country]/[locale]/(storefront)/account/register/page.tsx
Integrates PolicyConsent, adds policyConsent/policyError state, and blocks submission when consent is unchecked.
Checkout Page
src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx
Adds guest-only PolicyConsent validation guard in validateAndPay, stops payment flow if consent missing; switches error banner to shared Alert components.
Footer & Checkout Footer
src/components/layout/Footer.tsx, src/app/[country]/[locale]/(checkout)/layout.tsx
Footer layouts now compute basePath and render policy links from POLICY_LINKS; checkout footer appended policy links and updated layout.
Layout Typing
src/app/[country]/[locale]/(storefront)/layout.tsx
Made rootCategories fallback explicitly typed as Category[].

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Browser
  participant Server as Next.js Server
  participant Spree as Spree API

  Browser->>Server: GET /{country}/{locale}/policies/{slug}
  Server-->>Spree: client.policies.get(slug)
  Spree-->>Server: Policy data (or error)
  Server-->>Browser: Render policy page (200) or notFound()
Loading
sequenceDiagram
  participant User as Browser (User)
  participant UI as Storefront UI (PolicyConsent)
  participant Server as Next.js Server

  User->>UI: Fill form + toggle PolicyConsent
  UI->>UI: Validate local consent state
  alt consent checked
    User->>Server: Submit registration / initiate payment
    Server-->>User: Proceed (registration/payment flow)
  else consent missing
    UI-->>User: Show policyError, focus `#policy-consent`, block submit
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 In burrows bright I hop and sing,

Policies now have a spring.
Check the box, consent is done —
Signup and checkout, all in one.
Hooray! — a rabbit’s gentle run 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add store policies pages and consent checkboxes' clearly and concisely summarizes the main changes: adding policy pages and implementing consent mechanisms across registration and checkout.
Linked Issues check ✅ Passed The PR comprehensively implements all coding requirements from issue #60: policy detail pages, consent checkboxes in registration and checkout, proper guest/authenticated user handling, and footer policy links.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #60 requirements. The type casting adjustment in storefront layout and footer link localization are supporting changes necessary for the policy feature implementation.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/policies-consent

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
src/lib/data/policies.ts (1)

8-8: Use the app alias for this import.

Prefer @/lib/data/utils here so this module follows the repo’s absolute-import convention.

Suggested change
-import { withFallback } from "./utils";
+import { withFallback } from "@/lib/data/utils";

As per coding guidelines, "Use absolute imports with @/ alias prefix instead of relative imports."

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

In `@src/lib/data/policies.ts` at line 8, The import for withFallback in
policies.ts currently uses a relative path; update it to the repo's
absolute-import alias by replacing the "./utils" import with "@/lib/data/utils"
so the file uses the app alias convention and stays consistent with other
modules that import the withFallback symbol.
src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx (1)

14-31: Declare this dynamic route’s rendering mode.

This new [slug] page does not export generateStaticParams() or dynamic, so it misses the repo’s required route config for dynamic pages. If policy content is API-driven, export const dynamic = "force-dynamic" is likely the safer default.

Suggested change
+export const dynamic = "force-dynamic";
+
 export async function generateMetadata({

As per coding guidelines, "Use generateStaticParams for static generation of dynamic routes or set dynamic = 'force-dynamic' for dynamic rendering."

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

In `@src/app/`[country]/[locale]/(storefront)/policies/[slug]/page.tsx around
lines 14 - 31, The dynamic [slug] route (PolicyPage / generateMetadata) lacks a
route rendering mode; either implement export async function
generateStaticParams() to statically generate policy pages or explicitly declare
export const dynamic = "force-dynamic" to opt into runtime rendering; update the
file that defines generateMetadata and default PolicyPage to include one of
these exports (prefer export const dynamic = "force-dynamic" if policy content
is API-driven) so Next.js knows the route’s rendering strategy.
🤖 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/app/`[country]/[locale]/(checkout)/checkout/[id]/page.tsx:
- Around line 222-228: The current logic uses authStatus to remove
REGISTRATION_POLICY_SLUGS from the policies list, which incorrectly assumes
authenticated users have given consent; change the filter in the setPolicies
call to consult the user's stored consent metadata (e.g., customer.consents,
acceptedPolicies, or equivalent) instead of authStatus: include
REGISTRATION_POLICY_SLUGS only when a matching consent record exists for the
policy slug and version/timestamp matches the current policy version, otherwise
keep the policy in the checkout list so the user can re-consent; update the code
around setPolicies, allPolicies, REGISTRATION_POLICY_SLUGS and authStatus to
read and validate the consent record (and fallback to showing the policies when
metadata is missing or outdated).

In `@src/app/`[country]/[locale]/(storefront)/account/register/page.tsx:
- Around line 42-52: The form currently treats policies = [] as "no required
policies" while getPolicies() is still pending or has failed; add an explicit
loading/error flag (e.g., policiesLoading or policiesLoaded and setPolicyError
on rejection) in the useEffect that calls getPolicies(), set
policiesLoading=true before the call, set to false in both then and catch (and
setPolicyError(true) on catch), and update the submit enablement/validation
logic (the code around the submit handler / lines referenced 79-87) to
fail-closed: disable submit or return an error if policiesLoading is true or
policyError is true, and require policyConsent only after policiesLoaded is true
so users cannot submit while required policies are still loading or the request
failed.

In `@src/components/layout/Footer.tsx`:
- Line 3: The import of the non-existent Policy type from "@spree/sdk" causes a
TS error; update the Footer component by removing the invalid "Policy" import
and any "policies" prop typing/usages, or replace it with the correct type if
this prop should reference a different resource (locate the Footer component and
its props, the import line "import type { Policy } from \"@spree/sdk\"", and any
references to "policies" inside Footer) and adjust the prop signature and
internal usage accordingly so the component compiles without the missing type.

In `@src/lib/data/policies.ts`:
- Around line 10-16: The current helpers getPolicies and getPolicy swallow
failures via withFallback, causing critical flows (registration/checkout) to
fail open; introduce strict variants (e.g., getPoliciesStrict and
getPolicyStrict or add a strict boolean param) that directly await
_listPolicies()/_getPolicy(slug) and propagate errors instead of returning
[]/null, keep the existing best-effort getPolicies/getPolicy for non-critical UI
like the footer, and update consent-gating callers to use the strict variants so
API/auth outages do not bypass consent checks.

---

Nitpick comments:
In `@src/app/`[country]/[locale]/(storefront)/policies/[slug]/page.tsx:
- Around line 14-31: The dynamic [slug] route (PolicyPage / generateMetadata)
lacks a route rendering mode; either implement export async function
generateStaticParams() to statically generate policy pages or explicitly declare
export const dynamic = "force-dynamic" to opt into runtime rendering; update the
file that defines generateMetadata and default PolicyPage to include one of
these exports (prefer export const dynamic = "force-dynamic" if policy content
is API-driven) so Next.js knows the route’s rendering strategy.

In `@src/lib/data/policies.ts`:
- Line 8: The import for withFallback in policies.ts currently uses a relative
path; update it to the repo's absolute-import alias by replacing the "./utils"
import with "@/lib/data/utils" so the file uses the app alias convention and
stays consistent with other modules that import the withFallback symbol.
🪄 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

Run ID: c73f2553-a959-40ed-b0fd-0ca6c805575b

📥 Commits

Reviewing files that changed from the base of the PR and between 94773da and 27c1130.

📒 Files selected for processing (9)
  • README.md
  • src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx
  • src/app/[country]/[locale]/(storefront)/account/register/page.tsx
  • src/app/[country]/[locale]/(storefront)/layout.tsx
  • src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx
  • src/components/layout/Footer.tsx
  • src/components/policy/PolicyConsent.tsx
  • src/lib/constants/policies.ts
  • src/lib/data/policies.ts

@Cichorek Cichorek force-pushed the feature/policies-consent branch from 27c1130 to 14f85e3 Compare March 27, 2026 13:25
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx (1)

224-230: ⚠️ Potential issue | 🟠 Major

Don’t treat authenticated status as proof of accepted consent.

Line 225 removes checkout consent for all authenticated users, but authentication alone does not prove the user accepted the current required policy/version.

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

In `@src/app/`[country]/[locale]/(checkout)/checkout/[id]/page.tsx around lines
224 - 230, The current code blindly clears required checkout policies when
authStatus is true (setPolicies(...) uses authStatus ? [] : ...), which assumes
authentication implies consent; instead, update the logic in the setPolicies
call to check actual accepted policies for the authenticated user (e.g., a
userAcceptedPolicies or acceptedPolicySlugs collection) and only omit policies
that the user has already accepted, otherwise include required policies from
allPolicies filtered by CHECKOUT_CONSENT_POLICY_SLUGS; if accepted policy data
is not available, fall back to presenting the filtered required policies so
users can re-consent.
🤖 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/app/`[country]/[locale]/(checkout)/checkout/[id]/page.tsx:
- Around line 192-199: The current Promise.all call swallows failures from
getPoliciesStrict() by converting errors into an empty array which makes
policies.length === 0 and bypasses consent; update the code so
getPoliciesStrict() errors are not silently converted to [] (remove the
.catch(() => [] as Policy[])) and instead let the rejection propagate or map it
to a distinct sentinel (e.g., null or throw) so the surrounding logic in
page.tsx (where policies/policies.length and consent enforcement are checked)
can handle failures explicitly (show an error, block checkout, or display
consent UI) rather than treating an error as “no policies.” Ensure any
downstream code that expects an array is updated to handle the new
error/sentinel case.

In `@src/app/`[country]/[locale]/(checkout)/layout.tsx:
- Around line 57-61: The Link rendering for policy links (the JSX element using
Link with key={policy.id}, href={`${basePath}/policies/${policy.slug}`} and
target="_blank" in layout.tsx) is missing a rel attribute; update that Link to
include rel="noopener noreferrer" so external new-tab links are hardened against
tab-nabbing.

---

Duplicate comments:
In `@src/app/`[country]/[locale]/(checkout)/checkout/[id]/page.tsx:
- Around line 224-230: The current code blindly clears required checkout
policies when authStatus is true (setPolicies(...) uses authStatus ? [] : ...),
which assumes authentication implies consent; instead, update the logic in the
setPolicies call to check actual accepted policies for the authenticated user
(e.g., a userAcceptedPolicies or acceptedPolicySlugs collection) and only omit
policies that the user has already accepted, otherwise include required policies
from allPolicies filtered by CHECKOUT_CONSENT_POLICY_SLUGS; if accepted policy
data is not available, fall back to presenting the filtered required policies so
users can re-consent.
🪄 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

Run ID: 301839c7-ec96-4bbb-8e38-e66894cfc7cf

📥 Commits

Reviewing files that changed from the base of the PR and between 14f85e3 and 5289152.

📒 Files selected for processing (3)
  • src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx
  • src/app/[country]/[locale]/(checkout)/layout.tsx
  • src/lib/constants/policies.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/constants/policies.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 (4)
src/lib/constants/policies.ts (1)

1-12: Consider adding a shared type and deriving CONSENT_POLICIES from POLICY_LINKS.

The constants are well-organized. A minor improvement would be to define an explicit type and derive CONSENT_POLICIES from POLICY_LINKS to avoid duplication:

interface PolicyLink {
  name: string;
  slug: string;
}

export const POLICY_LINKS: PolicyLink[] = [
  { name: "Shipping Policy", slug: "shipping-policy" },
  // ...
];

const CONSENT_SLUGS = ["privacy-policy", "terms-of-service"] as const;

export const CONSENT_POLICIES = POLICY_LINKS.filter((p) =>
  CONSENT_SLUGS.includes(p.slug as typeof CONSENT_SLUGS[number])
);

This keeps consent policies in sync with the main list automatically.

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

In `@src/lib/constants/policies.ts` around lines 1 - 12, Define a shared
PolicyLink type and stop duplicating entries by deriving CONSENT_POLICIES from
POLICY_LINKS: add a PolicyLink interface/type, annotate POLICY_LINKS as
PolicyLink[], create a CONST array of consent slugs (e.g., CONSENT_SLUGS) and
set CONSENT_POLICIES = POLICY_LINKS.filter(p => CONSENT_SLUGS.includes(p.slug as
typeof CONSENT_SLUGS[number])); update any references to POLICY_LINKS and
CONSENT_POLICIES accordingly to use the new types.
src/components/policy/PolicyConsent.tsx (1)

32-35: Minor: Separator logic has an edge case for single-item arrays.

When CONSENT_POLICIES has only one item, the conditions work correctly. However, for exactly two items, the first item (index 0) shows nothing, and the second (index 1, which equals length - 1) shows " and ". This is correct behavior, but the logic could be clearer:

{index > 0 && (index === CONSENT_POLICIES.length - 1 ? " and " : ", ")}

This is a minor readability nit—the current code works correctly.

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

In `@src/components/policy/PolicyConsent.tsx` around lines 32 - 35, The separator
logic inside the CONSENT_POLICIES.map in PolicyConsent.tsx is split into two
conditionals and is slightly harder to read for the two-item case; replace the
two separate conditionals that render ", " and " and " (using index and
CONSENT_POLICIES.length) with a single conditional that for index > 0 renders
(index === CONSENT_POLICIES.length - 1 ? " and " : ", "), keeping the same
behavior but improving clarity in the map callback.
src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx (2)

14-37: Add generateStaticParams or set dynamic = 'force-dynamic'.

Per coding guidelines, dynamic routes should either use generateStaticParams for static generation or explicitly set dynamic = 'force-dynamic' for dynamic rendering.

Since policies are defined in POLICY_LINKS, you can statically generate these pages:

♻️ Proposed fix
+import { POLICY_LINKS } from "@/lib/constants/policies";
+
+export function generateStaticParams() {
+  return POLICY_LINKS.map((policy) => ({
+    slug: policy.slug,
+  }));
+}
+
 export async function generateMetadata({
   params,
 }: PolicyPageProps): Promise<Metadata> {

As per coding guidelines: "Use generateStaticParams for static generation of dynamic routes or set dynamic = 'force-dynamic' for dynamic rendering."

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

In `@src/app/`[country]/[locale]/(storefront)/policies/[slug]/page.tsx around
lines 14 - 37, The dynamic route lacks either generateStaticParams or an
explicit dynamic = 'force-dynamic' flag; add a generateStaticParams function
that returns entries for each policy from POLICY_LINKS (use the same slug shape
as PolicyPage params) so pages are statically generated, or alternatively export
const dynamic = 'force-dynamic' to force runtime rendering; update the module by
adding generateStaticParams (referencing POLICY_LINKS and the
PolicyPage/PolicyPageProps slug shape) or the dynamic export accordingly.

42-46: Sanitize HTML content to prevent XSS.

Using dangerouslySetInnerHTML with policy.body_html bypasses React's XSS protection. While policy content is typically admin-controlled, sanitizing the HTML adds defense-in-depth against compromised admin accounts or API responses.

Consider using a library like DOMPurify:

🛡️ Proposed fix
+import DOMPurify from "isomorphic-dompurify";

 {policy.body_html ? (
   <div
     className="prose prose-gray"
-    dangerouslySetInnerHTML={{ __html: policy.body_html }}
+    dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(policy.body_html) }}
   />

Install the package:

npm install isomorphic-dompurify
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[country]/[locale]/(storefront)/policies/[slug]/page.tsx around
lines 42 - 46, The policy page currently injects policy.body_html via
dangerouslySetInnerHTML without sanitization; update the component that renders
policy.body_html to sanitize the HTML first (use isomorphic-dompurify /
createDOMPurify import) and pass the sanitized string to dangerouslySetInnerHTML
(e.g., create a local safeHtml = DOMPurify.sanitize(policy.body_html || '')
before rendering). Ensure you import the sanitizer at top of the file, handle
null/undefined policy.body_html, and keep the same render path (the div that
uses dangerouslySetInnerHTML) but replace the raw value with the sanitized
safeHtml.
🤖 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/components/policy/PolicyConsent.tsx`:
- Around line 36-47: The PolicyConsent component currently builds policy links
with href={`/policies/${policy.slug}`} which omits localized basePath; update
the Link creation inside PolicyConsent (where Link and policy.slug are used) to
prefix the path with the passed-in basePath prop (use basePath + '/policies/' +
policy.slug) and ensure PolicyConsent accepts a basePath prop; then update call
sites (register/page.tsx and checkout/[id]/page.tsx) to pass the basePath prop
into PolicyConsent so localized URLs resolve correctly.

---

Nitpick comments:
In `@src/app/`[country]/[locale]/(storefront)/policies/[slug]/page.tsx:
- Around line 14-37: The dynamic route lacks either generateStaticParams or an
explicit dynamic = 'force-dynamic' flag; add a generateStaticParams function
that returns entries for each policy from POLICY_LINKS (use the same slug shape
as PolicyPage params) so pages are statically generated, or alternatively export
const dynamic = 'force-dynamic' to force runtime rendering; update the module by
adding generateStaticParams (referencing POLICY_LINKS and the
PolicyPage/PolicyPageProps slug shape) or the dynamic export accordingly.
- Around line 42-46: The policy page currently injects policy.body_html via
dangerouslySetInnerHTML without sanitization; update the component that renders
policy.body_html to sanitize the HTML first (use isomorphic-dompurify /
createDOMPurify import) and pass the sanitized string to dangerouslySetInnerHTML
(e.g., create a local safeHtml = DOMPurify.sanitize(policy.body_html || '')
before rendering). Ensure you import the sanitizer at top of the file, handle
null/undefined policy.body_html, and keep the same render path (the div that
uses dangerouslySetInnerHTML) but replace the raw value with the sanitized
safeHtml.

In `@src/components/policy/PolicyConsent.tsx`:
- Around line 32-35: The separator logic inside the CONSENT_POLICIES.map in
PolicyConsent.tsx is split into two conditionals and is slightly harder to read
for the two-item case; replace the two separate conditionals that render ", "
and " and " (using index and CONSENT_POLICIES.length) with a single conditional
that for index > 0 renders (index === CONSENT_POLICIES.length - 1 ? " and " : ",
"), keeping the same behavior but improving clarity in the map callback.

In `@src/lib/constants/policies.ts`:
- Around line 1-12: Define a shared PolicyLink type and stop duplicating entries
by deriving CONSENT_POLICIES from POLICY_LINKS: add a PolicyLink interface/type,
annotate POLICY_LINKS as PolicyLink[], create a CONST array of consent slugs
(e.g., CONSENT_SLUGS) and set CONSENT_POLICIES = POLICY_LINKS.filter(p =>
CONSENT_SLUGS.includes(p.slug as typeof CONSENT_SLUGS[number])); update any
references to POLICY_LINKS and CONSENT_POLICIES accordingly to use the new
types.
🪄 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

Run ID: 5736ee50-c977-4b49-8223-5c6c89e49ec9

📥 Commits

Reviewing files that changed from the base of the PR and between 5289152 and dc9a6bd.

📒 Files selected for processing (8)
  • src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx
  • src/app/[country]/[locale]/(checkout)/layout.tsx
  • src/app/[country]/[locale]/(storefront)/account/register/page.tsx
  • src/app/[country]/[locale]/(storefront)/layout.tsx
  • src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx
  • src/components/layout/Footer.tsx
  • src/components/policy/PolicyConsent.tsx
  • src/lib/constants/policies.ts
✅ Files skipped from review due to trivial changes (1)
  • src/app/[country]/[locale]/(storefront)/layout.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/app/[country]/[locale]/(checkout)/layout.tsx
  • src/components/layout/Footer.tsx

Cichorek and others added 7 commits March 30, 2026 13:31
- Add policy detail pages fetched from Spree API (/policies/[slug])
- Add policy consent checkbox on registration (Privacy Policy & Terms of Service)
- Add policy consent checkbox on checkout (all policies for guests, excluding
  registration policies for authenticated users)
- Add policies section in footer with dynamic links
- Fix footer links to use localized basePath

Closes #60

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When users submit without checking the policy consent checkbox, highlight
it with a red border, red label text, and scroll/focus to it. Replace the
custom error div in checkout with the shadcn Alert component for consistency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add getPoliciesStrict() that propagates errors for consent-gating flows
- Registration page blocks submit when policies fail to load (fail-closed)
- Checkout uses strict fetcher with graceful fallback
- Policy page declared as force-dynamic for API-driven content

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Authenticated users accepted terms during registration, so no checkbox
is shown at checkout. Guest users see only the Terms of Service checkbox.
All policy links are now displayed in the checkout footer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…config

Remove overengineered policy fetching from checkout, registration, and layouts.
Policies are now hardcoded in constants — no API calls, no loading states, no
error handling for consent. Footer and checkout footer use shared POLICY_LINKS,
consent checkbox uses CONSENT_POLICIES.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… 0.19

getPolicy was removed from @spree/next in 0.19.0. Use getClient() directly
via src/lib/data/policies.ts, matching the project's data-fetching pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Cichorek Cichorek force-pushed the feature/policies-consent branch from dc9a6bd to 218c50a Compare March 30, 2026 12:08
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx (1)

1-12: Add dynamic rendering configuration.

Per coding guidelines, dynamic routes should either use generateStaticParams for static generation or set dynamic = 'force-dynamic' for dynamic rendering. Since policies are admin-managed and may change, dynamic rendering is likely appropriate here.

🔧 Suggested fix
 import type { Metadata } from "next";
 import { notFound } from "next/navigation";
 import { getPolicy } from "@/lib/data/policies";
 import { getStoreName } from "@/lib/seo";
 
+export const dynamic = "force-dynamic";
+
 interface PolicyPageProps {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[country]/[locale]/(storefront)/policies/[slug]/page.tsx around
lines 1 - 12, This page currently lacks dynamic rendering config; add an
explicit export const dynamic = 'force-dynamic' at the top of the module to opt
this dynamic route into runtime rendering (since content served by getPolicy can
change) and keep existing imports/logic (getPolicy, getStoreName, notFound,
PolicyPageProps) unchanged so Next uses dynamic rendering for this policy page.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/app/`[country]/[locale]/(storefront)/policies/[slug]/page.tsx:
- Around line 1-12: This page currently lacks dynamic rendering config; add an
explicit export const dynamic = 'force-dynamic' at the top of the module to opt
this dynamic route into runtime rendering (since content served by getPolicy can
change) and keep existing imports/logic (getPolicy, getStoreName, notFound,
PolicyPageProps) unchanged so Next uses dynamic rendering for this policy page.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 21fa6f49-92c0-44e7-a5ad-a6405ca83a0e

📥 Commits

Reviewing files that changed from the base of the PR and between dc9a6bd and 218c50a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • README.md
  • src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx
  • src/app/[country]/[locale]/(checkout)/layout.tsx
  • src/app/[country]/[locale]/(storefront)/account/register/page.tsx
  • src/app/[country]/[locale]/(storefront)/layout.tsx
  • src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx
  • src/components/layout/Footer.tsx
  • src/components/policy/PolicyConsent.tsx
  • src/lib/constants/policies.ts
  • src/lib/data/index.ts
  • src/lib/data/policies.ts
✅ Files skipped from review due to trivial changes (5)
  • src/app/[country]/[locale]/(storefront)/layout.tsx
  • src/lib/data/index.ts
  • src/lib/constants/policies.ts
  • README.md
  • src/components/policy/PolicyConsent.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/[country]/[locale]/(checkout)/layout.tsx

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

Add policies (terms of conditions) checkboxes in signup and checkout

2 participants