fix: Google Ads parsing and UI/UX refinement
- Added robust JSON extraction logic to handle AI responses with markdown formatting. - Overhauled Google Ads Gen UI with glassmorphism and premium aesthetics. - Harmonized main content styling with the sidebar design ecosystem.
This commit is contained in:
@@ -147,20 +147,54 @@ export default function GoogleAdsGenerator() {
|
|||||||
|
|
||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
try {
|
try {
|
||||||
const parsedData = typeof result.data === 'string' ? JSON.parse(result.data) : result.data;
|
// Optimized parsing helper
|
||||||
|
const extractJson = (text: string) => {
|
||||||
|
try {
|
||||||
|
// 1. Direct parse
|
||||||
|
return JSON.parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
// 2. Extract from markdown code blocks
|
||||||
|
const jsonMatch = text.match(/```json\s*([\s\S]*?)\s*```/i) ||
|
||||||
|
text.match(/```\s*([\s\S]*?)\s*```/i);
|
||||||
|
|
||||||
|
if (jsonMatch && jsonMatch[1]) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(jsonMatch[1].trim());
|
||||||
|
} catch (e2) {
|
||||||
|
console.error("Failed to parse extracted JSON block", e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Last resort: extract anything between the first { and last }
|
||||||
|
const braceMatch = text.match(/(\{[\s\S]*\})/);
|
||||||
|
if (braceMatch) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(braceMatch[0].trim());
|
||||||
|
} catch (e3) {
|
||||||
|
console.error("Failed to parse content between braces", e3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Invalid format: AI response did not contain valid JSON");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const rawData = typeof result.data === 'string' ? result.data : JSON.stringify(result.data);
|
||||||
|
const parsedData = extractJson(rawData);
|
||||||
|
|
||||||
const adsResult: GoogleAdsResult = {
|
const adsResult: GoogleAdsResult = {
|
||||||
...parsedData,
|
...parsedData,
|
||||||
id: Math.random().toString(36).substr(2, 9),
|
id: Math.random().toString(36).substr(2, 9),
|
||||||
websiteUrl,
|
websiteUrl,
|
||||||
productsServices: filteredProducts,
|
productsServices: filteredProducts,
|
||||||
generatedAt: new Date(),
|
generatedAt: new Date(),
|
||||||
rawContent: typeof result.data === 'string' ? result.data : JSON.stringify(result.data, null, 2)
|
rawContent: rawData
|
||||||
};
|
};
|
||||||
setGoogleAdsResult(adsResult);
|
setGoogleAdsResult(adsResult);
|
||||||
setActiveTab("keywords");
|
setActiveTab("keywords");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to parse ads data:", e);
|
console.error("Failed to parse ads data:", e);
|
||||||
setError("Failed to parse the generated ads content. Please try again.");
|
setError("Failed to parse the generated ads content. Please try again or switch AI providers.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setError(result.error || "Failed to generate Google Ads campaign");
|
setError(result.error || "Failed to generate Google Ads campaign");
|
||||||
@@ -216,16 +250,16 @@ export default function GoogleAdsGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto max-w-7xl animate-in fade-in duration-500">
|
<div className="mx-auto max-w-7xl animate-in fade-in duration-700">
|
||||||
<div className="mb-6 flex flex-col md:flex-row md:items-center justify-between gap-4">
|
<div className="mb-8 flex flex-col md:flex-row md:items-center justify-between gap-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold tracking-tight bg-gradient-to-r from-blue-600 to-indigo-600 bg-clip-text text-transparent flex items-center gap-3">
|
<h1 className="text-4xl font-extrabold tracking-tight bg-gradient-to-br from-blue-700 via-blue-600 to-indigo-700 bg-clip-text text-transparent flex items-center gap-4">
|
||||||
<Megaphone className="h-8 w-8 text-blue-600" />
|
<Megaphone className="h-10 w-10 text-blue-600" />
|
||||||
Google Ads Gen
|
Google Ads Strategist
|
||||||
<Badge variant="secondary" className="bg-blue-100 text-blue-700 border-blue-200">PRO</Badge>
|
<Badge variant="secondary" className="bg-blue-50 text-blue-700 border-blue-200 px-3 py-1 font-bold text-xs uppercase tracking-widest">Premium AI</Badge>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-muted-foreground mt-1">
|
<p className="text-muted-foreground mt-2 text-lg">
|
||||||
Generate high-converting keywords, ad copy, and campaign structures with AI.
|
Convert concepts into high-ROI Google Ads campaigns with precision-engineered keywords and copy.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -246,15 +280,15 @@ export default function GoogleAdsGenerator() {
|
|||||||
<div className="grid gap-6 grid-cols-1 lg:grid-cols-12">
|
<div className="grid gap-6 grid-cols-1 lg:grid-cols-12">
|
||||||
{/* Input Panel */}
|
{/* Input Panel */}
|
||||||
<div className={cn("lg:col-span-4 space-y-6", activeTab !== "input" && googleAdsResult && "hidden lg:block")}>
|
<div className={cn("lg:col-span-4 space-y-6", activeTab !== "input" && googleAdsResult && "hidden lg:block")}>
|
||||||
<Card className="border-blue-100 shadow-sm overflow-hidden">
|
<Card className="border-blue-100/50 shadow-xl shadow-blue-500/5 bg-white/70 backdrop-blur-md overflow-hidden transition-all hover:shadow-blue-500/10 active:scale-[0.995]">
|
||||||
<div className="h-1.5 bg-gradient-to-r from-blue-500 to-indigo-600" />
|
<div className="h-2 bg-gradient-to-r from-blue-500 via-indigo-500 to-blue-600" />
|
||||||
<CardHeader>
|
<CardHeader className="pb-4">
|
||||||
<CardTitle className="text-lg flex items-center gap-2">
|
<CardTitle className="text-xl flex items-center gap-3 text-slate-800">
|
||||||
<Target className="h-5 w-5 text-blue-600" />
|
<Target className="h-6 w-6 text-blue-600" />
|
||||||
Campaign Parameters
|
Campaign Inputs
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription className="text-slate-500 font-medium">
|
||||||
Define your goals and target audience.
|
Configure your primary triggers and constraints.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
@@ -264,10 +298,10 @@ export default function GoogleAdsGenerator() {
|
|||||||
Website URL
|
Website URL
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="e.g. https://www.your-business.com"
|
placeholder="e.g. www.startup-x.io"
|
||||||
value={websiteUrl}
|
value={websiteUrl}
|
||||||
onChange={(e) => setWebsiteUrl(e.target.value)}
|
onChange={(e) => setWebsiteUrl(e.target.value)}
|
||||||
className="bg-muted/30 focus-visible:ring-blue-500"
|
className="bg-white/50 border-blue-100 focus:border-blue-500 focus-visible:ring-blue-500 h-11 text-base shadow-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -476,20 +510,27 @@ export default function GoogleAdsGenerator() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{isProcessing && (
|
{isProcessing && (
|
||||||
<div className="flex-1 flex flex-col items-center justify-center p-12 text-center animate-pulse">
|
<div className="flex-1 flex flex-col items-center justify-center p-8 lg:p-12 text-center bg-white/30 backdrop-blur-xl rounded-[2.5rem] border border-white/50 shadow-2xl animate-in zoom-in-95 duration-500 relative overflow-hidden">
|
||||||
<div className="relative mb-8">
|
{/* Decorative background blur */}
|
||||||
<div className="w-24 h-24 border-4 border-blue-100 rounded-full animate-spin" style={{ borderTopColor: 'rgb(37 99 235)' }} />
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-blue-400/10 blur-[100px] rounded-full -z-10" />
|
||||||
|
|
||||||
|
<div className="relative mb-10">
|
||||||
|
<div className="w-32 h-32 border-[6px] border-blue-100 rounded-full animate-[spin_3s_linear_infinite]" style={{ borderTopColor: 'rgb(37 99 235)' }} />
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
<Search className="h-8 w-8 text-blue-600 animate-bounce" />
|
<div className="w-16 h-16 bg-blue-600 rounded-full flex items-center justify-center shadow-lg shadow-blue-500/40">
|
||||||
|
<Search className="h-8 w-8 text-white animate-pulse" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-foreground mb-2">Analyzing Domain Content</h3>
|
|
||||||
<div className="max-w-md w-full bg-muted/40 rounded-full h-2 mb-4 overflow-hidden">
|
<h3 className="text-3xl font-black text-slate-800 mb-3 tracking-tight">Strategizing Your Campaign...</h3>
|
||||||
<div className="bg-blue-600 h-full animate-progress-indeterminate w-[60%]" />
|
<p className="text-slate-500 font-medium mb-8 max-w-md mx-auto">
|
||||||
</div>
|
Scouring <span className="text-blue-600 font-bold">{websiteUrl}</span> for conversion triggers and competitive edges.
|
||||||
<p className="text-muted-foreground text-sm">
|
|
||||||
Scanning {websiteUrl}, fetching search volumes, and drafting high-converting copy...
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div className="max-w-md w-full bg-slate-200/50 rounded-full h-3 mb-10 overflow-hidden border border-white/20">
|
||||||
|
<div className="bg-gradient-to-r from-blue-600 via-indigo-500 to-blue-600 h-full animate-progress-indeterminate shadow-[0_0_15px_rgba(37,99,235,0.5)]" />
|
||||||
|
</div>
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mt-12 w-full max-w-2xl px-4">
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mt-12 w-full max-w-2xl px-4">
|
||||||
{[
|
{[
|
||||||
{ label: "Keywords", delay: "0s" },
|
{ label: "Keywords", delay: "0s" },
|
||||||
|
|||||||
Reference in New Issue
Block a user