From 32f53eb9f377eb65ea7e5e845fc9765251dce6a4 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 7 Nov 2024 13:35:40 +0530 Subject: [PATCH] vis --- .../photos/src/components/Sidebar/index.tsx | 25 ++++--- .../components/pages/gallery/PlanSelector.tsx | 65 +++++++------------ web/apps/photos/src/pages/gallery.tsx | 18 +++-- web/packages/new/photos/services/plan.ts | 25 ++++--- web/packages/new/photos/services/user.ts | 13 ++-- 5 files changed, 69 insertions(+), 77 deletions(-) diff --git a/web/apps/photos/src/components/Sidebar/index.tsx b/web/apps/photos/src/components/Sidebar/index.tsx index fefed27c1a..e5a335c6b0 100644 --- a/web/apps/photos/src/components/Sidebar/index.tsx +++ b/web/apps/photos/src/components/Sidebar/index.tsx @@ -238,20 +238,19 @@ const SubscriptionStatus: React.FC = ({ const handleClick = useMemo(() => { const eventHandler: MouseEventHandler = (e) => { e.stopPropagation(); - if (userDetails) { - if (isSubscriptionActive(userDetails.subscription)) { - if (hasExceededStorageQuota(userDetails)) { - showPlanSelectorModal(); - } + + if (isSubscriptionActive(userDetails.subscription)) { + if (hasExceededStorageQuota(userDetails)) { + showPlanSelectorModal(); + } + } else { + if ( + isSubscriptionStripe(userDetails.subscription) && + isSubscriptionPastDue(userDetails.subscription) + ) { + redirectToCustomerPortal(); } else { - if ( - isSubscriptionStripe(userDetails.subscription) && - isSubscriptionPastDue(userDetails.subscription) - ) { - redirectToCustomerPortal(); - } else { - showPlanSelectorModal(); - } + showPlanSelectorModal(); } } }; diff --git a/web/apps/photos/src/components/pages/gallery/PlanSelector.tsx b/web/apps/photos/src/components/pages/gallery/PlanSelector.tsx index cb3796e4b9..f7a92cb85a 100644 --- a/web/apps/photos/src/components/pages/gallery/PlanSelector.tsx +++ b/web/apps/photos/src/components/pages/gallery/PlanSelector.tsx @@ -1,4 +1,5 @@ import { genericRetriableErrorDialogAttributes } from "@/base/components/utils/dialog"; +import type { ModalVisibilityProps } from "@/base/components/utils/modal"; import log from "@/base/log"; import { useUserDetailsSnapshot } from "@/new/photos/components/utils/use-snapshot"; import type { @@ -13,9 +14,10 @@ import { cancelStripeSubscription, getPlansData, isSubscriptionActive, - isSubscriptionActiveFree, isSubscriptionActivePaid, isSubscriptionCancelled, + isSubscriptionForPlan, + isSubscriptionFree, isSubscriptionStripe, planUsage, redirectToCustomerPortal, @@ -55,24 +57,24 @@ import { Trans } from "react-i18next"; import { getFamilyPortalRedirectURL } from "services/userService"; import { SetLoading } from "types/gallery"; -interface PlanSelectorProps { - modalView: boolean; - closeModal: any; +type PlanSelectorProps = ModalVisibilityProps & { setLoading: SetLoading; -} +}; -function PlanSelector(props: PlanSelectorProps) { +const PlanSelector: React.FC = ({ + open, + onClose, + setLoading, +}: PlanSelectorProps) => { const fullScreen = useMediaQuery(useTheme().breakpoints.down("sm")); - if (!props.modalView) { + if (!open) { return <>; } return ( ({ width: { sm: "391px" }, @@ -81,23 +83,20 @@ function PlanSelector(props: PlanSelectorProps) { }), }} > - + ); -} +}; export default PlanSelector; interface PlanSelectorCardProps { - closeModal: any; + onClose: () => void; setLoading: SetLoading; } const PlanSelectorCard: React.FC = ({ - closeModal, + onClose, setLoading, }) => { const { showMiniDialog, setDialogMessage } = useContext(AppContext); @@ -126,16 +125,11 @@ const PlanSelectorCard: React.FC = ({ setLoading(true); const plansData = await getPlansData(); const { plans } = plansData; - if (isSubscriptionActive(subscription)) { - const planNotListed = - plans.filter((plan) => - isUserSubscribedPlan(plan, subscription), - ).length === 0; - if ( - subscription && - !isSubscriptionActiveFree(subscription) && - planNotListed - ) { + if (subscription && isSubscriptionActive(subscription)) { + const activePlan = plans.find((plan) => + isSubscriptionForPlan(subscription, plan), + ); + if (!isSubscriptionFree(subscription) && !activePlan) { plans.push({ id: subscription.productID, storage: subscription.storage, @@ -150,7 +144,7 @@ const PlanSelectorCard: React.FC = ({ setPlansData(plansData); } catch (e) { log.error("plan selector modal open failed", e); - closeModal(); + onClose(); showMiniDialog(genericRetriableErrorDialogAttributes()); } finally { setLoading(false); @@ -197,7 +191,7 @@ const PlanSelectorCard: React.FC = ({ }); } finally { setLoading(false); - closeModal(); + onClose(); } }, }, @@ -234,7 +228,7 @@ const PlanSelectorCard: React.FC = ({ const commonCardData = { subscription, addOnBonuses, - closeModal, + closeModal: onClose, planPeriod, togglePeriod, setLoading, @@ -247,7 +241,7 @@ const PlanSelectorCard: React.FC = ({ onPlanSelect={onPlanSelect} subscription={subscription} hasAddOnBonus={addOnBonuses.length > 0} - closeModal={closeModal} + closeModal={onClose} /> ); @@ -525,15 +519,6 @@ const Plans = ({ ); }; -function isUserSubscribedPlan(plan: Plan, subscription: Subscription) { - return ( - isSubscriptionActive(subscription) && - (plan.stripeID === subscription.productID || - plan.iosID === subscription.productID || - plan.androidID === subscription.productID) - ); -} - const isPopularPlan = (plan: Plan) => plan.storage === 100 * 1024 * 1024 * 1024; /* 100 GB */ diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 053af03166..249e2caa3e 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -50,7 +50,7 @@ import { import type { SearchOption } from "@/new/photos/services/search/types"; import { initSettings } from "@/new/photos/services/settings"; import { - initUserDetails, + initUserDetailsOrTriggerSync, userDetailsSnapshot, } from "@/new/photos/services/user"; import { useAppContext } from "@/new/photos/types/context"; @@ -186,7 +186,6 @@ export default function Gallery() { collectionID: 0, context: { mode: "albums", collectionID: ALL_SECTION }, }); - const [planModalView, setPlanModalView] = useState(false); const [blockingLoad, setBlockingLoad] = useState(false); const [collectionNamerAttributes, setCollectionNamerAttributes] = useState(null); @@ -248,8 +247,6 @@ export default function Gallery() { const [fixCreationTimeAttributes, setFixCreationTimeAttributes] = useState(null); - const showPlanSelectorModal = () => setPlanModalView(true); - const [uploadTypeSelectorView, setUploadTypeSelectorView] = useState(false); const [uploadTypeSelectorIntent, setUploadTypeSelectorIntent] = useState("upload"); @@ -296,6 +293,8 @@ export default function Gallery() { const [collectionSelectorAttributes, setCollectionSelectorAttributes] = useState(); + const { show: showPlanSelector, props: planSelectorVisibilityProps } = + useModalVisibility(); const { show: showWhatsNew, props: whatsNewVisibilityProps } = useModalVisibility(); @@ -353,13 +352,13 @@ export default function Gallery() { return; } initSettings(); - await initUserDetails(); + await initUserDetailsOrTriggerSync(); await downloadManager.init(token); setupSelectAllKeyBoardShortcutHandler(); dispatch({ type: "showAll" }); setIsFirstLoad(isFirstLogin()); if (justSignedUp()) { - setPlanModalView(true); + showPlanSelector(); } setIsFirstLogin(false); const user = getData(LS_KEYS.USER); @@ -500,7 +499,7 @@ export default function Gallery() { openCollectionSelector || collectionNamerView || fixCreationTimeView || - planModalView || + planSelectorVisibilityProps.open || exportModalView || authenticateUserModalView || isPhotoSwipeOpen || @@ -886,7 +885,7 @@ export default function Gallery() { dispatch({ @@ -929,8 +928,7 @@ export default function Gallery() { )} setPlanModalView(false)} + {...planSelectorVisibilityProps} setLoading={setBlockingLoad} /> { * Return true if the given {@link Subscription} has not expired. */ export const isSubscriptionActive = (subscription: Subscription) => - subscription && subscription.expiryTime > Date.now() * 1000; + subscription.expiryTime > Date.now() * 1000; /** - * Return true if the given active {@link Subscription} is for a paid plan. + * Return true if the given {@link Subscription} is active and for a paid plan. */ export const isSubscriptionActivePaid = (subscription: Subscription) => - subscription && - isSubscriptionActive(subscription) && - subscription.productID != "free"; + isSubscriptionActive(subscription) && subscription.productID != "free"; /** - * Return true if the given active {@link Subscription} is for a free plan. + * Return true if the given {@link Subscription} is for a free plan. */ -export const isSubscriptionActiveFree = (subscription: Subscription) => - subscription && - isSubscriptionActive(subscription) && +export const isSubscriptionFree = (subscription: Subscription) => subscription.productID == "free"; +/** + * Return true if the given {@link Subscription} is active and for the given + * {@link Plan}. + */ +export const isSubscriptionForPlan = (subscription: Subscription, plan: Plan) => + plan.stripeID === subscription.productID || + plan.iosID === subscription.productID || + plan.androidID === subscription.productID; + /** * Return true if the given {@link Subscription} is using Stripe. */ export const isSubscriptionStripe = (subscription: Subscription) => - subscription && subscription.paymentProvider == "stripe"; + subscription.paymentProvider == "stripe"; /** * Return true if the given {@link Subscription} has the cancelled attribute. diff --git a/web/packages/new/photos/services/user.ts b/web/packages/new/photos/services/user.ts index 551f09f599..5a1835d8fc 100644 --- a/web/packages/new/photos/services/user.ts +++ b/web/packages/new/photos/services/user.ts @@ -48,14 +48,19 @@ export const logoutUserDetails = () => { }; /** - * Read in the locally persisted settings into memory, but otherwise do not - * initate any network requests to fetch the latest values. + * Read in the locally persisted settings into memory, otherwise initate a + * network requests to fetch the latest values (but don't wait for it to + * complete). * * This assumes that the user is already logged in. */ -export const initUserDetails = async () => { +export const initUserDetailsOrTriggerSync = async () => { const saved = await getKV("userDetails"); - if (saved) setUserDetailsSnapshot(UserDetails.parse(saved)); + if (saved) { + setUserDetailsSnapshot(UserDetails.parse(saved)); + } else { + void syncUserDetails(); + } }; /**