Skip to content

Change request/ID verification flow to use separate pages instead of a modal#7238

Merged
jpople merged 23 commits intomainfrom
jpople/eng-2283/id-verification-flow
Feb 25, 2026
Merged

Change request/ID verification flow to use separate pages instead of a modal#7238
jpople merged 23 commits intomainfrom
jpople/eng-2283/id-verification-flow

Conversation

@jpople
Copy link
Copy Markdown
Contributor

@jpople jpople commented Jan 16, 2026

Ticket ENG-2283

Description Of Changes

Converted the privacy request flow from a modal-based approach to full-page routes. The form, verification, and success states now use dedicated Next.js pages with a consistent layout. Refactored ExternalAuthLayout to use a shared AuthFormLayout component to reduce code duplication.

Code Changes

  • Extracted request creation and ID verification to separate pages instead of modals
  • Refactored ExternalAuthLayout to use AuthFormLayout instead of duplicating layout code
  • Added e2e tests for the privacy request flow

Steps to Confirm

  1. Navigate to the privacy center homepage and click a privacy request action
  2. Fill out the form and verify request is submitted as expected
  3. Update your privacy center config so requests require identity verification
  4. Confirm navigation to the verification page and ability to enter OTP code
  5. After successful verification, confirm navigation to the success page
  6. Verify external manual task authentication pages still render correctly with the refactored layout

Pre-Merge Checklist

  • Issue requirements met
  • All CI pipelines succeeded
  • CHANGELOG.md updated
    • Add a db-migration This indicates that a change includes a database migration label to the entry if your change includes a DB migration
    • Add a high-risk This issue suggests changes that have a high-probability of breaking existing code label to the entry if your change includes a high-risk change (i.e. potential for performance impact or unexpected regression) that should be flagged
    • Updates unreleased work already in Changelog, no new entry necessary
  • UX feedback:
    • All UX related changes have been reviewed by a designer
    • No UX review needed
  • Followup issues:
    • Followup issues created
    • No followup issues
  • Database migrations:
    • Ensure that your downrev is up to date with the latest revision on main
    • Ensure that your downgrade() migration is correct and works
      • If a downgrade migration is not possible for this change, please call this out in the PR description!
    • No migrations
  • Documentation:
    • Documentation complete, PR opened in fidesdocs
    • Documentation issue created in fidesdocs
    • If there are any new client scopes created as part of the pull request, remember to update public-facing documentation that references our scope registry
    • No documentation updates required

@jpople jpople requested a review from a team as a code owner January 16, 2026 08:02
@jpople jpople requested review from speaker-ender and removed request for a team January 16, 2026 08:02
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Jan 16, 2026

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

Project Deployment Actions Updated (UTC)
fides-plus-nightly Ready Ready Preview, Comment Feb 25, 2026 0:17am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
fides-privacy-center Ignored Ignored Feb 25, 2026 0:17am

Request Review

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Jan 16, 2026

Greptile Summary

Converts the privacy request flow from a modal-based approach to full-page Next.js routes, improving user experience with dedicated pages for form submission, ID verification, and success confirmation. Successfully refactored ExternalAuthLayout to use a new shared AuthFormLayout component, reducing ~80 lines of duplicated code.

