Skip to content

fix: force user onboarding#2639

Merged
baktun14 merged 15 commits intomainfrom
fix/force-user-onboarding
Feb 3, 2026
Merged

fix: force user onboarding#2639
baktun14 merged 15 commits intomainfrom
fix/force-user-onboarding

Conversation

@baktun14
Copy link
Contributor

@baktun14 baktun14 commented Feb 1, 2026

Summary by CodeRabbit

  • New Features

    • Onboarding redirect/effect and guarded page gating (OnboardingRedirect, Guard, useIsOnboarded).
    • Close Deployment action while waiting for bids.
    • WalletConnectionButtons component for unified wallet controls.
  • Improvements

    • Consolidated wallet connection UI across pages.
    • Smarter signup/login redirect to route to onboarding when appropriate.
    • Streamlined trial status UI and added logout button in onboarding.
  • Bug Fixes

    • Analytics event renamed to onboarding_logout and logout clears trial sign‑in flag.

@baktun14 baktun14 requested a review from a team as a code owner February 1, 2026 01:21
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 1, 2026

📝 Walkthrough

Walkthrough

Adds onboarding guards and redirect effects, replaces template array lookups with a templateService, consolidates wallet connection UI into WalletConnectionButtons, introduces logout analytics and storage key changes, refactors wallet auto-switching, and applies guarded exports to several protected pages.

Changes

Cohort / File(s) Summary
Onboarding Core
apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx, apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx, apps/deploy-web/src/components/onboarding/OnboardingView/OnboardingView.tsx, apps/deploy-web/src/components/onboarding/OnboardingPage.tsx
Wired a templateService (from useServices) into onboarding and tests (replacing template array usage), removed the back-to-console navigation from OnboardingPage, and added a logout action + analytics in OnboardingView.
Onboarding Redirect System
apps/deploy-web/src/components/onboarding/OnboardingRedirect/OnboardingRedirect.tsx, apps/deploy-web/src/components/onboarding/OnboardingRedirectEffect/OnboardingRedirectEffect.tsx, apps/deploy-web/src/hooks/useIsOnboarded.ts, apps/deploy-web/src/pages/_app.tsx
Added OnboardingRedirect component and OnboardingRedirectEffect; implemented useIsOnboarded hook and injected the redirect effect into the app bootstrap.
Route Guards (pages)
apps/deploy-web/src/pages/deployments/[dseq]/index.tsx, apps/deploy-web/src/pages/deployments/index.tsx, apps/deploy-web/src/pages/new-deployment/index.tsx, apps/deploy-web/src/pages/payment.tsx
Wrapped several page default exports with Guard(..., useIsOnboarded, OnboardingRedirect) to gate access based on onboarding status.
Wallet Connection UI Consolidation
apps/deploy-web/src/components/wallet/WalletConnectionButtons.tsx, apps/deploy-web/src/components/wallet/ConnectWalletButton.tsx, apps/deploy-web/src/components/layout/WalletStatus.tsx, apps/deploy-web/src/components/home/NoDeploymentsState.tsx, apps/deploy-web/src/components/get-started/GetStartedStepper.tsx, apps/deploy-web/src/components/shared/ConnectWallet.tsx
Introduced WalletConnectionButtons and replaced scattered ConnectManaged/ConnectWallet usage with this composite; removed several sign-in links and reduced prop drilling of trial/user flags.
Onboarding Steps & UI
apps/deploy-web/src/components/onboarding/steps/WelcomeStep/WelcomeStep.tsx, apps/deploy-web/src/components/onboarding/steps/WelcomeStep/TrialStatusBar.tsx
Reworked WelcomeStep layout and action button (added icon), simplified TrialStatusBar (removed progress details, streamlined UI).
CreateLease / Trial logic
apps/deploy-web/src/components/new-deployment/CreateLease/CreateLease.tsx
Removed useBlock dependency and trial zero-bids timing logic, simplified waiting-for-bids UI, and added Close Deployment actions.
Auth / Storage / Analytics
apps/deploy-web/src/services/auth/auth/auth.service.ts, apps/deploy-web/src/services/storage/keys.ts, apps/deploy-web/src/services/analytics/analytics.service.ts, apps/deploy-web/src/store/walletStore.ts
Added IS_SIGNED_IN_WITH_TRIAL_KEY, removed it on logout, switched walletStore to use the constant, and renamed analytics event onboarding_back_to_consoleonboarding_logout.
Payment & Forms / Login redirect
apps/deploy-web/src/components/shared/PaymentMethodForm/PaymentMethodForm.tsx, apps/deploy-web/src/pages/login/index.tsx
PaymentMethodForm now accepts optional onReady forwarded to Stripe's PaymentElement. getAuthRedirectDestination gained fromSignup?: boolean and uses it to decide routing to onboarding vs. signup.
Hook tweak
apps/deploy-web/src/hooks/useManagedWallet.ts
Refactored auto-switch logic to a one-time guarded effect using a ref (hasAutoSwitched), removing wallet/chain-related deps from the effect.
Tests / Specs
apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
Updated mocks to include a template service with findById and adjusted useServices mock return shape accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as User/Browser
    participant App as _app.tsx<br/>OnboardingRedirectEffect
    participant Hook as useIsOnboarded
    participant Guard as Guard HOC
    participant Router as Next.js Router
    participant Page as Protected Page

    Browser->>App: load app / navigate
    App->>Hook: evaluate onboarding state (user + wallet)
    Hook-->>App: { isLoading, canVisit }
    alt not onboarded
        App->>Router: replace → onboarding (UrlService.onboarding)
        Router->>Browser: onboarding page
    else onboarded
        App->>Page: allow render
        Page->>Browser: show content
    end

    Browser->>Page: direct navigation to protected route
    Page->>Guard: Guard checks useIsOnboarded
    Guard->>Hook: get canVisit
    alt not onboarded
        Guard->>Router: redirect → onboarding
    else
        Guard->>Page: render
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • ygrishajev
  • stalniy

