feat(onboarding): smart step filtering and stability fixes#84
feat(onboarding): smart step filtering and stability fixes#84mtlouzada wants to merge 6 commits intoSkateHive:mainfrom
Conversation
- Add OnboardingModal with welcome screen + 3 steps (photo, bio, intro snap) - Add OnboardingDetector with persistent floating card for incomplete steps - Track progress via bitmask (onboarding_step_flag) in profile PATCH API - Post intro as a snap (comment on peak.snaps container) not a blog post - Hot-swap photo preview: instant blob URL replaced by IPFS URL on upload - Add HIVE_DRY_RUN env flag to skip broadcasts in local dev - Add dev-only TLS bypass for nodemailer self-signed cert issue - Mount OnboardingDetector globally in RootLayoutClient
- Use bitmask equality check instead of >= for onboarding completion - Guard HIVE_DRY_RUN behind NODE_ENV=development to prevent use in staging/CI - Remove soft post status update in dry-run (no broadcast = no attribution) - Narrow TLS bypass from !== production to === development
- Filter photo step from pendingSteps when user.avatar_url is set - Sync ONBOARDING_FLAG_PHOTO to server silently on mount if needed - Use useCallback for saveToServer to stabilize deps in useEffect - Welcome screen now iterates pendingSteps instead of hardcoded list
- Skip photo step when user already has a non-dicebear avatar - Skip bio step when user.bio is already filled - Sync bitmask flags silently to server for pre-existing data - Extract DICEBEAR_URL_PATTERN constant shared across both components - Filter existing users via ONBOARDING_LAUNCH_DATE (2026-04-22) - Add bio and created_at fields to UserbaseUser context type - Fix areUsersEqual to include bio and created_at comparisons - Remove isLoading from OnboardingDetector guard to prevent modal unmount during background session refreshes (focus/visibility events) - Pass windowId="onboarding-modal" to prevent remount on title change - Dynamic ctaLabel in floating card: shows pending step name when only one remains
|
@mtlouzada is attempting to deploy a commit to the sktbrd's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 22 minutes and 45 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughA comprehensive onboarding system for new users has been added, featuring client-side detector and modal components that guide users through photo upload, bio creation, and post submission. Supporting API routes, context fields, and development-mode utilities have been updated to enable this flow. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser
participant OnboardingDetector
participant API
participant IPFS
participant Hive as Hive RPC
participant User DB
Browser->>OnboardingDetector: Render (check sessionStorage)
alt Session seen before
OnboardingDetector->>Browser: Return null
else First visit
OnboardingDetector->>API: Fetch user (bio, created_at, onboarding_step)
API->>User DB: Query user fields
User DB-->>API: Return user data
API-->>OnboardingDetector: User with progress status
OnboardingDetector->>OnboardingDetector: Compute pending steps (bitmask)
OnboardingDetector->>Browser: Auto-open OnboardingModal (1.2s delay)
end
rect rgba(100, 150, 255, 0.5)
Note over Browser,User DB: User completes photo step
Browser->>OnboardingDetector: Upload photo
OnboardingDetector->>IPFS: Upload blob
IPFS-->>OnboardingDetector: IPFS hash (avatar_url)
OnboardingDetector->>API: PATCH profile (avatar_url + photo flag)
API->>User DB: Update user
User DB-->>API: Confirmed
API-->>OnboardingDetector: Updated user
end
rect rgba(150, 255, 100, 0.5)
Note over Browser,User DB: User completes bio step
Browser->>OnboardingDetector: Submit bio text
OnboardingDetector->>API: PATCH profile (bio + bio flag)
API->>User DB: Update user
User DB-->>API: Confirmed
API-->>OnboardingDetector: Updated user
end
rect rgba(255, 200, 100, 0.5)
Note over Browser,Hive: User completes post step
Browser->>OnboardingDetector: Submit post
OnboardingDetector->>Hive: Query post parent permlink
Hive-->>OnboardingDetector: Permlink data
OnboardingDetector->>API: POST comment (Hive operation)
API-->>OnboardingDetector: Success
OnboardingDetector->>API: PATCH profile (post flag)
API->>User DB: Update user (onboarding_step = 7)
User DB-->>API: Confirmed
API-->>OnboardingDetector: Updated user
OnboardingDetector->>Browser: Close modal, set sessionStorage done
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/api/userbase/profile/route.ts`:
- Around line 265-275: The current read-then-write on onboarding_step (the block
using onboarding_step_flag, fetching userbase_users.onboarding_step into
currentStep and setting updateData.onboarding_step = currentStep |
onboarding_step_flag) can lose bits under concurrency; replace that logic with
an atomic DB-side bitwise OR via a Postgres RPC: add a migration that creates a
function like set_onboarding_flag(p_user_id uuid, p_flag int) returning the
updated onboarding_step, then call it from route.ts with
supabase.rpc('set_onboarding_flag', { p_user_id: userId, p_flag:
onboarding_step_flag }) and use the returned onboarding_step (or rely on the RPC
result) instead of doing the local read/OR/write so the OR happens inside the DB
in one transaction.
In `@components/onboarding/OnboardingDetector.tsx`:
- Line 105: The current check in OnboardingDetector uses new
Date(user.created_at) < ONBOARDING_LAUNCH_DATE but doesn't guard against
missing/invalid created_at; parse user.created_at into a Date (e.g., createdAt =
new Date(user.created_at)) and explicitly validate it
(isNaN(createdAt.getTime()) or !user?.created_at) and if it is missing/invalid
treat it as pre-launch by returning null (fail closed); update the conditional
around user.created_at / createdAt so only a valid date that is >=
ONBOARDING_LAUNCH_DATE allows onboarding to be shown.
In `@components/onboarding/OnboardingModal.tsx`:
- Line 265: Replace all hardcoded user-facing strings in the OnboardingModal
component with translations from useTranslations(namespace) (import from
`@/lib/i18n/hooks`); create corresponding keys in the translation files for
modalTitle, toastTitle, stepLabel, stepDescription, placeholders, CTA labels,
etc., and call const t = useTranslations('onboarding') at the top of the
component, then replace usages like the toast call (toast({ title: "Posted to
the feed! Welcome 🛹", ... })) with toast({ title: t('toastTitle'), ... });
replace the template-literal modal title that uses stepIndex + 1 and totalSteps
with an interpolated translation t('modalTitle', { step: stepIndex + 1, total:
totalSteps }) (do not concatenate strings), and similarly swap all inline
strings referenced in the review (lines noted) to t('...') keys so all
user-facing text is driven by the translation files.
- Around line 237-272: submitPost currently always calls advance() in the
finally block, causing the UI to move past the post step even when
getSnapContainer() or the fetch to /api/userbase/hive/comment fails; remove the
unconditional advance() from the finally block and instead call advance() only
on the successful-post path (after toast success and updating completedFlagsRef
and saveToServer), keep the early-return advance() for the empty post case, and
leave setIsPosting(false) in finally so the spinner resets but the modal stays
on the post step on error to allow retry; reference submitPost,
getSnapContainer, advance, completedFlagsRef, ONBOARDING_FLAG_POST.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7a59c2e9-cdd6-46a0-ba05-a052d12fa3ed
📒 Files selected for processing (8)
app/RootLayoutClient.tsxapp/api/userbase/auth/magic-link/route.tsapp/api/userbase/auth/session/route.tsapp/api/userbase/hive/comment/route.tsapp/api/userbase/profile/route.tscomponents/onboarding/OnboardingDetector.tsxcomponents/onboarding/OnboardingModal.tsxcontexts/UserbaseAuthContext.tsx
| // onboarding_step_flag: bitmask OR — only adds bits, never removes them | ||
| // bit 0 (1) = photo, bit 1 (2) = bio, bit 2 (4) = intro post | ||
| if (typeof onboarding_step_flag === "number" && onboarding_step_flag > 0) { | ||
| const { data: currentUser } = await supabase | ||
| .from("userbase_users") | ||
| .select("onboarding_step") | ||
| .eq("id", userId) | ||
| .single(); | ||
| const currentStep = currentUser?.onboarding_step ?? 0; | ||
| updateData.onboarding_step = currentStep | onboarding_step_flag; | ||
| } |
There was a problem hiding this comment.
Race condition: read-then-write on onboarding_step can drop flag bits under concurrency.
This does a non-atomic read → OR → write. Concurrent PATCHes (which the client can easily produce — e.g., OnboardingModal.savePhoto and the silent-sync effect at OnboardingModal.tsx lines 117‑131 firing at nearly the same time) both read the same currentStep, each OR in their own flag, and the second write clobbers the first. Net result: one of the bits is lost and the user stays stuck on onboarding.
Perform the OR atomically at the database level via a Postgres RPC so the read/OR/write happens in a single transaction:
🛡️ Suggested fix — atomic bitwise OR via RPC
-- migration
create or replace function set_onboarding_flag(p_user_id uuid, p_flag int)
returns int
language sql
as $$
update userbase_users
set onboarding_step = coalesce(onboarding_step, 0) | p_flag
where id = p_user_id
returning onboarding_step;
$$;- if (typeof onboarding_step_flag === "number" && onboarding_step_flag > 0) {
- const { data: currentUser } = await supabase
- .from("userbase_users")
- .select("onboarding_step")
- .eq("id", userId)
- .single();
- const currentStep = currentUser?.onboarding_step ?? 0;
- updateData.onboarding_step = currentStep | onboarding_step_flag;
- }
+ if (typeof onboarding_step_flag === "number" && onboarding_step_flag > 0) {
+ const { data: newStep, error: flagErr } = await supabase.rpc(
+ "set_onboarding_flag",
+ { p_user_id: userId, p_flag: onboarding_step_flag }
+ );
+ if (flagErr) {
+ console.error("Failed to OR onboarding_step_flag:", flagErr);
+ return NextResponse.json(
+ { error: "Failed to update onboarding flag" },
+ { status: 500 }
+ );
+ }
+ // Skip adding onboarding_step to updateData — the RPC already persisted it.
+ // (Keep other fields in updateData; below block will still return updatedUser.)
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/api/userbase/profile/route.ts` around lines 265 - 275, The current
read-then-write on onboarding_step (the block using onboarding_step_flag,
fetching userbase_users.onboarding_step into currentStep and setting
updateData.onboarding_step = currentStep | onboarding_step_flag) can lose bits
under concurrency; replace that logic with an atomic DB-side bitwise OR via a
Postgres RPC: add a migration that creates a function like
set_onboarding_flag(p_user_id uuid, p_flag int) returning the updated
onboarding_step, then call it from route.ts with
supabase.rpc('set_onboarding_flag', { p_user_id: userId, p_flag:
onboarding_step_flag }) and use the returned onboarding_step (or rely on the RPC
result) instead of doing the local read/OR/write so the OR happens inside the DB
in one transaction.
- Move advance() from finally to success path in submitPost so a failed post lets the user retry instead of advancing past the step - Guard created_at with isNaN check in OnboardingDetector so invalid or null dates fail closed (hide onboarding) instead of falling through
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Summary
user.biois already filled — covers users signing up via Hive, Farcaster, MetaMask or email who already have profile dataONBOARDING_LAUNCH_DATE = 2026-04-22gates the entire flow — accounts created before this date never see onboarding, no database migration requiredisLoadingfromOnboardingDetectorguard to prevent modal unmount during background session refreshes (window focus/visibility events were triggeringmaybeRefresh, resetting modal state mid-flow)windowId="onboarding-modal"toSkateModalso the title change between welcome screen and step view no longer causes a full unmountonboarding_step_flagwhen pre-existing avatar or bio data is detected, keeping the server state consistentbioandcreated_attoUserbaseUsertype and session API queries; updatedareUsersEqualto include both fieldsTest plan
Summary by CodeRabbit