Initial commit
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
'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('');
|
||||
|
||||
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);
|
||||
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();
|
||||
setSessionId(data.session_id);
|
||||
setPreview(data.preview);
|
||||
setIsPaid(data.is_paid);
|
||||
} catch (e) {
|
||||
console.error('Generate failed:', e);
|
||||
} 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]">
|
||||
<OutputPanel
|
||||
tool={slug}
|
||||
sessionId={sessionId}
|
||||
preview={preview}
|
||||
fullOutput={fullOutput}
|
||||
isPaid={isPaid}
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user