Poem

🐇 I hopped through guards and routes anew,
Wallet buttons formed a tidy crew,
Templates fetched by a service hand,
Redirects guide users to onboarding land,
A cheerful logout and forward plans in view.

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title succinctly conveys the main change of enforcing user onboarding across the application.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/force-user-onboarding

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Feb 1, 2026

Codecov Report

❌ Patch coverage is 17.24138% with 96 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.56%. Comparing base (18fb857) to head (5a009fe).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...oardingRedirectEffect/OnboardingRedirectEffect.tsx 0.00% 17 Missing ⚠️
...nents/onboarding/OnboardingView/OnboardingView.tsx 0.00% 11 Missing ⚠️
...loy-web/src/components/home/NoDeploymentsState.tsx 0.00% 9 Missing ⚠️
...boarding/OnboardingRedirect/OnboardingRedirect.tsx 0.00% 9 Missing ⚠️
apps/deploy-web/src/hooks/useIsOnboarded.ts 0.00% 7 Missing and 1 partial ⚠️
...nents/onboarding/steps/WelcomeStep/WelcomeStep.tsx 0.00% 6 Missing ⚠️
.../src/components/wallet/WalletConnectionButtons.tsx 70.58% 5 Missing ⚠️
apps/deploy-web/src/hooks/useManagedWallet.ts 16.66% 5 Missing ⚠️
.../deploy-web/src/pages/deployments/[dseq]/index.tsx 0.00% 4 Missing ⚠️
apps/deploy-web/src/pages/deployments/index.tsx 0.00% 4 Missing ⚠️
... and 9 more

