Files
claudemesh/apps/web/src/modules/billing/pricing/plans/plans.tsx
Alejandro Gutiérrez d3163a5bff feat(db): mesh data model — meshes, members, invites, audit log
- pgSchema "mesh" with 4 tables isolating the peer mesh domain
- Enums: visibility, transport, tier, role
- audit_log is metadata-only (E2E encryption enforced at broker/client)
- Cascade on mesh delete, soft-delete via archivedAt/revokedAt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 21:19:32 +01:00

57 lines
1.4 KiB
TypeScript

import { memo } from "react";
import { Skeleton } from "@turbostarter/ui-web/skeleton";
import { Plan } from "./plan/plan";
import type { User } from "@turbostarter/auth";
import type {
BillingModel,
Discount,
PricingPlan,
RecurringInterval,
} from "@turbostarter/billing";
interface PlansProps {
readonly plans: PricingPlan[];
readonly discounts: Discount[];
readonly user: User | null;
readonly interval: RecurringInterval;
readonly model: BillingModel;
readonly currency: string;
}
export const Plans = memo<PlansProps>(
({ plans, discounts, interval, user, model, currency }) => {
return (
<div className="flex w-full flex-wrap items-stretch justify-center gap-8 md:gap-6 lg:gap-4">
{plans.map((plan) => (
<Plan
key={plan.id}
plan={plan}
interval={interval}
model={model}
currency={currency}
user={user}
discounts={discounts}
/>
))}
</div>
);
},
);
export const PlansSkeleton = () => {
return (
<div className="flex w-full flex-wrap items-center justify-center gap-12 md:gap-6 lg:gap-4">
{Array.from({ length: 2 }).map((_, i) => (
<div key={i} className="grow-0 basis-[25rem] md:shrink-0">
<Skeleton className="h-[32rem] w-full" />
</div>
))}
</div>
);
};
Plans.displayName = "Plans";