Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 26 additions & 18 deletions apps/web/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,48 @@
"use client";

import { useEffect } from "react";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useGuestMode } from "@/hooks/useGuestMode";
import { GuestOnboardingFlow } from "@/components/features/onboarding/GuestOnboardingFlow";

const BG = "bg-[radial-gradient(circle_at_top_left,_rgba(124,92,255,0.16),_transparent_28rem),linear-gradient(180deg,_#fffefc,_#f7f2ff)]";

function LoadingSpinner() {
return (
<div className={`flex min-h-[100dvh] items-center justify-center ${BG}`}>
<div className="flex items-center gap-3 rounded-full border border-white/70 bg-white/80 px-4 py-3 shadow-xl backdrop-blur">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
<span className="text-sm font-semibold text-foreground">Preparing your budget world</span>
</div>
</div>
);
}

export default function RootPage() {
const router = useRouter();
const { isGuest, enableGuestMode, guestData } = useGuestMode();
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
}, []);

useEffect(() => {
if (!mounted) return;
if (!isGuest) {
enableGuestMode();
} else if (guestData.onboarding?.questions_answered) {
router.replace("/dashboard");
}
}, [isGuest, guestData.onboarding?.questions_answered, enableGuestMode, router]);

const handleOnboardingComplete = () => {
router.replace("/dashboard");
};

if (!isGuest) {
return (
<div className="flex min-h-[100dvh] items-center justify-center bg-[radial-gradient(circle_at_top,_rgba(124,92,255,0.16),_transparent_32rem),linear-gradient(180deg,_#fffefc,_#f8f4ff)]">
<div className="flex items-center gap-3 rounded-full border border-white/70 bg-white/80 px-4 py-3 shadow-xl backdrop-blur">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
<span className="text-sm font-semibold text-foreground">Preparing your budget world</span>
</div>
</div>
);
}, [mounted, isGuest, guestData.onboarding?.questions_answered, enableGuestMode, router]);

if (!mounted || !isGuest) {
return <LoadingSpinner />;
}

if (guestData.onboarding?.questions_answered) {
return null;
return <LoadingSpinner />;
}

