Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
18 changes: 15 additions & 3 deletions apps/agent/components/sidebar/SettingsSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import { NavLink, useLocation } from 'react-router'
import { ThemeToggle } from '@/components/elements/theme-toggle'
import { Feature } from '@/lib/browseros/capabilities'
import { useCapabilities } from '@/lib/browseros/useCapabilities'
import {
getOnboardingFeaturesPath,
getOnboardingRevisitPath,
} from '@/lib/onboarding/onboardingFlow'
import { cn } from '@/lib/utils'

type NavItem = {
Expand Down Expand Up @@ -43,8 +47,16 @@ const settingsNavItems: NavItem[] = [
feature: Feature.SOUL_SUPPORT,
},
{ name: 'Skills', to: '/settings/skills', icon: Wand2 },
{ name: 'Explore Features', to: '/onboarding/features', icon: Compass },
{ name: 'Revisit Onboarding', to: '/onboarding', icon: RotateCcw },
{
name: 'Explore Features',
to: getOnboardingFeaturesPath('settings'),
icon: Compass,
},
{
name: 'Revisit Onboarding',
to: getOnboardingRevisitPath(),
icon: RotateCcw,
},
]

export const SettingsSidebar: FC = () => {
Expand Down Expand Up @@ -78,7 +90,7 @@ export const SettingsSidebar: FC = () => {
<nav className="space-y-1">
{filteredItems.map((item) => {
const Icon = item.icon
const isActive = location.pathname === item.to
const isActive = location.pathname === item.to.split('?')[0]

return (
<NavLink
Expand Down
3 changes: 3 additions & 0 deletions apps/agent/entrypoints/app/login/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Separator } from '@/components/ui/separator'
import { signIn, useSession } from '@/lib/auth/auth-client'
import { authRedirectPathStorage } from '@/lib/onboarding/onboardingStorage'

type LoginState = 'idle' | 'loading' | 'magic-link-sent' | 'error'

Expand All @@ -45,6 +46,7 @@ export const LoginPage: FC = () => {
setError(null)

try {
await authRedirectPathStorage.removeValue()
const result = await signIn.magicLink({
email: email.trim(),
callbackURL: '/home',
Expand All @@ -68,6 +70,7 @@ export const LoginPage: FC = () => {
setError(null)

try {
await authRedirectPathStorage.removeValue()
await signIn.social({
provider: 'google',
callbackURL: '/home',
Expand Down
18 changes: 16 additions & 2 deletions apps/agent/entrypoints/app/login/MagicLinkCallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
CardTitle,
} from '@/components/ui/card'
import { useSession } from '@/lib/auth/auth-client'
import { authRedirectPathStorage } from '@/lib/onboarding/onboardingStorage'

export const MagicLinkCallback: FC = () => {
const navigate = useNavigate()
Expand All @@ -20,14 +21,27 @@ export const MagicLinkCallback: FC = () => {
const [error, setError] = useState<string | null>(null)

useEffect(() => {
let cancelled = false
const errorParam = searchParams.get('error')
if (errorParam) {
setError(decodeURIComponent(errorParam))
return
}

if (!isPending && session) {
navigate('/home', { replace: true })
if (isPending || !session) return

const redirectAfterAuth = async () => {
const redirectPath = await authRedirectPathStorage.getValue()
if (redirectPath) {
await authRedirectPathStorage.removeValue()
}
if (cancelled) return
navigate(redirectPath || '/home', { replace: true })
}

void redirectAfterAuth()
return () => {
cancelled = true
}
}, [session, isPending, searchParams, navigate])

Expand Down
134 changes: 81 additions & 53 deletions apps/agent/entrypoints/onboarding/demo/OnboardingDemo.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ArrowRight, Sparkles } from 'lucide-react'
import { ArrowLeft, ArrowRight, Sparkles } from 'lucide-react'
import { useEffect, useState } from 'react'
import { NavLink, useSearchParams } from 'react-router'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import {
Expand All @@ -8,10 +9,15 @@ import {
} from '@/lib/constants/analyticsEvents'
import { openSidePanelWithSearch } from '@/lib/messaging/sidepanel/openSidepanelWithSearch'
import { track } from '@/lib/metrics/track'
import {
getOnboardingFlowSource,
getOnboardingStepPath,
} from '@/lib/onboarding/onboardingFlow'
import {
onboardingCompletedStorage,
onboardingProfileStorage,
} from '@/lib/onboarding/onboardingStorage'
import { OnboardingProgress } from '../steps/OnboardingProgress'

function buildDemoSuggestions(company?: string) {
return [
Expand Down Expand Up @@ -41,14 +47,18 @@ function buildDemoSuggestions(company?: string) {
}

export const OnboardingDemo = () => {
const [searchParams] = useSearchParams()
const [customQuery, setCustomQuery] = useState('')
const [companyName, setCompanyName] = useState<string | null>(null)
const [demoSuggestions, setDemoSuggestions] = useState(() =>
buildDemoSuggestions(),
)
const source = getOnboardingFlowSource(searchParams)

useEffect(() => {
onboardingProfileStorage.getValue().then((profile) => {
if (profile?.company) {
setCompanyName(profile.company)
setDemoSuggestions(buildDemoSuggestions(profile.company))
}
})
Expand Down Expand Up @@ -103,62 +113,80 @@ export const OnboardingDemo = () => {
}

return (
<div className="flex h-screen flex-col items-center justify-center bg-background px-6">
<div className="w-full max-w-lg space-y-8">
<div className="space-y-2 text-center">
<div className="mx-auto mb-4 flex size-12 items-center justify-center rounded-full bg-[var(--accent-orange)]/10">
<Sparkles className="size-6 text-[var(--accent-orange)]" />
</div>
<h2 className="font-bold text-3xl tracking-tight">
Try your first task
</h2>
<p className="text-base text-muted-foreground">
Pick a suggestion or type your own to see BrowserOS in action
</p>
</div>
<div className="flex h-screen flex-col overflow-hidden bg-background">
<OnboardingProgress currentStep={3} />
<main className="flex flex-1 items-center justify-center overflow-y-auto px-6">
<div className="w-full max-w-4xl">
<div className="mx-auto w-full max-w-lg space-y-8">
<div className="space-y-2 text-center">
<div className="mx-auto mb-4 flex size-12 items-center justify-center rounded-full bg-[var(--accent-orange)]/10">
<Sparkles className="size-6 text-[var(--accent-orange)]" />
</div>
<h2 className="font-bold text-3xl tracking-tight">
Let&apos;s put BrowserOS to work
</h2>
<p className="text-base text-muted-foreground">
{companyName
? `We tailored a few starter tasks around ${companyName}. Pick one or type a real task of your own.`
: 'Pick a suggestion or type a real task of your own to see BrowserOS in action.'}
</p>
</div>

<div className="space-y-3">
{demoSuggestions.map((suggestion, index) => (
<button
key={suggestion.label}
type="button"
onClick={() =>
handleDemoTask(suggestion.query, suggestion.mode, index)
}
className="flex w-full items-center justify-between rounded-lg border border-border bg-card p-4 text-left transition-colors hover:border-[var(--accent-orange)]/50 hover:bg-accent"
>
<span className="font-medium text-sm">{suggestion.label}</span>
<ArrowRight className="size-4 text-muted-foreground" />
</button>
))}
</div>
<div className="space-y-3">
{demoSuggestions.map((suggestion, index) => (
<button
key={suggestion.label}
type="button"
onClick={() =>
handleDemoTask(suggestion.query, suggestion.mode, index)
}
className="flex w-full items-center justify-between rounded-lg border border-border bg-card p-4 text-left transition-colors hover:border-[var(--accent-orange)]/50 hover:bg-accent"
>
<span className="font-medium text-sm">
{suggestion.label}
</span>
<ArrowRight className="size-4 text-muted-foreground" />
</button>
))}
</div>

<form onSubmit={handleCustomQuery} className="flex gap-2">
<Input
placeholder="Or type your own task..."
value={customQuery}
onChange={(e) => setCustomQuery(e.target.value)}
className="flex-1"
/>
<Button
type="submit"
disabled={!customQuery.trim()}
className="bg-[var(--accent-orange)] text-white hover:bg-[var(--accent-orange)]/90"
>
Go
</Button>
</form>
<form onSubmit={handleCustomQuery} className="flex gap-2">
<Input
placeholder="Or type your own task..."
value={customQuery}
onChange={(e) => setCustomQuery(e.target.value)}
className="flex-1"
/>
<Button
type="submit"
disabled={!customQuery.trim()}
className="bg-[var(--accent-orange)] text-white hover:bg-[var(--accent-orange)]/90"
>
Go
</Button>
</form>

<div className="text-center">
<Button
variant="ghost"
onClick={handleSkip}
className="text-muted-foreground"
>
Skip and go to homepage
</Button>
<div className="text-center">
<Button
variant="ghost"
onClick={handleSkip}
className="text-muted-foreground"
>
Skip and go to homepage
</Button>
</div>
</div>

<div className="pt-8">
<Button variant="ghost" asChild className="group">
<NavLink to={getOnboardingStepPath(2, source)}>
<ArrowLeft className="mr-2 h-4 w-4 transition-transform group-hover:-translate-x-0.5" />
Back
</NavLink>
</Button>
</div>
</div>
</div>
</main>
</div>
)
}
55 changes: 45 additions & 10 deletions apps/agent/entrypoints/onboarding/features/Features.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
SplitSquareHorizontal,
} from 'lucide-react'
import { type FC, useEffect, useState } from 'react'
import { NavLink, useSearchParams } from 'react-router'
import DiscordLogo from '@/assets/discord-logo.svg'
import GithubLogo from '@/assets/github-logo.svg'
import SlackLogo from '@/assets/slack-logo.svg'
Expand All @@ -31,6 +32,11 @@ import {
productRepositoryUrl,
slackUrl,
} from '@/lib/constants/productUrls'
import {
getOnboardingFlowSource,
getOnboardingStepPath,
ONBOARDING_ENTRY_PATH,
} from '@/lib/onboarding/onboardingFlow'
import { cn } from '@/lib/utils'
import { BentoCard, type Feature } from './BentoCard'
import { VideoFrame } from './VideoFrame'
Expand Down Expand Up @@ -153,7 +159,10 @@ const features: Feature[] = [
* @public
*/
export const FeaturesPage: FC = () => {
const [searchParams] = useSearchParams()
const [mounted, setMounted] = useState(false)
const source = getOnboardingFlowSource(searchParams)
const isSetupFlow = source === 'setup'

useEffect(() => {
setMounted(true)
Expand Down Expand Up @@ -206,8 +215,9 @@ export const FeaturesPage: FC = () => {
: 'translate-y-4 opacity-0',
)}
>
Watch our launch video to understand the vision of BrowserOS
and key features!
{isSetupFlow
? 'Before setup, here is the fastest way to understand what BrowserOS can actually do.'
: 'Watch our launch video to understand the vision of BrowserOS and key features!'}
</p>
</div>
</div>
Expand Down Expand Up @@ -395,14 +405,39 @@ export const FeaturesPage: FC = () => {

<section className="mx-auto max-w-7xl px-6 pt-16 pb-56">
<div className="space-y-4 text-center">
<Button
onClick={handleStart}
size="lg"
className="bg-[var(--accent-orange)] text-white shadow-[var(--accent-orange)]/25 shadow-lg hover:bg-[var(--accent-orange)]/90"
>
Start Using BrowserOS
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
{isSetupFlow ? (
<>
<Button
size="lg"
asChild
className="bg-[var(--accent-orange)] text-white shadow-[var(--accent-orange)]/25 shadow-lg hover:bg-[var(--accent-orange)]/90"
>
<NavLink to={getOnboardingStepPath(1, source)}>
Continue Setup
<ArrowRight className="ml-2 h-4 w-4" />
</NavLink>
</Button>
<Button variant="ghost" asChild>
<NavLink to={ONBOARDING_ENTRY_PATH}>Back to Welcome</NavLink>
</Button>
</>
) : (
<>
<Button
onClick={handleStart}
size="lg"
className="bg-[var(--accent-orange)] text-white shadow-[var(--accent-orange)]/25 shadow-lg hover:bg-[var(--accent-orange)]/90"
>
Start Using BrowserOS
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
<Button variant="ghost" asChild>
<NavLink to={getOnboardingStepPath(1, source)}>
Revisit Guided Setup
</NavLink>
</Button>
</>
)}
</div>
</section>
</div>
Expand Down
Loading
Loading