213 lines
12 KiB
TypeScript
213 lines
12 KiB
TypeScript
|
|
import React, { useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Sparkles, ArrowRight, Activity, Copy, CheckCircle, AlertTriangle, ScanEye } from 'lucide-react';
|
|
import axios from 'axios';
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
|
const XRayPage: React.FC = () => {
|
|
const { t } = useTranslation();
|
|
const navigate = useNavigate();
|
|
const [url, setUrl] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
const [result, setResult] = useState<any>(null);
|
|
const [error, setError] = useState('');
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
// Mock result for dev preview if needed, but we connect to real API
|
|
|
|
const handleAnalyze = async () => {
|
|
if (!url) return;
|
|
setLoading(true);
|
|
setError('');
|
|
setResult(null);
|
|
|
|
try {
|
|
const response = await axios.post('/api/xray', { url }, {
|
|
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
|
|
});
|
|
setResult(response.data);
|
|
} catch (err: any) {
|
|
console.error(err);
|
|
setError(err.response?.data?.error || 'Failed to analyze. Please check the URL and try again.');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const copyPrompt = () => {
|
|
if (result?.analysis?.superiorPrompt) {
|
|
navigator.clipboard.writeText(result.analysis.superiorPrompt);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-stone-50 p-8 text-stone-900 font-sans">
|
|
<div className="max-w-6xl mx-auto space-y-12">
|
|
|
|
{/* Header Section */}
|
|
<div className="text-center space-y-4">
|
|
<div className="inline-flex items-center justify-center p-3 bg-indigo-100 rounded-full mb-4">
|
|
<ScanEye className="w-8 h-8 text-indigo-600" />
|
|
</div>
|
|
<h1 className="text-4xl font-serif font-bold tracking-tight text-stone-900">
|
|
Competitor X-Ray
|
|
</h1>
|
|
<p className="text-lg text-stone-500 max-w-2xl mx-auto">
|
|
Paste any Etsy or Pinterest product URL. Our AI will deconstruct its success formula and generate a superior prompt for you.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Input Section */}
|
|
<div className="bg-white/80 backdrop-blur-xl rounded-2xl shadow-xl border border-stone-200 p-2 flex items-center max-w-3xl mx-auto transition-all focus-within:ring-4 ring-indigo-500/10">
|
|
<input
|
|
type="text"
|
|
placeholder="Paste Etsy or Pinterest URL here..."
|
|
className="flex-1 bg-transparent border-none text-lg px-6 py-4 focus:ring-0 placeholder:text-stone-400"
|
|
value={url}
|
|
onChange={(e) => setUrl(e.target.value)}
|
|
onKeyDown={(e) => e.key === 'Enter' && handleAnalyze()}
|
|
/>
|
|
<button
|
|
onClick={handleAnalyze}
|
|
disabled={loading || !url}
|
|
className={`
|
|
px-8 py-4 rounded-xl font-medium text-white shadow-lg transition-all flex items-center gap-2
|
|
${loading || !url ? 'bg-stone-300 cursor-not-allowed' : 'bg-indigo-600 hover:bg-indigo-700 hover:shadow-indigo-500/30'}
|
|
`}
|
|
>
|
|
{loading ? (
|
|
<>Running X-Ray...</>
|
|
) : (
|
|
<>Analyze <ArrowRight className="w-5 h-5" /></>
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="max-w-3xl mx-auto bg-red-50 text-red-600 p-4 rounded-xl border border-red-100 flex items-center gap-3">
|
|
<AlertTriangle className="w-5 h-5" />
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{/* Results Section */}
|
|
{result && (
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 animate-in fade-in slide-in-from-bottom-8 duration-700">
|
|
|
|
{/* Left: Competitor Analysis */}
|
|
<div className="space-y-6">
|
|
<div className="bg-white rounded-2xl shadow-sm border border-stone-100 overflow-hidden">
|
|
<div className="aspect-[4/3] bg-stone-100 relative overflow-hidden">
|
|
<img
|
|
src={result.metadata.image}
|
|
alt="Competitor"
|
|
className="w-full h-full object-cover transform hover:scale-105 transition-transform duration-700"
|
|
/>
|
|
<div className="absolute top-4 left-4 bg-black/70 backdrop-blur-md text-white px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider">
|
|
Competitor Asset
|
|
</div>
|
|
</div>
|
|
<div className="p-6">
|
|
<h3 className="text-xl font-bold font-serif mb-2 line-clamp-2">{result.metadata.title}</h3>
|
|
<p className="text-stone-500 text-sm line-clamp-3 mb-4">{result.metadata.description}</p>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<h4 className="text-xs font-bold text-stone-400 uppercase tracking-wider mb-2">Visual DNA Detected</h4>
|
|
<div className="flex flex-wrap gap-2">
|
|
{result.analysis.visualDna?.map((tag: string, i: number) => (
|
|
<span key={i} className="px-3 py-1 bg-stone-100 text-stone-600 rounded-full text-xs font-medium border border-stone-200">
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h4 className="text-xs font-bold text-red-400 uppercase tracking-wider mb-2">Identified Weaknesses (Sentiment Gap)</h4>
|
|
<div className="bg-red-50/50 p-4 rounded-xl border border-red-100 text-sm text-stone-700 italic">
|
|
"{result.analysis.sentimentGap}"
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right: The Solution (Superior Prompt) */}
|
|
<div className="space-y-6">
|
|
<div className="bg-indigo-900 text-white rounded-2xl shadow-xl overflow-hidden relative">
|
|
{/* Decorative background glow */}
|
|
<div className="absolute top-0 right-0 w-64 h-64 bg-indigo-500/20 blur-3xl rounded-full translate-x-1/2 -translate-y-1/2"></div>
|
|
|
|
<div className="p-8 relative z-10">
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<div className="p-2 bg-indigo-500/20 rounded-lg">
|
|
<Sparkles className="w-6 h-6 text-indigo-300" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-2xl font-serif font-bold">Superior Formula</h3>
|
|
<p className="text-indigo-200 text-sm">Optimized for high-conversion & aesthetics</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-black/30 backdrop-blur-sm p-6 rounded-xl border border-white/10 relative group">
|
|
<p className="font-mono text-sm text-indigo-100 leading-relaxed">
|
|
{result.analysis.superiorPrompt}
|
|
</p>
|
|
<button
|
|
onClick={copyPrompt}
|
|
className="absolute top-4 right-4 p-2 bg-white/10 hover:bg-white/20 rounded-lg transition-colors text-white"
|
|
title="Copy Prompt"
|
|
>
|
|
{copied ? <CheckCircle className="w-5 h-5 text-green-400" /> : <Copy className="w-5 h-5" />}
|
|
</button>
|
|
</div>
|
|
|
|
<div className="mt-8">
|
|
<h4 className="text-xs font-bold text-indigo-300 uppercase tracking-wider mb-3">Why This Wins</h4>
|
|
<div className="space-y-3">
|
|
<p className="text-sm text-indigo-100/80 leading-relaxed border-l-2 border-indigo-500 pl-4">
|
|
{result.analysis.gapAnalysis}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-indigo-950/50 p-4 flex justify-between items-center border-t border-white/10">
|
|
<span className="text-xs text-indigo-400 font-medium">Ready to dominate?</span>
|
|
<div className="flex gap-3">
|
|
<button
|
|
onClick={() => navigate('/', { state: { prompt: result.analysis.superiorPrompt } })} // Assuming Home takes state
|
|
className="px-4 py-2 bg-white text-indigo-900 rounded-lg text-sm font-bold hover:bg-indigo-50 transition-colors flex items-center gap-2"
|
|
>
|
|
Generate Asset <ArrowRight className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Pro Tip */}
|
|
<div className="bg-yellow-50 border border-yellow-100 rounded-xl p-4 flex items-start gap-3">
|
|
<Activity className="w-5 h-5 text-yellow-600 mt-0.5" />
|
|
<div>
|
|
<h4 className="font-bold text-yellow-800 text-sm">Pro Tip</h4>
|
|
<p className="text-xs text-yellow-700 mt-1">
|
|
Use this prompt in the "Analyst" mode for best results. Consider generating 4 variations to test different lighting setups.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default XRayPage;
|