166 lines
5.2 KiB
TypeScript
166 lines
5.2 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { useParams } from 'next/navigation';
|
|
import { tools } from '@/lib/tools';
|
|
import ToolForm from '@/components/ToolForm';
|
|
import OutputPanel from '@/components/OutputPanel';
|
|
import { ToolName } from '@/lib/types';
|
|
|
|
export default function ToolPage() {
|
|
const params = useParams();
|
|
const slug = params.tool as string;
|
|
const tool = tools.find((t) => t.slug === slug);
|
|
|
|
const [sessionId, setSessionId] = useState<string | null>(null);
|
|
const [preview, setPreview] = useState<string | null>(null);
|
|
const [fullOutput, setFullOutput] = useState<string | null>(null);
|
|
const [isPaid, setIsPaid] = useState(false);
|
|
const [generating, setGenerating] = useState(false);
|
|
const [paymentLoading, setPaymentLoading] = useState(false);
|
|
const [email, setEmail] = useState('');
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
if (!tool) {
|
|
return (
|
|
<div className="flex items-center justify-center h-[60vh]">
|
|
<p className="text-gray-500">Tool not found</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const handleGenerate = async (input: Record<string, string>) => {
|
|
setGenerating(true);
|
|
setPreview(null);
|
|
setSessionId(null);
|
|
setError(null);
|
|
try {
|
|
const res = await fetch(`/api/${slug}/generate-draft`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ input, email: email || undefined }),
|
|
});
|
|
const data = await res.json();
|
|
if (!res.ok) {
|
|
setError(data.error || `Server error: ${res.status}`);
|
|
return;
|
|
}
|
|
setSessionId(data.session_id);
|
|
setPreview(data.preview);
|
|
setIsPaid(data.is_paid);
|
|
if (data.full_output) {
|
|
setFullOutput(data.full_output);
|
|
}
|
|
} catch (e) {
|
|
console.error('Generate failed:', e);
|
|
setError(e instanceof Error ? e.message : 'An unexpected error occurred. Please try again.');
|
|
} finally {
|
|
setGenerating(false);
|
|
}
|
|
};
|
|
|
|
const handlePayment = async () => {
|
|
if (!sessionId) return;
|
|
setPaymentLoading(true);
|
|
try {
|
|
const res = await fetch('/api/payment/create-checkout', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ session_id: sessionId, email }),
|
|
});
|
|
const data = await res.json();
|
|
if (data.checkout_url) {
|
|
window.location.href = data.checkout_url;
|
|
}
|
|
} catch (e) {
|
|
console.error('Payment failed:', e);
|
|
} finally {
|
|
setPaymentLoading(false);
|
|
}
|
|
};
|
|
|
|
// Check if returning from payment (poll session status)
|
|
const checkPaymentStatus = async () => {
|
|
if (!sessionId) return;
|
|
const res = await fetch(`/api/${slug}/validate`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ session_id: sessionId }),
|
|
});
|
|
if (res.status !== 402) {
|
|
setIsPaid(true);
|
|
// Fetch full output
|
|
const exportRes = await fetch('/api/export/copy', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ session_id: sessionId }),
|
|
});
|
|
const data = await exportRes.json();
|
|
if (data.text) setFullOutput(data.text);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="max-w-7xl mx-auto px-4 py-8">
|
|
<div className="mb-6">
|
|
<h1 className="text-3xl font-bold text-dark">
|
|
{tool.icon} {tool.name}
|
|
</h1>
|
|
<p className="text-gray-600 mt-1">{tool.description}</p>
|
|
</div>
|
|
|
|
<div className="grid lg:grid-cols-2 gap-8">
|
|
{/* Left: Form */}
|
|
<div>
|
|
<div className="sticky top-20">
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-dark mb-1">
|
|
Email (optional — for receiving your output)
|
|
</label>
|
|
<input
|
|
type="email"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
placeholder="your@email.com"
|
|
className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-accent/50 focus:border-accent"
|
|
/>
|
|
</div>
|
|
<ToolForm
|
|
fields={tool.fields}
|
|
onSubmit={handleGenerate}
|
|
loading={generating}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right: Output */}
|
|
<div className="min-h-[400px]">
|
|
{error && (
|
|
<div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm">
|
|
<strong>Error:</strong> {error}
|
|
</div>
|
|
)}
|
|
<OutputPanel
|
|
tool={slug}
|
|
sessionId={sessionId}
|
|
preview={preview}
|
|
fullOutput={fullOutput}
|
|
isPaid={isPaid}
|
|
generating={generating}
|
|
onPaymentRequest={handlePayment}
|
|
paymentLoading={paymentLoading}
|
|
/>
|
|
{sessionId && !isPaid && (
|
|
<button
|
|
onClick={checkPaymentStatus}
|
|
className="mt-4 text-sm text-accent underline"
|
|
>
|
|
Already paid? Check status
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|