Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/next/components/store/util.ts
Views: 687
/*1* This file is part of CoCalc: Copyright © 2022 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import {6delete_local_storage,7get_local_storage,8set_local_storage,9} from "@cocalc/frontend/misc/local-storage";10import { ONE_DAY_MS } from "@cocalc/util/consts/billing";11import { isValidUUID } from "@cocalc/util/misc";12import { endOfDay, getDays, startOfDay } from "@cocalc/util/stripe/timecalcs";13import { DateRange } from "@cocalc/util/upgrades/shopping";14import useCustomize from "lib/use-customize";15import dayjs from "dayjs";16import { NextRouter } from "next/router";17import { useEffect, useMemo, useState } from "react";18import { LicenseTypeInForms } from "./add-to-cart";1920// site license type in a form, we have 4 forms hence 4 types21// later, this will be mapped to just "LicenseType", where boost and regular22// are "quota" and item.boost = true|false.23export function getType(item): LicenseTypeInForms {24const descr = item.description;25if (descr.dedicated_disk != null && descr.dedicated_disk !== false) {26return "disk";27} else if (descr.dedicated_vm != null && descr.dedicated_vm !== false) {28return "vm";29} else if (descr.boost === true) {30return "boost";31} else {32return "regular";33}34}3536// 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.37export function loadDateRange(range?: DateRange): DateRange {38if (range == null) {39const now = new Date();40return [startOfDay(now), endOfDay(now)];41}4243for (const idx of [0, 1]) {44const v = range[idx];45if (typeof v === "string") {46range[idx] = new Date(v);47}48}4950if (range[0] instanceof Date) {51const today = startOfDay(new Date());52const prevStart = range[0];5354if (range[0].getTime() < today.getTime()) {55range[0] = today;56}5758// we we have to shift the start, move the end forward as well and preserve the duration.59if (range[1] instanceof Date) {60if (range[0].getTime() > range[1].getTime()) {61const days = getDays({ start: prevStart, end: range[1] });62range[1] = endOfDay(new Date(Date.now() + ONE_DAY_MS * days));63}64}65}66return range;67}6869/**70* use serverTime to fix the users exact time and return an object with these properties:71* - offset: difference in milliseconds between server time and user time72* - timezone: the user's timezone73* - serverTime: the Date object of serverTime74* - userTime: the Date object of userTime75* - function toServerTime(date): converts a Date object from the user's time to the server time76*77* @param serverTime -- milliseconds since epoch from the server78*/79export function useTimeFixer() {80const { serverTime: customizeServerTime } = useCustomize();8182return useMemo(() => {83// server time is supposed to be always set, but just in case …84if (customizeServerTime == null) {85console.warn(86"WARNING: customize.serverTime is not set, using Date.now()"87);88}89const serverTime = customizeServerTime ?? Date.now();90const localTime = Date.now();9192// important: useMemo, b/c we calculate the offset only *once* when the page loads, not each time the hook is called93const offset = localTime - serverTime;9495function toTimestamp(date: Date | string): number {96return dayjs(date).toDate().getTime();97}9899function toServerTime(date: Date | string) {100return new Date(toTimestamp(date) - offset);101}102103function fromServerTime(date: Date | string) {104return new Date(toTimestamp(date) + offset);105}106107return {108offset,109timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,110serverTimeDate: new Date(serverTime),111toServerTime,112fromServerTime,113};114}, []);115}116117export const LS_KEY_LICENSE_PROJECT = "store_site_license_project_id";118const LS_KEY_LICENSE_ASSOCIATION = "store_site_license_association";119120/**121* We want to make it possible to purchase a license and applying it automatically to a project.122* For that, we check if there is a query param "project_id" (and save it in local storage) or just check local storage.123*/124export function useLicenseProject(router: NextRouter) {125const [upgradeProjectId, setUpgradeProjectId] = useState<126string | undefined127>();128129useEffect(() => {130const { project_id } = router.query;131const projectIdLS = get_local_storage(LS_KEY_LICENSE_PROJECT);132133if (typeof project_id === "string" && isValidUUID(project_id)) {134setUpgradeProjectId(project_id);135set_local_storage(LS_KEY_LICENSE_PROJECT, project_id);136} else if (typeof projectIdLS === "string" && isValidUUID(projectIdLS)) {137setUpgradeProjectId(projectIdLS);138} else {139if (project_id != null) {140console.warn(`Invalid ?project_id=... query param: '${project_id}'`);141}142}143}, []);144145// this removes the project_id from local storage and the query param146function upgradeProjectDelete() {147delete_local_storage(LS_KEY_LICENSE_PROJECT);148setUpgradeProjectId(undefined);149const { pathname } = router;150const query = router.query;151delete query.project_id;152router.replace({ pathname, query }, undefined, { shallow: true });153}154155// the ID created in the shopping cart, not the actual ID!156// when this is called, we kind of "consume" the project_id157// and remove it from the query param and local storage158function storeLicenseProjectAssociation(id: number) {159const project_id = get_local_storage(LS_KEY_LICENSE_PROJECT);160if (typeof project_id !== "string" || !isValidUUID(project_id)) {161console.warn(`Invalid project_id in local storage: '${project_id}'`);162}163set_local_storage(LS_KEY_LICENSE_ASSOCIATION, `${id}::${project_id}`);164upgradeProjectDelete();165}166167return {168upgradeProjectId,169upgradeProjectDelete,170storeLicenseProjectAssociation,171};172}173174175