return <GuestOnboardingFlow onComplete={handleOnboardingComplete} />;
return <GuestOnboardingFlow onComplete={() => router.replace("/dashboard")} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export function ConversationalOnboarding() {

if (phase === "scan") {
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-background p-6 text-foreground">
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center overflow-y-auto bg-background p-6 text-foreground">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using justify-center on a container with overflow-y-auto can cause the top of the content to be clipped and unreachable if the content height exceeds the viewport. It is safer to remove justify-center and rely on padding or a wrapper to ensure the content remains accessible on smaller screens, similar to the implementation in the result phase (line 357).

Suggested change
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center overflow-y-auto bg-background p-6 text-foreground">
<div className="fixed inset-0 z-50 flex flex-col items-center overflow-y-auto bg-background p-6 text-foreground">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: justify-center combined with overflow-y-auto on a flex column causes top content to be clipped and unreachable when content exceeds viewport height. Remove justify-center and rely on padding or an inner wrapper to vertically center shorter content.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/components/features/onboarding/ConversationalOnboarding.tsx, line 172:

<comment>`justify-center` combined with `overflow-y-auto` on a flex column causes top content to be clipped and unreachable when content exceeds viewport height. Remove `justify-center` and rely on padding or an inner wrapper to vertically center shorter content.</comment>

<file context>
@@ -169,7 +169,7 @@ export function ConversationalOnboarding() {
   if (phase === "scan") {
     return (
-      <div className="flex min-h-screen flex-col items-center justify-center bg-background p-6 text-foreground">
+      <div className="fixed inset-0 z-50 flex flex-col items-center justify-center overflow-y-auto bg-background p-6 text-foreground">
         <div className="w-full max-w-md space-y-6">
           <motion.div
</file context>
Suggested change
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center overflow-y-auto bg-background p-6 text-foreground">
<div className="fixed inset-0 z-50 flex flex-col items-center overflow-y-auto bg-background p-6 text-foreground">

<div className="w-full max-w-md space-y-6">
<motion.div
initial={{ opacity: 0, y: 20 }}
Expand Down Expand Up @@ -262,7 +262,7 @@ export function ConversationalOnboarding() {
if (phase === "pet") {
const SPECIES_ORDER: PetSpecies[] = ["squirrel", "fox", "raccoon", "rabbit"];
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-background p-6 text-foreground">
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center overflow-y-auto bg-background p-6 text-foreground">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Similar to the scan phase, combining justify-center with overflow-y-auto may lead to content clipping at the top on small devices. Removing justify-center ensures that all pet options are scrollable and visible.

Suggested change
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center overflow-y-auto bg-background p-6 text-foreground">
<div className="fixed inset-0 z-50 flex flex-col items-center overflow-y-auto bg-background p-6 text-foreground">

<div className="w-full max-w-md space-y-6">
<motion.div
initial={{ opacity: 0, y: 20 }}
Expand Down Expand Up @@ -327,7 +327,7 @@ export function ConversationalOnboarding() {

if (phase === "loading") {
return (
<div className="min-h-screen flex flex-col items-center justify-center bg-background p-6 text-foreground">
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center bg-background p-6 text-foreground">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This container is missing overflow-y-auto. While the loading state is currently minimal, adding it ensures the UI doesn't break on extremely small screens or in landscape orientation.

Suggested change
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center bg-background p-6 text-foreground">
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center overflow-y-auto bg-background p-6 text-foreground">

<motion.div
animate={{ scale: [1, 1.1, 1] }}
transition={{ repeat: Infinity, duration: 1.5 }}
Expand All @@ -354,7 +354,7 @@ export function ConversationalOnboarding() {

if (phase === "result" && result) {
return (
<div className="flex min-h-screen flex-col items-center bg-background p-6 text-foreground">
<div className="fixed inset-0 z-50 flex flex-col items-center overflow-y-auto bg-background p-6 text-foreground">
<div className="w-full max-w-md space-y-6 py-10">
{/* Persona emoji */}
<motion.div
Expand Down Expand Up @@ -445,7 +445,7 @@ export function ConversationalOnboarding() {
}

return (
<div className="flex min-h-screen flex-col items-center justify-center bg-background p-6 text-foreground">
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center bg-background p-6 text-foreground">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The main questions phase is missing overflow-y-auto. Since this phase contains dynamic text and multiple options, it is highly likely to overflow on mobile devices. Additionally, removing justify-center is recommended to prevent top-clipping during overflow.

Suggested change
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center bg-background p-6 text-foreground">
<div className="fixed inset-0 z-50 flex flex-col items-center overflow-y-auto bg-background p-6 text-foreground">

<div className="w-full max-w-md space-y-8">
{/* Progress dots */}
<div className="flex justify-center gap-2">
Expand Down
66 changes: 34 additions & 32 deletions apps/web/components/features/onboarding/GuestOnboardingFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,40 +218,42 @@ export function GuestOnboardingFlow({ onComplete }: GuestOnboardingFlowProps) {
// ── Render ──

return (
<AnimatePresence mode="wait">
{step === "questions" && (
<motion.div key="questions" initial={{ opacity: 0, x: 50 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -50 }}
className="fixed inset-0 z-50 bg-[radial-gradient(circle_at_top_left,_rgba(124,92,255,0.16),_transparent_28rem),linear-gradient(180deg,_#fffefc,_#f7f2ff)]">
<OnboardingChat onComplete={handleQuestionsComplete} />
</motion.div>
)}
<div className="fixed inset-0 z-50 bg-[radial-gradient(circle_at_top_left,_rgba(124,92,255,0.16),_transparent_28rem),linear-gradient(180deg,_#fffefc,_#f7f2ff)]">
<AnimatePresence mode="wait">
{step === "questions" && (
<motion.div key="questions" initial={{ x: 40, opacity: 0 }} animate={{ x: 0, opacity: 1 }} exit={{ x: -40, opacity: 0 }}
className="absolute inset-0">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The absolute inset-0 container lacks overflow-y-auto. If the OnboardingChat content exceeds the viewport height, users will be unable to scroll to see the rest of the conversation, which is a critical issue on mobile devices.

Suggested change
className="absolute inset-0">
className="absolute inset-0 overflow-y-auto">

<OnboardingChat onComplete={handleQuestionsComplete} />
</motion.div>
)}

{step === "analyzing" && (
<motion.div key="analyzing" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
className="fixed inset-0 z-50 flex items-center justify-center bg-[radial-gradient(circle_at_top_left,_rgba(124,92,255,0.16),_transparent_28rem),linear-gradient(180deg,_#fffefc,_#f7f2ff)]">
<AnalyzingAnimation />
</motion.div>
)}
{step === "analyzing" && (
<motion.div key="analyzing" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
className="absolute inset-0 flex items-center justify-center">
<AnalyzingAnimation />
</motion.div>
)}

{step === "statement" && (
<motion.div key="statement" initial={{ opacity: 0, x: 50 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -50 }}
className="fixed inset-0 z-50 bg-[radial-gradient(circle_at_top_left,_rgba(124,92,255,0.16),_transparent_28rem),linear-gradient(180deg,_#fffefc,_#f7f2ff)]">
<BankStatementUpload onComplete={handleStatementComplete} onSkip={handleStatementSkip} />
</motion.div>
)}

{step === "roast" && persona && (
<motion.div key="roast" initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, y: 50 }}
className="fixed inset-0 z-50 bg-[radial-gradient(circle_at_top_left,_rgba(124,92,255,0.16),_transparent_28rem),linear-gradient(180deg,_#fffefc,_#f7f2ff)]">
<PersonaSummary
persona={persona}
estimatedBudget={budget}
xpEarned={totalXP}
hasRealData={!!scannedTxns.length}
/>
</motion.div>
)}
</AnimatePresence>
{step === "statement" && (
<motion.div key="statement" initial={{ x: 40, opacity: 0 }} animate={{ x: 0, opacity: 1 }} exit={{ x: -40, opacity: 0 }}
className="absolute inset-0">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The bank statement upload step should be scrollable to ensure that the upload button and any potential error messages remain accessible if the content overflows the screen.

Suggested change
className="absolute inset-0">
className="absolute inset-0 overflow-y-auto">

<BankStatementUpload onComplete={handleStatementComplete} onSkip={handleStatementSkip} />
</motion.div>
)}

{step === "roast" && persona && (
<motion.div key="roast" initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, y: 50 }}
className="absolute inset-0">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The persona summary can contain a significant amount of text (roast, budget breakdown, etc.). Without overflow-y-auto, this content will be cut off on smaller screens.

Suggested change
className="absolute inset-0">
className="absolute inset-0 overflow-y-auto">

<PersonaSummary
persona={persona}
estimatedBudget={budget}
xpEarned={totalXP}
hasRealData={!!scannedTxns.length}
/>
</motion.div>
)}
</AnimatePresence>
</div>
);
}

Expand Down
2 changes: 1 addition & 1 deletion apps/web/tsconfig.tsbuildinfo

Large diffs are not rendered by default.

Loading