Path: blob/master/src/packages/next/components/store/index.tsx
1492 views
/*1* This file is part of CoCalc: Copyright © 2022 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/4import { Alert, Layout } from "antd";5import { useRouter } from "next/router";6import { useEffect, useState, type JSX } from "react";7import * as purchasesApi from "@cocalc/frontend/purchases/api";8import { COLORS } from "@cocalc/util/theme";9import Anonymous from "components/misc/anonymous";10import Loading from "components/share/loading";11import SiteName from "components/share/site-name";12import { StoreBalanceContext } from "lib/balance";13import { MAX_WIDTH } from "lib/config";14import useProfile from "lib/hooks/profile";15import useCustomize from "lib/use-customize";16import Cart from "./cart";17import Checkout from "./checkout";18import Processing from "./processing";19import Congrats from "./congrats";20import Menu from "./menu";21import Overview from "./overview";22import SiteLicense from "./site-license";23import { StoreInplaceSignInOrUp } from "./store-inplace-signup";24import Vouchers from "./vouchers";2526const { Content } = Layout;2728interface Props {29page: (30| "site-license"31| "boost"32| "dedicated"33| "cart"34| "checkout"35| "processing"36| "congrats"37| "vouchers"38| undefined39)[];40}4142export default function StoreLayout({ page }: Props) {43const { isCommercial } = useCustomize();44const router = useRouter();45const profile = useProfile({ noCache: true });4647const [loading, setLoading] = useState<boolean>(false);4849const [balance, setBalance] = useState<number>();5051const refreshBalance = async () => {52if (!profile || !profile.account_id) {53setBalance(undefined);54return;55}5657// Set balance if user is logged in58//59try {60setLoading(true);61setBalance(await purchasesApi.getBalance());62} catch (err) {63console.warn("Error updating balance", err);64} finally {65setLoading(false);66}67};6869useEffect(() => {70router.prefetch("/store/site-license");71}, []);7273useEffect(() => {74refreshBalance();75}, [profile]);7677function renderNotCommercial(): JSX.Element {78return (79<Alert80showIcon81style={{82margin: "30px auto",83maxWidth: "400px",84fontSize: "12pt",85padding: "15px 30px",86}}87type="warning"88message={89<>90The <SiteName /> store is not enabled.91</>92}93/>94);95}9697if (!isCommercial) {98return renderNotCommercial();99}100101if (!profile) {102return <Loading large center />;103}104const { account_id, is_anonymous } = profile;105const noAccount = account_id == null;106107// wrapper: only the pages showing the prices will be shown to the general public or anonymous users108function requireAccount(StorePage): JSX.Element {109if (noAccount) {110return (111<Alert112style={{ margin: "15px auto" }}113type="warning"114message={<StoreInplaceSignInOrUp />}115/>116);117}118119return <StorePage />;120}121122const [main] = page;123124function body() {125if (main == null) return <Overview />;126127if (is_anonymous) {128return <Anonymous />;129}130131switch (main) {132case "site-license":133return <SiteLicense noAccount={noAccount} />;134case "cart":135return requireAccount(Cart);136case "checkout":137return requireAccount(Checkout);138case "processing":139return requireAccount(Processing);140case "vouchers":141return requireAccount(Vouchers);142case "congrats":143return requireAccount(Congrats);144default:145return <Alert type="error" message={`Invalid page ${main}`} />;146}147}148149// this layout is the same as ../licenses/layout.tsx and ../billing/layout.tsx150function renderMain(): JSX.Element {151return (152<Layout153style={{154padding: "0 24px 24px",155backgroundColor: "white",156color: COLORS.GRAY_D,157}}158>159<Content160style={{161margin: "0 30px",162minHeight: "60vh",163}}164>165<div style={{ maxWidth: MAX_WIDTH, margin: "auto" }}>166<StoreBalanceContext.Provider167value={{ balance, refreshBalance, loading }}168>169<Menu main={main} />170{body()}171</StoreBalanceContext.Provider>172</div>173</Content>174</Layout>175);176}177178return renderMain();179}180181182