fix(i18n): Fix syntax errors and add missing translation keys - Restored broken useStore destructuring in PromptEnhancer, UXDesignerPrompt, ActionPlanGenerator, SlidesGenerator - Fixed GoogleAdsGenerator scope issue with renderMagicWandSectionContent - Added missing inputLabel keys to promptEnhancer for all languages - Fixed t.resultTitle to t.enhancedTitle in PromptEnhancer
This commit is contained in:
@@ -394,408 +394,407 @@ export default function GoogleAdsGenerator() {
|
||||
default:
|
||||
return <pre className="whitespace-pre-wrap text-xs">{googleAdsResult.rawContent}</pre>;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const renderMagicWandSectionContent = (sectionId: string) => {
|
||||
if (!magicWandResult) return null;
|
||||
const renderMagicWandSectionContent = (sectionId: string) => {
|
||||
if (!magicWandResult) return null;
|
||||
|
||||
switch (sectionId) {
|
||||
case "market":
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="p-3 rounded-md bg-indigo-50/50 border border-indigo-100">
|
||||
<div className="text-[10px] uppercase font-bold text-indigo-600 mb-1 flex items-center gap-1">
|
||||
<BarChart3 className="h-3 w-3" /> Industry Size
|
||||
switch (sectionId) {
|
||||
case "market":
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="p-3 rounded-md bg-indigo-50/50 border border-indigo-100">
|
||||
<div className="text-[10px] uppercase font-bold text-indigo-600 mb-1 flex items-center gap-1">
|
||||
<BarChart3 className="h-3 w-3" /> Industry Size
|
||||
</div>
|
||||
<div className="text-sm font-semibold">{magicWandResult.marketAnalysis.industrySize}</div>
|
||||
</div>
|
||||
<div className="p-3 rounded-md bg-emerald-50/50 border border-emerald-100">
|
||||
<div className="text-[10px] uppercase font-bold text-emerald-600 mb-1 flex items-center gap-1">
|
||||
<TrendingUp className="h-3 w-3" /> Growth Rate
|
||||
</div>
|
||||
<div className="text-sm font-semibold">{magicWandResult.marketAnalysis.growthRate}</div>
|
||||
</div>
|
||||
<div className="text-sm font-semibold">{magicWandResult.marketAnalysis.industrySize}</div>
|
||||
</div>
|
||||
<div className="p-3 rounded-md bg-emerald-50/50 border border-emerald-100">
|
||||
<div className="text-[10px] uppercase font-bold text-emerald-600 mb-1 flex items-center gap-1">
|
||||
<TrendingUp className="h-3 w-3" /> Growth Rate
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h4 className="text-xs font-bold text-slate-700 mb-2 flex items-center gap-1.5">
|
||||
<Users className="h-3.5 w-3.5" /> Market Leaders
|
||||
</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{magicWandResult.marketAnalysis.topCompetitors.map((c, i) => (
|
||||
<span key={i} className="text-xs px-2 py-1 bg-slate-100 text-slate-700 rounded-md border border-slate-200">{c}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-xs font-bold text-slate-700 mb-2 flex items-center gap-1.5">
|
||||
<Rocket className="h-3.5 w-3.5" /> Emerging Trends
|
||||
</h4>
|
||||
<ul className="space-y-1">
|
||||
{magicWandResult.marketAnalysis.marketTrends.map((t, i) => (
|
||||
<li key={i} className="text-xs text-slate-600 flex items-start gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-indigo-400 mt-1 shrink-0" />
|
||||
{t}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="text-sm font-semibold">{magicWandResult.marketAnalysis.growthRate}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h4 className="text-xs font-bold text-slate-700 mb-2 flex items-center gap-1.5">
|
||||
<Users className="h-3.5 w-3.5" /> Market Leaders
|
||||
</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{magicWandResult.marketAnalysis.topCompetitors.map((c, i) => (
|
||||
<span key={i} className="text-xs px-2 py-1 bg-slate-100 text-slate-700 rounded-md border border-slate-200">{c}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-xs font-bold text-slate-700 mb-2 flex items-center gap-1.5">
|
||||
<Rocket className="h-3.5 w-3.5" /> Emerging Trends
|
||||
</h4>
|
||||
<ul className="space-y-1">
|
||||
{magicWandResult.marketAnalysis.marketTrends.map((t, i) => (
|
||||
<li key={i} className="text-xs text-slate-600 flex items-start gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-indigo-400 mt-1 shrink-0" />
|
||||
{t}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
case "competitors":
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{magicWandResult.competitorInsights.map((comp, i) => (
|
||||
<div key={i} className="p-4 rounded-xl border bg-white/50 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="font-bold text-slate-900">{comp.competitor}</h4>
|
||||
<ShieldAlert className="h-4 w-4 text-amber-500" />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<div className="text-[10px] font-black uppercase text-emerald-600">Strengths</div>
|
||||
<ul className="space-y-1">
|
||||
{comp.strengths.map((s, j) => (
|
||||
<li key={j} className="text-xs text-slate-600 flex gap-1.5">
|
||||
<span className="text-emerald-500">✓</span> {s}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
case "competitors":
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{magicWandResult.competitorInsights.map((comp, i) => (
|
||||
<div key={i} className="p-4 rounded-xl border bg-white/50 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="font-bold text-slate-900">{comp.competitor}</h4>
|
||||
<ShieldAlert className="h-4 w-4 text-amber-500" />
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<div className="text-[10px] font-black uppercase text-rose-600">Weaknesses</div>
|
||||
<ul className="space-y-1">
|
||||
{comp.weaknesses.map((w, j) => (
|
||||
<li key={j} className="text-xs text-slate-600 flex gap-1.5">
|
||||
<span className="text-rose-500">✗</span> {w}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-2 border-t border-slate-100 italic text-xs text-slate-500">
|
||||
<span className="font-bold text-slate-700 not-italic uppercase text-[9px]">Spy Report:</span> {comp.adStrategy}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
case "strategies":
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{magicWandResult.strategies.map((strat, i) => (
|
||||
<div key={i} className="relative p-5 rounded-2xl border bg-white shadow-sm hover:shadow-md transition-all group overflow-hidden">
|
||||
<div className="absolute top-0 right-0 h-1 w-full bg-gradient-to-r from-indigo-500 to-violet-500" />
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<h4 className="text-lg font-black text-slate-900 tracking-tight">{strat.direction}</h4>
|
||||
<p className="text-sm text-indigo-600 font-bold">{strat.targetAudience}</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-end">
|
||||
<span className={cn(
|
||||
"text-[10px] font-black uppercase px-2 py-0.5 rounded-full",
|
||||
strat.riskLevel === 'low' ? "bg-emerald-100 text-emerald-700" :
|
||||
strat.riskLevel === 'medium' ? "bg-amber-100 text-amber-700" : "bg-rose-100 text-rose-700"
|
||||
)}>
|
||||
{strat.riskLevel} risk
|
||||
</span>
|
||||
<span className="text-[10px] text-slate-400 mt-1 font-bold italic">{strat.timeToResults} to results</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<p className="text-xs text-slate-600 leading-relaxed font-medium">
|
||||
<span className="text-indigo-500 font-black uppercase text-[9px] block mb-0.5">The "Why":</span>
|
||||
{strat.rationale}
|
||||
</p>
|
||||
|
||||
<div className="p-3 bg-slate-50 rounded-xl border border-dashed text-xs space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Target className="h-3.5 w-3.5 text-indigo-500" />
|
||||
<span className="font-bold text-slate-700">Edge: {strat.competitiveAdvantage}</span>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<div className="text-[10px] font-black uppercase text-emerald-600">Strengths</div>
|
||||
<ul className="space-y-1">
|
||||
{comp.strengths.map((s, j) => (
|
||||
<li key={j} className="text-xs text-slate-600 flex gap-1.5">
|
||||
<span className="text-emerald-500">✓</span> {s}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{strat.keyMessages.map((msg, j) => (
|
||||
<span key={j} className="text-[10px] bg-white border px-1.5 py-0.5 rounded-md text-slate-500 shadow-sm">{msg}</span>
|
||||
))}
|
||||
<div className="space-y-1.5">
|
||||
<div className="text-[10px] font-black uppercase text-rose-600">Weaknesses</div>
|
||||
<ul className="space-y-1">
|
||||
{comp.weaknesses.map((w, j) => (
|
||||
<li key={j} className="text-xs text-slate-600 flex gap-1.5">
|
||||
<span className="text-rose-500">✗</span> {w}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-2 border-t border-slate-100 italic text-xs text-slate-500">
|
||||
<span className="font-bold text-slate-700 not-italic uppercase text-[9px]">Spy Report:</span> {comp.adStrategy}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
case "strategies":
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{magicWandResult.strategies.map((strat, i) => (
|
||||
<div key={i} className="relative p-5 rounded-2xl border bg-white shadow-sm hover:shadow-md transition-all group overflow-hidden">
|
||||
<div className="absolute top-0 right-0 h-1 w-full bg-gradient-to-r from-indigo-500 to-violet-500" />
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<h4 className="text-lg font-black text-slate-900 tracking-tight">{strat.direction}</h4>
|
||||
<p className="text-sm text-indigo-600 font-bold">{strat.targetAudience}</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-end">
|
||||
<span className={cn(
|
||||
"text-[10px] font-black uppercase px-2 py-0.5 rounded-full",
|
||||
strat.riskLevel === 'low' ? "bg-emerald-100 text-emerald-700" :
|
||||
strat.riskLevel === 'medium' ? "bg-amber-100 text-amber-700" : "bg-rose-100 text-rose-700"
|
||||
)}>
|
||||
{strat.riskLevel} risk
|
||||
</span>
|
||||
<span className="text-[10px] text-slate-400 mt-1 font-bold italic">{strat.timeToResults} to results</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 items-center">
|
||||
<div className="space-y-1">
|
||||
<div className="text-[9px] font-black text-slate-400 uppercase">Channel Mix</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{strat.recommendedChannels.map((c, j) => (
|
||||
<span key={j} className="text-[9px] font-bold text-slate-600">{c}</span>
|
||||
<div className="space-y-4">
|
||||
<p className="text-xs text-slate-600 leading-relaxed font-medium">
|
||||
<span className="text-indigo-500 font-black uppercase text-[9px] block mb-0.5">The "Why":</span>
|
||||
{strat.rationale}
|
||||
</p>
|
||||
|
||||
<div className="p-3 bg-slate-50 rounded-xl border border-dashed text-xs space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Target className="h-3.5 w-3.5 text-indigo-500" />
|
||||
<span className="font-bold text-slate-700">Edge: {strat.competitiveAdvantage}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{strat.keyMessages.map((msg, j) => (
|
||||
<span key={j} className="text-[10px] bg-white border px-1.5 py-0.5 rounded-md text-slate-500 shadow-sm">{msg}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-[9px] font-black text-slate-400 uppercase text-right">Expected ROI</div>
|
||||
<div className="text-lg font-black text-emerald-600 tracking-tighter">{strat.expectedROI}</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 items-center">
|
||||
<div className="space-y-1">
|
||||
<div className="text-[9px] font-black text-slate-400 uppercase">Channel Mix</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{strat.recommendedChannels.map((c, j) => (
|
||||
<span key={j} className="text-[9px] font-bold text-slate-600">{c}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-[9px] font-black text-slate-400 uppercase text-right">Expected ROI</div>
|
||||
<div className="text-lg font-black text-emerald-600 tracking-tighter">{strat.expectedROI}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return <pre className="whitespace-pre-wrap text-xs">{magicWandResult.rawContent}</pre>;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<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 className="p-4 lg:p-6 text-start">
|
||||
<CardTitle className="flex items-center gap-2 text-base lg:text-lg">
|
||||
<Megaphone className="h-4 w-4 lg:h-5 lg:w-5" />
|
||||
{t.title}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs lg:text-sm">
|
||||
{t.description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<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-xs lg:text-sm font-medium">{common.aiProvider}</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 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>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return <pre className="whitespace-pre-wrap text-xs">{magicWandResult.rawContent}</pre>;
|
||||
}
|
||||
};
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs lg:text-sm font-medium">{common.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-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}>
|
||||
{model}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs lg:text-sm font-medium">{t.websiteUrl}</label>
|
||||
<Input
|
||||
placeholder="e.g., www.your-business.com"
|
||||
value={websiteUrl}
|
||||
onChange={(e) => setWebsiteUrl(e.target.value)}
|
||||
className="text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs lg:text-sm font-medium">{t.products}</label>
|
||||
return (
|
||||
<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 className="p-4 lg:p-6 text-start">
|
||||
<CardTitle className="flex items-center gap-2 text-base lg:text-lg">
|
||||
<Megaphone className="h-4 w-4 lg:h-5 lg:w-5" />
|
||||
{t.title}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs lg:text-sm">
|
||||
{t.description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 lg:space-y-4 p-4 lg:p-6 pt-0 lg:pt-0">
|
||||
<div className="space-y-2">
|
||||
{products.map((product, index) => (
|
||||
<div key={index} className="flex gap-2">
|
||||
<Input
|
||||
placeholder={`${language === "ru" ? "Продукт" : language === "he" ? "מוצר" : "Product"} ${index + 1}`}
|
||||
value={product}
|
||||
onChange={(e) => updateProduct(index, e.target.value)}
|
||||
className="text-sm"
|
||||
/>
|
||||
{products.length > 1 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => removeProduct(index)}
|
||||
className="h-10 w-10 shrink-0"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<Button variant="outline" size="sm" onClick={addProduct} className="w-full text-xs">
|
||||
<Plus className="mr-1.5 h-3.5 w-3.5" />
|
||||
{language === "ru" ? "Добавить продукт" : language === "he" ? "הוסף מוצר" : "Add Product"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs lg:text-sm font-medium">{t.budget}</label>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="Min"
|
||||
value={budgetMin}
|
||||
onChange={(e) => setBudgetMin(e.target.value)}
|
||||
className="text-sm"
|
||||
/>
|
||||
<span className="text-muted-foreground text-xs font-bold text-center">-</span>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="Max"
|
||||
value={budgetMax}
|
||||
onChange={(e) => setBudgetMax(e.target.value)}
|
||||
className="text-sm"
|
||||
/>
|
||||
<label className="text-xs lg:text-sm font-medium">{common.aiProvider}</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 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>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs lg:text-sm font-medium">{t.industry}</label>
|
||||
<label className="text-xs lg:text-sm font-medium">{common.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-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}>
|
||||
{model}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs lg:text-sm font-medium">{t.websiteUrl}</label>
|
||||
<Input
|
||||
placeholder="e.g., SaaS"
|
||||
value={industry}
|
||||
onChange={(e) => setIndustry(e.target.value)}
|
||||
placeholder="e.g., www.your-business.com"
|
||||
value={websiteUrl}
|
||||
onChange={(e) => setWebsiteUrl(e.target.value)}
|
||||
className="text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs lg:text-sm font-medium">{t.targetAudience}</label>
|
||||
<Textarea
|
||||
placeholder="e.g., Small business owners in USA looking for productivity tools"
|
||||
value={targetAudience}
|
||||
onChange={(e) => setTargetAudience(e.target.value)}
|
||||
className="min-h-[80px] lg:min-h-[100px] resize-y text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<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-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">{common.configApiKey}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs lg:text-sm font-medium">{t.products}</label>
|
||||
<div className="space-y-2">
|
||||
{products.map((product, index) => (
|
||||
<div key={index} className="flex gap-2">
|
||||
<Input
|
||||
placeholder={`${language === "ru" ? "Продукт" : language === "he" ? "מוצר" : "Product"} ${index + 1}`}
|
||||
value={product}
|
||||
onChange={(e) => updateProduct(index, e.target.value)}
|
||||
className="text-sm"
|
||||
/>
|
||||
{products.length > 1 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => removeProduct(index)}
|
||||
className="h-10 w-10 shrink-0"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<Button variant="outline" size="sm" onClick={addProduct} className="w-full text-xs">
|
||||
<Plus className="mr-1.5 h-3.5 w-3.5" />
|
||||
{language === "ru" ? "Добавить продукт" : language === "he" ? "הוסף מוצר" : "Add Product"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Button
|
||||
onClick={handleGenerate}
|
||||
disabled={isProcessing || isMagicThinking || !websiteUrl.trim()}
|
||||
className="h-9 lg:h-10 text-xs lg:text-sm bg-primary/90 hover:bg-primary shadow-sm"
|
||||
>
|
||||
{isProcessing ? (
|
||||
<>
|
||||
<Loader2 className="mr-1.5 lg:mr-2 h-3.5 w-3.5 animate-spin" />
|
||||
{common.generating}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Megaphone className="mr-1.5 lg:mr-2 h-3.5 w-3.5" />
|
||||
{t.generateAds}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleMagicWand}
|
||||
disabled={isProcessing || isMagicThinking || !websiteUrl.trim()}
|
||||
className="h-9 lg:h-10 text-xs lg:text-sm bg-gradient-to-r from-indigo-600 to-violet-600 hover:from-indigo-700 hover:to-violet-700 text-white shadow-md transition-all active:scale-[0.98]"
|
||||
>
|
||||
{isMagicThinking ? (
|
||||
<>
|
||||
<Loader2 className="mr-1.5 lg:mr-2 h-3.5 w-3.5 animate-spin" />
|
||||
{t.researching}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Wand2 className="mr-1.5 h-3.5 w-3.5" />
|
||||
{t.magicWand}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs lg:text-sm font-medium">{t.budget}</label>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="Min"
|
||||
value={budgetMin}
|
||||
onChange={(e) => setBudgetMin(e.target.value)}
|
||||
className="text-sm"
|
||||
/>
|
||||
<span className="text-muted-foreground text-xs font-bold text-center">-</span>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="Max"
|
||||
value={budgetMax}
|
||||
onChange={(e) => setBudgetMax(e.target.value)}
|
||||
className="text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs lg:text-sm font-medium">{t.industry}</label>
|
||||
<Input
|
||||
placeholder="e.g., SaaS"
|
||||
value={industry}
|
||||
onChange={(e) => setIndustry(e.target.value)}
|
||||
className="text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className={cn(!googleAdsResult && "opacity-50")}>
|
||||
<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">
|
||||
{magicWandResult ? (
|
||||
<Wand2 className="h-4 w-4 lg:h-5 lg:w-5 text-indigo-500" />
|
||||
) : (
|
||||
<CheckCircle2 className="h-4 w-4 lg:h-5 lg:w-5 text-green-500" />
|
||||
)}
|
||||
{magicWandResult ? t.strategicDirections : t.generatedCampaign}
|
||||
</span>
|
||||
{(googleAdsResult || magicWandResult) && (
|
||||
<Button variant="ghost" size="icon" onClick={handleCopy} className="h-8 w-8 lg:h-9 lg:w-9">
|
||||
{copied ? (
|
||||
<CheckCircle2 className="h-3.5 w-3.5 lg:h-4 lg:w-4 text-green-500" />
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs lg:text-sm font-medium">{t.targetAudience}</label>
|
||||
<Textarea
|
||||
placeholder="e.g., Small business owners in USA looking for productivity tools"
|
||||
value={targetAudience}
|
||||
onChange={(e) => setTargetAudience(e.target.value)}
|
||||
className="min-h-[80px] lg:min-h-[100px] resize-y text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<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-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">{common.configApiKey}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Button
|
||||
onClick={handleGenerate}
|
||||
disabled={isProcessing || isMagicThinking || !websiteUrl.trim()}
|
||||
className="h-9 lg:h-10 text-xs lg:text-sm bg-primary/90 hover:bg-primary shadow-sm"
|
||||
>
|
||||
{isProcessing ? (
|
||||
<>
|
||||
<Loader2 className="mr-1.5 lg:mr-2 h-3.5 w-3.5 animate-spin" />
|
||||
{common.generating}
|
||||
</>
|
||||
) : (
|
||||
<Copy className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
|
||||
<>
|
||||
<Megaphone className="mr-1.5 lg:mr-2 h-3.5 w-3.5" />
|
||||
{t.generateAds}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs lg:text-sm">
|
||||
{magicWandResult
|
||||
? (language === "ru" ? "Глубокое исследование конкурентов и темы кампаний" : language === "he" ? "מחקר תחרותי מעמיק ונושאי קמפיין" : "Deep competitive research and campaign themes")
|
||||
: (language === "ru" ? "Ключевые слова, объявления и структура кампании" : language === "he" ? "מילות מפתח, עותקי מודעות ומבנה קמפיין מוכנים" : "Keywords, ad copy, and campaign structure ready")
|
||||
}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="p-4 lg:p-6 pt-0 lg:pt-0">
|
||||
{googleAdsResult || magicWandResult ? (
|
||||
<div className="space-y-2 lg:space-y-3">
|
||||
{(magicWandResult
|
||||
? [
|
||||
{ id: "market", title: t.marketIntelligence },
|
||||
{ id: "competitors", title: t.competitiveInsights },
|
||||
{ id: "strategies", title: t.campaignDirections }
|
||||
]
|
||||
: 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-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-3.5 w-3.5 lg:h-4 lg: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-3 lg:px-4 py-2.5 lg:py-3 animate-in fade-in slide-in-from-top-1 duration-200">
|
||||
{magicWandResult
|
||||
? renderMagicWandSectionContent(section.id)
|
||||
: renderSectionContent(section.id)
|
||||
}
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleMagicWand}
|
||||
disabled={isProcessing || isMagicThinking || !websiteUrl.trim()}
|
||||
className="h-9 lg:h-10 text-xs lg:text-sm bg-gradient-to-r from-indigo-600 to-violet-600 hover:from-indigo-700 hover:to-violet-700 text-white shadow-md transition-all active:scale-[0.98]"
|
||||
>
|
||||
{isMagicThinking ? (
|
||||
<>
|
||||
<Loader2 className="mr-1.5 lg:mr-2 h-3.5 w-3.5 animate-spin" />
|
||||
{t.researching}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Wand2 className="mr-1.5 h-3.5 w-3.5" />
|
||||
{t.magicWand}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className={cn(!googleAdsResult && "opacity-50")}>
|
||||
<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">
|
||||
{magicWandResult ? (
|
||||
<Wand2 className="h-4 w-4 lg:h-5 lg:w-5 text-indigo-500" />
|
||||
) : (
|
||||
<CheckCircle2 className="h-4 w-4 lg:h-5 lg:w-5 text-green-500" />
|
||||
)}
|
||||
{magicWandResult ? t.strategicDirections : t.generatedCampaign}
|
||||
</span>
|
||||
{(googleAdsResult || magicWandResult) && (
|
||||
<Button variant="ghost" size="icon" onClick={handleCopy} className="h-8 w-8 lg:h-9 lg:w-9">
|
||||
{copied ? (
|
||||
<CheckCircle2 className="h-3.5 w-3.5 lg:h-4 lg:w-4 text-green-500" />
|
||||
) : (
|
||||
<Copy className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-[200px] lg:h-[300px] items-center justify-center text-center text-xs lg:text-sm text-muted-foreground">
|
||||
{language === "ru" ? "Здесь появится созданная кампания" : language === "he" ? "קמפיין שחולל יופיע כאן" : "Generated campaign will appear here"}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
</Button>
|
||||
)}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs lg:text-sm">
|
||||
{magicWandResult
|
||||
? (language === "ru" ? "Глубокое исследование конкурентов и темы кампаний" : language === "he" ? "מחקר תחרותי מעמיק ונושאי קמפיין" : "Deep competitive research and campaign themes")
|
||||
: (language === "ru" ? "Ключевые слова, объявления и структура кампании" : language === "he" ? "מילות מפתח, עותקי מודעות ומבנה קמפיין מוכנים" : "Keywords, ad copy, and campaign structure ready")
|
||||
}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="p-4 lg:p-6 pt-0 lg:pt-0">
|
||||
{googleAdsResult || magicWandResult ? (
|
||||
<div className="space-y-2 lg:space-y-3">
|
||||
{(magicWandResult
|
||||
? [
|
||||
{ id: "market", title: t.marketIntelligence },
|
||||
{ id: "competitors", title: t.competitiveInsights },
|
||||
{ id: "strategies", title: t.campaignDirections }
|
||||
]
|
||||
: 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-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-3.5 w-3.5 lg:h-4 lg: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-3 lg:px-4 py-2.5 lg:py-3 animate-in fade-in slide-in-from-top-1 duration-200">
|
||||
{magicWandResult
|
||||
? renderMagicWandSectionContent(section.id)
|
||||
: renderSectionContent(section.id)
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-[200px] lg:h-[300px] items-center justify-center text-center text-xs lg:text-sm text-muted-foreground">
|
||||
{language === "ru" ? "Здесь появится созданная кампания" : language === "he" ? "קמפיין שחולל יופיע כאן" : "Generated campaign will appear here"}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user