diff --git a/backend/src/api.rs b/backend/src/api.rs index dae26ae..528e823 100644 --- a/backend/src/api.rs +++ b/backend/src/api.rs @@ -349,13 +349,8 @@ async fn list_tenants( } } -async fn list_plans( - State(state): State, - headers: HeaderMap, -) -> std::result::Result { - let _ = state.api.extract_auth_pubkey(&headers)?; - - Ok(ok(StatusCode::OK, Repo::list_plans())) +async fn list_plans() -> Response { + ok(StatusCode::OK, Repo::list_plans()) } async fn get_identity( @@ -392,16 +387,10 @@ async fn get_identity( )) } -async fn get_plan( - State(state): State, - headers: HeaderMap, - Path(id): Path, -) -> std::result::Result { - let _ = state.api.extract_auth_pubkey(&headers)?; - +async fn get_plan(Path(id): Path) -> Response { match Repo::list_plans().into_iter().find(|p| p.id == id) { - Some(plan) => Ok(ok(StatusCode::OK, plan)), - None => Ok(err(StatusCode::NOT_FOUND, "not-found", "plan not found")), + Some(plan) => ok(StatusCode::OK, plan), + None => err(StatusCode::NOT_FOUND, "not-found", "plan not found"), } } diff --git a/frontend/src/components/PricingTable.tsx b/frontend/src/components/PricingTable.tsx index b469f4a..86cfb81 100644 --- a/frontend/src/components/PricingTable.tsx +++ b/frontend/src/components/PricingTable.tsx @@ -1,5 +1,6 @@ import { For } from "solid-js" -import { PLANS, type PlanId } from "@/lib/api" +import type { PlanId } from "@/lib/api" +import { plans } from "@/lib/state" function CheckIcon() { return ( @@ -19,6 +20,17 @@ function XIcon() { ) } +function priceLabel(sats: number) { + if (sats === 0) return "0" + if (sats >= 1000) return `${(sats / 1000).toLocaleString()}K` + return sats.toLocaleString() +} + +function memberLabel(members: number | null) { + if (members === null) return "Unlimited members" + return `Up to ${members} members` +} + type PricingTableProps = { selectable?: boolean selectedPlan?: PlanId @@ -29,7 +41,7 @@ type PricingTableProps = { export default function PricingTable(props: PricingTableProps) { return (
- + {(plan) => { const isPopular = plan.id === "basic" const isSelected = () => props.selectable && props.selectedPlan === plan.id @@ -41,14 +53,13 @@ export default function PricingTable(props: PricingTableProps) { POPULAR )} -

{plan.label}

-

{plan.subtitle}

+

{plan.name}

- {plan.priceLabel} + {priceLabel(plan.sats)} sats / mo
    -
  • {plan.memberLabel}
  • +
  • {memberLabel(plan.members)}
  • {plan.blossom ? : } Blossom storage diff --git a/frontend/src/components/RelayDetailCard.tsx b/frontend/src/components/RelayDetailCard.tsx index a917b90..9808b48 100644 --- a/frontend/src/components/RelayDetailCard.tsx +++ b/frontend/src/components/RelayDetailCard.tsx @@ -7,6 +7,7 @@ import PricingTable from "@/components/PricingTable" import ToggleButton from "@/components/ToggleButton" import ToggleField from "@/components/ToggleField" import { setToastMessage } from "@/components/Toast" +import { plans } from "@/lib/state" function DetailSection(props: { title: string; children: any }) { return ( @@ -61,13 +62,11 @@ export default function RelayDetailCard(props: RelayDetailCardProps) { let menuContainerRef: HTMLDivElement | undefined - const memberLimitByPlan: Record = { - free: "10", - basic: "100", - growth: "∞", + const memberLimitLabel = () => { + const p = plans().find(p => p.id === r().plan) + if (!p) return "?" + return p.members === null ? "∞" : String(p.members) } - - const memberLimitLabel = () => memberLimitByPlan[r().plan] ?? "?" const planLimited = () => (props.enforcePlanLimits ?? true) && r().plan === "free" const showPlanActions = () => props.showPlanActions ?? true diff --git a/frontend/src/components/RelayForm.tsx b/frontend/src/components/RelayForm.tsx index 2bed481..cfcd071 100644 --- a/frontend/src/components/RelayForm.tsx +++ b/frontend/src/components/RelayForm.tsx @@ -1,8 +1,8 @@ -import { createEffect, createSignal } from "solid-js" +import { createEffect, createMemo, createSignal, For } from "solid-js" import type { Relay } from "@/lib/hooks" import { slugify } from "@/lib/slugify" -import { PLANS } from "@/lib/api" import { setToastMessage } from "@/components/Toast" +import { plans } from "@/lib/state" export type RelayFormValues = Pick @@ -15,7 +15,8 @@ type RelayFormProps = { } export default function RelayForm(props: RelayFormProps) { - const [plan, setPlan] = createSignal(props.initialValues?.plan ?? PLANS[0].id) + const defaultPlanId = createMemo(() => props.initialValues?.plan ?? plans()[0]?.id ?? "free") + const [plan, setPlan] = createSignal(defaultPlanId()) const [name, setName] = createSignal(props.initialValues?.info_name ?? "") const [subdomain, setSubdomain] = createSignal(props.initialValues?.subdomain ?? "") const [icon, setIcon] = createSignal(props.initialValues?.info_icon ?? "") @@ -48,6 +49,8 @@ export default function RelayForm(props: RelayFormProps) { } } + createEffect(() => setPlan(defaultPlanId())) + createEffect(() => { if (props.syncSubdomainWithName) { setSubdomain(slugify(name())) @@ -98,23 +101,23 @@ export default function RelayForm(props: RelayFormProps) {
    - {PLANS.map(p => ( - - ))} + + {(p) => ( + + )} +