Files
Kordant/web/src/routes/billing/checkout.tsx

90 lines
3.2 KiB
TypeScript

import { createSignal, onMount, Show } from "solid-js";
import { Title } from "@solidjs/meta";
import { useNavigate, useSearchParams } from "@solidjs/router";
import EmbeddedCheckout from "~/components/EmbeddedCheckout";
import { api } from "~/lib/api";
import PageContainer from "~/components/layout/PageContainer";
const priceMap: Record<string, string> = {
basic: process.env.STRIPE_PRICE_BASIC ?? "",
plus: process.env.STRIPE_PRICE_PLUS ?? "",
premium: process.env.STRIPE_PRICE_PREMIUM ?? "",
};
export default function CheckoutPage() {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const [clientSecret, setClientSecret] = createSignal("");
const [error, setError] = createSignal<string | null>(null);
const [loading, setLoading] = createSignal(true);
onMount(async () => {
const plan = Array.isArray(searchParams.plan) ? searchParams.plan[0] : searchParams.plan;
const priceIdParam = Array.isArray(searchParams.priceId) ? searchParams.priceId[0] : searchParams.priceId;
const priceId = priceIdParam ?? (plan ? priceMap[plan] : "");
if (!priceId) {
setError("No plan selected. Please select a plan to continue.");
setLoading(false);
return;
}
try {
const returnUrl = `${window.location.origin}/billing/return`;
const result = await api.billing.createCheckoutSession.mutate({
priceId,
returnUrl,
});
setClientSecret(result.clientSecret);
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to create checkout session");
} finally {
setLoading(false);
}
});
return (
<main class="min-h-screen py-8 md:py-12">
<Title>Checkout Kordant</Title>
<PageContainer>
<div class="max-w-2xl mx-auto">
<div class="mb-8">
<h1 class="text-2xl font-bold text-[var(--color-text-primary)]">Complete your purchase</h1>
<p class="text-sm text-[var(--color-text-secondary)] mt-1">
Secure payment powered by Stripe
</p>
</div>
<Show when={loading()}>
<div class="flex items-center justify-center min-h-[300px]">
<div class="text-center">
<div class="animate-spin h-8 w-8 border-2 border-[var(--color-brand-primary)] border-t-transparent rounded-full mx-auto mb-3" />
<p class="text-sm text-[var(--color-text-secondary)]">Preparing checkout...</p>
</div>
</div>
</Show>
<Show when={error() && !loading()}>
<div class="text-center py-8 border border-[var(--color-border)] rounded-xl">
<p class="text-[var(--color-error)] mb-4">{error()}</p>
<button
onClick={() => navigate("/pricing")}
class="text-sm text-[var(--color-brand-primary)] hover:underline"
>
Back to pricing
</button>
</div>
</Show>
<Show when={clientSecret() && !loading() && !error()}>
<EmbeddedCheckout
clientSecret={clientSecret()}
onCheckoutComplete={() => navigate("/dashboard")}
/>
</Show>
</div>
</PageContainer>
</main>
);
}