Key changes:

  • Extracted privacy request form, verification, and success states into separate Next.js pages under /privacy-request/[actionKey]/*
  • Created reusable AuthFormLayout component that both privacy request and external auth flows now share
  • Updated routing in HomePage to navigate to pages instead of opening modals
  • Adapted existing form components to work in both modal and page contexts with new callback props
  • Added comprehensive e2e tests covering the full page-based flow including verification and edge cases
  • Uses sessionStorage to pass privacy request ID between pages during verification flow

Minor improvements:

  • The PR uses direct window.sessionStorage access in a couple places where the global context pattern would be more consistent with the codebase standards

Confidence Score: 4/5

  • This PR is safe to merge with minor style improvements recommended
  • The refactor is well-executed with proper routing, comprehensive e2e tests, and successful code deduplication. The only concerns are minor style issues around direct window access that don't affect functionality but could be more consistent with codebase patterns.
  • Pay close attention to PrivacyRequestFormPage.tsx and VerificationPage.tsx for the sessionStorage usage pattern

Important Files Changed

Filename Overview
clients/privacy-center/components/privacy-request/PrivacyRequestFormPage.tsx Uses sessionStorage API directly instead of accessing through config; otherwise handles routing and state management properly
clients/privacy-center/components/privacy-request/VerificationPage.tsx Uses sessionStorage API directly instead of accessing through config; otherwise handles verification flow correctly with proper redirects
clients/privacy-center/components/common/AuthFormLayout.tsx Well-structured shared layout component that properly uses Flex components and reduces duplication
clients/privacy-center/features/external-manual-tasks/components/ExternalAuthLayout.tsx Successfully refactored to use shared AuthFormLayout, greatly reducing code duplication

Last reviewed commit: 7c0d9df

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

13 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

}}
data-testid={dataTestId}
>
<div style={{ width: "100%", maxWidth, padding: "48px 24px" }}>
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.

style: uses div instead of semantic components - per coding standards, prefer Ant Design's Flex component or other semantic elements

Suggested change
<div style={{ width: "100%", maxWidth, padding: "48px 24px" }}>
<Flex style={{ width: "100%", maxWidth, padding: "48px 24px" }}>

Context Used: Rule from dashboard - Avoid using div elements when possible. Use semantic HTML elements or component library alternativ... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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.

Not as worried about semantic dom here, but some of the styles could use tailwind instead of being inline

Comment on lines +59 to +67
<div
style={{
backgroundColor: "white",
padding: "48px",
width: "100%",
borderRadius: "4px",
boxShadow:
"0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)",
}}
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.

style: uses div for form container - prefer semantic or Flex component

Suggested change
<div
style={{
backgroundColor: "white",
padding: "48px",
width: "100%",
borderRadius: "4px",
boxShadow:
"0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)",
}}
<Flex
style={{
backgroundColor: "white",
padding: "48px",
width: "100%",
borderRadius: "4px",
boxShadow:
"0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)",
}}
>

Context Used: Rule from dashboard - Avoid using div elements when possible. Use semantic HTML elements or component library alternativ... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor

@speaker-ender speaker-ender left a comment

Choose a reason for hiding this comment

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

Some minor nits and questions but overall looks good.

}}
data-testid={dataTestId}
>
<div style={{ width: "100%", maxWidth, padding: "48px 24px" }}>
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.

Not as worried about semantic dom here, but some of the styles could use tailwind instead of being inline

iconPath={action.icon_path}
description={action.description}
onOpen={onPrivacyModalOpen}
onOpen={handlePrivacyRequestOpen}
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.

nit: should we change the onOpen prop to onClick to match the native ant card component since it's not "opening" something?

if (onSuccessWithoutVerification) {
onSuccessWithoutVerification();
} else {
onClose();
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.

nit: can we rename onClose since we aren't closing a modal?

}, [getIdVerificationConfigQuery]);

if (Number.isNaN(parsedActionIndex)) {
return null;
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.

Should we re-direct or throw some kind of error message if the route isn't valid instead of displaying nothing?

Comment on lines +39 to +41
const action = config.actions[parsedActionIndex] as
| PrivacyRequestOption
| undefined;
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.

Is there a reason to type cast here?

params: Promise<{ actionIndex: string }>;
searchParams: NextSearchParams;
}) => {
const { actionIndex } = await params;
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.

I like the idea of having a dynamic route for the different actions.
Are we able to/do you think it makes sense to use the action title or some other identifier instead of an index?
Using the index is a little less stable. If someone re-organizes the config, the links to actions would be mixed up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good suggestion, I've updated to use the action key instead of the index.

title,
}: PrivacyRequestLayoutProps) => {
return (
<AuthFormLayout
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.

Is this component doing anything other than passing some defaults to the AuthFormLayout component? If so, why not just use the component directly?

@jpople jpople requested a review from speaker-ender February 10, 2026 00:30
@jpople jpople marked this pull request as ready for review February 24, 2026 08:20
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

16 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +61 to +63
if (typeof window !== "undefined") {
sessionStorage.setItem("privacyRequestId", id);
}
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.

accesses window.sessionStorage directly - prefer using global context when available rather than window access

Suggested change
if (typeof window !== "undefined") {
sessionStorage.setItem("privacyRequestId", id);
}
sessionStorage.setItem("privacyRequestId", id);

Context Used: Rule from dashboard - Prefer passing configuration options as props to components instead of accessing global window objec... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +22 to +30
if (typeof window !== "undefined") {
const storedId = sessionStorage.getItem("privacyRequestId");
if (storedId) {
setPrivacyRequestId(storedId);
} else {
// If no request ID, redirect back to form
router.push(`/privacy-request/${encodeURIComponent(policyKey)}`);
}
}
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.

accesses window.sessionStorage directly - prefer using global context when available rather than window access

Suggested change
if (typeof window !== "undefined") {
const storedId = sessionStorage.getItem("privacyRequestId");
if (storedId) {
setPrivacyRequestId(storedId);
} else {
// If no request ID, redirect back to form
router.push(`/privacy-request/${encodeURIComponent(policyKey)}`);
}
}
const storedId = sessionStorage.getItem("privacyRequestId");

Context Used: Rule from dashboard - Prefer passing configuration options as props to components instead of accessing global window objec... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@jpople jpople added this pull request to the merge queue Feb 25, 2026
Merged via the queue into main with commit f5dd02e Feb 25, 2026
40 of 41 checks passed
@jpople jpople deleted the jpople/eng-2283/id-verification-flow branch February 25, 2026 15:57
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