flexier parsing

This commit is contained in:
Manav Rathi
2024-11-07 12:30:04 +05:30
parent 93edaed4fc
commit 33beb2824a
5 changed files with 44 additions and 25 deletions

View File

@@ -111,7 +111,7 @@ const IndividualSubscriptionCardContent: React.FC<
IndividualSubscriptionCardContentProps
> = ({ userDetails }) => {
const totalStorage =
userDetails.subscription.storage + (userDetails.storageBonus ?? 0);
userDetails.subscription.storage + userDetails.storageBonus;
return (
<>
<StorageSection storage={totalStorage} usage={userDetails.usage} />
@@ -230,7 +230,7 @@ const FamilySubscriptionCardContent: React.FC<
}
}, [userDetails]);
const totalStorage =
userDetails.familyData.storage + (userDetails.storageBonus ?? 0);
userDetails.familyData.storage + userDetails.storageBonus;
return (
<>

View File

@@ -105,8 +105,8 @@ const PlanSelectorCard: React.FC<PlanSelectorCardProps> = ({
const userDetails = useUserDetailsSnapshot();
const [plansData, setPlansData] = useState<PlansData | undefined>();
const [planPeriod, setPlanPeriod] = useState<PlanPeriod>(
userDetails?.subscription?.period || "month",
const [planPeriod, setPlanPeriod] = useState<PlanPeriod | undefined>(
userDetails?.subscription?.period,
);
const usage = userDetails ? planUsage(userDetails) : 0;
@@ -443,9 +443,7 @@ function PaidSubscriptionPlanSelectorCard({
function PeriodToggler({ planPeriod, togglePeriod }) {
const handleChange = (_, newPlanPeriod: PlanPeriod) => {
if (newPlanPeriod !== null && newPlanPeriod !== planPeriod) {
togglePeriod();
}
if (newPlanPeriod !== planPeriod) togglePeriod();
};
return (

View File

@@ -10,28 +10,40 @@ import { z } from "zod";
import type { UserDetails } from "./user";
import { syncUserDetails, userDetailsSnapshot } from "./user";
const PlanPeriod = z.enum(["month", "year"]);
/**
* Validity of the plan.
*/
export type PlanPeriod = z.infer<typeof PlanPeriod>;
export type PlanPeriod = "month" | "year";
export const Subscription = z.object({
/**
* Store-specific ID of the product ("plan") that the user has subscribed
* to. e.g. if the user has subscribed to a plan using Stripe, then this
* will be the stripeID of the corresponding {@link Plan}.
*
* For free plans, the productID will be the constant "free".
*/
productID: z.string(),
/**
* Storage (in bytes) that the user can use.
*/
storage: z.number(),
/**
* Epoch microseconds indicating the time until which the user's
* subscription is valid.
*/
expiryTime: z.number(),
paymentProvider: z.string(),
price: z.string(),
period: z
.string()
.transform((p) => (p == "month" || p == "year" ? p : undefined)),
attributes: z
.object({
isCancelled: z.boolean().nullish().transform(nullToUndefined),
})
.nullish()
.transform(nullToUndefined),
price: z.string(),
// TODO: We get back subscriptions without a period on cancel / reactivate.
// Handle them better, or remove this TODO.
period: z.enum(["month", "year", ""]).transform((s) => (s ? s : "month")),
});
/**
@@ -121,7 +133,9 @@ const Plan = z.object({
stripeID: z.string().nullish().transform(nullToUndefined),
storage: z.number(),
price: z.string(),
period: PlanPeriod,
period: z
.string()
.transform((p) => (p == "month" || p == "year" ? p : undefined)),
});
/**
@@ -131,7 +145,7 @@ export type Plan = z.infer<typeof Plan>;
const PlansData = z.object({
freePlan: z.object({
/* Number of bytes available in the free plan */
/* Number of bytes available in the free plan. */
storage: z.number(),
}),
plans: z.array(Plan),
@@ -399,9 +413,8 @@ export const hasExceededStorageQuota = (userDetails: UserDetails) => {
storage = userDetails.familyData?.storage ?? 0;
} else {
usage = userDetails.usage;
storage = userDetails.subscription?.storage ?? 0;
storage = userDetails.subscription.storage;
}
const bonusStorage = userDetails.storageBonus ?? 0;
return usage > storage + bonusStorage;
return usage > storage + userDetails.storageBonus;
};

View File

@@ -1,9 +1,10 @@
import { authenticatedRequestHeaders, ensureOk } from "@/base/http";
import { getKV, setKV } from "@/base/kv";
import { apiURL } from "@/base/origins";
import { nullishToZero, nullToUndefined } from "@/utils/transform";
import { getData, LS_KEYS, setLSUser } from "@ente/shared/storage/localStorage";
import { z } from "zod";
import { FamilyData, Subscription, BonusData } from "./plan";
import { BonusData, FamilyData, Subscription } from "./plan";
/**
* Zod schema for {@link UserDetails}
@@ -11,11 +12,11 @@ import { FamilyData, Subscription, BonusData } from "./plan";
const UserDetails = z.object({
email: z.string(),
usage: z.number(),
fileCount: z.number().optional(),
fileCount: z.number().nullish().transform(nullishToZero),
subscription: Subscription,
familyData: FamilyData.optional(),
storageBonus: z.number().optional(),
bonusData: BonusData.optional(),
familyData: FamilyData.nullish().transform(nullToUndefined),
storageBonus: z.number().nullish().transform(nullishToZero),
bonusData: BonusData.nullish().transform(nullToUndefined),
});
export type UserDetails = z.infer<typeof UserDetails>;

View File

@@ -1,3 +1,10 @@
/** Convert `null` to `undefined`, passthrough everything else unchanged. */
/**
* Convert `null` to `undefined`, passthrough everything else unchanged.
*/
export const nullToUndefined = <T>(v: T | null | undefined): T | undefined =>
v === null ? undefined : v;
/**
* Convert `null` and `undefined` to `0`, passthrough everything else unchanged.
*/
export const nullishToZero = (v: number | null | undefined) => v ?? 0;