Skip to content

Feature/295 eyebrow cta email login invite flow#300

Closed
OtavioXimarelli wants to merge 5 commits intoQuoteVote:mainfrom
OtavioXimarelli:feature/295-eyebrow-cta-email-login-invite-flow
Closed

Feature/295 eyebrow cta email login invite flow#300
OtavioXimarelli wants to merge 5 commits intoQuoteVote:mainfrom
OtavioXimarelli:feature/295-eyebrow-cta-email-login-invite-flow

Conversation

@OtavioXimarelli
Copy link
Copy Markdown
Member

Feature #299: Eyebrow CTA — Email-Based Login & Invite Flow

Closes #299

What this PR does

The Eyebrow is a top bar shown to unauthenticated visitors. The user types their email and clicks Continue. The backend checks the email against User and UserInvite collections and returns one of four statuses. The frontend then decides what to show:

Status Meaning What happens in the UI
registered Has an account with password Opens LoginOptionsModal — send magic link or go to password login
not_requested Email not in the system Creates a UserInvite (pending), shows "Your request has been received!"
requested_pending Invite already submitted Shows "Your invite request is still waiting for approval."
approved_no_password Approved but hasn't set a password Opens OnboardingCompletionModal — send magic link or create password

env.ts fix (src/config/env.ts)

The env config was reading NEXT_PUBLIC_* vars via process.env[name] (dynamic bracket access). Next.js only inlines these vars when accessed as static literals (process.env.NEXT_PUBLIC_FOO). On the client side, the dynamic access returned undefined, so Apollo was sending requests to localhost:3000 instead of localhost:4000. Fixed by switching to direct static access.

CSP update (next.config.ts)

Added ws://localhost:4000 to the connect-src directive so WebSocket subscriptions aren't blocked.


Tests

All passing.

Backend (28 new tests, 239 total):

  • checkEmailStatus resolver — 13 tests covering all 4 status paths, case normalization, whitespace trimming

  • sendMagicLink resolver — 13 tests covering JWT generation, SendGrid integration, non-existent user handling

  • requireAuth — 2 tests confirming public query registration

Frontend (52 related tests):

  • Eyebrow — 10 tests: all 4 flows, auth gating, error handling with Apollo mocks

  • LoginOptionsModal — 9 tests: magic link, password redirect, loading/error states

  • OnboardingCompletionModal — 9 tests: magic link, password redirect, loading/error states

  • Environment config — 12 tests: static access, fallbacks, error messages

  • Apollo client — 12 tests: client creation, singleton behavior


Before merging

  • Replace the placeholder MAGIC_LOGIN_LINK template ID in send-grid-mail.ts with the actual ID from SendGrid
  • The magic link landing page (/auth/magic-link) is not part of this PR — needs a follow-up

19 files changed — 1,105 additions, 94 deletions

OtavioXimarelli and others added 4 commits March 3, 2026 01:16
…te flow

Backend:
- Add UserInvite Mongoose model for invite tracking
- Add checkEmailStatus query and sendMagicLink mutation to GraphQL schema
- Implement resolvers with JWT magic link generation and SendGrid integration
- Register new operations as public queries (no auth required)
- Add MAGIC_LOGIN_LINK SendGrid template ID
- Add TypeScript types for new resolvers

Frontend:
- Add EmailStatus type and CheckEmailStatusResult interface
- Add CHECK_EMAIL_STATUS query and SEND_MAGIC_LINK mutation
- Wire Eyebrow component to Apollo Client (replace fetch placeholders)
- Complete LoginOptionsModal with magic link and password login options
- Complete OnboardingCompletionModal with onboarding link and create password
- Add toast notifications for success/error feedback

Tests:
- 13 unit tests for checkEmailStatus and sendMagicLink resolvers
- 2 new requireAuth tests for public query registration
- 10 Eyebrow component tests with Apollo mocks
- 9 LoginOptionsModal tests
- 9 OnboardingCompletionModal tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace dynamic bracket access (process.env[name]) with static literals
- Next.js/Turbopack only inlines NEXT_PUBLIC_* vars with static access
- Fix GraphQL requests going to localhost:3000 instead of localhost:4000

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Set CORS origin to FRONTEND_URL/CLIENT_URL instead of wildcard '*'
- Enable credentials: true for cookie/auth header support
- Add ws://localhost:4000 to CSP connect-src for WebSocket subscriptions
- Add requestUserAccess mutation to GraphQL schema and resolvers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 4, 2026 03:48
@OtavioXimarelli
Copy link
Copy Markdown
Member Author

Tested the full flow locally and everything works as expected. Let me know if this matches what you had in mind or if anything needs adjusting, thank you guys.
@flyblackbox

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements the “Eyebrow CTA” email-based entry flow by adding frontend UI (Eyebrow + modals) and backend GraphQL support to determine email status, request access, and send magic login links. It also fixes a Next.js env var inlining issue and updates CSP to allow local WebSocket connections.

