sushilideaclan01's picture
Update ad generation features and enhance user input options
addcf34
"use client";
import React, { useState, useEffect } from "react";
import { X, Wand2, Edit3, Sparkles, Loader2, CheckCircle2, AlertCircle } from "lucide-react";
import { editAdCopy } from "@/lib/api/endpoints";
import type { AdCreativeDB } from "@/types/api";
import { Button } from "@/components/ui/Button";
import { Input } from "@/components/ui/Input";
import { Card, CardContent } from "@/components/ui/Card";
interface EditCopyModalProps {
isOpen: boolean;
onClose: () => void;
adId: string;
ad?: AdCreativeDB | null;
field: "title" | "headline" | "primary_text" | "description" | "body_story" | "cta";
fieldLabel: string;
currentValue: string;
onSuccess?: () => void;
}
type EditMode = "manual" | "ai";
export const EditCopyModal: React.FC<EditCopyModalProps> = ({
isOpen,
onClose,
adId,
ad,
field,
fieldLabel,
currentValue,
onSuccess,
}) => {
const [mode, setMode] = useState<EditMode>("manual");
const [editedValue, setEditedValue] = useState(currentValue);
const [aiSuggestion, setAiSuggestion] = useState("");
const [userSuggestion, setUserSuggestion] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
useEffect(() => {
if (isOpen) {
setEditedValue(currentValue);
setAiSuggestion("");
setUserSuggestion("");
setError(null);
setSuccess(false);
setMode("manual");
}
}, [isOpen, currentValue]);
const handleManualSave = async () => {
if (!editedValue.trim()) {
setError("This field cannot be empty");
return;
}
setIsLoading(true);
setError(null);
try {
await editAdCopy({
ad_id: adId,
field,
value: editedValue,
mode: "manual",
});
setSuccess(true);
setTimeout(() => {
onSuccess?.();
onClose();
}, 1000);
} catch (err: any) {
setError(err.message || "Failed to update. Please try again.");
} finally {
setIsLoading(false);
}
};
const handleAIGenerate = async () => {
setIsLoading(true);
setError(null);
try {
const result = await editAdCopy({
ad_id: adId,
field,
value: currentValue,
mode: "ai",
user_suggestion: userSuggestion || undefined,
});
setAiSuggestion(result.edited_value || "");
} catch (err: any) {
setError(err.message || "Failed to generate AI suggestion. Please try again.");
} finally {
setIsLoading(false);
}
};
const handleAIApply = async () => {
if (!aiSuggestion.trim()) {
setError("No AI suggestion available");
return;
}
setIsLoading(true);
setError(null);
try {
await editAdCopy({
ad_id: adId,
field,
value: aiSuggestion,
mode: "manual",
});
setSuccess(true);
setTimeout(() => {
onSuccess?.();
onClose();
}, 1000);
} catch (err: any) {
setError(err.message || "Failed to apply AI suggestion. Please try again.");
} finally {
setIsLoading(false);
}
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm animate-fade-in">
<Card className="w-full max-w-2xl max-h-[90vh] overflow-y-auto shadow-2xl border-2 border-blue-200 animate-scale-in">
<CardContent className="p-6">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<div className="p-2 bg-gradient-to-r from-blue-500 to-cyan-500 rounded-lg">
<Edit3 className="h-5 w-5 text-white" />
</div>
<div>
<h2 className="text-xl font-bold bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">
Edit {fieldLabel}
</h2>
<p className="text-sm text-gray-500">Update your ad copy</p>
</div>
</div>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
>
<X className="h-5 w-5 text-gray-500" />
</button>
</div>
{/* Mode Toggle */}
<div className="flex gap-2 mb-6 p-1 bg-gray-100 rounded-lg">
<button
onClick={() => setMode("manual")}
className={`flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-md font-medium transition-all ${
mode === "manual"
? "bg-white text-blue-600 shadow-sm"
: "text-gray-600 hover:text-gray-900"
}`}
>
<Edit3 className="h-4 w-4" />
Manual Edit
</button>
<button
onClick={() => setMode("ai")}
className={`flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-md font-medium transition-all ${
mode === "ai"
? "bg-white text-blue-600 shadow-sm"
: "text-gray-600 hover:text-gray-900"
}`}
>
<Wand2 className="h-4 w-4" />
AI Edit
</button>
</div>
{/* Manual Edit Mode */}
{mode === "manual" && (
<div className="space-y-4">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
{fieldLabel}
</label>
<textarea
value={editedValue}
onChange={(e) => setEditedValue(e.target.value)}
className="w-full px-4 py-3 rounded-xl border-2 border-gray-300 bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all resize-none"
rows={field === "body_story" ? 8 : field === "description" ? 4 : 3}
placeholder={`Enter your ${fieldLabel.toLowerCase()}...`}
/>
</div>
{error && (
<div className="flex items-center gap-2 p-3 bg-red-50 border border-red-200 rounded-lg text-red-600">
<AlertCircle className="h-4 w-4" />
<span className="text-sm">{error}</span>
</div>
)}
{success && (
<div className="flex items-center gap-2 p-3 bg-green-50 border border-green-200 rounded-lg text-green-600">
<CheckCircle2 className="h-4 w-4" />
<span className="text-sm">Successfully updated!</span>
</div>
)}
<div className="flex gap-3">
<Button
onClick={handleManualSave}
isLoading={isLoading}
variant="primary"
className="flex-1"
>
Save Changes
</Button>
<Button
onClick={onClose}
variant="ghost"
disabled={isLoading}
>
Cancel
</Button>
</div>
</div>
)}
{/* AI Edit Mode */}
{mode === "ai" && (
<div className="space-y-4">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Your Suggestion (Optional)
</label>
<textarea
value={userSuggestion}
onChange={(e) => setUserSuggestion(e.target.value)}
className="w-full px-4 py-3 rounded-xl border-2 border-gray-300 bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all resize-none"
rows={3}
placeholder="e.g., Make it more emotional, Add urgency, Make it shorter, Focus on benefits..."
/>
<p className="text-xs text-gray-500 mt-1">
Tell AI how you'd like the {fieldLabel.toLowerCase()} improved
</p>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Current {fieldLabel}
</label>
<div className="px-4 py-3 rounded-xl border-2 border-gray-200 bg-gray-50 text-gray-700">
{currentValue || <span className="text-gray-400">No content</span>}
</div>
</div>
<Button
onClick={handleAIGenerate}
isLoading={isLoading}
variant="primary"
className="w-full"
disabled={isLoading}
>
<Wand2 className="h-4 w-4 mr-2" />
{isLoading ? "Generating..." : "Generate AI Suggestion"}
</Button>
{aiSuggestion && (
<div className="space-y-3">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
AI Suggestion
</label>
<div className="px-4 py-3 rounded-xl border-2 border-blue-200 bg-blue-50 text-gray-700 whitespace-pre-line">
{aiSuggestion}
</div>
</div>
{error && (
<div className="flex items-center gap-2 p-3 bg-red-50 border border-red-200 rounded-lg text-red-600">
<AlertCircle className="h-4 w-4" />
<span className="text-sm">{error}</span>
</div>
)}
{success && (
<div className="flex items-center gap-2 p-3 bg-green-50 border border-green-200 rounded-lg text-green-600">
<CheckCircle2 className="h-4 w-4" />
<span className="text-sm">Successfully updated!</span>
</div>
)}
<div className="flex gap-3">
<Button
onClick={handleAIApply}
isLoading={isLoading}
variant="primary"
className="flex-1"
>
Apply AI Suggestion
</Button>
<Button
onClick={() => {
setAiSuggestion("");
setUserSuggestion("");
}}
variant="ghost"
disabled={isLoading}
>
Regenerate
</Button>
<Button
onClick={onClose}
variant="ghost"
disabled={isLoading}
>
Cancel
</Button>
</div>
</div>
)}
</div>
)}
</CardContent>
</Card>
</div>
);
};