Onboarding tweaks (#589)

* onboarding improvements

* fmt

* css
This commit is contained in:
Louis Knight-Webb
2025-08-29 11:53:18 +01:00
committed by GitHub
parent 3fbc5c29ba
commit 58a29c1cc2
9 changed files with 185 additions and 207 deletions

View File

@@ -23,8 +23,7 @@ import {
} from '@/components/ui/dropdown-menu';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Sparkles, Code, ChevronDown } from 'lucide-react';
import { Sparkles, Code, ChevronDown, HandMetal } from 'lucide-react';
import { EditorType, ProfileVariantLabel } from 'shared/types';
import { useUserSystem } from '@/components/config-provider';
@@ -65,10 +64,10 @@ export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) {
return (
<Dialog open={open} onOpenChange={() => {}}>
<DialogContent className="sm:max-w-[600px]">
<DialogContent className="sm:max-w-[600px] space-y-4">
<DialogHeader>
<div className="flex items-center gap-3">
<Sparkles className="h-6 w-6 text-primary" />
<HandMetal className="h-6 w-6 text-primary text-primary-foreground" />
<DialogTitle>Welcome to Vibe Kanban</DialogTitle>
</div>
<DialogDescription className="text-left pt-2">
@@ -76,159 +75,148 @@ export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) {
later in Settings.
</DialogDescription>
</DialogHeader>
<div className="space-y-2">
<h2 className="text-xl flex items-center gap-2">
<Sparkles className="h-4 w-4" />
Choose Your Coding Agent
</h2>
<div className="space-y-2">
<Label htmlFor="profile">Default Profile</Label>
<div className="flex gap-2">
<Select
value={profile.profile}
onValueChange={(value) =>
setProfile({ profile: value, variant: null })
}
>
<SelectTrigger id="profile" className="flex-1">
<SelectValue placeholder="Select your preferred coding agent" />
</SelectTrigger>
<SelectContent>
{profiles?.map((profile) => (
<SelectItem key={profile.label} value={profile.label}>
{profile.label}
</SelectItem>
))}
</SelectContent>
</Select>
<div className="space-y-6 py-4">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Sparkles className="h-4 w-4" />
Choose Your Coding Agent
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="profile">Default Profile</Label>
<div className="flex gap-2">
<Select
value={profile.profile}
onValueChange={(value) =>
setProfile({ profile: value, variant: null })
}
>
<SelectTrigger id="profile" className="flex-1">
<SelectValue placeholder="Select your preferred coding agent" />
</SelectTrigger>
<SelectContent>
{profiles?.map((profile) => (
<SelectItem key={profile.label} value={profile.label}>
{profile.label}
</SelectItem>
))}
</SelectContent>
</Select>
{/* Show variant selector if selected profile has variants */}
{(() => {
const selectedProfile = profiles?.find(
(p) => p.label === profile.profile
);
const hasVariants =
selectedProfile?.variants &&
selectedProfile.variants.length > 0;
{/* Show variant selector if selected profile has variants */}
{(() => {
const selectedProfile = profiles?.find(
(p) => p.label === profile.profile
);
const hasVariants =
selectedProfile?.variants &&
selectedProfile.variants.length > 0;
if (hasVariants) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="w-24 px-2 flex items-center justify-between"
>
<span className="text-xs truncate flex-1 text-left">
{profile.variant || 'Default'}
</span>
<ChevronDown className="h-3 w-3 ml-1 flex-shrink-0" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
onClick={() =>
setProfile({ ...profile, variant: null })
}
className={!profile.variant ? 'bg-accent' : ''}
>
Default
</DropdownMenuItem>
{selectedProfile.variants.map((variant) => (
<DropdownMenuItem
key={variant.label}
onClick={() =>
setProfile({
...profile,
variant: variant.label,
})
}
className={
profile.variant === variant.label
? 'bg-accent'
: ''
}
>
{variant.label}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
} else if (selectedProfile) {
// Show disabled button when profile exists but has no variants
return (
if (hasVariants) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="w-24 px-2 flex items-center justify-between"
disabled
>
<span className="text-xs truncate flex-1 text-left">
Default
{profile.variant || 'Default'}
</span>
<ChevronDown className="h-3 w-3 ml-1 flex-shrink-0" />
</Button>
);
}
return null;
})()}
</div>
</div>
</CardContent>
</Card>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
onClick={() =>
setProfile({ ...profile, variant: null })
}
className={!profile.variant ? 'bg-accent' : ''}
>
Default
</DropdownMenuItem>
{selectedProfile.variants.map((variant) => (
<DropdownMenuItem
key={variant.label}
onClick={() =>
setProfile({
...profile,
variant: variant.label,
})
}
className={
profile.variant === variant.label
? 'bg-accent'
: ''
}
>
{variant.label}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
} else if (selectedProfile) {
// Show disabled button when profile exists but has no variants
return (
<Button
variant="outline"
className="w-24 px-2 flex items-center justify-between"
disabled
>
<span className="text-xs truncate flex-1 text-left">
Default
</span>
</Button>
);
}
return null;
})()}
</div>
</div>
</div>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Code className="h-4 w-4" />
Choose Your Code Editor
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<h2 className="text-xl flex items-center gap-2">
<Code className="h-4 w-4" />
Choose Your Code Editor
</h2>
<div className="space-y-2">
<Label htmlFor="editor">Preferred Editor</Label>
<Select
value={editorType}
onValueChange={(value: EditorType) => setEditorType(value)}
>
<SelectTrigger id="editor">
<SelectValue placeholder="Select your preferred editor" />
</SelectTrigger>
<SelectContent>
{Object.values(EditorType).map((type) => (
<SelectItem key={type} value={type}>
{toPrettyCase(type)}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-sm text-muted-foreground">
This editor will be used to open task attempts and project files.
</p>
{editorType === EditorType.CUSTOM && (
<div className="space-y-2">
<Label htmlFor="editor">Preferred Editor</Label>
<Select
value={editorType}
onValueChange={(value: EditorType) => setEditorType(value)}
>
<SelectTrigger id="editor">
<SelectValue placeholder="Select your preferred editor" />
</SelectTrigger>
<SelectContent>
{Object.values(EditorType).map((type) => (
<SelectItem key={type} value={type}>
{toPrettyCase(type)}
</SelectItem>
))}
</SelectContent>
</Select>
<Label htmlFor="custom-command">Custom Command</Label>
<Input
id="custom-command"
placeholder="e.g., code, subl, vim"
value={customCommand}
onChange={(e) => setCustomCommand(e.target.value)}
/>
<p className="text-sm text-muted-foreground">
This editor will be used to open task attempts and project
files.
Enter the command to run your custom editor. Use spaces for
arguments (e.g., "code --wait").
</p>
</div>
{editorType === EditorType.CUSTOM && (
<div className="space-y-2">
<Label htmlFor="custom-command">Custom Command</Label>
<Input
id="custom-command"
placeholder="e.g., code, subl, vim"
value={customCommand}
onChange={(e) => setCustomCommand(e.target.value)}
/>
<p className="text-sm text-muted-foreground">
Enter the command to run your custom editor. Use spaces for
arguments (e.g., "code --wait").
</p>
</div>
)}
</CardContent>
</Card>
)}
</div>
</div>
<DialogFooter>

View File

@@ -8,7 +8,6 @@ import {
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Shield, CheckCircle, XCircle, Settings } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useConfig } from '@/components/config-provider';
interface PrivacyOptInDialogProps {
@@ -39,7 +38,7 @@ export function PrivacyOptInDialog({
<DialogContent className="sm:max-w-[700px]">
<DialogHeader>
<div className="flex items-center gap-3">
<Shield className="h-6 w-6 text-primary" />
<Shield className="h-6 w-6 text-primary text-primary-foreground" />
<DialogTitle>Feedback Opt-In</DialogTitle>
</div>
<DialogDescription className="text-left pt-1">
@@ -48,62 +47,54 @@ export function PrivacyOptInDialog({
</DialogDescription>
</DialogHeader>
<div className="space-y-3 py-3">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base">
What data do we collect?
</CardTitle>
</CardHeader>
<CardContent className="space-y-2 pt-0">
{isGitHubAuthenticated && (
<div className="flex items-start gap-2">
<CheckCircle className="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<div className="min-w-0">
<p className="text-sm font-medium">
GitHub profile information
</p>
<p className="text-xs text-muted-foreground">
Username and email address to send you only very important
updates about the project. We promise not to abuse this
</p>
</div>
</div>
)}
<div className="space-y-3">
<h2>What data do we collect?</h2>
<div>
{isGitHubAuthenticated && (
<div className="flex items-start gap-2">
<CheckCircle className="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<div className="min-w-0">
<p className="text-sm font-medium">
High-level usage metrics
GitHub profile information
</p>
<p className="text-xs text-muted-foreground">
Number of tasks created, projects managed, feature usage
Username and email address to send you only very important
updates about the project. We promise not to abuse this
</p>
</div>
</div>
<div className="flex items-start gap-2">
<CheckCircle className="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<div className="min-w-0">
<p className="text-sm font-medium">
Performance and error data
</p>
<p className="text-xs text-muted-foreground">
Application crashes, response times, technical issues
</p>
</div>
)}
<div className="flex items-start gap-2">
<CheckCircle className="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<div className="min-w-0">
<p className="text-sm font-medium">High-level usage metrics</p>
<p className="text-xs text-muted-foreground">
Number of tasks created, projects managed, feature usage
</p>
</div>
<div className="flex items-start gap-2">
<XCircle className="h-4 w-4 text-destructive mt-0.5 flex-shrink-0" />
<div className="min-w-0">
<p className="text-sm font-medium">We do NOT collect</p>
<p className="text-xs text-muted-foreground">
Task contents, code snippets, project names, or other
personal data
</p>
</div>
</div>
<div className="flex items-start gap-2">
<CheckCircle className="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<div className="min-w-0">
<p className="text-sm font-medium">
Performance and error data
</p>
<p className="text-xs text-muted-foreground">
Application crashes, response times, technical issues
</p>
</div>
</CardContent>
</Card>
</div>
<div className="flex items-start gap-2">
<XCircle className="h-4 w-4 text-destructive mt-0.5 flex-shrink-0" />
<div className="min-w-0">
<p className="text-sm font-medium">We do NOT collect</p>
<p className="text-xs text-muted-foreground">
Task contents, code snippets, project names, or other personal
data
</p>
</div>
</div>
</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground bg-muted/50 p-2 rounded-lg">
<Settings className="h-3 w-3 flex-shrink-0" />

View File

@@ -10,13 +10,12 @@ const buttonVariants = cva(
variants: {
variant: {
default:
'bg-primary text-primary-foreground hover:bg-primary/90 border border-foreground',
'text-primary-foreground hover:bg-primary/90 border border-foreground',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80 border',
'border border-input hover:bg-accent hover:text-accent-foreground',
secondary: 'text-secondary-foreground hover:bg-secondary/80 border',
ghost: 'hover:text-primary-foreground/50',
link: 'hover:underline',
},

View File

@@ -30,7 +30,7 @@ const Dialog = React.forwardRef<
<div
ref={ref}
className={cn(
'relative z-[9999] grid w-full max-w-lg gap-4 bg-background p-6 shadow-lg duration-200 sm:rounded-lg my-8',
'relative z-[9999] grid w-full max-w-lg gap-4 bg-primary p-6 shadow-lg duration-200 sm:rounded-lg my-8',
className
)}
{...props}

View File

@@ -25,7 +25,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
'flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
'flex cursor-default select-none items-center gap-2 px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
inset && 'pl-8',
className
)}
@@ -45,7 +45,7 @@ const DropdownMenuSubContent = React.forwardRef<
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
'z-[10000] min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]',
'z-[10000] min-w-[8rem] overflow-hidden border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]',
className
)}
{...props}
@@ -63,7 +63,7 @@ const DropdownMenuContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-[10000] max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]',
'z-[10000] max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden border p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]',
className
)}
{...props}
@@ -81,7 +81,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
'relative flex cursor-default select-none items-center gap-2 px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
inset && 'pl-8',
className
)}
@@ -97,7 +97,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
checked={checked}
@@ -121,7 +121,7 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
{...props}

View File

@@ -11,7 +11,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input
type={type}
className={cn(
'flex h-10 w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
'flex h-10 w-full border px-3 py-2 text-sm ring-offset-background file:border-0 bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
className
)}
ref={ref}

View File

@@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
'flex h-10 w-full items-center justify-between border border-input px-3 py-2 text-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
className
)}
{...props}
@@ -75,7 +75,7 @@ const SelectContent = React.forwardRef<
<SelectPrimitive.Content
ref={ref}
className={cn(
'relative z-[10000] max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]',
'relative z-[10000] max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden border bg-primary text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]',
position === 'popper' &&
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
className
@@ -118,7 +118,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex w-full cursor-default select-none items-center py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
{...props}

View File

@@ -96,7 +96,7 @@ export const KanbanCard = ({
return (
<Card
className={cn(
'p-3 focus:ring-2 outline-none border-b flex-col space-y-2',
'p-3 focus:ring-2 ring-secondary-foreground outline-none border-b flex-col space-y-2',
isDragging && 'cursor-grabbing',
className
)}

View File

@@ -315,7 +315,7 @@ export function ProjectTasks() {
{/* Left Column - Kanban Section */}
<div className={getKanbanSectionClasses(isPanelOpen, isFullscreen)}>
{tasks.length === 0 ? (
<div className="max-w-7xl mx-auto">
<div className="max-w-7xl mx-auto mt-8">
<Card>
<CardContent className="text-center py-8">
<p className="text-muted-foreground">