CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/next/components/store/util.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2022 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import {
7
delete_local_storage,
8
get_local_storage,
9
set_local_storage,
10
} from "@cocalc/frontend/misc/local-storage";
11
import { ONE_DAY_MS } from "@cocalc/util/consts/billing";
12
import { isValidUUID } from "@cocalc/util/misc";
13
import { endOfDay, getDays, startOfDay } from "@cocalc/util/stripe/timecalcs";
14
import { DateRange } from "@cocalc/util/upgrades/shopping";
15
import useCustomize from "lib/use-customize";
16
import dayjs from "dayjs";
17
import { NextRouter } from "next/router";
18
import { useEffect, useMemo, useState } from "react";
19
import { LicenseTypeInForms } from "./add-to-cart";
20
21
// site license type in a form, we have 4 forms hence 4 types
22
// later, this will be mapped to just "LicenseType", where boost and regular
23
// are "quota" and item.boost = true|false.
24
export function getType(item): LicenseTypeInForms {
25
const descr = item.description;
26
if (descr.dedicated_disk != null && descr.dedicated_disk !== false) {
27
return "disk";
28
} else if (descr.dedicated_vm != null && descr.dedicated_vm !== false) {
29
return "vm";
30
} else if (descr.boost === true) {
31
return "boost";
32
} else {
33
return "regular";
34
}
35
}
36
37
// when loading an item from the cart/saved for later, we have to fix the start/end dates to be at least "today" at the start of the day in the users time zone.
38
export function loadDateRange(range?: DateRange): DateRange {
39
if (range == null) {
40
const now = new Date();
41
return [startOfDay(now), endOfDay(now)];
42
}
43
44
for (const idx of [0, 1]) {
45
const v = range[idx];
46
if (typeof v === "string") {
47
range[idx] = new Date(v);
48
}
49
}
50
51
if (range[0] instanceof Date) {
52
const today = startOfDay(new Date());
53
const prevStart = range[0];
54
55
if (range[0].getTime() < today.getTime()) {
56
range[0] = today;
57
}
58
59
// we we have to shift the start, move the end forward as well and preserve the duration.
60
if (range[1] instanceof Date) {
61
if (range[0].getTime() > range[1].getTime()) {
62
const days = getDays({ start: prevStart, end: range[1] });
63
range[1] = endOfDay(new Date(Date.now() + ONE_DAY_MS * days));
64
}
65
}
66
}
67
return range;
68
}
69
70
/**
71
* use serverTime to fix the users exact time and return an object with these properties:
72
* - offset: difference in milliseconds between server time and user time
73
* - timezone: the user's timezone
74
* - serverTime: the Date object of serverTime
75
* - userTime: the Date object of userTime
76
* - function toServerTime(date): converts a Date object from the user's time to the server time
77
*
78
* @param serverTime -- milliseconds since epoch from the server
79
*/
80
export function useTimeFixer() {
81
const { serverTime: customizeServerTime } = useCustomize();
82
83
return useMemo(() => {
84
// server time is supposed to be always set, but just in case …
85
if (customizeServerTime == null) {
86
console.warn(
87
"WARNING: customize.serverTime is not set, using Date.now()"
88
);
89
}
90
const serverTime = customizeServerTime ?? Date.now();
91
const localTime = Date.now();
92
93
// important: useMemo, b/c we calculate the offset only *once* when the page loads, not each time the hook is called
94
const offset = localTime - serverTime;
95
96
function toTimestamp(date: Date | string): number {
97
return dayjs(date).toDate().getTime();
98
}
99
100
function toServerTime(date: Date | string) {
101
return new Date(toTimestamp(date) - offset);
102
}
103
104
function fromServerTime(date: Date | string) {
105
return new Date(toTimestamp(date) + offset);
106
}
107
108
return {
109
offset,
110
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
111
serverTimeDate: new Date(serverTime),
112
toServerTime,
113
fromServerTime,
114
};
115
}, []);
116
}
117
118
export const LS_KEY_LICENSE_PROJECT = "store_site_license_project_id";
119
const LS_KEY_LICENSE_ASSOCIATION = "store_site_license_association";
120
121
/**
122
* We want to make it possible to purchase a license and applying it automatically to a project.
123
* For that, we check if there is a query param "project_id" (and save it in local storage) or just check local storage.
124
*/
125
export function useLicenseProject(router: NextRouter) {
126
const [upgradeProjectId, setUpgradeProjectId] = useState<
127
string | undefined
128
>();
129
130
useEffect(() => {
131
const { project_id } = router.query;
132
const projectIdLS = get_local_storage(LS_KEY_LICENSE_PROJECT);
133
134
if (typeof project_id === "string" && isValidUUID(project_id)) {
135
setUpgradeProjectId(project_id);
136
set_local_storage(LS_KEY_LICENSE_PROJECT, project_id);
137
} else if (typeof projectIdLS === "string" && isValidUUID(projectIdLS)) {
138
setUpgradeProjectId(projectIdLS);
139
} else {
140
if (project_id != null) {
141
console.warn(`Invalid ?project_id=... query param: '${project_id}'`);
142
}
143
}
144
}, []);
145
146
// this removes the project_id from local storage and the query param
147
function upgradeProjectDelete() {
148
delete_local_storage(LS_KEY_LICENSE_PROJECT);
149
setUpgradeProjectId(undefined);
150
const { pathname } = router;
151
const query = router.query;
152
delete query.project_id;
153
router.replace({ pathname, query }, undefined, { shallow: true });
154
}
155
156
// the ID created in the shopping cart, not the actual ID!
157
// when this is called, we kind of "consume" the project_id
158
// and remove it from the query param and local storage
159
function storeLicenseProjectAssociation(id: number) {
160
const project_id = get_local_storage(LS_KEY_LICENSE_PROJECT);
161
if (typeof project_id !== "string" || !isValidUUID(project_id)) {
162
console.warn(`Invalid project_id in local storage: '${project_id}'`);
163
}
164
set_local_storage(LS_KEY_LICENSE_ASSOCIATION, `${id}::${project_id}`);
165
upgradeProjectDelete();
166
}
167
168
return {
169
upgradeProjectId,
170
upgradeProjectDelete,
171
storeLicenseProjectAssociation,
172
};
173
}
174
175