diff --git a/web/apps/photos/src/components/SubscriptionCard.tsx b/web/apps/photos/src/components/SubscriptionCard.tsx
index 071a0e798b..b389edd62b 100644
--- a/web/apps/photos/src/components/SubscriptionCard.tsx
+++ b/web/apps/photos/src/components/SubscriptionCard.tsx
@@ -3,6 +3,7 @@ import type { ButtonishProps } from "@/base/components/mui";
import { UnstyledButton } from "@/new/photos/components/UnstyledButton";
import type { UserDetails } from "@/new/photos/services/user-details";
import {
+ familyMemberStorageLimit,
familyUsage,
isPartOfFamilyWithOtherMembers,
} from "@/new/photos/services/user-details";
@@ -118,42 +119,63 @@ interface SubscriptionCardContentOverlayProps {
export const SubscriptionCardContentOverlay: React.FC<
SubscriptionCardContentOverlayProps
-> = ({ userDetails }) => (
-
-
- {isPartOfFamilyWithOtherMembers(userDetails) ? (
-
- ) : (
-
- )}
-
-
-);
-
-const IndividualSubscriptionCardContents: React.FC<
- SubscriptionCardContentOverlayProps
> = ({ userDetails }) => {
- const totalStorage =
- userDetails.subscription.storage + userDetails.storageBonus;
+ const inFamily = isPartOfFamilyWithOtherMembers(userDetails);
+ const storageLimit = inFamily
+ ? familyMemberStorageLimit(userDetails)
+ : undefined;
return (
- <>
-
-
- >
+
+
+ {inFamily ? (
+ storageLimit ? (
+
+ ) : (
+
+ )
+ ) : (
+
+ )}
+
+
);
};
+type UserSubscriptionCardContentsProps = SubscriptionCardContentOverlayProps & {
+ totalStorage: number;
+};
+
+const UserSubscriptionCardContents: React.FC<
+ UserSubscriptionCardContentsProps
+> = ({ userDetails, totalStorage }) => (
+ <>
+
+
+ >
+);
+
interface StorageSectionProps {
usage: number;
storage: number;
diff --git a/web/packages/new/photos/services/user-details.ts b/web/packages/new/photos/services/user-details.ts
index a4b96d0f21..79d0ea9c2c 100644
--- a/web/packages/new/photos/services/user-details.ts
+++ b/web/packages/new/photos/services/user-details.ts
@@ -56,6 +56,12 @@ const FamilyMember = z.object({
* Email address of the family member.
*/
email: z.string(),
+ /**
+ * `true` if this is the admin.
+ *
+ * This field will not be sent for invited members until they accept.
+ */
+ isAdmin: z.boolean().nullish().transform(nullToUndefined),
/**
* Storage used by the family member.
*
@@ -63,11 +69,13 @@ const FamilyMember = z.object({
*/
usage: z.number().nullish().transform(nullToUndefined),
/**
- * `true` if this is the admin.
+ * Storage limit allocated to the family member.
*
- * This field will not be sent for invited members until they accept.
+ * This field will not be present unless the admin for the family plan has
+ * configured a member specific limit, in which case this will be the limit
+ * (in bytes) specifying the storage which this member can use.
*/
- isAdmin: z.boolean().nullish().transform(nullToUndefined),
+ storageLimit: z.number().nullish().transform(nullToUndefined),
});
type FamilyMember = z.infer;
@@ -484,6 +492,14 @@ export const isPartOfFamilyWithOtherMembers = (userDetails: UserDetails) =>
export const isFamilyAdmin = (userDetails: UserDetails) =>
userDetails.email == familyAdminEmail(userDetails);
+/**
+ * Return the member specific storage limit for the user (represented by the
+ * given {@link userDetails}).
+ */
+export const familyMemberStorageLimit = (userDetails: UserDetails) =>
+ userDetails.familyData?.members.find((m) => m.email == userDetails.email)
+ ?.storageLimit;
+
/**
* Return the email of the admin for the family plan, if any, that the user
* (represented by the given {@link userDetails}) is a part of.