Path: blob/master/src/packages/next/components/store/add-box.tsx
5903 views
/*1* This file is part of CoCalc: Copyright © 2022 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/*6Add a cash voucher to your shopping cart.7*/8import { useState, type JSX } from "react";9import { Alert, Button, Spin } from "antd";10import { CostInputPeriod } from "@cocalc/util/licenses/purchase/types";11import { round2up } from "@cocalc/util/misc";12import { money } from "@cocalc/util/licenses/purchase/utils";13import { addToCart } from "./add-to-cart";14import { DisplayCost } from "./site-license-cost";15import { periodicCost } from "@cocalc/util/licenses/purchase/compute-cost";16import { decimalDivide } from "@cocalc/util/stripe/calc";17import ShowError from "@cocalc/frontend/components/error";18import type { LicenseSource } from "@cocalc/util/upgrades/shopping";1920export const ADD_STYLE = {21display: "inline-block",22maxWidth: "550px",23minWidth: "400px",24background: "#fafafa",25border: "1px solid #ccc",26padding: "10px 20px",27borderRadius: "5px",28margin: "15px 0",29fontSize: "12pt",30} as const;3132interface Props {33cost?: CostInputPeriod;34router;35form;36cartError: string | undefined;37setCartError: (error) => void;38dedicatedItem?: boolean;39disabled?: boolean;40noAccount: boolean;41source: LicenseSource;42}4344export function AddBox({45cost,46router,47form,48cartError,49setCartError,50dedicatedItem = false,51noAccount,52disabled = false,53source,54}: Props) {55if (cost?.input.type == "cash-voucher") {56return null;57}58// if any of the fields in cost that start with the string "cost" are NaN, disable submission:59if (60!cost ||61Object.keys(cost).some((k) => k.startsWith("cost") && isNaN(cost[k]))62) {63disabled = true;64}6566function costPerProject() {67if (cost?.input.type != "quota") {68return;69}70if (dedicatedItem || cost.input.quantity == null) {71return;72}73const costPer = decimalDivide(periodicCost(cost), cost.input.quantity);74return (75<Alert76type="warning"77style={{78margin: "10px",79}}80message={81<>82{money(round2up(costPer))}{" "}83<b>per {source === "course" ? "student" : "project"}</b>{" "}84{!!cost.period && cost.period != "range" ? cost.period : ""}85</>86}87/>88);89}9091function renderButton(): JSX.Element | null {92if (noAccount) return null;9394return (95<div style={{ textAlign: "center", marginTop: "5px" }}>96{router.query.id != null && (97<Button98size="large"99style={{ marginRight: "5px" }}100onClick={() => router.push("/store/cart")}101disabled={disabled}102>103Cancel104</Button>105)}106<AddToCartButton107cartError={cartError}108cost={cost}109disabled={disabled}110form={form}111router={router}112setCartError={setCartError}113/>114<ShowError115error={cartError}116setError={setCartError}117style={{ marginTop: "5px" }}118/>119</div>120);121}122123return (124<div style={{ textAlign: "center" }}>125<div style={ADD_STYLE}>126{cost && <DisplayCost cost={cost} />}127{cost && costPerProject()}128{renderButton()}129</div>130</div>131);132}133134interface CartButtonProps {135cost: CostInputPeriod | undefined;136router;137form;138setCartError: (error) => void;139disabled?: boolean;140cartError: string | undefined;141variant?: "primary" | "small";142}143144export function AddToCartButton({145cost,146form,147router,148setCartError,149cartError,150variant = "primary",151disabled: disabled0,152}: CartButtonProps) {153const [clicked, setClicked] = useState<boolean>(false);154const disabled =155clicked ||156(disabled0 ?? false) ||157!!cartError ||158cost == null ||159cost.cost === 0;160161return (162<Button163size={variant === "small" ? "small" : "large"}164type="primary"165htmlType="submit"166onClick={async () => {167// you can only click this add to cart button *once* -- due to slow168// turnaround, if we don't change this state, then the user could169// click multiple times and add the same item more than once, thus170// accidentally ending up with a "dobule purchase"171try {172setClicked(true);173await addToCart({ form, setCartError, router });174} catch (_err) {175// error is reported via setCartError. But also176// give a chance to click the button again, since item177// wasn't actually added.178setClicked(false);179}180}}181disabled={disabled}182>183{clicked184? "Moving to Cart..."185: router.query.id != null186? "Save Changes"187: "Add to Cart"}188{clicked && <Spin style={{ marginLeft: "15px" }} />}189</Button>190);191}192193194