initial commit

This commit is contained in:
root
2026-05-29 17:07:00 +02:00
commit c935e39fa1
30 changed files with 1263 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
'use client';
import { useState, useTransition } from 'react';
export default function BillingPage() {
const [error, setError] = useState<string | null>(null);
const [isPending, startTransition] = useTransition();
function onClick() {
setError(null);
startTransition(async () => {
const res = await fetch('/api/billing/checkout', { method: 'POST' });
if (!res.ok) {
setError(`Checkout failed (${res.status})`);
return;
}
const body = (await res.json()) as { url?: string };
if (!body.url) {
setError('No checkout URL returned');
return;
}
window.location.href = body.url;
});
}
return (
<div>
<h1>Billing</h1>
<div className="card">
<h2>Upgrade</h2>
<p className="muted">
v1 MVP uses a Stripe stub clicking Upgrade simulates a successful
checkout and unlocks unlimited bandwidth.
</p>
{error && <p className="error">{error}</p>}
<button onClick={onClick} disabled={isPending}>
{isPending ? 'Redirecting…' : 'Upgrade'}
</button>
</div>
</div>
);
}
+62
View File
@@ -0,0 +1,62 @@
'use client';
import { useEffect, useState } from 'react';
import Link from 'next/link';
export default function BillingSuccessPage() {
const [status, setStatus] = useState<'pending' | 'ok' | 'error'>('pending');
const [message, setMessage] = useState<string | null>(null);
useEffect(() => {
const session =
typeof window !== 'undefined'
? new URLSearchParams(window.location.search).get('session')
: null;
(async () => {
try {
const res = await fetch('/api/billing/finalize', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ session }),
});
if (!res.ok) {
const body = (await res.json().catch(() => ({}))) as {
error?: string;
};
setStatus('error');
setMessage(body.error ?? `Finalize failed (${res.status})`);
return;
}
setStatus('ok');
} catch (e) {
setStatus('error');
setMessage((e as Error).message);
}
})();
}, []);
return (
<div>
<h1>Upgrade complete</h1>
<div className="card">
{status === 'pending' && <p>Finalizing your upgrade</p>}
{status === 'ok' && (
<>
<p className="success">Your account has been upgraded.</p>
<Link className="btn" href="/dashboard">
Back to dashboard
</Link>
</>
)}
{status === 'error' && (
<>
<p className="error">{message}</p>
<Link className="btn secondary" href="/billing">
Try again
</Link>
</>
)}
</div>
</div>
);
}