Use plans from backend
This commit is contained in:
@@ -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 (
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 items-start">
|
||||
<For each={PLANS}>
|
||||
<For each={plans()}>
|
||||
{(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
|
||||
</span>
|
||||
)}
|
||||
<h3 class="text-lg font-bold text-gray-900 mb-1">{plan.label}</h3>
|
||||
<p class="text-sm text-gray-400 mb-6">{plan.subtitle}</p>
|
||||
<h3 class="text-lg font-bold text-gray-900 mb-1">{plan.name}</h3>
|
||||
<div class="mb-8">
|
||||
<span class="text-4xl font-extrabold text-gray-900">{plan.priceLabel}</span>
|
||||
<span class="text-4xl font-extrabold text-gray-900">{priceLabel(plan.sats)}</span>
|
||||
<span class="text-sm text-gray-400 ml-1">sats / mo</span>
|
||||
</div>
|
||||
<ul class="mb-8 text-sm text-gray-600 space-y-3">
|
||||
<li class="flex items-start gap-2"><CheckIcon />{plan.memberLabel}</li>
|
||||
<li class="flex items-start gap-2"><CheckIcon />{memberLabel(plan.members)}</li>
|
||||
<li class={`flex items-start gap-2 ${plan.blossom ? "" : "text-gray-300"}`}>
|
||||
{plan.blossom ? <CheckIcon /> : <XIcon />}
|
||||
Blossom storage
|
||||
|
||||
@@ -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<string, string> = {
|
||||
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
|
||||
|
||||
|
||||
@@ -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<Relay, "info_name" | "subdomain" | "info_icon" | "info_description" | "plan">
|
||||
|
||||
@@ -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) {
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-3">Plan</label>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
{PLANS.map(p => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPlan(p.id)}
|
||||
class={`border-2 rounded-xl p-4 text-left transition-colors ${
|
||||
plan() === p.id
|
||||
? "border-blue-600 bg-blue-50"
|
||||
: "border-gray-200 hover:border-gray-300"
|
||||
}`}
|
||||
>
|
||||
<div class="font-bold text-gray-900">{p.label}</div>
|
||||
<div class="text-sm text-gray-500 mt-1">
|
||||
{p.price === 0 ? "Free" : `${p.price.toLocaleString()} sats/mo`}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-2">{p.memberLabel}</div>
|
||||
</button>
|
||||
))}
|
||||
<For each={plans()}>
|
||||
{(p) => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPlan(p.id)}
|
||||
class={`border-2 rounded-xl p-4 text-left transition-colors ${plan() === p.id ? "border-blue-600 bg-blue-50" : "border-gray-200 hover:border-gray-300"}`}
|
||||
>
|
||||
<div class="font-bold text-gray-900">{p.name}</div>
|
||||
<div class="text-sm text-gray-500 mt-1">
|
||||
{p.sats === 0 ? "Free" : `${p.sats.toLocaleString()} sats/mo`}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-2">
|
||||
{p.members === null ? "Unlimited members" : `Up to ${p.members} members`}
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user