sushilideaclan01's picture
remove the variations option
4a56a0b
"use client";
import React, { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/Card";
import { Select } from "@/components/ui/Select";
import { IMAGE_MODELS } from "@/lib/constants/models";
import { getAllAngles, getAllConcepts } from "@/lib/api/endpoints";
import type { ModificationMode, AnglesResponse, ConceptsResponse } from "@/types/api";
interface AngleOption {
value: string;
label: string;
trigger: string;
category: string;
}
interface ConceptOption {
value: string;
label: string;
structure: string;
category: string;
}
interface ModificationFormProps {
angle: string;
concept: string;
mode: ModificationMode;
imageModel: string | null;
userPrompt: string;
variationCount: number;
onAngleChange: (value: string) => void;
onConceptChange: (value: string) => void;
onModeChange: (mode: ModificationMode) => void;
onImageModelChange: (model: string | null) => void;
onUserPromptChange: (value: string) => void;
onVariationCountChange: (value: number) => void;
onSubmit: () => void;
isLoading: boolean;
}
import { Brain, Sparkles, Wand2, Lightbulb, ChevronDown, Check, Info } from "lucide-react";
export const ModificationForm: React.FC<ModificationFormProps> = ({
angle,
concept,
mode,
imageModel,
userPrompt,
variationCount,
onAngleChange,
onConceptChange,
onModeChange,
onImageModelChange,
onUserPromptChange,
onVariationCountChange,
onSubmit,
isLoading,
}) => {
const [angleOptions, setAngleOptions] = useState<AngleOption[]>([]);
const [conceptOptions, setConceptOptions] = useState<ConceptOption[]>([]);
const [loadingOptions, setLoadingOptions] = useState(true);
const [angleInputMode, setAngleInputMode] = useState<"dropdown" | "custom">("dropdown");
const [conceptInputMode, setConceptInputMode] = useState<"dropdown" | "custom">("dropdown");
// Fetch angles and concepts on mount
useEffect(() => {
const fetchOptions = async () => {
try {
setLoadingOptions(true);
const [anglesData, conceptsData] = await Promise.all([
getAllAngles(),
getAllConcepts(),
]);
const angles: AngleOption[] = [];
Object.entries(anglesData.categories).forEach(([categoryKey, category]) => {
category.angles.forEach((a) => {
angles.push({
value: a.name,
label: a.name,
trigger: a.trigger,
category: category.name,
});
});
});
setAngleOptions(angles);
const concepts: ConceptOption[] = [];
Object.entries(conceptsData.categories).forEach(([categoryKey, category]) => {
category.concepts.forEach((c) => {
concepts.push({
value: c.name,
label: c.name,
structure: c.structure,
category: category.name,
});
});
});
setConceptOptions(concepts);
} catch (error) {
console.error("Failed to fetch angles/concepts:", error);
} finally {
setLoadingOptions(false);
}
};
fetchOptions();
}, []);
const isValid = angle.trim() || concept.trim() || userPrompt.trim();
const groupedAngles = angleOptions.reduce((acc, angle) => {
if (!acc[angle.category]) acc[angle.category] = [];
acc[angle.category].push(angle);
return acc;
}, {} as Record<string, AngleOption[]>);
const groupedConcepts = conceptOptions.reduce((acc, concept) => {
if (!acc[concept.category]) acc[concept.category] = [];
acc[concept.category].push(concept);
return acc;
}, {} as Record<string, ConceptOption[]>);
return (
<Card variant="glass" className="overflow-hidden border-none shadow-2xl p-0">
<CardHeader className="bg-gradient-to-r from-blue-600/10 to-cyan-500/10 border-b border-blue-500/10 p-8 pb-10">
<div className="flex items-center gap-4 mb-3">
<div className="p-2.5 bg-blue-600 rounded-xl shadow-lg shadow-blue-500/30">
<Sparkles className="w-6 h-6 text-white" />
</div>
<CardTitle className="text-3xl font-black tracking-tight text-gray-900 border-none p-0 m-0 bg-transparent flex items-center">
Refine & Transform
</CardTitle>
</div>
<CardDescription className="text-gray-600 text-lg font-medium leading-relaxed max-w-2xl">
Apply powerful psychological angles and visual concepts to your creative.
</CardDescription>
</CardHeader>
<CardContent className="p-8 pt-10 space-y-10">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Angle Selection */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Brain className="w-4 h-4 text-orange-500" />
<label className="text-sm font-bold text-gray-700 uppercase tracking-wider">
Psychological Angle
</label>
</div>
<div className="inline-flex p-1 bg-gray-100 rounded-lg">
<button
type="button"
onClick={() => setAngleInputMode("dropdown")}
className={`px-3 py-1 text-xs font-bold rounded-md transition-all ${angleInputMode === "dropdown" ? "bg-white text-orange-600 shadow-sm" : "text-gray-500 hover:text-gray-700"
}`}
>
Presets
</button>
<button
type="button"
onClick={() => setAngleInputMode("custom")}
className={`px-3 py-1 text-xs font-bold rounded-md transition-all ${angleInputMode === "custom" ? "bg-white text-orange-600 shadow-sm" : "text-gray-500 hover:text-gray-700"
}`}
>
Custom
</button>
</div>
</div>
<div className="relative group">
{angleInputMode === "dropdown" ? (
<div className="relative">
<select
value={angle}
onChange={(e) => onAngleChange(e.target.value)}
disabled={loadingOptions}
className="w-full pl-4 pr-10 py-4 rounded-xl border-2 border-gray-100 bg-gray-50/50 hover:bg-white focus:bg-white focus:border-orange-500 focus:ring-4 focus:ring-orange-500/10 transition-all appearance-none font-medium text-gray-900"
>
<option value="">Choose an angle...</option>
{Object.entries(groupedAngles).map(([category, angles]) => (
<optgroup key={category} label={category} className="font-bold text-gray-400 uppercase text-[10px]">
{angles.map((a) => (
<option key={a.value} value={a.value} className="text-gray-900 font-medium">
{a.label}
</option>
))}
</optgroup>
))}
</select>
<ChevronDown className="absolute right-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 pointer-events-none group-focus-within:text-orange-500 transition-colors" />
</div>
) : (
<input
type="text"
value={angle}
onChange={(e) => onAngleChange(e.target.value)}
placeholder="e.g., Scarcity, Fear of Missing Out..."
className="w-full px-4 py-4 rounded-xl border-2 border-gray-100 bg-gray-50/50 hover:bg-white focus:bg-white focus:border-orange-500 focus:ring-4 focus:ring-orange-500/10 transition-all font-medium text-gray-900"
/>
)}
</div>
<p className="text-xs text-gray-500 flex items-center gap-1.5 px-1">
<Info className="w-3 h-3" />
The "Why" - triggers that motivate user action.
</p>
</div>
{/* Concept Selection */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Lightbulb className="w-4 h-4 text-green-500" />
<label className="text-sm font-bold text-gray-700 uppercase tracking-wider">
Visual Concept
</label>
</div>
<div className="inline-flex p-1 bg-gray-100 rounded-lg">
<button
type="button"
onClick={() => setConceptInputMode("dropdown")}
className={`px-3 py-1 text-xs font-bold rounded-md transition-all ${conceptInputMode === "dropdown" ? "bg-white text-green-600 shadow-sm" : "text-gray-500 hover:text-gray-700"
}`}
>
Presets
</button>
<button
type="button"
onClick={() => setConceptInputMode("custom")}
className={`px-3 py-1 text-xs font-bold rounded-md transition-all ${conceptInputMode === "custom" ? "bg-white text-green-600 shadow-sm" : "text-gray-500 hover:text-gray-700"
}`}
>
Custom
</button>
</div>
</div>
<div className="relative group">
{conceptInputMode === "dropdown" ? (
<div className="relative">
<select
value={concept}
onChange={(e) => onConceptChange(e.target.value)}
disabled={loadingOptions}
className="w-full pl-4 pr-10 py-4 rounded-xl border-2 border-gray-100 bg-gray-50/50 hover:bg-white focus:bg-white focus:border-green-500 focus:ring-4 focus:ring-green-500/10 transition-all appearance-none font-medium text-gray-900"
>
<option value="">Choose a concept...</option>
{Object.entries(groupedConcepts).map(([category, concepts]) => (
<optgroup key={category} label={category} className="font-bold text-gray-400 uppercase text-[10px]">
{concepts.map((c) => (
<option key={c.value} value={c.value} className="text-gray-900 font-medium">
{c.label}
</option>
))}
</optgroup>
))}
</select>
<ChevronDown className="absolute right-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 pointer-events-none group-focus-within:text-green-500 transition-colors" />
</div>
) : (
<input
type="text"
value={concept}
onChange={(e) => onConceptChange(e.target.value)}
placeholder="e.g., Before/After Comparison, User Testimonial..."
className="w-full px-4 py-4 rounded-xl border-2 border-gray-100 bg-gray-50/50 hover:bg-white focus:bg-white focus:border-green-500 focus:ring-4 focus:ring-green-500/10 transition-all font-medium text-gray-900"
/>
)}
</div>
<p className="text-xs text-gray-500 flex items-center gap-1.5 px-1">
<Info className="w-3 h-3" />
The "How" - visual structure and format.
</p>
</div>
</div>
{/* Mode Selection */}
<div className="space-y-4 pt-4 border-t border-gray-100">
<label className="text-sm font-bold text-gray-700 uppercase tracking-wider block">
Transformation Mode
</label>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<button
type="button"
onClick={() => onModeChange("modify")}
className={`group p-5 rounded-2xl border-2 text-left transition-all ${mode === "modify"
? "border-blue-500 bg-blue-50/50 shadow-md ring-4 ring-blue-500/5"
: "border-gray-100 bg-gray-50/30 hover:border-gray-200 hover:bg-gray-50/60"
}`}
>
<div className="flex items-center justify-between mb-3">
<div className={`p-2 rounded-lg ${mode === "modify" ? "bg-blue-500 text-white" : "bg-gray-200 text-gray-500 group-hover:bg-gray-300 transition-colors"}`}>
<Wand2 className="w-5 h-5" />
</div>
{mode === "modify" && <Check className="w-5 h-5 text-blue-500" />}
</div>
<h4 className={`font-bold text-lg mb-1 ${mode === "modify" ? "text-blue-900" : "text-gray-900"}`}>Modify Image</h4>
<p className="text-sm text-gray-600 leading-relaxed">
Smart edits to existing elements. Preserves ~90% of your original creative while applying the new angle.
</p>
</button>
<button
type="button"
onClick={() => onModeChange("inspired")}
className={`group p-5 rounded-2xl border-2 text-left transition-all ${mode === "inspired"
? "border-purple-500 bg-purple-50/50 shadow-md ring-4 ring-purple-500/5"
: "border-gray-100 bg-gray-50/30 hover:border-gray-200 hover:bg-gray-50/60"
}`}
>
<div className="flex items-center justify-between mb-3">
<div className={`p-2 rounded-lg ${mode === "inspired" ? "bg-purple-500 text-white" : "bg-gray-200 text-gray-500 group-hover:bg-gray-300 transition-colors"}`}>
<Sparkles className="w-5 h-5" />
</div>
{mode === "inspired" && <Check className="w-5 h-5 text-purple-500" />}
</div>
<h4 className={`font-bold text-lg mb-1 ${mode === "inspired" ? "text-purple-900" : "text-gray-900"}`}>Inspired Creation</h4>
<p className="text-sm text-gray-600 leading-relaxed">
Generates a fresh creative from scratch using the original's style and essence as inspiration.
</p>
</button>
</div>
</div>
{/* Variation Count */}
<div className="space-y-4 pt-4 border-t border-gray-100">
<div className="flex items-center gap-2">
<Sparkles className="w-4 h-4 text-blue-500" />
<label className="text-sm font-bold text-gray-700 uppercase tracking-wider">
Variations to Generate
</label>
</div>
<div>
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-semibold text-gray-600">
{variationCount} variation{variationCount > 1 ? "s" : ""}
</span>
<span className="text-xs text-gray-500">Up to 3 total</span>
</div>
<input
type="range"
min={1}
max={3}
step={1}
value={variationCount}
onChange={(e) => onVariationCountChange(Number(e.target.value))}
disabled={isLoading}
className="w-full accent-blue-500"
/>
<div className="flex justify-between text-xs text-gray-400 mt-1 uppercase tracking-wide">
<span>1</span>
<span>2</span>
<span>3</span>
</div>
<p className="text-xs text-gray-500 flex items-center gap-1.5 px-1 mt-2">
<Info className="w-3 h-3" />
Generates that many distinct remixes of your original creative.
</p>
</div>
</div>
{/* User Prompt (Optional) */}
<div className="space-y-4 pt-4 border-t border-gray-100">
<div className="flex items-center gap-2">
<Lightbulb className="w-4 h-4 text-purple-500" />
<label className="text-sm font-bold text-gray-700 uppercase tracking-wider">
Custom Instructions (Optional)
</label>
</div>
<textarea
value={userPrompt}
onChange={(e) => onUserPromptChange(e.target.value)}
placeholder="e.g., Make the image more vibrant, add a sense of urgency, change the background to blue..."
className="w-full px-4 py-3 rounded-xl border-2 border-gray-100 bg-gray-50/50 hover:bg-white focus:bg-white focus:border-purple-500 focus:ring-4 focus:ring-purple-500/10 transition-all font-medium text-gray-900 min-h-[100px] resize-y"
/>
<p className="text-xs text-gray-500 flex items-center gap-1.5 px-1">
<Info className="w-3 h-3" />
Provide specific instructions for how you want the image modified.
</p>
</div>
{/* Image Model Selection */}
<div className="space-y-4 pt-4 border-t border-gray-100">
<label className="text-sm font-bold text-gray-700 uppercase tracking-wider block">
AI Engine
</label>
<div className="relative group max-w-sm">
<select
value={imageModel || ""}
onChange={(e) => onImageModelChange(e.target.value || null)}
className="w-full pl-4 pr-10 py-3 rounded-xl border-2 border-gray-100 bg-gray-50/50 hover:bg-white focus:bg-white focus:border-blue-500 focus:ring-4 focus:ring-blue-500/10 transition-all appearance-none font-medium text-gray-900"
>
{IMAGE_MODELS.map((model) => (
<option key={model.value} value={model.value}>
{model.label}
</option>
))}
</select>
<ChevronDown className="absolute right-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 pointer-events-none group-focus-within:text-blue-500 transition-colors" />
</div>
</div>
{/* Footer / Submit */}
<div className="pt-6">
<button
type="button"
onClick={onSubmit}
disabled={!isValid || isLoading}
className={`w-full relative group overflow-hidden bg-gradient-to-r ${mode === "modify" ? "from-blue-600 to-cyan-500" : "from-purple-600 to-pink-500"
} text-white font-black text-lg py-5 px-8 rounded-2xl transition-all duration-300 shadow-xl hover:shadow-2xl hover:-translate-y-1 disabled:opacity-50 disabled:translate-y-0 disabled:shadow-none`}
>
<div className="absolute inset-0 bg-white/20 translate-y-full group-hover:translate-y-0 transition-transform duration-300" />
<span className="flex items-center justify-center gap-3 relative z-10">
{isLoading ? (
<>
<Loader2 className="animate-spin h-6 w-6" />
Bringing your vision to life...
</>
) : (
<>
{mode === "modify" ? <Wand2 className="w-6 h-6" /> : <Sparkles className="w-6 h-6" />}
Generate {mode === "modify" ? "Modified" : "Inspired"} Creative
</>
)}
</span>
</button>
{!isValid && (
<div className="mt-4 flex items-center justify-center gap-2 text-amber-600 animate-pulse">
<Info className="w-4 h-4" />
<p className="text-sm font-bold">Please provide at least one: angle, concept, or custom instructions</p>
</div>
)}
</div>
</CardContent>
</Card>
);
};
const Loader2 = ({ className }: { className?: string }) => (
<svg className={className} viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
);