diff --git a/packages/files-ui/src/Components/Elements/Notifications/NotificationsDropdown.tsx b/packages/files-ui/src/Components/Elements/Notifications/NotificationsDropdown.tsx index 61485236f7..fd36185ef9 100644 --- a/packages/files-ui/src/Components/Elements/Notifications/NotificationsDropdown.tsx +++ b/packages/files-ui/src/Components/Elements/Notifications/NotificationsDropdown.tsx @@ -77,6 +77,7 @@ export interface Notification { title: string createdAt: number onClick?: () => void + dismissOnClick?: boolean } const NotificationsDropdown = () => { diff --git a/packages/files-ui/src/Components/Layouts/AppHeader.tsx b/packages/files-ui/src/Components/Layouts/AppHeader.tsx index 1acca783a0..8917ea255d 100644 --- a/packages/files-ui/src/Components/Layouts/AppHeader.tsx +++ b/packages/files-ui/src/Components/Layouts/AppHeader.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useState } from "react" import { createStyles, makeStyles, useThemeSwitcher } from "@chainsafe/common-theme" import clsx from "clsx" -import { Link, Typography, ChainsafeFilesLogo, HamburgerMenu, Button, SearchIcon } from "@chainsafe/common-components" +import { Link, Typography, ChainsafeFilesLogo, HamburgerMenu, Button, SearchIcon, useHistory } from "@chainsafe/common-components" import { ROUTE_LINKS } from "../FilesRoutes" import SearchModule from "../Modules/SearchModule" import { Trans } from "@lingui/macro" @@ -10,6 +10,7 @@ import { CSFTheme } from "../../Themes/types" import { useFilesApi } from "../../Contexts/FilesApiContext" import BetaModal from "../Elements/BetaModal" import NotificationsDropdown from "../Elements/Notifications/NotificationsDropdown" +import { useBilling } from "../../Contexts/BillingContext" const useStyles = makeStyles( ({ palette, animation, breakpoints, constants, zIndex }: CSFTheme) => { @@ -140,11 +141,13 @@ interface IAppHeader { const AppHeader = ({ navOpen, setNavOpen }: IAppHeader) => { const { desktop } = useThemeSwitcher() + const { isBillingEnabled } = useBilling() const classes = useStyles() const { isLoggedIn, secured } = useFilesApi() const { publicKey, isNewDevice, shouldInitializeAccount } = useThresholdKey() const [searchActive, setSearchActive] = useState(false) const [isBetaModalOpen, setIsBetaModalOpen] = useState(false) + const { redirect } = useHistory() const onReportBugClick = useCallback(() => { window.open(ROUTE_LINKS.DiscordInvite, "_blank") @@ -154,6 +157,10 @@ const AppHeader = ({ navOpen, setNavOpen }: IAppHeader) => { setIsBetaModalOpen(true) }, []) + const onUpgradeClick = useCallback(() => { + redirect(ROUTE_LINKS.SettingsPath("plan")) + }, [redirect]) + return (
{ > Report a bug - + } + {isBillingEnabled && }
diff --git a/packages/files-ui/src/Contexts/BillingContext.tsx b/packages/files-ui/src/Contexts/BillingContext.tsx index 253e962a7e..48e80de930 100644 --- a/packages/files-ui/src/Contexts/BillingContext.tsx +++ b/packages/files-ui/src/Contexts/BillingContext.tsx @@ -68,16 +68,21 @@ const BillingProvider = ({ children }: BillingContextProps) => { const { filesApiClient, isLoggedIn, accountRestricted } = useFilesApi() const { redirect } = useHistory() const { addNotification, removeNotification } = useNotifications() - const { refreshBuckets } = useFiles() + const { refreshBuckets, storageSummary } = useFiles() const [currentSubscription, setCurrentSubscription] = useState() const [defaultCard, setDefaultCard] = useState(undefined) const [invoices, setInvoices] = useState() const isPendingInvoice = useMemo(() => currentSubscription?.status === "pending_update", [currentSubscription]) const openInvoice = useMemo(() => invoices?.find((i) => i.status === "open"), [invoices]) const [restrictedNotification, setRestrictedNotification] = useState() + const [upgradeNotification, setUpgradeNotification] = useState() const [unpaidInvoiceNotification, setUnpaidInvoiceNotification] = useState() const [cardExpiringNotification, setCardExpiringNotification] = useState() const [isBillingEnabled, setIsBillingEnabled] = useState(false) + const shouldProposeUpgrade = useMemo(() => storageSummary + ? storageSummary.used_storage > storageSummary.total_storage * 0.75 + : false + , [storageSummary]) const refreshInvoices = useCallback(() => { if (!currentSubscription) return @@ -118,6 +123,24 @@ const BillingProvider = ({ children }: BillingContextProps) => { } }, [accountRestricted, addNotification, redirect, removeNotification, restrictedNotification]) + useEffect(() => { + if (shouldProposeUpgrade && !upgradeNotification) { + const notif = addNotification({ + createdAt: dayjs().unix(), + title: isBillingEnabled + ? t`Space running low. Upgrade here.` + : t`Space running low. Join the beta to upgrade.`, + onClick: () => { + isBillingEnabled + ? redirect(ROUTE_LINKS.SettingsPath("plan")) + : window.open(ROUTE_LINKS.SubscriptionWhitelistForm, "_blank") + }, + dismissOnClick: true + }) + setUpgradeNotification(notif) + } + }, [addNotification, isBillingEnabled, redirect, shouldProposeUpgrade, upgradeNotification]) + useEffect(() => { if (!!openInvoice && !unpaidInvoiceNotification) { const notif = addNotification({ diff --git a/packages/files-ui/src/Contexts/NotificationsContext.tsx b/packages/files-ui/src/Contexts/NotificationsContext.tsx index dd3b9ea73c..6c501816ca 100644 --- a/packages/files-ui/src/Contexts/NotificationsContext.tsx +++ b/packages/files-ui/src/Contexts/NotificationsContext.tsx @@ -17,18 +17,22 @@ const NotificationsContext = React.createContext { const [notifications, setNotifications] = useState([]) + const removeNotification = useCallback((id: string) => { + setNotifications(notifications.filter((notification) => notification.id !== id)) + }, [notifications]) + const addNotification = useCallback((notification: Omit) => { const id = uuidv4() setNotifications([...notifications, { + ...notification, id, - ...notification + onClick: () => { + notification.dismissOnClick && removeNotification(id) + notification.onClick && notification.onClick() + } }]) return id - }, [notifications]) - - const removeNotification = useCallback((id: string) => { - setNotifications(notifications.filter((notification) => notification.id !== id)) - }, [notifications]) + }, [notifications, removeNotification]) return (