Skip to content

new onboarding flow#26

Merged
ArjunCodess merged 6 commits intomainfrom
arjuncodess/new-onboarding-flow
Mar 13, 2026
Merged

new onboarding flow#26
ArjunCodess merged 6 commits intomainfrom
arjuncodess/new-onboarding-flow

Conversation

@ArjunCodess
Copy link
Member

@ArjunCodess ArjunCodess commented Mar 13, 2026

Greptile Summary

This PR introduces a redesigned onboarding flow that prepends two new steps — Instagram connect and an AI-generated DM preview — before the existing profile/goals questionnaire, then redirects users to a sidekick onboarding page. On the sidekick side, the "Offers" and "What You Sell" steps are merged into one combined step, reducing total steps from 5 to 4. A new instagram-auth.ts utility centralises shared OAuth helpers, and the Instagram auth routes now support a cookie-based returnTo mechanism so the user lands back at the correct page after connecting.

Key changes:

  • New server actions prepareInstagramPreview and getInstagramOnboardingState fetch real Instagram DM context and generate an AI reply preview during onboarding.
  • onboarding/page.tsx gains two new steps (Instagram connect at index 0, preview at index 1) and shifts the original three questionnaire steps to indices 2–4.
  • sidekick-onboarding/page.tsx consolidates initialization into a single Promise.all, adds an isMounted guard, and merges steps 1 and 2.
  • Instagram auth routes store returnTo in an httpOnly cookie and redirect back to the originating page on success or failure, rather than always going to /settings.
  • instagram-auth.ts extracted as a shared module — fixing the previously flagged normalizeReturnTo duplication.

Issues found:

  • business_type value format regression (onboarding/page.tsx): The radio group now stores raw option strings (e.g. "Coach/Consultant", "SaaS") instead of the optionToValue-normalised forms ("coach_consultant", "saas") used previously. Any user whose business_type was saved under the old format will see a blank (unselected) radio group on return, and the "Other" conditional (watchedBusinessType === "Other") won't fire for values stored as "other". All other radio groups in the same file still use optionToValue, making this inconsistent.
  • INSTAGRAM_RETURN_TO_COOKIE constant duplicated (route.ts and callback/route.ts): The string "pilot_instagram_return_to" is defined identically in both files rather than exported from the shared instagram-auth.ts. A one-sided rename would silently break the OAuth flow.

Confidence Score: 2/5

  • Not safe to merge without addressing the business_type value format regression, which will silently break pre-fill for any user with existing partial onboarding data.
  • The core Instagram auth flow and new server actions look solid, but the business_type radio group now stores raw option strings instead of the optionToValue-normalised values used by every other field — and by the existing DB rows. This is a silent data-mismatch regression affecting returning/partial-onboarding users. The duplicated cookie constant is a lower-severity maintenance hazard but not immediately breaking.
  • Pay close attention to apps/app/src/app/(onboarding)/onboarding/page.tsx — specifically the business_type radio group refactor and how it interacts with pre-filled data from checkOnboardingStatusAndPrefill.

Important Files Changed

Filename Overview
apps/app/src/actions/onboarding.ts Adds prepareInstagramPreview and getInstagramOnboardingState server actions. Logic is well-structured with fallbacks and error boundaries. No new critical issues beyond the previously flagged message.from guard.
apps/app/src/app/(onboarding)/onboarding/page.tsx Major revamp adding Instagram connect and preview steps. Contains a data-format regression: business_type radio values changed from optionToValue(option) to raw option strings, breaking pre-fill for any user with previously saved data. Previously flagged infinite-retry loop on preview failure now guarded by previewError in the effect dependency array.
apps/app/src/app/(onboarding)/sidekick-onboarding/page.tsx Merges the "Offers" and "What You Sell" steps into a single combined step. Initialization consolidated into one Promise.all, and step indices updated throughout. isMounted guard added for async cleanup. No critical bugs identified.
apps/app/src/app/api/auth/instagram/callback/route.ts Reads returnTo from a cookie and uses it for all redirects. Previously-flagged normalizeReturnTo duplication is now fixed via shared instagram-auth.ts. The INSTAGRAM_RETURN_TO_COOKIE string constant is still duplicated here and in the initiation route.
apps/app/src/app/api/auth/instagram/route.ts Stores returnTo from search params into an httpOnly cookie before redirecting to Instagram. Cookie attributes (sameSite: "lax", short maxAge) are appropriate for OAuth. INSTAGRAM_RETURN_TO_COOKIE constant is duplicated from the callback route.
apps/app/src/lib/instagram-auth.ts New shared utility extracting normalizeInstagramReturnTo, getInstagramAppBaseUrl, and getInstagramCallbackUrl. The open-redirect guard in normalizeInstagramReturnTo is thorough and correct.
apps/app/src/lib/constants/sidekick-onboarding.ts Removes "Your Offers" as a separate step (now merged into "What You Sell" step), renumbering remaining steps. Straightforward constants change.

