* refactor: move DataForSEO response cache from KV to R2 (#46) * refactor: move lighthouse audits to dataforseo (#43) * refactor: move lighthouse audits to dataforseo * chore: remove obsolete audit settings modal * refactor: rename psi flows to lighthouse * save * refactor: simplify audit lighthouse storage flow * fix: separate lighthouse metrics from actionable audits * refactor: remove redundant audit project inputs * feat: redesign lighthouse issues screen with score gauges and table layout Replace flat score cards with circular SVG gauges, condense metrics into a compact grid, and switch issue list from cards to an expandable table with fixed column widths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: harden lighthouse regression coverage * fix: restore project-scoped audit inputs * refactor: simplify lighthouse payload handling * refactor: inline lighthouse server handlers * refactor: share audit workflow types * refactor: simplify lighthouse payload flows * save * refactor: drop project pagespeed api key * fix: restore lighthouse issues loading with resilient project context * fix: restore audit issues back navigation * refactor: simplify project context and lighthouse error handling * fix: tolerate DataForSEO lighthouse payload drift * refactor: route audit lighthouse through dataforseo client --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: move DataForSEO response cache from KV to R2 KV TTL-based expiry is imprecise for cache freshness. Switch to R2 with soft TTL via custom metadata (expiresAt) and a 7-day lifecycle rule for cleanup. Cache objects live under the `dataforseo-cache/` prefix, separate from durable audit payloads in `site-audit/`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: use Workers crypto for R2 cache keys Keep the cache helper aligned with the Cloudflare runtime and avoid pulling Worker bindings into Vitest. Also clarify that R2 lifecycle cleanup is optional but recommended to control storage growth. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: move hosted billing to Autumn hooks (#48) * refactor: move lighthouse audits to dataforseo (#43) * refactor: move lighthouse audits to dataforseo * chore: remove obsolete audit settings modal * refactor: rename psi flows to lighthouse * save * refactor: simplify audit lighthouse storage flow * fix: separate lighthouse metrics from actionable audits * refactor: remove redundant audit project inputs * feat: redesign lighthouse issues screen with score gauges and table layout Replace flat score cards with circular SVG gauges, condense metrics into a compact grid, and switch issue list from cards to an expandable table with fixed column widths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: harden lighthouse regression coverage * fix: restore project-scoped audit inputs * refactor: simplify lighthouse payload handling * refactor: inline lighthouse server handlers * refactor: share audit workflow types * refactor: simplify lighthouse payload flows * save * refactor: drop project pagespeed api key * fix: restore lighthouse issues loading with resilient project context * fix: restore audit issues back navigation * refactor: simplify project context and lighthouse error handling * fix: tolerate DataForSEO lighthouse payload drift * refactor: route audit lighthouse through dataforseo client --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: move hosted billing to Autumn hooks * fix: satisfy ci checks for hosted billing * refactor: simplify hosted Autumn billing integration Limit Autumn wiring to the billing route and remove billing-specific indirection so the hosted billing flow is easier to reason about. Stop sending organization IDs as fake customer names to keep customer identity data honest. * fix: satisfy ci line-limit check for hosted billing --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: split SEO credits into monthly included and top-up pools Replaces the single usage_credits balance with two separate pools: - usage_credits: monthly grant from base-plan, resets each cycle - topup_credits: one-off purchases from credit-top-up, never expire Usage now deducts from monthly credits first, then falls back to top-ups. The billing UI shows both balances separately with a reset date on the monthly card. Requires Autumn dashboard changes to create the topup_credits credit system and update the credit-top-up plan. * formt --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
114 lines
3.6 KiB
TypeScript
114 lines
3.6 KiB
TypeScript
import { CreditCard } from "lucide-react";
|
|
import type { ReactNode } from "react";
|
|
|
|
export function BillingHeader(args: {
|
|
hasManagedServiceAccess: boolean;
|
|
basePlanName: string;
|
|
includedCreditsLabel: string;
|
|
}) {
|
|
return (
|
|
<div className="flex flex-col gap-2">
|
|
<div className="flex items-center gap-2 text-sm text-base-content/60">
|
|
<CreditCard className="h-4 w-4" />
|
|
Hosted billing
|
|
</div>
|
|
<h1 className="text-3xl font-semibold">
|
|
{args.hasManagedServiceAccess ? "Billing" : "Choose a plan"}
|
|
</h1>
|
|
<p className="max-w-2xl text-base-content/70">
|
|
{args.hasManagedServiceAccess
|
|
? `${args.basePlanName} includes ${args.includedCreditsLabel} of usage credits each month. Monthly credits are used first; purchased top-ups never expire.`
|
|
: `You need an active ${args.basePlanName} subscription to use OpenSEO's managed service. It includes ${args.includedCreditsLabel} of usage credits each month, and you can buy more at any time.`}
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function SubscriptionIntro(args: {
|
|
hasManagedServiceAccess: boolean;
|
|
basePlanName: string;
|
|
}) {
|
|
return (
|
|
<div>
|
|
<h2 className="text-xl font-semibold">
|
|
{args.hasManagedServiceAccess
|
|
? "Subscription"
|
|
: "Managed service access"}
|
|
</h2>
|
|
<p className="text-sm text-base-content/65">
|
|
{args.hasManagedServiceAccess
|
|
? "Hosted workspaces need an active paid plan before project pages and DataForSEO-backed features are available."
|
|
: `Start ${args.basePlanName} to unlock OpenSEO's managed service and your included monthly credits.`}
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function SubscriptionStatusCard(args: {
|
|
hasManagedServiceAccess: boolean;
|
|
basePlanName: string;
|
|
basePlanPrice: string;
|
|
includedCreditsLabel: string;
|
|
}) {
|
|
return (
|
|
<div className="rounded-2xl border border-base-300 bg-base-200/50 p-4">
|
|
<div className="text-sm text-base-content/60">
|
|
{args.hasManagedServiceAccess ? "Current status" : args.basePlanName}
|
|
</div>
|
|
<div className="mt-1 text-2xl font-semibold">
|
|
{args.hasManagedServiceAccess ? "Active" : args.basePlanPrice}
|
|
</div>
|
|
<div className="mt-2 text-sm text-base-content/70">
|
|
{args.hasManagedServiceAccess
|
|
? "Your organization can use hosted OpenSEO features."
|
|
: `Includes ${args.includedCreditsLabel} of usage credits every cycle.`}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function BillingAlerts(args: {
|
|
actionError: string | null;
|
|
hasManagedServiceAccess: boolean;
|
|
basePlanName: string;
|
|
}) {
|
|
return (
|
|
<>
|
|
{args.actionError ? (
|
|
<div className="alert alert-error">
|
|
<span>{args.actionError}</span>
|
|
</div>
|
|
) : null}
|
|
|
|
{!args.hasManagedServiceAccess ? (
|
|
<div className="alert alert-warning">
|
|
<span>
|
|
Subscribe to {args.basePlanName} first. After that, you can manage
|
|
your plan and buy more credits here.
|
|
</span>
|
|
</div>
|
|
) : null}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export function CenteredCard(args: {
|
|
title: string;
|
|
body: string;
|
|
action?: ReactNode;
|
|
}) {
|
|
return (
|
|
<div className="mx-auto flex h-full w-full max-w-3xl items-center justify-center p-6">
|
|
<div className="card w-full max-w-xl border border-base-300 bg-base-100 shadow-xl">
|
|
<div className="card-body gap-3">
|
|
<h1 className="text-2xl font-semibold">{args.title}</h1>
|
|
<p className="text-base-content/70">{args.body}</p>
|
|
{args.action ? (
|
|
<div className="card-actions justify-start pt-2">{args.action}</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|