mirror of
https://github.com/ente-io/ente.git
synced 2025-08-07 23:18:10 +00:00
wip checkpoint
This commit is contained in:
parent
ec1b294425
commit
1f8ddb0c08
@ -19,7 +19,6 @@ import {
|
||||
} from "@/new/photos/services/collection";
|
||||
import type { CollectionSummaries } from "@/new/photos/services/collection/ui";
|
||||
import {
|
||||
hasAddOnBonus,
|
||||
hasExceededStorageQuota,
|
||||
isFamilyAdmin,
|
||||
isPartOfFamily,
|
||||
@ -30,6 +29,7 @@ import {
|
||||
isSubscriptionPastDue,
|
||||
isSubscriptionStripe,
|
||||
redirectToCustomerPortal,
|
||||
userDetailsAddOnBonuses,
|
||||
} from "@/new/photos/services/plan";
|
||||
import { isInternalUser } from "@/new/photos/services/settings";
|
||||
import { syncUserDetails, type UserDetails } from "@/new/photos/services/user";
|
||||
@ -195,7 +195,9 @@ const UserDetailsSection: React.FC<UserDetailsSectionProps> = ({
|
||||
userDetails={userDetails}
|
||||
onClick={handleSubscriptionCardClick}
|
||||
/>
|
||||
{userDetails && (
|
||||
<SubscriptionStatus userDetails={userDetails} />
|
||||
)}
|
||||
</Box>
|
||||
{isMemberSubscription && (
|
||||
<MemberSubscriptionManage
|
||||
@ -260,8 +262,10 @@ const SubscriptionStatus: React.FC<SubscriptionStatusProps> = ({
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const hasAddOnBonus = userDetailsAddOnBonuses(userDetails).length > 0;
|
||||
|
||||
let message: React.ReactNode;
|
||||
if (!hasAddOnBonus(userDetails.bonusData)) {
|
||||
if (!hasAddOnBonus) {
|
||||
if (isSubscriptionActive(userDetails.subscription)) {
|
||||
if (isSubscriptionActiveFree(userDetails.subscription)) {
|
||||
message = t("subscription_info_free");
|
||||
|
@ -2,6 +2,7 @@ import { genericRetriableErrorDialogAttributes } from "@/base/components/utils/d
|
||||
import log from "@/base/log";
|
||||
import { useUserDetailsSnapshot } from "@/new/photos/components/utils/use-snapshot";
|
||||
import type {
|
||||
Bonus,
|
||||
Plan,
|
||||
PlanPeriod,
|
||||
PlansData,
|
||||
@ -11,7 +12,6 @@ import {
|
||||
activateSubscription,
|
||||
cancelSubscription,
|
||||
getPlansData,
|
||||
hasAddOnBonus,
|
||||
isSubscriptionActive,
|
||||
isSubscriptionActiveFree,
|
||||
isSubscriptionActivePaid,
|
||||
@ -20,8 +20,8 @@ import {
|
||||
planUsage,
|
||||
redirectToCustomerPortal,
|
||||
redirectToPaymentsApp,
|
||||
userDetailsAddOnBonuses,
|
||||
} from "@/new/photos/services/plan";
|
||||
import { BonusData } from "@/new/photos/services/user";
|
||||
import { AppContext } from "@/new/photos/types/context";
|
||||
import { bytesInGB, formattedStorageByteSize } from "@/new/photos/utils/units";
|
||||
import { openURL } from "@/new/photos/utils/web";
|
||||
@ -111,7 +111,9 @@ const PlanSelectorCard: React.FC<PlanSelectorCardProps> = ({
|
||||
|
||||
const usage = userDetails ? planUsage(userDetails) : 0;
|
||||
const subscription = userDetails?.subscription;
|
||||
const bonusData = userDetails?.bonusData;
|
||||
const addOnBonuses = userDetails
|
||||
? userDetailsAddOnBonuses(userDetails)
|
||||
: [];
|
||||
|
||||
const togglePeriod = useCallback(
|
||||
() => setPlanPeriod((prev) => (prev == "month" ? "year" : "month")),
|
||||
@ -231,7 +233,7 @@ const PlanSelectorCard: React.FC<PlanSelectorCardProps> = ({
|
||||
|
||||
const commonCardData = {
|
||||
subscription,
|
||||
bonusData,
|
||||
addOnBonuses,
|
||||
closeModal,
|
||||
planPeriod,
|
||||
togglePeriod,
|
||||
@ -244,7 +246,7 @@ const PlanSelectorCard: React.FC<PlanSelectorCardProps> = ({
|
||||
planPeriod={planPeriod}
|
||||
onPlanSelect={onPlanSelect}
|
||||
subscription={subscription}
|
||||
bonusData={bonusData}
|
||||
hasAddOnBonus={addOnBonuses.length > 0}
|
||||
closeModal={closeModal}
|
||||
/>
|
||||
);
|
||||
@ -307,7 +309,7 @@ const planSelectionOutcome = (subscription: Subscription | undefined) => {
|
||||
function FreeSubscriptionPlanSelectorCard({
|
||||
children,
|
||||
subscription,
|
||||
bonusData,
|
||||
addOnBonuses,
|
||||
closeModal,
|
||||
setLoading,
|
||||
planPeriod,
|
||||
@ -331,19 +333,19 @@ function FreeSubscriptionPlanSelectorCard({
|
||||
</Typography>
|
||||
</Box>
|
||||
{children}
|
||||
{hasAddOnBonus(bonusData) && (
|
||||
<BFAddOnRow
|
||||
bonusData={bonusData}
|
||||
{addOnBonuses.length > 0 && (
|
||||
<>
|
||||
<AddOnBonusRows
|
||||
addOnBonuses={addOnBonuses}
|
||||
closeModal={closeModal}
|
||||
/>
|
||||
)}
|
||||
{hasAddOnBonus(bonusData) && (
|
||||
<ManageSubscription
|
||||
subscription={subscription}
|
||||
bonusData={bonusData}
|
||||
hasAddOnBonus={true}
|
||||
closeModal={closeModal}
|
||||
setLoading={setLoading}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
@ -354,7 +356,7 @@ function FreeSubscriptionPlanSelectorCard({
|
||||
function PaidSubscriptionPlanSelectorCard({
|
||||
children,
|
||||
subscription,
|
||||
bonusData,
|
||||
addOnBonuses,
|
||||
closeModal,
|
||||
usage,
|
||||
planPeriod,
|
||||
@ -420,9 +422,9 @@ function PaidSubscriptionPlanSelectorCard({
|
||||
date: subscription.expiryTime,
|
||||
})}
|
||||
</Typography>
|
||||
{hasAddOnBonus(bonusData) && (
|
||||
<BFAddOnRow
|
||||
bonusData={bonusData}
|
||||
{addOnBonuses.length > 0 && (
|
||||
<AddOnBonusRows
|
||||
addOnBonuses={addOnBonuses}
|
||||
closeModal={closeModal}
|
||||
/>
|
||||
)}
|
||||
@ -431,7 +433,7 @@ function PaidSubscriptionPlanSelectorCard({
|
||||
|
||||
<ManageSubscription
|
||||
subscription={subscription}
|
||||
bonusData={bonusData}
|
||||
hasAddOnBonus={addOnBonuses.length > 0}
|
||||
closeModal={closeModal}
|
||||
setLoading={setLoading}
|
||||
/>
|
||||
@ -485,7 +487,7 @@ interface PlansProps {
|
||||
plansData: PlansData | undefined;
|
||||
planPeriod: PlanPeriod;
|
||||
subscription: Subscription;
|
||||
bonusData?: BonusData;
|
||||
hasAddOnBonus: boolean;
|
||||
onPlanSelect: (plan: Plan) => void;
|
||||
closeModal: () => void;
|
||||
}
|
||||
@ -494,7 +496,7 @@ const Plans = ({
|
||||
plansData,
|
||||
planPeriod,
|
||||
subscription,
|
||||
bonusData,
|
||||
hasAddOnBonus,
|
||||
onPlanSelect,
|
||||
closeModal,
|
||||
}: PlansProps) => {
|
||||
@ -514,7 +516,7 @@ const Plans = ({
|
||||
/>
|
||||
))}
|
||||
{!isSubscriptionActivePaid(subscription) &&
|
||||
!hasAddOnBonus(bonusData) &&
|
||||
!hasAddOnBonus &&
|
||||
freePlan && (
|
||||
<FreePlanRow
|
||||
storage={freePlan.storage}
|
||||
@ -672,34 +674,31 @@ const FreePlanRow_ = styled(SpaceBetweenFlex)(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
function BFAddOnRow({ bonusData, closeModal }) {
|
||||
return (
|
||||
interface AddOnBonusRowsProps {
|
||||
addOnBonuses: Bonus[];
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
const AddOnBonusRows: React.FC<AddOnBonusRowsProps> = ({
|
||||
addOnBonuses,
|
||||
closeModal,
|
||||
}) => (
|
||||
<>
|
||||
{bonusData.storageBonuses.map((bonus) => {
|
||||
if (bonus.type.startsWith("ADD_ON")) {
|
||||
return (
|
||||
<AddOnRowContainer key={bonus.id} onClick={closeModal}>
|
||||
<Box>
|
||||
{addOnBonuses.map((bonus, i) => (
|
||||
<AddOnRowContainer key={i} onClick={closeModal}>
|
||||
<Typography color="text.muted">
|
||||
<Trans
|
||||
i18nKey={"add_on_valid_till"}
|
||||
values={{
|
||||
storage: formattedStorageByteSize(
|
||||
bonus.storage,
|
||||
),
|
||||
storage: formattedStorageByteSize(bonus.storage),
|
||||
date: bonus.validTill,
|
||||
}}
|
||||
/>
|
||||
</Typography>
|
||||
</Box>
|
||||
</AddOnRowContainer>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const AddOnRowContainer = styled(SpaceBetweenFlex)(({ theme }) => ({
|
||||
// gap: theme.spacing(1.5),
|
||||
@ -712,14 +711,14 @@ const AddOnRowContainer = styled(SpaceBetweenFlex)(({ theme }) => ({
|
||||
|
||||
interface ManageSubscriptionProps {
|
||||
subscription: Subscription;
|
||||
bonusData?: BonusData;
|
||||
hasAddOnBonus: boolean;
|
||||
closeModal: () => void;
|
||||
setLoading: SetLoading;
|
||||
}
|
||||
|
||||
function ManageSubscription({
|
||||
subscription,
|
||||
bonusData,
|
||||
hasAddOnBonus,
|
||||
closeModal,
|
||||
setLoading,
|
||||
}: ManageSubscriptionProps) {
|
||||
@ -745,7 +744,7 @@ function ManageSubscription({
|
||||
{isSubscriptionStripe(subscription) && (
|
||||
<StripeSubscriptionOptions
|
||||
subscription={subscription}
|
||||
bonusData={bonusData}
|
||||
hasAddOnBonus={hasAddOnBonus}
|
||||
closeModal={closeModal}
|
||||
setLoading={setLoading}
|
||||
/>
|
||||
@ -762,7 +761,7 @@ function ManageSubscription({
|
||||
|
||||
function StripeSubscriptionOptions({
|
||||
subscription,
|
||||
bonusData,
|
||||
hasAddOnBonus,
|
||||
setLoading,
|
||||
closeModal,
|
||||
}: ManageSubscriptionProps) {
|
||||
@ -810,7 +809,7 @@ function StripeSubscriptionOptions({
|
||||
const confirmCancel = () =>
|
||||
appContext.setDialogMessage({
|
||||
title: t("CANCEL_SUBSCRIPTION"),
|
||||
content: hasAddOnBonus(bonusData) ? (
|
||||
content: hasAddOnBonus ? (
|
||||
<Trans i18nKey={"CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE"} />
|
||||
) : (
|
||||
<Trans i18nKey={"CANCEL_SUBSCRIPTION_MESSAGE"} />
|
||||
|
@ -104,6 +104,8 @@ const m3 = () =>
|
||||
// TODO: Not enabled yet since it is not critical. Enable with next batch of changes.
|
||||
// // Added: Nov 2025 (v1.7.7-beta). Prunable.
|
||||
// const m4 = () => {
|
||||
// // Delete the legacy key that used to store the map preference.
|
||||
// // Delete old local storage keys that have been subsumed elsewhere.
|
||||
// localStorage.removeItem("mapEnabled");
|
||||
// localStorage.removeItem("userDetails");
|
||||
// localStorage.removeItem("familyData");
|
||||
// };
|
||||
|
@ -7,7 +7,7 @@ import { nullToUndefined } from "@/utils/transform";
|
||||
import { LS_KEYS, setData } from "@ente/shared/storage/localStorage";
|
||||
import isElectron from "is-electron";
|
||||
import { z } from "zod";
|
||||
import type { BonusData, UserDetails } from "./user";
|
||||
import type { UserDetails } from "./user";
|
||||
import { syncUserDetails, userDetailsSnapshot } from "./user";
|
||||
|
||||
const PlanPeriod = z.enum(["month", "year"]);
|
||||
@ -79,6 +79,38 @@ export const FamilyData = z.object({
|
||||
*/
|
||||
export type FamilyData = z.infer<typeof FamilyData>;
|
||||
|
||||
const Bonus = z.object({
|
||||
/**
|
||||
* The type of the bonus.
|
||||
*/
|
||||
type: z.string(),
|
||||
/**
|
||||
* Amount of storage bonus (in bytes) added to the account.
|
||||
*/
|
||||
storage: z.number(),
|
||||
/**
|
||||
* Validity of the storage bonus. If it is 0, it is valid forever.
|
||||
*/
|
||||
validTill: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Details about an individual bonus applied for the user.
|
||||
*/
|
||||
export type Bonus = z.infer<typeof Bonus>;
|
||||
|
||||
export const BonusData = z.object({
|
||||
/**
|
||||
* List of bonuses applied for the user.
|
||||
*/
|
||||
storageBonuses: Bonus.array(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Information about bonuses applied for the user.
|
||||
*/
|
||||
export type BonusData = z.infer<typeof BonusData>;
|
||||
|
||||
/**
|
||||
* Zod schema for an individual plan received in the list of plans.
|
||||
*/
|
||||
@ -262,40 +294,6 @@ export const isSubscriptionStripe = (subscription: Subscription) =>
|
||||
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 (userDetails.familyData && isPartOfFamily(userDetails.familyData)) {
|
||||
const usage = getTotalFamilyUsage(userDetails.familyData);
|
||||
return usage > userDetails.familyData.storage + bonusStorage;
|
||||
} else {
|
||||
return (
|
||||
userDetails.usage > userDetails.subscription.storage + bonusStorage
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the user (represented by the given {@link userDetails}) is
|
||||
* part of a family plan.
|
||||
@ -374,3 +372,46 @@ export const leaveFamily = async () => {
|
||||
);
|
||||
return syncUserDetails();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return true if the given {@link Subscription} has expired, and is also beyond
|
||||
* the grace period.
|
||||
*/
|
||||
export const 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
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the bonuses whose type starts with "ADD_ON" applicable for the user
|
||||
* (represented by the given {@link userDetails}).
|
||||
*/
|
||||
export const userDetailsAddOnBonuses = (userDetails: UserDetails) =>
|
||||
userDetails.bonusData?.storageBonuses?.filter((bonus) =>
|
||||
bonus.type.startsWith("ADD_ON"),
|
||||
) ?? [];
|
||||
|
||||
/**
|
||||
* Return true if the user (represented by the given {@link userDetails}) has a
|
||||
* exceeded their storage quota (individual or of the family plan they are a
|
||||
* part of).
|
||||
*/
|
||||
export const hasExceededStorageQuota = (userDetails: UserDetails) => {
|
||||
let usage: number;
|
||||
let storage: number;
|
||||
if (isPartOfFamily(userDetails)) {
|
||||
usage = familyUsage(userDetails);
|
||||
storage = userDetails.familyData?.storage ?? 0;
|
||||
} else {
|
||||
usage = userDetails.usage;
|
||||
storage = userDetails.subscription?.storage ?? 0;
|
||||
}
|
||||
|
||||
const bonusStorage = userDetails.storageBonus ?? 0;
|
||||
return usage > storage + bonusStorage;
|
||||
};
|
||||
|
@ -3,23 +3,7 @@ import { getKV, setKV } from "@/base/kv";
|
||||
import { apiURL } from "@/base/origins";
|
||||
import { getData, LS_KEYS, setLSUser } from "@ente/shared/storage/localStorage";
|
||||
import { z } from "zod";
|
||||
import { FamilyData, Subscription } from "./plan";
|
||||
|
||||
const BonusData = z.object({
|
||||
/**
|
||||
* List of bonuses applied for the user.
|
||||
*/
|
||||
storageBonuses: z
|
||||
.object({
|
||||
type: z.string() /** The type of the bonus. */,
|
||||
})
|
||||
.array(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Information about bonuses applied to the user.
|
||||
*/
|
||||
export type BonusData = z.infer<typeof BonusData>;
|
||||
import { FamilyData, Subscription, BonusData } from "./plan";
|
||||
|
||||
/**
|
||||
* Zod schema for {@link UserDetails}
|
||||
|
@ -6,13 +6,11 @@ export enum LS_KEYS {
|
||||
KEY_ATTRIBUTES = "keyAttributes",
|
||||
ORIGINAL_KEY_ATTRIBUTES = "originalKeyAttributes",
|
||||
SUBSCRIPTION = "subscription",
|
||||
FAMILY_DATA = "familyData",
|
||||
IS_FIRST_LOGIN = "isFirstLogin",
|
||||
JUST_SIGNED_UP = "justSignedUp",
|
||||
SHOW_BACK_BUTTON = "showBackButton",
|
||||
EXPORT = "export",
|
||||
// LOGS = "logs",
|
||||
USER_DETAILS = "userDetails",
|
||||
// Migrated to (and only used by) useCollectionsSortByLocalState.
|
||||
COLLECTION_SORT_BY = "collectionSortBy",
|
||||
THEME = "theme",
|
||||
|
Loading…
x
Reference in New Issue
Block a user