Sequence Diagram

sequenceDiagram
    participant User
    participant OnboardingPage
    participant InstagramRoute as /api/auth/instagram
    participant Instagram
    participant CallbackRoute as /api/auth/instagram/callback
    participant OnboardingActions

    User->>OnboardingPage: Visit /onboarding (Step 0)
    User->>OnboardingPage: Click "Connect Instagram"
    OnboardingPage->>InstagramRoute: GET /api/auth/instagram?returnTo=/onboarding
    InstagramRoute->>InstagramRoute: Store returnTo in httpOnly cookie
    InstagramRoute->>Instagram: Redirect to Instagram OAuth URL
    Instagram->>CallbackRoute: GET /api/auth/instagram/callback?code=...
    CallbackRoute->>CallbackRoute: Read returnTo cookie & delete it
    CallbackRoute->>Instagram: Exchange code for access token
    CallbackRoute->>Instagram: Exchange for long-lived token
    CallbackRoute->>OnboardingActions: saveInstagramConnection()
    CallbackRoute->>OnboardingPage: Redirect to /onboarding?success=instagram_connected

    OnboardingPage->>OnboardingPage: Detect success param → advance to Step 1 (Preview)
    OnboardingPage->>OnboardingActions: prepareInstagramPreview()
    OnboardingActions->>Instagram: fetchConversationsForSync()
    OnboardingActions->>OnboardingActions: selectPreviewConversation()
    OnboardingActions->>OnboardingActions: buildReplyPreview() via Gemini AI
    OnboardingActions->>OnboardingPage: Return preview data

    OnboardingPage->>OnboardingPage: Step 2 – About You
    OnboardingPage->>OnboardingPage: Step 3 – How You Work
    OnboardingPage->>OnboardingPage: Step 4 – Goals
    OnboardingPage->>User: Redirect to /sidekick-onboarding
Loading

Comments Outside Diff (1)

  1. apps/app/src/app/(onboarding)/onboarding/page.tsx, line 1671 (link)

    business_type option value format changed, breaking pre-fill for returning users

    The business_type radio group was refactored to use the raw option string (e.g. "Coach/Consultant", "SaaS", "Other") as the form value, instead of the optionToValue(option) transformation used everywhere else (which produces "coach_consultant", "saas", "other").

    When checkOnboardingStatusAndPrefill runs, it calls:

    step2Form.setValue("business_type", userDataResult.userData.business_type);

    Any user who saved a business_type value under the old format will have the field pre-filled with e.g. "coach_consultant" or "saas", but none of the radio <RadioGroupItem value={option}> elements will have those values — they now expect "Coach/Consultant" or "SaaS". The result is a blank (unselected) radio group, and the "Tell us about your business type" conditional (watchedBusinessType === "Other") will also never fire for users who previously selected "Other" (stored as "other").

    This affects any user who partially completed onboarding and returns to the page, or any database row where business_type was previously saved.

    Either revert to using optionToValue(option) as the radio value (consistent with gender, use_case, active_platforms, and current_tracking), or migrate existing DB values to the new casing.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/app/src/app/api/auth/instagram/route.ts
Line: 14

Comment:
**`INSTAGRAM_RETURN_TO_COOKIE` duplicated across both route files**

`INSTAGRAM_RETURN_TO_COOKIE = "pilot_instagram_return_to"` is defined identically here and in `apps/app/src/app/api/auth/instagram/callback/route.ts` (line 17). If the cookie name is ever changed in one file but not the other, the OAuth flow breaks silently — the callback route reads a cookie that was stored under a different name, `returnTo` resolves to `"/settings"`, and the user ends up in the wrong place after connecting Instagram.

