|  | 
|  | 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