Enhance WelcomeStep component in onboarding flow with new feature highlights and animations. Introduce icons for memory, connectivity, and privacy features. Update logo display with ambient glow effect and improve user feedback during connection states.

This commit is contained in:
tusharmagar 2026-03-16 21:49:19 +05:30
parent 74d4172f54
commit da001e5a24
2 changed files with 132 additions and 53 deletions

View file

@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "renderer-dev",
"runtimeExecutable": "/Users/tusharmagar/Rowboat/rowboat-V2/apps/x/apps/renderer/node_modules/.bin/vite",
"runtimeArgs": ["--port", "5173"],
"port": 5173
}
]
}

View file

@ -1,4 +1,5 @@
import { Loader2, CheckCircle2 } from "lucide-react" import { Loader2, CheckCircle2, Brain, Plug, ShieldCheck } from "lucide-react"
import { motion } from "motion/react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import type { OnboardingState } from "../use-onboarding-state" import type { OnboardingState } from "../use-onboarding-state"
@ -6,78 +7,145 @@ interface WelcomeStepProps {
state: OnboardingState state: OnboardingState
} }
const features = [
{
icon: Brain,
label: "Memory",
desc: "Builds a knowledge graph from your work",
},
{
icon: Plug,
label: "Connected",
desc: "Syncs email, calendar, and meetings",
},
{
icon: ShieldCheck,
label: "Private",
desc: "Your data stays local on your machine",
},
]
export function WelcomeStep({ state }: WelcomeStepProps) { export function WelcomeStep({ state }: WelcomeStepProps) {
const rowboatState = state.providerStates['rowboat'] || { isConnected: false, isLoading: false, isConnecting: false } const rowboatState = state.providerStates['rowboat'] || { isConnected: false, isLoading: false, isConnecting: false }
return ( return (
<div className="flex flex-col items-center justify-center text-center flex-1"> <div className="flex flex-col items-center justify-center text-center flex-1">
{/* Logo */} {/* Logo with ambient glow */}
<img src="/logo-only.png" alt="Rowboat" className="size-14 mb-6" /> <motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }}
className="relative mb-6"
>
<div className="absolute inset-0 size-14 rounded-2xl bg-primary/10 blur-xl scale-[2]" />
<img src="/logo-only.png" alt="Rowboat" className="relative size-14" />
</motion.div>
{/* Tagline badge */} {/* Tagline badge */}
<div className="inline-flex items-center gap-2 rounded-full border bg-muted/50 px-3.5 py-1.5 text-xs font-medium text-muted-foreground mb-6"> <motion.div
initial={{ opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.15 }}
className="inline-flex items-center gap-2 rounded-full border bg-muted/50 px-3.5 py-1.5 text-xs font-medium text-muted-foreground mb-6"
>
<span className="size-1.5 rounded-full bg-green-500 animate-pulse" /> <span className="size-1.5 rounded-full bg-green-500 animate-pulse" />
Your AI coworker, with memory Your AI coworker, with memory
</div> </motion.div>
{/* Main heading */} {/* Main heading */}
<h1 className="text-3xl font-bold tracking-tight mb-3"> <motion.h1
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="text-3xl font-bold tracking-tight mb-3"
>
Welcome to Rowboat Welcome to Rowboat
</h1> </motion.h1>
<p className="text-base text-muted-foreground leading-relaxed max-w-sm mb-8"> <motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.3 }}
className="text-base text-muted-foreground leading-relaxed max-w-sm mb-8"
>
Connect your Rowboat account for instant access to all models through our gateway no API keys needed. Connect your Rowboat account for instant access to all models through our gateway no API keys needed.
</p> </motion.p>
{/* Product preview placeholder */} {/* Feature highlights */}
<div className="w-full max-w-sm rounded-xl border-2 border-dashed border-muted-foreground/20 bg-muted/30 aspect-video flex items-center justify-center mb-8"> <div className="grid grid-cols-3 gap-3 w-full max-w-md mb-8">
<span className="text-sm text-muted-foreground/50">Product Preview</span> {features.map((f, i) => (
<motion.div
key={f.label}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.35 + i * 0.08 }}
className="flex flex-col items-center gap-2 rounded-xl border bg-muted/30 p-4"
>
<div className="size-9 rounded-lg bg-primary/10 flex items-center justify-center">
<f.icon className="size-4.5 text-primary/80" />
</div>
<span className="text-xs font-semibold">{f.label}</span>
<span className="text-[11px] leading-tight text-muted-foreground">{f.desc}</span>
</motion.div>
))}
</div> </div>
{/* Sign in / connected state */} {/* Sign in / connected state */}
{rowboatState.isConnected ? ( <motion.div
<div className="flex flex-col items-center gap-4 w-full max-w-xs"> initial={{ opacity: 0, y: 8 }}
<div className="flex items-center gap-2 text-green-600 dark:text-green-400"> animate={{ opacity: 1, y: 0 }}
<CheckCircle2 className="size-5" /> transition={{ delay: 0.6 }}
<span className="text-sm font-medium">Connected to Rowboat</span> className="w-full max-w-xs"
>
{rowboatState.isConnected ? (
<div className="flex flex-col items-center gap-4">
<div className="flex items-center gap-2 text-green-600 dark:text-green-400">
<CheckCircle2 className="size-5" />
<span className="text-sm font-medium">Connected to Rowboat</span>
</div>
<Button
onClick={() => {
state.setOnboardingPath('rowboat')
state.setCurrentStep(2)
}}
size="lg"
className="w-full h-12 text-base font-medium"
>
Continue
</Button>
</div> </div>
<Button ) : (
onClick={() => { <div className="flex flex-col items-center gap-4">
state.setOnboardingPath('rowboat') <Button
state.setCurrentStep(2) onClick={() => {
}} state.setOnboardingPath('rowboat')
size="lg" state.startConnect('rowboat')
className="w-full h-12 text-base font-medium" }}
> size="lg"
Continue className="w-full h-12 text-base font-medium"
</Button> disabled={rowboatState.isConnecting}
</div> >
) : ( {rowboatState.isConnecting ? (
<div className="flex flex-col items-center gap-4 w-full max-w-xs"> <><Loader2 className="size-5 animate-spin mr-2" />Waiting for sign in...</>
<Button ) : (
onClick={() => { "Sign in with Rowboat"
state.setOnboardingPath('rowboat') )}
state.startConnect('rowboat') </Button>
}} {rowboatState.isConnecting && (
size="lg" <p className="text-xs text-muted-foreground animate-pulse">
className="w-full h-12 text-base font-medium" Complete sign in in your browser, then return here.
disabled={rowboatState.isConnecting} </p>
>
{rowboatState.isConnecting ? (
<><Loader2 className="size-5 animate-spin mr-2" />Waiting for sign in...</>
) : (
"Sign in with Rowboat"
)} )}
</Button> </div>
{rowboatState.isConnecting && ( )}
<p className="text-xs text-muted-foreground animate-pulse"> </motion.div>
Complete sign in in your browser, then return here.
</p>
)}
</div>
)}
{/* BYOK link */} {/* BYOK link */}
<div className="mt-8"> <motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.7 }}
className="mt-8"
>
<button <button
onClick={() => { onClick={() => {
state.setOnboardingPath('byok') state.setOnboardingPath('byok')
@ -87,7 +155,7 @@ export function WelcomeStep({ state }: WelcomeStepProps) {
> >
I want to bring my own API key I want to bring my own API key
</button> </button>
</div> </motion.div>
</div> </div>
) )
} }