Since `instagram-auth.ts` already exists as the shared utility module for this flow, the constant belongs there:

```ts
// apps/app/src/lib/instagram-auth.ts
export const INSTAGRAM_RETURN_TO_COOKIE = "pilot_instagram_return_to";
```

Then import it in both route files instead of redefining it. The same issue is present in the callback route at `apps/app/src/app/api/auth/instagram/callback/route.ts:17`.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/app/src/app/(onboarding)/onboarding/page.tsx
Line: 1671

Comment:
**`business_type` option value format changed, breaking pre-fill for returning users**

The `business_type` radio group was refactored to use the raw option string (e.g. `"Coach/Consultant"`, `"SaaS"`, `"Other"`) as the form value, instead of the `optionToValue(option)` transformation used everywhere else (which produces `"coach_consultant"`, `"saas"`, `"other"`).

When `checkOnboardingStatusAndPrefill` runs, it calls:
```ts
step2Form.setValue("business_type", userDataResult.userData.business_type);
```
Any user who saved a `business_type` value under the old format will have the field pre-filled with e.g. `"coach_consultant"` or `"saas"`, but none of the radio `<RadioGroupItem value={option}>` elements will have those values — they now expect `"Coach/Consultant"` or `"SaaS"`. The result is a blank (unselected) radio group, and the "Tell us about your business type" conditional (`watchedBusinessType === "Other"`) will also never fire for users who previously selected "Other" (stored as `"other"`).

This affects any user who partially completed onboarding and returns to the page, or any database row where `business_type` was previously saved.

Either revert to using `optionToValue(option)` as the radio value (consistent with `gender`, `use_case`, `active_platforms`, and `current_tracking`), or migrate existing DB values to the new casing.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 45583e1

@ArjunCodess ArjunCodess self-assigned this Mar 13, 2026
@vercel
Copy link
Contributor

vercel bot commented Mar 13, 2026

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

Project Deployment Actions Updated (UTC)
pilot-app Ready Ready Preview, Comment Mar 13, 2026 8:51pm
pilot-web Ready Ready Preview, Comment Mar 13, 2026 8:51pm

@greptile-apps
Copy link

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR introduces a redesigned onboarding flow that front-loads the Instagram connection and shows users a live AI-generated DM reply preview before collecting their profile and business details. It also consolidates the sidekick onboarding from 5 steps to 4 by merging "Your Offers" and "What You Sell" into a single step, and upgrades the Instagram OAuth callback to support a cookie-based dynamic returnTo redirect.

Key changes:

  • Two new steps prepended to the main onboarding: step 0 (Instagram connect) and step 1 (DM preview with AI reply), shifting the existing 3 steps to positions 2–4.
  • New prepareInstagramPreview server action fetches up to 12 recent conversations, picks a suitable thread, and calls Gemini to generate a personalized reply preview.
  • New getInstagramOnboardingState server action allows the page to check Instagram connection status without fetching full conversation data.
  • Instagram OAuth routes now store the desired returnTo path in a secure httpOnly cookie (10-minute TTL) rather than a query param, with proper path-only validation to prevent open-redirect abuse.
  • Sidekick onboarding initialization is consolidated from five separate useEffect calls into a single parallel Promise.all, and adds an isMounted guard to prevent state updates after unmount.
  • Bug: The useEffect that auto-loads the DM preview does not include previewError in its early-return guard. When prepareInstagramPreview() fails, isPreparingPreview flips true → false (triggering a re-run) while instagramPreview is still null — all guard conditions pass and loadPreview() fires again, creating an infinite retry loop.
  • Bug: formatPreviewMessages accesses message.from.username without checking whether message.from is defined, which can cause a runtime crash on system or automated Instagram messages.

