Path: blob/master/src/packages/next/components/store/add-box.tsx
1450 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 { CostInputPeriod } from "@cocalc/util/licenses/purchase/types";10import { round2up } from "@cocalc/util/misc";11import { money } from "@cocalc/util/licenses/purchase/utils";12import { Alert, Button, Spin } from "antd";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";1718export const ADD_STYLE = {19display: "inline-block",20maxWidth: "550px",21minWidth: "400px",22background: "#fafafa",23border: "1px solid #ccc",24padding: "10px 20px",25borderRadius: "5px",26margin: "15px 0",27fontSize: "12pt",28} as const;2930interface Props {31cost?: CostInputPeriod;32router;33form;34cartError: string | undefined;35setCartError: (error) => void;36dedicatedItem?: boolean;37disabled?: boolean;38noAccount: boolean;39}4041export function AddBox({42cost,43router,44form,45cartError,46setCartError,47dedicatedItem = false,48noAccount,49disabled = false,50}: Props) {51if (cost?.input.type == "cash-voucher") {52return null;53}54// if any of the fields in cost that start with the string "cost" are NaN, disable submission:55if (56!cost ||57Object.keys(cost).some((k) => k.startsWith("cost") && isNaN(cost[k]))58) {59disabled = true;60}6162function costPerProject() {63if (cost?.input.type != "quota") {64return;65}66if (dedicatedItem || cost.input.quantity == null) {67return;68}69const costPer = decimalDivide(periodicCost(cost), cost.input.quantity);70return (71<Alert72type="warning"73style={{74margin: "10px",75}}76message={77<>78{money(round2up(costPer))} <b>per project</b>{" "}79{!!cost.period && cost.period != "range" ? cost.period : ""}80</>81}82/>83);84}8586function renderButton(): JSX.Element | null {87if (noAccount) return null;8889return (90<div style={{ textAlign: "center", marginTop: "5px" }}>91{router.query.id != null && (92<Button93size="large"94style={{ marginRight: "5px" }}95onClick={() => router.push("/store/cart")}96disabled={disabled}97>98Cancel99</Button>100)}101<AddToCartButton102cartError={cartError}103cost={cost}104disabled={disabled}105form={form}106router={router}107setCartError={setCartError}108/>109{cartError && <Alert type="error" message={cartError} />}110</div>111);112}113114return (115<div style={{ textAlign: "center" }}>116<div style={ADD_STYLE}>117{cost && <DisplayCost cost={cost} />}118{cost && costPerProject()}119{renderButton()}120</div>121</div>122);123}124125interface CartButtonProps {126cost: CostInputPeriod | undefined;127router;128form;129setCartError: (error) => void;130disabled?: boolean;131cartError: string | undefined;132variant?: "primary" | "small";133}134135export function AddToCartButton({136cost,137form,138router,139setCartError,140cartError,141variant = "primary",142disabled: disabled0,143}: CartButtonProps) {144const [clicked, setClicked] = useState<boolean>(false);145const disabled =146clicked ||147(disabled0 ?? false) ||148!!cartError ||149cost == null ||150cost.cost === 0;151152return (153<Button154size={variant === "small" ? "small" : "large"}155type="primary"156htmlType="submit"157onClick={async () => {158// you can only click this add to cart button *once* -- due to slow159// turnaround, if we don't change this state, then the user could160// click multiple times and add the same item more than once, thus161// accidentally ending up with a "dobule purchase"162try {163setClicked(true);164await addToCart({ form, setCartError, router });165} catch (_err) {166// error is reported via setCartError. But also167// give a chance to click the button again, since item168// wasn't actually added.169setClicked(false);170}171}}172disabled={disabled}173>174{clicked175? "Moving to Cart..."176: router.query.id != null177? "Save Changes"178: "Add to Cart"}179{clicked && <Spin style={{ marginLeft: "15px" }} />}180</Button>181);182}183184185