Fix Fitting Room overflow and add Save as Outfit to Extractor

- Fixed Fitting Room page overflow by constraining height and using ScrollArea for the Generated Looks section.
- Added "Save as Outfit" checkbox and input to the Refine stage of the Extractor dialog.
- Installed @radix-ui/react-checkbox and added Shadcn Checkbox component.
- Updated Dockerfile to include example-data directory in the final image.
This commit is contained in:
google-labs-jules[bot] committed 2026-02-18 02:50:22 +00:00
1 parent 9eaf136550
commit 7f12e5f909
7 files changed
+112 -6

No files matched your search

+1
View File
@@ -38,6 +38,7 @@ COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/bunfig.toml ./bunfig.toml
COPY --from=builder /app/public ./public
COPY --from=builder /app/prisma.config.ts ./prisma.config.ts
COPY --from=builder /app/example-data ./example-data
# Create data directory
RUN mkdir -p data
+1
View File
@@ -10,6 +10,7 @@
"@prisma/adapter-libsql": "7.3.0",
"@prisma/client": "^7.3.0",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-scroll-area": "^1.2.10",
+1
View File
@@ -17,6 +17,7 @@
"@prisma/adapter-libsql": "7.3.0",
"@prisma/client": "^7.3.0",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-scroll-area": "^1.2.10",
+34 -1
View File
@@ -34,6 +34,10 @@ export const ExtractorDialog: React.FC<ExtractorDialogProps> = ({
const [results, setResults] = useState<ExtractedAsset[]>([])
const [isRefineComplete, setIsRefineComplete] = useState(false)
// Save as Outfit state
const [saveAsOutfit, setSaveAsOutfit] = useState(false)
const [outfitName, setOutfitName] = useState('')
// Refs for cleanup
const analyzeController = useRef<AbortController | null>(null)
const refineController = useRef<AbortController | null>(null)
@@ -48,6 +52,8 @@ export const ExtractorDialog: React.FC<ExtractorDialogProps> = ({
setSelectedIndices([])
setResults([])
setIsRefineComplete(false)
setSaveAsOutfit(false)
setOutfitName('')
analyzeController.current?.abort()
refineController.current?.abort()
}
@@ -194,7 +200,30 @@ export const ExtractorDialog: React.FC<ExtractorDialogProps> = ({
const handleClose = () => onOpenChange(false)
const handleDone = () => {
const handleDone = async () => {
if (saveAsOutfit) {
if (!outfitName.trim()) {
toast.error('Please enter an outfit name')
return
}
try {
const equipmentIds = results.map((r) => r.id)
const res = await client.outfits.$post({
json: {
name: outfitName,
equipmentIds,
},
})
if (res.ok) {
toast.success('Outfit created')
} else {
toast.error('Failed to create outfit')
}
} catch (e) {
console.error(e)
toast.error('Failed to create outfit')
}
}
onSuccess(results)
handleClose()
}
@@ -234,6 +263,10 @@ export const ExtractorDialog: React.FC<ExtractorDialogProps> = ({
selectedCandidates={candidates.filter((_, i) => selectedIndices.includes(i))}
results={results}
isComplete={isRefineComplete}
saveAsOutfit={saveAsOutfit}
setSaveAsOutfit={setSaveAsOutfit}
outfitName={outfitName}
setOutfitName={setOutfitName}
/>
)}
</div>
+41 -2
View File
@@ -2,24 +2,35 @@ import { CheckCircle2, Loader2 } from 'lucide-react'
import React from 'react'
import { cn } from '../../lib/utils'
import { CandidateAsset, ExtractedAsset } from '../../types'
import { Checkbox } from '../ui/checkbox'
import { Input } from '../ui/input'
import { Label } from '../ui/label'
import { ScrollArea } from '../ui/scroll-area'
interface RefineStageProps {
selectedCandidates: CandidateAsset[]
results: ExtractedAsset[]
isComplete: boolean
saveAsOutfit: boolean
setSaveAsOutfit: (value: boolean) => void
outfitName: string
setOutfitName: (value: string) => void
}
export const RefineStage: React.FC<RefineStageProps> = ({
selectedCandidates,
results,
isComplete,
saveAsOutfit,
setSaveAsOutfit,
outfitName,
setOutfitName,
}) => {
return (
<div className="flex flex-col h-full w-full max-w-6xl mx-auto px-4 py-6">
{/* Grid */}
<ScrollArea className="h-full px-2">
<div className="grid grid-cols-3 gap-2 p-1 h-full pb-6">
<ScrollArea className="flex-1 px-2">
<div className="grid grid-cols-3 gap-2 p-1 pb-6">
{selectedCandidates.map((candidate, index) => {
const isFinished = index < results.length
const isProcessing = index === results.length && !isComplete
@@ -114,6 +125,34 @@ export const RefineStage: React.FC<RefineStageProps> = ({
})}
</div>
</ScrollArea>
{isComplete && (
<div className="flex items-center gap-4 mt-4 p-4 bg-white rounded-xl border border-slate-200 shadow-sm shrink-0 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="flex items-center space-x-2">
<Checkbox
id="saveOutfit"
checked={saveAsOutfit}
onCheckedChange={(c) => setSaveAsOutfit(c === true)}
/>
<Label
htmlFor="saveOutfit"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Save as Outfit
</Label>
</div>
{saveAsOutfit && (
<Input
placeholder="Outfit Name"
value={outfitName}
onChange={(e) => setOutfitName(e.target.value)}
className="max-w-xs animate-in fade-in zoom-in duration-300"
autoFocus
/>
)}
</div>
)}
<style>{`
@keyframes scanline {
0% {
+30
View File
@@ -0,0 +1,30 @@
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { cn } from "@/ui/lib/utils"
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }
+4 -3
View File
@@ -10,6 +10,7 @@ import { LookDetailsDialog } from '../components/LookDetailsDialog'
import { PageHeader } from '../components/PageHeader'
import { Button } from '../components/ui/button'
import { Card } from '../components/ui/card'
import { ScrollArea } from '../components/ui/scroll-area'
import {
DropdownMenu,
DropdownMenuContent,
@@ -82,7 +83,7 @@ export default function FittingRoom() {
}
return (
<div className="p-8 pt-2 h-full flex flex-col bg-stone-50/30 relative">
<div className="px-8 pb-8 pt-2 h-[calc(100vh-6rem)] flex flex-col bg-stone-50/30 relative">
<PageHeader
title="Fitting Room"
subtitle="System // FITTING_ROOM"
@@ -142,7 +143,7 @@ export default function FittingRoom() {
<span className="text-xs font-mono text-stone-400">{generations.length} LOOKS</span>
</div>
<div className="flex-1 overflow-y-auto p-4 custom-scrollbar">
<ScrollArea className="flex-1 p-4">
{generations.length === 0 ? (
<div className="h-full flex flex-col items-center justify-center text-stone-400 gap-4">
<div className="w-16 h-16 rounded-full bg-stone-100 flex items-center justify-center">
@@ -208,7 +209,7 @@ export default function FittingRoom() {
))}
</div>
)}
</div>
</ScrollArea>
</div>
</div>