Confidence Score: 2/5

  • Not safe to merge as-is — the preview loading step has an infinite retry loop that will spam the API and degrade UX whenever the preview call fails.
  • The infinite retry loop in the preview useEffect is a concrete, reproducible runtime bug: any transient failure of prepareInstagramPreview() while on step 1 will cause continuous repeated server calls until the user navigates away. The unguarded message.from.username access is a secondary crash risk. Both need to be fixed before this ships to users going through onboarding.
  • apps/app/src/app/(onboarding)/onboarding/page.tsx (infinite retry loop in the preview useEffect) and apps/app/src/actions/onboarding.ts (unguarded message.from access in formatPreviewMessages).

Important Files Changed

Filename Overview
apps/app/src/app/(onboarding)/onboarding/page.tsx Major rework adding Instagram connection (step 0) and DM preview (step 1) to the onboarding flow; contains a critical infinite retry loop when the preview load fails (previewError not checked in useEffect guard).
apps/app/src/actions/onboarding.ts Adds prepareInstagramPreview and getInstagramOnboardingState server actions; formatPreviewMessages accesses message.from.username without a null guard, which can throw if Instagram returns system messages.
apps/app/src/app/api/auth/instagram/route.ts Adds returnTo cookie-based redirect support using a properly sanitized normalizeReturnTo helper; logic is sound but the helper is duplicated with the callback route.
apps/app/src/app/api/auth/instagram/callback/route.ts Reads returnTo from the cookie set by the initiation route and redirects accordingly; normalizeReturnTo is duplicated from route.ts rather than shared.
apps/app/src/app/(onboarding)/sidekick-onboarding/page.tsx Merges the old "Your Offers" and "What You Sell" steps into a single combined step 1, reducing total steps from 5 to 4; consolidates five separate init useEffects into one parallel Promise.all, and introduces isMounted guard correctly.
apps/app/src/lib/constants/sidekick-onboarding.ts Removes the separate "Your Offers" step and renumbers remaining steps from 1-3; tone_options formatting cleaned up. Safe change.

Sequence Diagram

sequenceDiagram
    participant U as User
    participant OP as Onboarding Page
    participant IG_API as /api/auth/instagram
    participant IG_CB as /api/auth/instagram/callback
    participant Instagram as Instagram OAuth
    participant SA as Server Actions

    U->>OP: Visit /onboarding (step 0)
    OP->>SA: getInstagramOnboardingState()
    SA-->>OP: { connected: false }

    U->>IG_API: GET /api/auth/instagram?returnTo=/onboarding
    IG_API->>IG_API: normalizeReturnTo("/onboarding")
    IG_API->>IG_API: Set pilot_instagram_return_to cookie
    IG_API-->>Instagram: Redirect to OAuth URL

    Instagram-->>IG_CB: GET /callback?code=...
    IG_CB->>IG_CB: Read & delete return_to cookie
    IG_CB->>Instagram: exchangeCodeForAccessToken()
    Instagram-->>IG_CB: accessToken, appScopedUserId
    IG_CB->>Instagram: fetchInstagramProfile()
    Instagram-->>IG_CB: username, user_id
    IG_CB->>Instagram: exchangeLongLivedInstagramToken()
    Instagram-->>IG_CB: longLivedToken
    IG_CB->>SA: saveInstagramConnection()
    SA-->>IG_CB: { success: true }
    IG_CB-->>OP: Redirect to /onboarding?success=instagram_connected

    OP->>SA: getInstagramOnboardingState()
    SA-->>OP: { connected: true, username }
    OP->>OP: setActiveStep(1) — advance to Preview step

    OP->>SA: prepareInstagramPreview()
    SA->>Instagram: fetchConversationsForSync()
    Instagram-->>SA: conversations[]
    SA->>SA: getPersonalizedAutoReplyPrompt()
    SA->>SA: generateText() via Gemini
    SA-->>OP: { data: { previewMessages, replyPreview, ... } }
    OP->>OP: Show DM preview + AI reply

    U->>OP: Continue → steps 2-4 (personal info, usage, business)
    OP->>SA: updateOnboardingStep() × 3
    OP->>SA: completeOnboarding()
    OP-->>U: Redirect to /sidekick-onboarding
Loading

Last reviewed commit: 0954fe5

@ArjunCodess ArjunCodess merged commit 797d929 into main Mar 13, 2026
5 checks passed
@ArjunCodess ArjunCodess deleted the arjuncodess/new-onboarding-flow branch March 13, 2026 21:31
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.

1 participant