From 033e06e8ef81677668f770ad554fcbf40d299143 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 6 Nov 2024 14:16:23 +0530 Subject: [PATCH] Move --- .../components/MemberSubscriptionManage.tsx | 2 +- .../photos/src/components/Sidebar/index.tsx | 8 +- .../photos/src/components/Upload/Uploader.tsx | 2 +- .../components/pages/gallery/PlanSelector.tsx | 21 ++---- web/apps/photos/src/pages/gallery.tsx | 4 +- .../services/{billingService.ts => plan.ts} | 73 ++++++++++++++++++ web/apps/photos/src/types/user/index.ts | 2 +- web/apps/photos/src/utils/billing/index.ts | 74 ------------------- web/apps/photos/src/utils/ui/index.tsx | 2 +- web/apps/photos/tsconfig.json | 2 +- 10 files changed, 90 insertions(+), 100 deletions(-) rename web/apps/photos/src/services/{billingService.ts => plan.ts} (72%) delete mode 100644 web/apps/photos/src/utils/billing/index.ts diff --git a/web/apps/photos/src/components/MemberSubscriptionManage.tsx b/web/apps/photos/src/components/MemberSubscriptionManage.tsx index 62be7e4473..8dfd5e900c 100644 --- a/web/apps/photos/src/components/MemberSubscriptionManage.tsx +++ b/web/apps/photos/src/components/MemberSubscriptionManage.tsx @@ -9,7 +9,7 @@ import DialogTitleWithCloseButton from "@ente/shared/components/DialogBox/TitleW import { Box, Button, Dialog, DialogContent, Typography } from "@mui/material"; import { t } from "i18next"; import { useContext } from "react"; -import billingService from "services/billingService"; +import billingService from "services/plan"; export function MemberSubscriptionManage({ open, userDetails, onClose }) { const { setDialogMessage } = useContext(AppContext); diff --git a/web/apps/photos/src/components/Sidebar/index.tsx b/web/apps/photos/src/components/Sidebar/index.tsx index ca58f5264b..91d23cf600 100644 --- a/web/apps/photos/src/components/Sidebar/index.tsx +++ b/web/apps/photos/src/components/Sidebar/index.tsx @@ -63,11 +63,8 @@ import React, { useState, } from "react"; import { Trans } from "react-i18next"; -import { redirectToCustomerPortal } from "services/billingService"; import { getUncategorizedCollection } from "services/collectionService"; import exportService from "services/export"; -import { getUserDetailsV2 } from "services/userService"; -import { UserDetails } from "types/user"; import { hasAddOnBonus, hasExceededStorageQuota, @@ -77,7 +74,10 @@ import { isSubscriptionCancelled, isSubscriptionPastDue, isSubscriptionStripe, -} from "utils/billing"; + redirectToCustomerPortal, +} from "services/plan"; +import { getUserDetailsV2 } from "services/userService"; +import { UserDetails } from "types/user"; import { testUpload } from "../../../tests/upload.test"; import { MemberSubscriptionManage } from "../MemberSubscriptionManage"; import { Preferences } from "./Preferences"; diff --git a/web/apps/photos/src/components/Upload/Uploader.tsx b/web/apps/photos/src/components/Upload/Uploader.tsx index 99c1c6d59a..4e0c2d69fe 100644 --- a/web/apps/photos/src/components/Upload/Uploader.tsx +++ b/web/apps/photos/src/components/Upload/Uploader.tsx @@ -23,8 +23,8 @@ import { t } from "i18next"; import isElectron from "is-electron"; import { GalleryContext } from "pages/gallery"; import { useContext, useEffect, useRef, useState } from "react"; -import { redirectToCustomerPortal } from "services/billingService"; import { getLatestCollections } from "services/collectionService"; +import { redirectToCustomerPortal } from "services/plan"; import { getPublicCollectionUID, getPublicCollectionUploaderName, diff --git a/web/apps/photos/src/components/pages/gallery/PlanSelector.tsx b/web/apps/photos/src/components/pages/gallery/PlanSelector.tsx index 2b8e50786b..d96e422d0b 100644 --- a/web/apps/photos/src/components/pages/gallery/PlanSelector.tsx +++ b/web/apps/photos/src/components/pages/gallery/PlanSelector.tsx @@ -35,28 +35,21 @@ import Typography from "@mui/material/Typography"; import { t } from "i18next"; import { useContext, useEffect, useMemo, useState } from "react"; import { Trans } from "react-i18next"; -import type { - Plan, - PlanPeriod, - PlansData, - Subscription, -} from "services/billingService"; +import type { Plan, PlanPeriod, PlansData, Subscription } from "services/plan"; import billingService, { getPlansData, - redirectToCustomerPortal, - redirectToPaymentsApp, -} from "services/billingService"; -import { getFamilyPortalRedirectURL } from "services/userService"; -import { SetLoading } from "types/gallery"; -import { BonusData, UserDetails } from "types/user"; -import { hasAddOnBonus, isSubscriptionActive, isSubscriptionActiveFree, isSubscriptionActivePaid, isSubscriptionCancelled, isSubscriptionStripe, -} from "utils/billing"; + redirectToCustomerPortal, + redirectToPaymentsApp, +} from "services/plan"; +import { getFamilyPortalRedirectURL } from "services/userService"; +import { SetLoading } from "types/gallery"; +import { BonusData, UserDetails } from "types/user"; interface PlanSelectorProps { modalView: boolean; diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index d9125b7d9b..f22ae33855 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -106,9 +106,6 @@ import { t } from "i18next"; import { useRouter, type NextRouter } from "next/router"; import { createContext, useCallback, useEffect, useRef, useState } from "react"; import { useDropzone } from "react-dropzone"; -import billingService, { - redirectToCustomerPortal, -} from "services/billingService"; import { constructEmailList, constructUserIDToEmailMap, @@ -118,6 +115,7 @@ import { getAllLocalCollections, } from "services/collectionService"; import { syncFiles } from "services/fileService"; +import billingService, { redirectToCustomerPortal } from "services/plan"; import { preFileInfoSync, sync } from "services/sync"; import { syncTrash } from "services/trashService"; import uploadManager from "services/upload/uploadManager"; diff --git a/web/apps/photos/src/services/billingService.ts b/web/apps/photos/src/services/plan.ts similarity index 72% rename from web/apps/photos/src/services/billingService.ts rename to web/apps/photos/src/services/plan.ts index cdc3370489..2dcdfb906d 100644 --- a/web/apps/photos/src/services/billingService.ts +++ b/web/apps/photos/src/services/plan.ts @@ -1,6 +1,10 @@ import { authenticatedRequestHeaders, ensureOk } from "@/base/http"; import log from "@/base/log"; import { apiURL, paymentsAppOrigin } from "@/base/origins"; +import { + getTotalFamilyUsage, + isPartOfFamily, +} from "@/new/photos/services/user"; import { nullToUndefined } from "@/utils/transform"; import HTTPService from "@ente/shared/network/HTTPService"; import { @@ -10,6 +14,7 @@ import { } from "@ente/shared/storage/localStorage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import isElectron from "is-electron"; +import { BonusData, UserDetails } from "types/user"; import { z } from "zod"; const PlanPeriod = z.enum(["month", "year"]); @@ -223,3 +228,71 @@ export const redirectToCustomerPortal = async () => { .parse(await res.json()).data; window.location.href = data.url; }; + +/** + * Return true if the given {@link Subscription} has not expired. + */ +export const isSubscriptionActive = (subscription: Subscription) => + subscription && subscription.expiryTime > Date.now() * 1000; + +/** + * Return true if the given active {@link Subscription} is for a paid plan. + */ +export const isSubscriptionActivePaid = (subscription: Subscription) => + subscription && + isSubscriptionActive(subscription) && + subscription.productID != "free"; + +/** + * Return true if the given active {@link Subscription} is for a free plan. + */ +export const isSubscriptionActiveFree = (subscription: Subscription) => + subscription && + isSubscriptionActive(subscription) && + subscription.productID == "free"; + +/** + * Return true if the given {@link Subscription} is using Stripe. + */ +export const isSubscriptionStripe = (subscription: Subscription) => + subscription && subscription.paymentProvider == "stripe"; + +/** + * Return true if the given {@link Subscription} has the cancelled attribute. + */ +export const isSubscriptionCancelled = (subscription: Subscription) => + subscription && subscription.attributes.isCancelled; + +export function isSubscriptionPastDue(subscription: Subscription) { + const thirtyDaysMicroseconds = 30 * 24 * 60 * 60 * 1000 * 1000; + const currentTime = Date.now() * 1000; + return ( + !isSubscriptionCancelled(subscription) && + subscription.expiryTime < currentTime && + subscription.expiryTime >= currentTime - thirtyDaysMicroseconds + ); +} + +// Checks if the bonus data contain any bonus whose type starts with 'ADD_ON' +export function hasAddOnBonus(bonusData?: BonusData) { + return ( + bonusData && + bonusData.storageBonuses && + bonusData.storageBonuses.length > 0 && + bonusData.storageBonuses.some((bonus) => + bonus.type.startsWith("ADD_ON"), + ) + ); +} + +export function hasExceededStorageQuota(userDetails: UserDetails) { + const bonusStorage = userDetails.storageBonus ?? 0; + if (isPartOfFamily(userDetails.familyData)) { + const usage = getTotalFamilyUsage(userDetails.familyData); + return usage > userDetails.familyData.storage + bonusStorage; + } else { + return ( + userDetails.usage > userDetails.subscription.storage + bonusStorage + ); + } +} diff --git a/web/apps/photos/src/types/user/index.ts b/web/apps/photos/src/types/user/index.ts index 29be85169d..831d0ff6d1 100644 --- a/web/apps/photos/src/types/user/index.ts +++ b/web/apps/photos/src/types/user/index.ts @@ -1,5 +1,5 @@ import type { FamilyData } from "@/new/photos/services/user"; -import { Subscription } from "services/billingService"; +import { Subscription } from "services/plan"; export interface Bonus { storage: number; diff --git a/web/apps/photos/src/utils/billing/index.ts b/web/apps/photos/src/utils/billing/index.ts deleted file mode 100644 index f45f7b3d79..0000000000 --- a/web/apps/photos/src/utils/billing/index.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { - getTotalFamilyUsage, - isPartOfFamily, -} from "@/new/photos/services/user"; -import { Subscription } from "services/billingService"; -import { BonusData, UserDetails } from "types/user"; - -/** - * Return true if the given {@link Subscription} has not expired. - */ -export const isSubscriptionActive = (subscription: Subscription) => - subscription && subscription.expiryTime > Date.now() * 1000; - -/** - * Return true if the given active {@link Subscription} is for a paid plan. - */ -export const isSubscriptionActivePaid = (subscription: Subscription) => - subscription && - isSubscriptionActive(subscription) && - subscription.productID != "free"; - -/** - * Return true if the given active {@link Subscription} is for a free plan. - */ -export const isSubscriptionActiveFree = (subscription: Subscription) => - subscription && - isSubscriptionActive(subscription) && - subscription.productID == "free"; - -/** - * Return true if the given {@link Subscription} is using Stripe. - */ -export const isSubscriptionStripe = (subscription: Subscription) => - subscription && subscription.paymentProvider == "stripe"; - -/** - * Return true if the given {@link Subscription} has the cancelled attribute. - */ -export const isSubscriptionCancelled = (subscription: Subscription) => - subscription && subscription.attributes.isCancelled; - -export function isSubscriptionPastDue(subscription: Subscription) { - const thirtyDaysMicroseconds = 30 * 24 * 60 * 60 * 1000 * 1000; - const currentTime = Date.now() * 1000; - return ( - !isSubscriptionCancelled(subscription) && - subscription.expiryTime < currentTime && - subscription.expiryTime >= currentTime - thirtyDaysMicroseconds - ); -} - -// Checks if the bonus data contain any bonus whose type starts with 'ADD_ON' -export function hasAddOnBonus(bonusData?: BonusData) { - return ( - bonusData && - bonusData.storageBonuses && - bonusData.storageBonuses.length > 0 && - bonusData.storageBonuses.some((bonus) => - bonus.type.startsWith("ADD_ON"), - ) - ); -} - -export function hasExceededStorageQuota(userDetails: UserDetails) { - const bonusStorage = userDetails.storageBonus ?? 0; - if (isPartOfFamily(userDetails.familyData)) { - const usage = getTotalFamilyUsage(userDetails.familyData); - return usage > userDetails.familyData.storage + bonusStorage; - } else { - return ( - userDetails.usage > userDetails.subscription.storage + bonusStorage - ); - } -} diff --git a/web/apps/photos/src/utils/ui/index.tsx b/web/apps/photos/src/utils/ui/index.tsx index 267d7cf62d..ce8f690b3e 100644 --- a/web/apps/photos/src/utils/ui/index.tsx +++ b/web/apps/photos/src/utils/ui/index.tsx @@ -2,7 +2,7 @@ import { DialogBoxAttributes } from "@ente/shared/components/DialogBox/types"; import InfoOutlined from "@mui/icons-material/InfoRounded"; import { t } from "i18next"; import { Trans } from "react-i18next"; -import { Subscription } from "services/billingService"; +import { Subscription } from "services/plan"; export const getTrashFilesMessage = ( deleteFileHelper, diff --git a/web/apps/photos/tsconfig.json b/web/apps/photos/tsconfig.json index 9ccccd5cbe..6a6a7b2a1b 100644 --- a/web/apps/photos/tsconfig.json +++ b/web/apps/photos/tsconfig.json @@ -22,6 +22,6 @@ "**/*.js", "../../packages/shared/themes/mui-theme.d.ts", "../../packages/base/global-electron.d.ts" - ], +, "../../packages/new/photos/services/plan.ts" ], "exclude": ["node_modules", "out", ".next", "thirdparty"] }