This commit is contained in:
Manav Rathi
2024-11-06 14:16:23 +05:30
parent ac05f9fbd7
commit 033e06e8ef
10 changed files with 90 additions and 100 deletions

View File

@@ -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);

View File

@@ -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";

View File

@@ -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,

View File

@@ -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;

View File

@@ -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";

View File

@@ -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
);
}
}

View File

@@ -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;

View File

@@ -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
);
}
}

View File

@@ -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,

View File

@@ -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"]
}