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