Add full mobile responsive design with hamburger menu, responsive components, and mobile optimizations

This commit is contained in:
Gemini AI
2025-12-26 15:57:31 +04:00
Unverified
parent d3c3490655
commit 6b33913471
10 changed files with 341 additions and 243 deletions

View File

@@ -60,7 +60,7 @@ export default function PRDGenerator() {
const loadAvailableModels = async () => {
const fallbackModels = modelAdapter.getAvailableModels(selectedProvider);
setAvailableModels(selectedProvider, fallbackModels);
try {
const result = await modelAdapter.listModels(selectedProvider);
if (result.success && result.data) {
@@ -140,28 +140,28 @@ export default function PRDGenerator() {
];
return (
<div className="mx-auto grid max-w-7xl gap-6 lg:grid-cols-2">
<div className="mx-auto grid max-w-7xl gap-4 lg:gap-6 grid-cols-1 lg:grid-cols-2">
<Card className="h-fit">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText className="h-5 w-5" />
<CardHeader className="p-4 lg:p-6">
<CardTitle className="flex items-center gap-2 text-base lg:text-lg">
<FileText className="h-4 w-4 lg:h-5 lg:w-5" />
PRD Generator
</CardTitle>
<CardDescription>
<CardDescription className="text-xs lg:text-sm">
Generate comprehensive Product Requirements Document from your idea
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<CardContent className="space-y-3 lg:space-y-4 p-4 lg:p-6 pt-0 lg:pt-0">
<div className="space-y-2">
<label className="text-sm font-medium">AI Provider</label>
<div className="flex gap-2">
<label className="text-xs lg:text-sm font-medium">AI Provider</label>
<div className="flex flex-wrap gap-1.5 lg:gap-2">
{(["qwen", "ollama", "zai"] as const).map((provider) => (
<Button
key={provider}
variant={selectedProvider === provider ? "default" : "outline"}
size="sm"
onClick={() => setSelectedProvider(provider)}
className="capitalize"
className="capitalize text-xs lg:text-sm h-8 lg:h-9 px-2.5 lg:px-3"
>
{provider === "qwen" ? "Qwen" : provider === "ollama" ? "Ollama" : "Z.AI"}
</Button>
@@ -170,11 +170,11 @@ export default function PRDGenerator() {
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Model</label>
<label className="text-xs lg:text-sm font-medium">Model</label>
<select
value={selectedModel}
onChange={(e) => setSelectedModel(selectedProvider, e.target.value)}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
className="w-full rounded-md border border-input bg-background px-3 py-2 text-xs lg:text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
{models.map((model) => (
<option key={model} value={model}>
@@ -185,36 +185,36 @@ export default function PRDGenerator() {
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Your Idea</label>
<label className="text-xs lg:text-sm font-medium">Your Idea</label>
<Textarea
placeholder="e.g., A task management app with real-time collaboration features"
value={currentPrompt}
onChange={(e) => setCurrentPrompt(e.target.value)}
className="min-h-[200px] resize-y"
className="min-h-[150px] lg:min-h-[200px] resize-y text-sm"
/>
</div>
{error && (
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
<div className="rounded-md bg-destructive/10 p-2.5 lg:p-3 text-xs lg:text-sm text-destructive">
{error}
{!apiKeys[selectedProvider] && (
<div className="mt-2 flex items-center gap-2">
<Settings className="h-4 w-4" />
<span className="text-xs">Configure API key in Settings</span>
<div className="mt-1.5 lg:mt-2 flex items-center gap-2">
<Settings className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
<span className="text-[10px] lg:text-xs">Configure API key in Settings</span>
</div>
)}
</div>
)}
<Button onClick={handleGenerate} disabled={isProcessing || !currentPrompt.trim()} className="w-full">
<Button onClick={handleGenerate} disabled={isProcessing || !currentPrompt.trim()} className="w-full h-9 lg:h-10 text-xs lg:text-sm">
{isProcessing ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="mr-1.5 lg:mr-2 h-3.5 w-3.5 lg:h-4 lg:w-4 animate-spin" />
Generating PRD...
</>
) : (
<>
<FileText className="mr-2 h-4 w-4" />
<FileText className="mr-1.5 lg:mr-2 h-3.5 w-3.5 lg:h-4 lg:w-4" />
Generate PRD
</>
)}
@@ -223,52 +223,52 @@ export default function PRDGenerator() {
</Card>
<Card className={cn(!prd && "opacity-50")}>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<CardHeader className="p-4 lg:p-6">
<CardTitle className="flex items-center justify-between text-base lg:text-lg">
<span className="flex items-center gap-2">
<CheckCircle2 className="h-5 w-5 text-green-500" />
<CheckCircle2 className="h-4 w-4 lg:h-5 lg:w-5 text-green-500" />
Generated PRD
</span>
{prd && (
<Button variant="ghost" size="icon" onClick={handleCopy}>
<Button variant="ghost" size="icon" onClick={handleCopy} className="h-8 w-8 lg:h-9 lg:w-9">
{copied ? (
<CheckCircle2 className="h-4 w-4 text-green-500" />
<CheckCircle2 className="h-3.5 w-3.5 lg:h-4 lg:w-4 text-green-500" />
) : (
<Copy className="h-4 w-4" />
<Copy className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
)}
</Button>
)}
</CardTitle>
<CardDescription>
<CardDescription className="text-xs lg:text-sm">
Structured requirements document ready for development
</CardDescription>
</CardHeader>
<CardContent>
<CardContent className="p-4 lg:p-6 pt-0 lg:pt-0">
{prd ? (
<div className="space-y-3">
<div className="space-y-2 lg:space-y-3">
{sections.map((section) => (
<div key={section.id} className="rounded-md border bg-muted/30">
<button
onClick={() => toggleSection(section.id)}
className="flex w-full items-center justify-between px-4 py-3 text-left font-medium transition-colors hover:bg-muted/50"
className="flex w-full items-center justify-between px-3 lg:px-4 py-2.5 lg:py-3 text-left font-medium transition-colors hover:bg-muted/50 text-xs lg:text-sm"
>
<span>{section.title}</span>
{expandedSections.includes(section.id) ? (
<ChevronUp className="h-4 w-4" />
<ChevronUp className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
) : (
<ChevronDown className="h-4 w-4" />
<ChevronDown className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
)}
</button>
{expandedSections.includes(section.id) && (
<div className="border-t bg-background px-4 py-3">
<pre className="whitespace-pre-wrap text-sm">{prd.overview}</pre>
<div className="border-t bg-background px-3 lg:px-4 py-2.5 lg:py-3">
<pre className="whitespace-pre-wrap text-xs lg:text-sm">{prd.overview}</pre>
</div>
)}
</div>
))}
</div>
) : (
<div className="flex h-[300px] items-center justify-center text-center text-sm text-muted-foreground">
<div className="flex h-[200px] lg:h-[300px] items-center justify-center text-center text-xs lg:text-sm text-muted-foreground">
PRD will appear here
</div>
)}