Feature/295 eyebrow cta email login invite flow#300
Feature/295 eyebrow cta email login invite flow#300OtavioXimarelli wants to merge 5 commits intoQuoteVote:mainfrom
Conversation
…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>
|
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. |
There was a problem hiding this comment.
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
checkEmailStatusquery +sendMagicLinkmutation wiring, new Eyebrow flows, and modal components/tests. - Frontend: fix env var access to use static
process.env.NEXT_PUBLIC_*literals and update CSPconnect-srcfor ws. - Backend: add
checkEmailStatus,requestUserAccess, andsendMagicLinkresolvers + newUserInvitemodel 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.
quotevote-frontend/src/__tests__/components/LoginOptionsModal.test.tsx
Outdated
Show resolved
Hide resolved
quotevote-frontend/src/app/components/Eyebrow/LoginOptionsModal.tsx
Outdated
Show resolved
Hide resolved
quotevote-frontend/src/__tests__/components/LoginOptionsModal.test.tsx
Outdated
Show resolved
Hide resolved
quotevote-frontend/src/app/components/Eyebrow/LoginOptionsModal.tsx
Outdated
Show resolved
Hide resolved
|
Hello @OtavioXimarelli can you please help me learn why no Deploy Preview Link was generated? |
|
Of course, I'll investigate what is happening @flyblackbox |
|
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
|
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. |
|
@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: Again, my sincere apologies for the mixup. How do you think we should proceed? |
|
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 |
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
UserandUserInvitecollections and returns one of four statuses. The frontend then decides what to show:env.tsfix (src/config/env.ts)The env config was reading
NEXT_PUBLIC_*vars viaprocess.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 returnedundefined, so Apollo was sending requests tolocalhost:3000instead oflocalhost:4000. Fixed by switching to direct static access.CSP update (
next.config.ts)Added
ws://localhost:4000to theconnect-srcdirective so WebSocket subscriptions aren't blocked.Tests
All passing.
Backend (28 new tests, 239 total):
checkEmailStatusresolver — 13 tests covering all 4 status paths, case normalization, whitespace trimmingsendMagicLinkresolver — 13 tests covering JWT generation, SendGrid integration, non-existent user handlingrequireAuth— 2 tests confirming public query registrationFrontend (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
MAGIC_LOGIN_LINKtemplate ID insend-grid-mail.tswith the actual ID from SendGrid/auth/magic-link) is not part of this PR — needs a follow-up19 files changed — 1,105 additions, 94 deletions