Ben Senescu 570b995248 Refactor billing + onboarding (#61)
* fix: use full page reload after email verification

Client-side navigation via TanStack Router during the auth→app
transition can race with Vite HMR, causing "action is not a function"
server function errors.

* feat: add minimal /subscribe onboarding page

New post-auth subscribe page at /subscribe using the same centered
layout as auth pages. Shows plan details and a single Subscribe CTA.
Redirects PAYMENT_REQUIRED users here instead of /billing.

* redesign: rewrite billing page with usage chart and cleaner layout

Delete the sprawling multi-component billing page and replace it with a
single-file implementation. Two cards sit side by side at the top
(subscription summary + buy credits), with a 30-day usage bar chart
below powered by Autumn's useAggregateEvents hook and recharts.

Removed BillingRouteParts.tsx, HostedBillingContent.tsx, and trimmed
HostedBillingContentUtils to only parseTopUpAmount.

* polish: billing page improvements and OpenSEO nav link

- Two-column layout with subscription summary and buy credits side by side
- Usage bar chart using ResizeObserver instead of ResponsiveContainer
- Input validation with inline error message
- Full-page redirect state when navigating to Stripe
- Make OpenSEO logo in navbar link to /

* fix: guard app routes and include top-up usage

* fix: restore billing onboarding guards

Keep unpaid orgs on /subscribe and avoid misleading billing states when Autumn customer lookups fail.

* fix: split billing usage chart for ci checks
2026-04-08 14:09:01 -04:00

44 lines
1.1 KiB
TypeScript

import { Outlet, createFileRoute, useNavigate } from "@tanstack/react-router";
import { useEffect } from "react";
import { AuthenticatedAppLayout } from "@/client/layout/AppShell";
import { useSession } from "@/lib/auth-client";
import { isHostedClientAuthMode } from "@/lib/auth-mode";
import {
getCurrentAuthRedirectFromHref,
getSignInSearch,
} from "@/lib/auth-redirect";
export const Route = createFileRoute("/_app")({
component: AppRouteLayout,
});
function AppRouteLayout() {
const navigate = useNavigate();
const { data: session, isPending } = useSession();
const isHostedMode = isHostedClientAuthMode();
useEffect(() => {
if (isPending || !isHostedMode || session?.user?.id) {
return;
}
void navigate({
to: "/sign-in",
search: getSignInSearch(
getCurrentAuthRedirectFromHref(window.location.href),
),
replace: true,
});
}, [isPending, isHostedMode, session?.user?.id, navigate]);
if (isHostedMode && (isPending || !session?.user?.id)) {
return null;
}
return (
<AuthenticatedAppLayout>
<Outlet />
</AuthenticatedAppLayout>
);
}