|
| 1 | +import { Rivet } from "@rivet-gg/cloud"; |
1 | 2 | import {
|
2 | 3 | useMutation,
|
3 | 4 | useQuery,
|
@@ -101,61 +102,135 @@ export default function BillingFrameContent() {
|
101 | 102 | </BillingDetailsButton>
|
102 | 103 | </div>
|
103 | 104 | <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4 mt-4">
|
104 |
| - <CommunityPlan |
105 |
| - current={ |
106 |
| - billing?.activePlan === "free" || |
107 |
| - !billing?.activePlan |
108 |
| - } |
| 105 | + {[ |
| 106 | + [Rivet.BillingPlan.Free, CommunityPlan], |
| 107 | + [Rivet.BillingPlan.Pro, ProPlan], |
| 108 | + [Rivet.BillingPlan.Team, TeamPlan], |
| 109 | + ].map(([plan, PlanComponent]) => { |
| 110 | + const config = getConfig(plan, billing); |
| 111 | + return ( |
| 112 | + <PlanComponent |
| 113 | + key={plan} |
| 114 | + {...config} |
| 115 | + buttonProps={{ |
| 116 | + ...config.buttonProps, |
| 117 | + disabled: |
| 118 | + config.buttonProps.disabled || |
| 119 | + isPending, |
| 120 | + isLoading: |
| 121 | + variables?.__from === plan && isPending, |
| 122 | + onClick: () => { |
| 123 | + if (billing.futurePlan === plan) { |
| 124 | + return mutate({ |
| 125 | + plan: Rivet.BillingPlan.Free, |
| 126 | + __from: plan, |
| 127 | + }); |
| 128 | + } |
| 129 | + mutate({ plan, __from: plan }); |
| 130 | + }, |
| 131 | + }} |
| 132 | + /> |
| 133 | + ); |
| 134 | + })} |
| 135 | + <EnterprisePlan |
109 | 136 | buttonProps={{
|
110 |
| - isLoading: isPending, |
111 |
| - onClick: () => mutate({ plan: "community" }), |
112 |
| - disabled: |
113 |
| - billing?.activePlan === "free" || |
114 |
| - !billing?.activePlan || |
115 |
| - !billing?.canChangePlan, |
116 |
| - }} |
117 |
| - /> |
118 |
| - <ProPlan |
119 |
| - current={billing?.activePlan === "pro"} |
120 |
| - buttonProps={{ |
121 |
| - isLoading: isPending, |
122 | 137 | onClick: () => {
|
123 |
| - if (billing?.activePlan === "pro") { |
124 |
| - return mutate({ plan: "free" }); |
125 |
| - } |
126 |
| - return mutate({ plan: "pro" }); |
| 138 | + window.open( |
| 139 | + "https://www.rivet.dev/sales", |
| 140 | + "_blank", |
| 141 | + ); |
127 | 142 | },
|
128 |
| - disabled: !billing?.canChangePlan, |
129 |
| - ...(billing?.activePlan === "pro" && |
130 |
| - billing?.futurePlan !== "free" |
131 |
| - ? { children: "Cancel" } |
132 |
| - : {}), |
133 | 143 | }}
|
134 | 144 | />
|
135 |
| - <TeamPlan |
136 |
| - current={billing?.activePlan === "team"} |
137 |
| - buttonProps={{ |
138 |
| - isLoading: isPending, |
139 |
| - onClick: () => { |
140 |
| - if (billing?.activePlan === "team") { |
141 |
| - return mutate({ plan: "free" }); |
142 |
| - } |
143 |
| - return mutate({ plan: "team" }); |
144 |
| - }, |
145 |
| - disabled: !billing?.canChangePlan, |
146 |
| - ...(billing?.activePlan === "team" && |
147 |
| - billing?.futurePlan !== "free" |
148 |
| - ? { children: "Cancel" } |
149 |
| - : {}), |
150 |
| - }} |
151 |
| - /> |
152 |
| - <EnterprisePlan /> |
153 | 145 | </div>
|
154 | 146 | </Frame.Content>
|
155 | 147 | </>
|
156 | 148 | );
|
157 | 149 | }
|
158 | 150 |
|
| 151 | +function isCurrent( |
| 152 | + plan: Rivet.BillingPlan, |
| 153 | + data: Rivet.BillingDetailsResponse.Billing, |
| 154 | +) { |
| 155 | + return ( |
| 156 | + plan === data.activePlan || |
| 157 | + (plan === Rivet.BillingPlan.Free && !data.activePlan) |
| 158 | + ); |
| 159 | +} |
| 160 | + |
| 161 | +function getConfig( |
| 162 | + plan: Rivet.BillingPlan, |
| 163 | + billing: Rivet.BillingDetailsResponse.Billing | undefined, |
| 164 | +) { |
| 165 | + return { |
| 166 | + current: isCurrent(plan, billing), |
| 167 | + buttonProps: { |
| 168 | + children: buttonText(plan, billing), |
| 169 | + variant: buttonVariant(plan, billing), |
| 170 | + disabled: !billing?.canChangePlan || buttonDisabled(plan, billing), |
| 171 | + }, |
| 172 | + }; |
| 173 | +} |
| 174 | + |
| 175 | +function buttonVariant( |
| 176 | + plan: Rivet.BillingPlan, |
| 177 | + data: Rivet.BillingDetailsResponse.Billing, |
| 178 | +) { |
| 179 | + if (plan === data.activePlan && data.futurePlan !== data.activePlan) |
| 180 | + return "default"; |
| 181 | + if (plan === data.futurePlan && data.futurePlan !== data.activePlan) |
| 182 | + return "secondary"; |
| 183 | + |
| 184 | + if (comparePlans(plan, data.futurePlan) > 0) return "default"; |
| 185 | + return "secondary"; |
| 186 | +} |
| 187 | + |
| 188 | +function buttonDisabled( |
| 189 | + plan: Rivet.BillingPlan, |
| 190 | + data: Rivet.BillingDetailsResponse.Billing, |
| 191 | +) { |
| 192 | + return plan === data.futurePlan && data.futurePlan !== data.activePlan; |
| 193 | +} |
| 194 | + |
| 195 | +function buttonText( |
| 196 | + plan: Rivet.BillingPlan, |
| 197 | + data: Rivet.BillingDetailsResponse.Billing, |
| 198 | +) { |
| 199 | + if (plan === data.activePlan && data.futurePlan !== data.activePlan) |
| 200 | + return <>Resubscribe</>; |
| 201 | + if (plan === data.futurePlan && data.futurePlan !== data.activePlan) |
| 202 | + return ( |
| 203 | + <> |
| 204 | + Downgrades on{" "} |
| 205 | + {new Date(data.currentPeriodEnd).toLocaleDateString(undefined, { |
| 206 | + month: "short", |
| 207 | + day: "numeric", |
| 208 | + })} |
| 209 | + </> |
| 210 | + ); |
| 211 | + if (plan === data.activePlan) return "Cancel"; |
| 212 | + return comparePlans(plan, data.futurePlan) > 0 ? "Upgrade" : "Downgrade"; |
| 213 | +} |
| 214 | + |
| 215 | +export function comparePlans( |
| 216 | + a: Rivet.BillingPlan, |
| 217 | + b: Rivet.BillingPlan, |
| 218 | +): number { |
| 219 | + const plans = [ |
| 220 | + Rivet.BillingPlan.Free, |
| 221 | + Rivet.BillingPlan.Pro, |
| 222 | + Rivet.BillingPlan.Team, |
| 223 | + Rivet.BillingPlan.Enterprise, |
| 224 | + ]; |
| 225 | + |
| 226 | + const tierA = plans.indexOf(a); |
| 227 | + const tierB = plans.indexOf(b); |
| 228 | + |
| 229 | + if (tierA > tierB) return 1; |
| 230 | + if (tierA < tierB) return -1; |
| 231 | + return 0; |
| 232 | +} |
| 233 | + |
159 | 234 | function CurrentPlan({ plan }: { plan?: string }) {
|
160 | 235 | if (!plan || plan === "free") return <>Free</>;
|
161 | 236 | if (plan === "pro") return <>Hobby</>;
|
|
0 commit comments