Files
ngo-toolkit/app/[tool]/page.tsx
T

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>
);
}