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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,30 @@ import type { AnalysisSuggestion } from '@automaker/types';
// Helper for consistent pluralization of "idea/ideas"
const pluralizeIdea = (count: number) => `idea${count !== 1 ? 's' : ''}`;

// Helper to map priority to Badge variant
const getPriorityVariant = (
priority: string
):
| 'default'
| 'secondary'
| 'destructive'
| 'outline'
| 'success'
| 'warning'
| 'error'
| 'info' => {
Comment on lines +25 to +33
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The return type for getPriorityVariant is broader than the actual values returned by the function. It includes variants like 'default', 'destructive', 'outline', and 'success' which are not part of the function's output. To improve type safety and make the function's contract more precise, it's better to tighten the return type to only include the variants that can actually be returned: 'error', 'warning', 'info', and 'secondary'.

): 'error' | 'warning' | 'info' | 'secondary' => {

switch (priority.toLowerCase()) {
case 'high':
return 'error';
case 'medium':
return 'warning';
case 'low':
return 'info';
default:
return 'secondary';
}
};

interface IdeationDashboardProps {
onGenerateIdeas: () => void;
onAcceptAllReady?: (isReady: boolean, count: number, handler: () => Promise<void>) => void;
Expand All @@ -39,37 +63,51 @@ function SuggestionCard({
isAdding: boolean;
}) {
return (
<Card className="transition-all hover:border-primary/50">
<CardContent className="p-4">
<Card className="group transition-all hover:border-primary/50 hover:shadow-sm">
<CardContent className="p-5">
<div className="flex items-start gap-4">
<div className="flex-1 min-w-0">
<div className="flex items-start gap-2 mb-1">
<h4 className="font-medium shrink-0">{suggestion.title}</h4>
<div className="flex-1 min-w-0 space-y-3">
<div className="flex flex-col gap-2">
<div className="flex items-start justify-between gap-4">
<h4 className="font-semibold text-base leading-tight">{suggestion.title}</h4>
</div>
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="outline" className="text-xs whitespace-nowrap">
<Badge
variant={getPriorityVariant(suggestion.priority)}
className="text-xs font-medium capitalize"
>
{suggestion.priority}
</Badge>
<Badge variant="secondary" className="text-xs whitespace-nowrap">
<Badge
variant="secondary"
className="text-xs text-muted-foreground bg-secondary/40"
>
{job.prompt.title}
</Badge>
</div>
</div>
<p className="text-sm text-muted-foreground">{suggestion.description}</p>

<p className="text-sm text-muted-foreground leading-relaxed">
{suggestion.description}
</p>

{suggestion.rationale && (
<p className="text-xs text-muted-foreground mt-2 italic">{suggestion.rationale}</p>
<div className="relative pl-3 border-l-2 border-primary/20 mt-3 py-1">
<p className="text-xs text-muted-foreground/80 italic">{suggestion.rationale}</p>
</div>
)}
</div>
<div className="flex items-center gap-2 shrink-0">

<div className="flex flex-col gap-2 shrink-0 pt-1">
<Button
size="sm"
variant="ghost"
onClick={onRemove}
onClick={onAccept}
disabled={isAdding}
className="text-muted-foreground hover:text-destructive"
className={cn(
'w-full gap-1.5 shadow-none transition-all',
isAdding ? 'opacity-80' : 'hover:ring-2 hover:ring-primary/20'
)}
>
<X className="w-4 h-4" />
</Button>
<Button size="sm" onClick={onAccept} disabled={isAdding} className="gap-1">
{isAdding ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
Expand All @@ -79,6 +117,15 @@ function SuggestionCard({
</>
)}
</Button>
<Button
size="sm"
variant="ghost"
onClick={onRemove}
disabled={isAdding}
className="w-full text-muted-foreground hover:text-destructive hover:bg-destructive/10 h-8"
>
Dismiss
</Button>
</div>
</div>
</CardContent>
Expand All @@ -91,19 +138,33 @@ function GeneratingCard({ job }: { job: GenerationJob }) {
const isError = job.status === 'error';

return (
<Card className={cn('transition-all', isError ? 'border-red-500/50' : 'border-blue-500/50')}>
<CardContent className="p-4">
<Card
className={cn(
'transition-all',
isError ? 'border-destructive/50' : 'border-blue-500/30 bg-blue-50/5 dark:bg-blue-900/5'
)}
>
<CardContent className="p-5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
{isError ? (
<AlertCircle className="w-5 h-5 text-red-500" />
) : (
<Loader2 className="w-5 h-5 text-blue-500 animate-spin" />
)}
<div className="flex items-center gap-4">
<div
className={cn(
'w-10 h-10 rounded-full flex items-center justify-center shrink-0',
isError ? 'bg-destructive/10 text-destructive' : 'bg-blue-500/10 text-blue-500'
)}
>
{isError ? (
<AlertCircle className="w-5 h-5" />
) : (
<Loader2 className="w-5 h-5 animate-spin" />
)}
</div>
<div>
<p className="font-medium">{job.prompt.title}</p>
<p className="text-sm text-muted-foreground">
{isError ? job.error || 'Failed to generate' : 'Generating ideas...'}
{isError
? job.error || 'Failed to generate'
: 'Analyzing codebase and generating ideas...'}
</p>
</div>
</div>
Expand Down Expand Up @@ -135,7 +196,7 @@ function TagFilter({
if (tags.length === 0) return null;

return (
<div className="flex flex-wrap gap-2">
<div className="flex flex-wrap gap-2 py-2">
{tags.map((tag) => {
const isSelected = selectedTags.has(tag);
const count = tagCounts[tag] || 0;
Expand All @@ -144,28 +205,31 @@ function TagFilter({
key={tag}
onClick={() => onToggleTag(tag)}
className={cn(
'px-3 py-1.5 text-sm rounded-full border transition-all flex items-center gap-1.5',
'px-3.5 py-1.5 text-sm rounded-full border shadow-sm transition-all flex items-center gap-2',
isSelected
? 'bg-primary text-primary-foreground border-primary'
: 'bg-secondary/50 text-muted-foreground border-border hover:border-primary/50 hover:text-foreground'
? 'bg-primary text-primary-foreground border-primary ring-2 ring-primary/20'
: 'bg-card text-muted-foreground border-border hover:border-primary/50 hover:text-foreground hover:bg-accent/50'
)}
>
{tag}
<span className="font-medium">{tag}</span>
<span
className={cn(
'text-xs',
isSelected ? 'text-primary-foreground/70' : 'text-muted-foreground/70'
'text-xs py-0.5 px-1.5 rounded-full',
isSelected
? 'bg-primary-foreground/20 text-primary-foreground'
: 'bg-muted text-muted-foreground'
)}
>
({count})
{count}
</span>
</button>
);
})}
{selectedTags.size > 0 && <div className="h-8 w-px bg-border mx-1" />}
{selectedTags.size > 0 && (
<button
onClick={() => selectedTags.forEach((tag) => onToggleTag(tag))}
className="px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors"
className="px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors font-medium"
>
Clear filters
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,19 @@ export function PromptCategoryGrid({ onSelect, onBack }: PromptCategoryGridProps
return (
<Card
key={category.id}
className="cursor-pointer transition-all hover:border-primary hover:shadow-md"
className="group cursor-pointer transition-all duration-300 hover:border-primary hover:shadow-lg hover:-translate-y-1"
onClick={() => onSelect(category.id)}
>
<CardContent className="p-6">
<div className="flex flex-col items-center text-center gap-3">
<div className="p-4 rounded-full bg-primary/10">
<Icon className="w-8 h-8 text-primary" />
<div className="flex flex-col items-center text-center gap-4">
<div className="p-4 rounded-2xl bg-primary/10 text-primary group-hover:bg-primary/20 group-hover:scale-110 transition-all duration-300">
<Icon className="w-8 h-8" />
</div>
<div>
<h3 className="font-semibold text-lg">{category.name}</h3>
<p className="text-muted-foreground text-sm mt-1">{category.description}</p>
<div className="space-y-2">
<h3 className="font-semibold text-lg leading-tight group-hover:text-primary transition-colors">
{category.name}
</h3>
<p className="text-muted-foreground text-sm">{category.description}</p>
</div>
</div>
</CardContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,43 +141,51 @@ export function PromptList({ category, onBack }: PromptListProps) {
return (
<Card
key={prompt.id}
className={`transition-all ${
className={`group transition-all duration-300 ${
isDisabled
? 'opacity-60 cursor-not-allowed'
: 'cursor-pointer hover:border-primary hover:shadow-md'
} ${isLoading || isGenerating ? 'border-blue-500 ring-1 ring-blue-500' : ''} ${
isStarted && !isGenerating ? 'border-green-500/50' : ''
? 'opacity-60 cursor-not-allowed bg-muted/50'
: 'cursor-pointer hover:border-primary hover:shadow-md hover:-translate-x-1'
} ${isLoading || isGenerating ? 'border-blue-500/50 ring-1 ring-blue-500/20 bg-blue-50/10' : ''} ${
isStarted && !isGenerating ? 'border-green-500/50 bg-green-50/10' : ''
}`}
onClick={() => !isDisabled && handleSelectPrompt(prompt)}
>
<CardContent className="p-5">
<div className="flex items-start gap-4">
<div className="flex items-start gap-5">
<div
className={`p-2 rounded-lg mt-0.5 ${
className={`p-3 rounded-xl shrink-0 transition-all duration-300 ${
isLoading || isGenerating
? 'bg-blue-500/10'
? 'bg-blue-500/10 text-blue-500'
: isStarted
? 'bg-green-500/10'
: 'bg-primary/10'
? 'bg-green-500/10 text-green-500'
: 'bg-primary/10 text-primary group-hover:bg-primary/20 group-hover:scale-110'
}`}
>
{isLoading || isGenerating ? (
<Loader2 className="w-4 h-4 text-blue-500 animate-spin" />
<Loader2 className="w-5 h-5 animate-spin" />
) : isStarted ? (
<CheckCircle2 className="w-4 h-4 text-green-500" />
<CheckCircle2 className="w-5 h-5" />
) : (
<Lightbulb className="w-4 h-4 text-primary" />
<Lightbulb className="w-5 h-5" />
)}
</div>
<div className="flex-1 min-w-0">
<h3 className="font-semibold">{prompt.title}</h3>
<p className="text-muted-foreground text-sm mt-1">{prompt.description}</p>
<div className="flex-1 min-w-0 space-y-1">
<div className="flex items-center justify-between gap-2">
<h3 className="font-semibold text-lg group-hover:text-primary transition-colors">
{prompt.title}
</h3>
{isStarted && !isGenerating && (
<span className="text-xs font-medium text-green-600 bg-green-100 dark:bg-green-900/30 dark:text-green-400 px-2 py-0.5 rounded-full">
Generated
</span>
)}
</div>
<p className="text-muted-foreground text-sm leading-relaxed">
{prompt.description}
</p>
{(isLoading || isGenerating) && (
<p className="text-blue-500 text-sm mt-2">Generating in dashboard...</p>
)}
{isStarted && !isGenerating && (
<p className="text-green-500 text-sm mt-2">
Already generated - check dashboard
<p className="text-blue-500 text-sm font-medium animate-pulse pt-1">
Generating ideas...
</p>
)}
</div>
Expand Down