Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/next/components/store/index.tsx
5857 views
1
/*
2
* This file is part of CoCalc: Copyright © 2022 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
import { Alert, Layout } from "antd";
6
import { useRouter } from "next/router";
7
import { useEffect, useState, type JSX } from "react";
8
9
import * as purchasesApi from "@cocalc/frontend/purchases/api";
10
import { COLORS } from "@cocalc/util/theme";
11
import Anonymous from "components/misc/anonymous";
12
import Loading from "components/share/loading";
13
import SiteName from "components/share/site-name";
14
import { StoreBalanceContext } from "lib/balance";
15
import { MAX_WIDTH } from "lib/config";
16
import useProfile from "lib/hooks/profile";
17
import useCustomize from "lib/use-customize";
18
import Cart from "./cart";
19
import Checkout from "./checkout";
20
import Congrats from "./congrats";
21
import Menu from "./menu";
22
import Overview from "./overview";
23
import Processing from "./processing";
24
import SiteLicense from "./site-license";
25
import { StoreInplaceSignInOrUp } from "./store-inplace-signup";
26
import { StorePagesTypes } from "./types";
27
import Vouchers from "./vouchers";
28
29
const { Content } = Layout;
30
31
interface Props {
32
page: (StorePagesTypes | undefined)[];
33
}
34
35
export default function StoreLayout({ page }: Props) {
36
const { isCommercial } = useCustomize();
37
const router = useRouter();
38
const profile = useProfile({ noCache: true });
39
40
const [loading, setLoading] = useState<boolean>(false);
41
42
const [balance, setBalance] = useState<number>();
43
44
const refreshBalance = async () => {
45
if (!profile || !profile.account_id) {
46
setBalance(undefined);
47
return;
48
}
49
50
// Set balance if user is logged in
51
//
52
try {
53
setLoading(true);
54
setBalance(await purchasesApi.getBalance());
55
} catch (err) {
56
console.warn("Error updating balance", err);
57
} finally {
58
setLoading(false);
59
}
60
};
61
62
useEffect(() => {
63
router.prefetch("/store/site-license");
64
}, []);
65
66
useEffect(() => {
67
refreshBalance();
68
}, [profile]);
69
70
function renderNotCommercial(): JSX.Element {
71
return (
72
<Alert
73
showIcon
74
style={{
75
margin: "30px auto",
76
maxWidth: "400px",
77
fontSize: "12pt",
78
padding: "15px 30px",
79
}}
80
type="warning"
81
message={
82
<>
83
The <SiteName /> store is not enabled.
84
</>
85
}
86
/>
87
);
88
}
89
90
if (!isCommercial) {
91
return renderNotCommercial();
92
}
93
94
if (!profile) {
95
return <Loading large center />;
96
}
97
const { account_id, is_anonymous } = profile;
98
const noAccount = account_id == null;
99
100
// wrapper: only the pages showing the prices will be shown to the general public or anonymous users
101
function requireAccount(StorePage): JSX.Element {
102
if (noAccount) {
103
return (
104
<Alert
105
style={{ margin: "15px auto" }}
106
type="warning"
107
message={<StoreInplaceSignInOrUp />}
108
/>
109
);
110
}
111
112
return <StorePage />;
113
}
114
115
const [main] = page;
116
117
function body() {
118
if (main == null) return <Overview />;
119
120
if (is_anonymous) {
121
return <Anonymous />;
122
}
123
124
switch (main) {
125
case "site-license":
126
return <SiteLicense noAccount={noAccount} source="site-license" />;
127
case "course":
128
return <SiteLicense noAccount={noAccount} source="course" />;
129
case "cart":
130
return requireAccount(Cart);
131
case "checkout":
132
return requireAccount(Checkout);
133
case "processing":
134
return requireAccount(Processing);
135
case "vouchers":
136
return requireAccount(Vouchers);
137
case "congrats":
138
return requireAccount(Congrats);
139
default:
140
return <Alert type="error" message={`Invalid page ${main}`} />;
141
}
142
}
143
144
// this layout is the same as ../licenses/layout.tsx and ../billing/layout.tsx
145
function renderMain(): JSX.Element {
146
return (
147
<Layout
148
style={{
149
padding: "0 24px 24px",
150
backgroundColor: "white",
151
color: COLORS.GRAY_D,
152
}}
153
>
154
<Content
155
style={{
156
margin: "0 30px",
157
minHeight: "60vh",
158
}}
159
>
160
<div style={{ maxWidth: MAX_WIDTH, margin: "auto" }}>
161
<StoreBalanceContext.Provider
162
value={{ balance, refreshBalance, loading }}
163
>
164
<Menu main={main} />
165
{body()}
166
</StoreBalanceContext.Provider>
167
</div>
168
</Content>
169
</Layout>
170
);
171
}
172
173
return renderMain();
174
}
175
176