Changes:

  • Frontend: add checkEmailStatus query + sendMagicLink mutation wiring, new Eyebrow flows, and modal components/tests.
  • Frontend: fix env var access to use static process.env.NEXT_PUBLIC_* literals and update CSP connect-src for ws.
  • Backend: add checkEmailStatus, requestUserAccess, and sendMagicLink resolvers + new UserInvite model and unit tests.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
quotevote-frontend/src/types/eyebrow.ts Adds shared types for email status + query result shape
quotevote-frontend/src/graphql/queries.ts Adds CHECK_EMAIL_STATUS query
quotevote-frontend/src/graphql/mutations.ts Adds SEND_MAGIC_LINK mutation
quotevote-frontend/src/config/env.ts Fixes Next.js public env var access (static literals) + improves validation
quotevote-frontend/src/app/components/Eyebrow/OnboardingCompletionModal.tsx Hooks up magic-link sending + “create password” navigation
quotevote-frontend/src/app/components/Eyebrow/LoginOptionsModal.tsx Adds magic-link + password-login actions
quotevote-frontend/src/app/components/Eyebrow/Eyebrow.tsx Uses Apollo query/mutation to drive the 4-state flow
quotevote-frontend/src/tests/components/OnboardingCompletionModal.test.tsx Adds unit tests for onboarding modal behavior
quotevote-frontend/src/tests/components/LoginOptionsModal.test.tsx Adds unit tests for login options modal behavior
quotevote-frontend/src/tests/components/Eyebrow.test.tsx Updates Eyebrow tests to mock Apollo query/mutation and adds coverage
quotevote-frontend/next.config.ts Updates CSP connect-src to allow ws://localhost:4000
quotevote-backend/package.json Adds packageManager pin for pnpm
quotevote-backend/app/types/graphql.ts Adds typings for new query/mutation + result type
quotevote-backend/app/server.ts Implements checkEmailStatus, requestUserAccess, sendMagicLink + CORS origin config
quotevote-backend/app/data/utils/send-grid-mail.ts Adds SendGrid template ID constant for magic link
quotevote-backend/app/data/utils/requireAuth.ts Marks checkEmailStatus / sendMagicLink as public operations
quotevote-backend/app/data/models/UserInvite.ts Introduces UserInvite mongoose model
quotevote-backend/tests/unit/requireAuth.test.ts Adds tests ensuring the new operations are public
quotevote-backend/tests/unit/eyebrowResolvers.test.ts Adds unit tests for new resolver logic (currently duplicated inline)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@flyblackbox
Copy link
Copy Markdown
Contributor

Hello @OtavioXimarelli can you please help me learn why no Deploy Preview Link was generated?

@OtavioXimarelli
Copy link
Copy Markdown
Member Author

Of course, I'll investigate what is happening @flyblackbox

@flyblackbox
Copy link
Copy Markdown
Contributor

Sorry @OtavioXimarelli I meant to send you the PR on the legacy repo: QuoteVote/quotevote-monorepo#296

Yes this one is completed.

- Fix SearchContainer test mock: change @apollo/client → @apollo/client/react
  to match actual import path (pre-existing bug on main branch)
- Fix components returning undefined: change bare returns to return null
  in LoginOptionsModal, OnboardingCompletionModal, and Eyebrow
- Fix login route: /login → /auths/login in LoginOptionsModal and test
- Add default case to Eyebrow switch statement
- Add unique constraint to UserInvite email field
- Fix sendMagicLink to return false for non-existent users
- Replace SendGrid placeholder template with env var + PASSWORD_RESET fallback
- Fix misleading test name for dialog dismiss behavior
@OtavioXimarelli
Copy link
Copy Markdown
Member Author

I've probably finished all the fixes. Sorry for the delay! Let me know if anything needs more changes, @flyblackbox.

The build was broken due to 6 failing tests in SearchContainer.test.tsx—a pre-existing bug unrelated to the PR's changes.

Root cause: The test file mocked useQuery from @apollo/client, but the component (SidebarSearchView) actually imports it from @apollo/client/react. Jest treats these as different modules, so the mock never applied, and all tests using useQuery failed.

Fix: Changed the mock target from @apollo/client to @apollo/client/react. It's a one-line change, and all 20 tests pass now. This same failure also exists on the main branch.

@flyblackbox
Copy link
Copy Markdown
Contributor

@OtavioXimarelli I think this was already built in the 'next' repo. Take a look here: #234

I am sorry, I meant for you to bring parity to the codebases via this issue:
QuoteVote/quotevote-monorepo#296

Again, my sincere apologies for the mixup. How do you think we should proceed?

@OtavioXimarelli
Copy link
Copy Markdown
Member Author

No worries at all! I misunderstood the tasks. I will close this PR, head over to issue #296, and start working on bringing parity to the codebases. Thank you @flyblackbox

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.

🤨Feature: Eyebrow Call-to-Action (Email-Based Login + Invite Flow)

3 participants