❌ Your patch status has failed because the patch coverage (17.24%) is below the target coverage (50.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2639      +/-   ##
==========================================
- Coverage   51.41%   50.56%   -0.85%     
==========================================
  Files        1050     1019      -31     
  Lines       29553    28739     -814     
  Branches     6656     6552     -104     
==========================================
- Hits        15194    14532     -662     
+ Misses      14147    13997     -150     
+ Partials      212      210       -2     
Flag Coverage Δ *Carryforward flag
api 78.55% <ø> (ø) Carriedforward from 18699b0
deploy-web 32.63% <17.24%> (-0.13%) ⬇️
log-collector ?
notifications 87.94% <ø> (ø) Carriedforward from 18699b0
provider-console 81.48% <ø> (ø) Carriedforward from 18699b0
provider-proxy 84.35% <ø> (ø) Carriedforward from 18699b0
tx-signer ?

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
...-web/src/components/deployments/DeploymentList.tsx 0.00% <ø> (ø)
apps/deploy-web/src/components/layout/Nav.tsx 71.42% <100.00%> (+4.76%) ⬆️
...ponents/new-deployment/CreateLease/CreateLease.tsx 78.12% <100.00%> (-0.83%) ⬇️
...y-web/src/components/onboarding/OnboardingPage.tsx 0.00% <ø> (ø)
...deploy-web/src/components/shared/ConnectWallet.tsx 0.00% <ø> (ø)
...nts/shared/PaymentMethodForm/PaymentMethodForm.tsx 15.62% <ø> (ø)
...oy-web/src/services/analytics/analytics.service.ts 81.25% <ø> (ø)
...b/src/components/get-started/GetStartedStepper.tsx 0.00% <0.00%> (ø)
.../deploy-web/src/components/layout/WalletStatus.tsx 35.29% <50.00%> (-3.17%) ⬇️
...arding/OnboardingContainer/OnboardingContainer.tsx 78.32% <50.00%> (-0.30%) ⬇️
... and 16 more

... and 37 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/deploy-web/src/components/auth/AuthPage/AuthPage.tsx (1)

93-114: ⚠️ Potential issue | 🟡 Minor

Tighten fromSignup detection to avoid bypass.
searchParams.has("fromSignup") returns true even for ?fromSignup=false. If onboarding should only be bypassed when the value is explicitly "true", use get(...) === "true" to match the rest of the flow.

🔧 Suggested fix
-  const isFromSignup = searchParams.has("fromSignup");
+  const isFromSignup = searchParams.get("fromSignup") === "true";
apps/deploy-web/src/pages/login/index.tsx (1)

21-84: ⚠️ Potential issue | 🟡 Minor

Parse fromSignup explicitly instead of presence checks.
searchParams.has("fromSignup") and resolvedUrl.includes("fromSignup") treat any value as true (and the string check can false‑positive). Prefer reading the query value and checking for "true" in both client and server.

🔧 Suggested fix
-        fromSignup: searchParams.has("fromSignup"),
+        fromSignup: searchParams.get("fromSignup") === "true",
-        fromSignup: ctx.resolvedUrl.includes("fromSignup"),
+        fromSignup: ctx.query.fromSignup === "true",
-    query: z.object({
+    query: z.object({
       tab: z.enum(["login", "signup", "forgot-password"]).default("login"),
       returnTo: z.union([z.string(), z.array(z.string())]).optional(),
-      from: z.union([z.string(), z.array(z.string())]).optional()
+      from: z.union([z.string(), z.array(z.string())]).optional(),
+      fromSignup: z.union([z.literal("true"), z.literal("false")]).optional()
     })
🤖 Fix all issues with AI agents
In
`@apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx`:
- Around line 176-179: Replace the fragile string-concatenation redirect in
OnboardingContainer.tsx that does router.push(urlService.newSignup({ fromSignup:
"true" }) + "&fromSignup=true") with building the destination URL via URL or
URLSearchParams so the fromSignup query is set idempotently; call
urlService.newSignup() to get the base, create a URL object (or URLSearchParams)
from it, set or append the fromSignup=true parameter (overwriting if present),
and pass the resulting url.toString() into router.push to avoid duplicate params
or separator issues.
🧹 Nitpick comments (2)
apps/deploy-web/src/components/onboarding/OnboardingGuard/OnboardingGuard.tsx (2)

33-37: Children rendered during loading states may cause content flash.

While isUserLoading or isWalletLoading is true, the component renders children instead of a loading indicator. This can cause a brief flash of protected content before the redirect occurs.

♻️ Proposed fix to show loading during initial data fetch
   if (isRedirecting) {
     return <Loading text="Redirecting to onboarding..." />;
   }

+  if (!isExcluded && (isUserLoading || isWalletLoading)) {
+    return <Loading />;
+  }
+
   return <>{children}</>;

27-30: Consider handling potential redirect failures.

router.replace returns a Promise that could reject. If navigation fails (e.g., due to route guards or network issues), the component remains stuck showing the loading state indefinitely since isRedirecting is never reset.

♻️ Proposed fix with error handling
     if (user?.userId && !hasManagedWallet && !isWalletConnected) {
       setIsRedirecting(true);
-      router.replace(UrlService.onboarding({ returnTo: router.asPath }));
+      router.replace(UrlService.onboarding({ returnTo: router.asPath })).catch(() => {
+        setIsRedirecting(false);
+      });
     }

@baktun14 baktun14 marked this pull request as draft February 1, 2026 01:36
@baktun14 baktun14 marked this pull request as ready for review February 2, 2026 18:45
Copy link
Contributor

@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

🤖 Fix all issues with AI agents
In
`@apps/deploy-web/src/components/shared/PaymentMethodForm/PaymentMethodForm.tsx`:
- Around line 8-10: The onReady prop in PaymentMethodFormProps is too narrow;
update its type to match Stripe's PaymentElement handler so consumers receive
the StripePaymentElement param (e.g., derive it from PaymentElement's props or
use the same signature as Stripe's onReady handler). Locate
PaymentMethodFormProps and replace onReady?: () => void with a type derived from
PaymentElement (or React.ComponentProps<typeof PaymentElement>['onReady']) so
callers can access the StripePaymentElement argument.
🧹 Nitpick comments (1)
apps/deploy-web/src/components/onboarding/OnboardingPage.tsx (1)

16-19: Consider extracting the delay constant.

The 1500 timeout value is a magic number. For improved readability and maintainability, consider extracting it to a named constant.

🔧 Suggested refactor
+const LOGOUT_BUTTON_DELAY_MS = 1500;
+
 export const OnboardingPage: FC = () => {
   const { analyticsService, authService } = useServices();
   const [isLoginVisible, setIsLoginVisible] = useState(false);

   useEffect(() => {
-    const timer = setTimeout(() => setIsLoginVisible(true), 1500);
+    const timer = setTimeout(() => setIsLoginVisible(true), LOGOUT_BUTTON_DELAY_MS);
     return () => clearTimeout(timer);
   }, []);

Copy link
Contributor

@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

🤖 Fix all issues with AI agents
In `@apps/deploy-web/src/components/home/NoDeploymentsState.tsx`:
- Around line 35-36: Update the copy in the NoDeploymentsState component:
replace the hyphenated verb "sign-in" with the two-word verb "sign in" in the
JSX that renders when isSignedInWithTrial && !user (the paragraph currently
using "If you are expecting to see some, you may need to sign-in or connect a
wallet"); keep the rest of the sentence unchanged and preserve the className and
conditional rendering using the existing isSignedInWithTrial and user symbols.

In `@apps/deploy-web/src/components/onboarding/OnboardingPage.tsx`:
- Around line 15-20: The analytics event in handleLogout may be dropped because
authService.logout() runs immediately; make handleLogout async and await
analyticsService.track("onboarding_logout", { category: "onboarding" }) (or call
a provided analyticsService.flush() / promise-returning method) before calling
authService.logout(); also handle/retry or catch errors from the track promise
so logout still proceeds or logs failures appropriately while ensuring the event
has had a chance to send.
🧹 Nitpick comments (1)
apps/deploy-web/src/components/onboarding/OnboardingPage.tsx (1)

30-36: Add explicit type="button" for safety.

This prevents accidental form submissions if the component is ever rendered inside a <form>.

🔧 Suggested tweak
-        <button
+        <button
+          type="button"
           className={cn(buttonVariants({ variant: "ghost", size: "sm" }), "inline-flex items-center gap-1.5 text-xs text-muted-foreground")}
           onClick={handleLogout}
         >

Copy link
Contributor

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/deploy-web/src/components/home/NoDeploymentsState.tsx (1)

22-36: ⚠️ Potential issue | 🟡 Minor

Avoid showing the sign‑in prompt while the user is still loading.

If user is still loading, this message can flash for already-signed-in users. Consider gating with isLoading to prevent a brief misleading prompt.

Suggested change
-  const { user } = useCustomUser();
+  const { user, isLoading } = useCustomUser();
...
-        {isSignedInWithTrial && !user && (
+        {isSignedInWithTrial && !isLoading && !user && (
           <p className="mb-4 text-center text-sm text-muted-foreground">If you are expecting to see some, you may need to sign in or connect a wallet</p>
         )}
🧹 Nitpick comments (1)
apps/deploy-web/src/components/onboarding/steps/WelcomeStep/TrialStatusBar.tsx (1)

45-46: Handle pluralization for "days remaining".

The string renders as "1 days remaining" which is grammatically incorrect. Since you're already using react-intl, consider using FormattedMessage with ICU plural syntax to handle both singular and plural forms:

<FormattedMessage
  defaultMessage="{count, plural, one {# day remaining} other {# days remaining}}"
  values={{ count: daysRemaining }}
/>

stalniy
stalniy previously approved these changes Feb 3, 2026
@stalniy
Copy link
Contributor

stalniy commented Feb 3, 2026

I think that we also need to remove isSignedInWithTrial from localStorage. This thing can probably be checked in a different way. Because it can result in false-positives

Copy link
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/deploy-web/src/pages/login/index.tsx (1)

56-62: ⚠️ Potential issue | 🟠 Major

Avoid false positives by parsing the query instead of string includes.

resolvedUrl.includes("fromSignup") can match unrelated substrings (e.g., returnTo=/path?fromSignup=1) and incorrectly allow bypass of the onboarding guard. Prefer checking the parsed query param.

🔧 Suggested fix (handler logic)
-      const destination = getAuthRedirectDestination({
-        currentLocation: ctx.resolvedUrl,
-        tab: ctx.query.tab,
-        fromSignup: ctx.resolvedUrl.includes("fromSignup"),
+      const destination = getAuthRedirectDestination({
+        currentLocation: ctx.resolvedUrl,
+        tab: ctx.query.tab,
+        fromSignup: ctx.query.fromSignup !== undefined,
         services: {
           urlService: ctx.services.urlService,
           urlReturnToStack: ctx.services.urlReturnToStack
         }
       });
🔧 Suggested fix (schema)
   schema: z.object({
     query: z.object({
       tab: z.enum(["login", "signup", "forgot-password"]).default("login"),
       returnTo: z.union([z.string(), z.array(z.string())]).optional(),
-      from: z.union([z.string(), z.array(z.string())]).optional()
+      from: z.union([z.string(), z.array(z.string())]).optional(),
+      fromSignup: z.union([z.string(), z.array(z.string())]).optional()
     })
   }),
🤖 Fix all issues with AI agents
In `@apps/deploy-web/src/components/home/NoDeploymentsState.tsx`:
- Around line 22-23: The persisted flag walletStore.isSignedInWithTrial (used
via isSignedInWithTrial) can remain true after logout causing stale hint; either
clear that atom on sign-out (update the logout/signOut handler to set
walletStore.isSignedInWithTrial to false) or stop relying on the persisted flag
in NoDeploymentsState and derive the hint from current session
(useCustomUser().user or session status) so the message only shows when a live
user matches the trial condition; update references in NoDeploymentsState and
the logout flow accordingly.

In `@apps/deploy-web/src/hooks/useManagedWallet.ts`:
- Around line 21-30: The one-time auto-switch guard is being flipped too early
in the useEffect (hasAutoSwitched.current) before confirming a switch; move the
assignment so it only becomes true when you actually change the type. In the
useEffect that depends on queried, selectedWalletType, setSelectedWalletType,
check if !hasAutoSwitched.current && queried then if selectedWalletType ===
"custodial" call setSelectedWalletType("managed") and immediately after set
hasAutoSwitched.current = true; leave hasAutoSwitched alone if no switch occurs
so the logic still triggers later when selectedWalletType hydrates.

@baktun14 baktun14 merged commit c10dddb into main Feb 3, 2026
50 of 52 checks passed
@baktun14 baktun14 deleted the fix/force-user-onboarding branch February 3, 2026 17:01
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

Comments