mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-19 18:35:18 +02:00
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:
parent
74d4172f54
commit
da001e5a24
2 changed files with 132 additions and 53 deletions
11
apps/x/.claude/launch.json
Normal file
11
apps/x/.claude/launch.json
Normal 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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue