| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- import {
- createContext,
- ReactNode,
- useCallback,
- useContext,
- useEffect,
- useMemo,
- useState,
- } from 'react'
- import { useTranslation } from 'react-i18next'
- import {
- CustomSubscription,
- ManagedGroupSubscription,
- MemberGroupSubscription,
- PaidSubscription,
- } from '../../../../../types/subscription/dashboard/subscription'
- import {
- Plan,
- PriceForDisplayData,
- } from '../../../../../types/subscription/plan'
- import { Institution } from '../../../../../types/institution'
- import getMeta from '../../../utils/meta'
- import {
- loadDisplayPriceWithTaxPromise,
- loadGroupDisplayPriceWithTaxForRecurlyPromise,
- loadGroupDisplayPriceWithTaxForStripePromise,
- } from '../util/recurly-pricing'
- import { isRecurlyLoaded } from '../util/is-recurly-loaded'
- import { SubscriptionDashModalIds } from '../../../../../types/subscription/dashboard/modal-ids'
- import { debugConsole } from '@/utils/debugging'
- import { formatCurrency } from '@/shared/utils/currency'
- import { ManagedInstitution } from '../../../../../types/subscription/dashboard/managed-institution'
- import { Publisher } from '../../../../../types/subscription/dashboard/publisher'
- import { formatTime } from '@/features/utils/format-date'
- type SubscriptionDashboardContextValue = {
- groupPlanToChangeToCode: string
- groupPlanToChangeToSize: string
- groupPlanToChangeToUsage: string
- groupPlanToChangeToPrice?: PriceForDisplayData
- groupPlanToChangeToPriceError?: boolean
- handleCloseModal: () => void
- handleOpenModal: (
- modalIdToOpen: SubscriptionDashModalIds,
- planCode?: string
- ) => void
- hasDisplayedSubscription: boolean
- hasValidActiveSubscription: boolean
- institutionMemberships?: Institution[]
- managedGroupSubscriptions: ManagedGroupSubscription[]
- memberGroupSubscriptions: MemberGroupSubscription[]
- managedInstitutions: ManagedInstitution[]
- managedPublishers: Publisher[]
- updateManagedInstitution: (institution: ManagedInstitution) => void
- modalIdShown?: SubscriptionDashModalIds
- personalSubscription?: PaidSubscription | CustomSubscription
- hasSubscription: boolean
- plans: Plan[]
- planCodeToChangeTo?: string
- queryingGroupPlanToChangeToPrice: boolean
- queryingIndividualPlansData: boolean
- recurlyLoadError: boolean
- setGroupPlanToChangeToCode: React.Dispatch<React.SetStateAction<string>>
- setGroupPlanToChangeToSize: React.Dispatch<React.SetStateAction<string>>
- setGroupPlanToChangeToUsage: React.Dispatch<React.SetStateAction<string>>
- setModalIdShown: React.Dispatch<
- React.SetStateAction<SubscriptionDashModalIds | undefined>
- >
- setPlanCodeToChangeTo: React.Dispatch<
- React.SetStateAction<string | undefined>
- >
- setRecurlyLoadError: React.Dispatch<React.SetStateAction<boolean>>
- showCancellation: boolean
- setShowCancellation: React.Dispatch<React.SetStateAction<boolean>>
- leavingGroupId?: string
- setLeavingGroupId: React.Dispatch<React.SetStateAction<string | undefined>>
- userCanExtendTrial: boolean
- getFormattedRenewalDate: () => string
- }
- export const SubscriptionDashboardContext = createContext<
- SubscriptionDashboardContextValue | undefined
- >(undefined)
- export function SubscriptionDashboardProvider({
- children,
- }: {
- children: ReactNode
- }) {
- const { i18n } = useTranslation()
- const [modalIdShown, setModalIdShown] = useState<
- SubscriptionDashModalIds | undefined
- >()
- const [recurlyLoadError, setRecurlyLoadError] = useState(false)
- const [showCancellation, setShowCancellation] = useState(false)
- const [plans, setPlans] = useState<Plan[]>([])
- const [queryingIndividualPlansData, setQueryingIndividualPlansData] =
- useState(true)
- const [planCodeToChangeTo, setPlanCodeToChangeTo] = useState<
- string | undefined
- >()
- const [groupPlanToChangeToSize, setGroupPlanToChangeToSize] = useState('10')
- const [groupPlanToChangeToCode, setGroupPlanToChangeToCode] =
- useState('collaborator')
- const [groupPlanToChangeToUsage, setGroupPlanToChangeToUsage] =
- useState('enterprise')
- const [
- queryingGroupPlanToChangeToPrice,
- setQueryingGroupPlanToChangeToPrice,
- ] = useState(false)
- const [groupPlanToChangeToPrice, setGroupPlanToChangeToPrice] =
- useState<PriceForDisplayData>()
- const [groupPlanToChangeToPriceError, setGroupPlanToChangeToPriceError] =
- useState(false)
- const [leavingGroupId, setLeavingGroupId] = useState<string | undefined>()
- const plansWithoutDisplayPrice = getMeta('ol-plans')
- const institutionMemberships = getMeta('ol-currentInstitutionsWithLicence')
- const personalSubscription = getMeta('ol-subscription')
- const userCanExtendTrial = getMeta('ol-userCanExtendTrial')
- const managedGroupSubscriptions = getMeta('ol-managedGroupSubscriptions')
- const memberGroupSubscriptions = getMeta('ol-memberGroupSubscriptions')
- const [managedInstitutions, setManagedInstitutions] = useState(
- getMeta('ol-managedInstitutions')
- )
- const managedPublishers = getMeta('ol-managedPublishers')
- const hasSubscription = getMeta('ol-hasSubscription')
- const recurlyApiKey = getMeta('ol-recurlyApiKey')
- const hasDisplayedSubscription = Boolean(
- institutionMemberships?.length > 0 ||
- personalSubscription ||
- memberGroupSubscriptions?.length > 0 ||
- managedGroupSubscriptions?.length > 0 ||
- managedInstitutions?.length > 0 ||
- managedPublishers?.length > 0
- )
- const hasValidActiveSubscription = Boolean(
- ['active', 'canceled'].includes(personalSubscription?.payment?.state) ||
- institutionMemberships?.length > 0 ||
- memberGroupSubscriptions?.length > 0
- )
- const getFormattedRenewalDate = useCallback(() => {
- if (
- !personalSubscription.payment.pausedAt ||
- !personalSubscription.payment.remainingPauseCycles
- ) {
- return personalSubscription.payment.nextPaymentDueAt
- }
- const pausedDate = new Date(personalSubscription.payment.pausedAt)
- pausedDate.setMonth(
- pausedDate.getMonth() + personalSubscription.payment.remainingPauseCycles
- )
- return formatTime(pausedDate, 'MMMM Do, YYYY')
- }, [personalSubscription])
- useEffect(() => {
- if (!isRecurlyLoaded()) {
- setRecurlyLoadError(true)
- } else if (recurlyApiKey) {
- recurly.configure(recurlyApiKey)
- }
- }, [recurlyApiKey, setRecurlyLoadError])
- useEffect(() => {
- if (
- isRecurlyLoaded() &&
- plansWithoutDisplayPrice &&
- personalSubscription?.payment
- ) {
- const { currency, taxRate } = personalSubscription.payment
- const fetchPlansDisplayPrices = async () => {
- for (const plan of plansWithoutDisplayPrice) {
- try {
- const priceData = await loadDisplayPriceWithTaxPromise(
- plan.planCode,
- currency,
- taxRate,
- i18n.language
- )
- if (priceData?.totalAsNumber !== undefined) {
- plan.displayPrice = formatCurrency(
- priceData.totalAsNumber,
- currency,
- i18n.language
- )
- }
- } catch (error) {
- debugConsole.error(error)
- }
- }
- setPlans(plansWithoutDisplayPrice)
- setQueryingIndividualPlansData(false)
- }
- fetchPlansDisplayPrices().catch(debugConsole.error)
- }
- }, [personalSubscription, plansWithoutDisplayPrice, i18n.language])
- useEffect(() => {
- if (
- !groupPlanToChangeToCode ||
- !groupPlanToChangeToSize ||
- !groupPlanToChangeToUsage ||
- !personalSubscription?.payment
- ) {
- return
- }
- let loadGroupDisplayPrice
- if (personalSubscription.service?.includes('stripe')) {
- loadGroupDisplayPrice = loadGroupDisplayPriceWithTaxForStripePromise
- } else if (isRecurlyLoaded()) {
- loadGroupDisplayPrice = loadGroupDisplayPriceWithTaxForRecurlyPromise
- } else {
- return
- }
- setQueryingGroupPlanToChangeToPrice(true)
- const { currency, taxRate } = personalSubscription.payment
- const fetchGroupDisplayPrice = async () => {
- setGroupPlanToChangeToPriceError(false)
- let priceData
- try {
- priceData = await loadGroupDisplayPrice(
- groupPlanToChangeToCode,
- currency,
- taxRate,
- groupPlanToChangeToSize,
- groupPlanToChangeToUsage,
- i18n.language
- )
- } catch (e) {
- debugConsole.error(e)
- setGroupPlanToChangeToPriceError(true)
- }
- setQueryingGroupPlanToChangeToPrice(false)
- setGroupPlanToChangeToPrice(priceData)
- }
- fetchGroupDisplayPrice()
- }, [
- groupPlanToChangeToUsage,
- groupPlanToChangeToSize,
- personalSubscription,
- groupPlanToChangeToCode,
- i18n.language,
- ])
- const updateManagedInstitution = useCallback(
- (institution: ManagedInstitution) => {
- setManagedInstitutions(institutions => {
- return [
- ...(institutions || []).map(i =>
- i.v1Id === institution.v1Id ? institution : i
- ),
- ]
- })
- },
- []
- )
- const handleCloseModal = useCallback(() => {
- setModalIdShown(undefined)
- setPlanCodeToChangeTo(undefined)
- }, [setModalIdShown, setPlanCodeToChangeTo])
- const handleOpenModal = useCallback(
- (id: SubscriptionDashModalIds, planCode?: string) => {
- setModalIdShown(id)
- setPlanCodeToChangeTo(planCode)
- },
- [setModalIdShown, setPlanCodeToChangeTo]
- )
- const value = useMemo<SubscriptionDashboardContextValue>(
- () => ({
- groupPlanToChangeToCode,
- groupPlanToChangeToPrice,
- groupPlanToChangeToPriceError,
- groupPlanToChangeToSize,
- groupPlanToChangeToUsage,
- handleCloseModal,
- handleOpenModal,
- hasDisplayedSubscription,
- hasValidActiveSubscription,
- institutionMemberships,
- managedGroupSubscriptions,
- memberGroupSubscriptions,
- managedInstitutions,
- managedPublishers,
- updateManagedInstitution,
- modalIdShown,
- personalSubscription,
- hasSubscription,
- plans,
- planCodeToChangeTo,
- queryingGroupPlanToChangeToPrice,
- queryingIndividualPlansData,
- recurlyLoadError,
- setGroupPlanToChangeToCode,
- setGroupPlanToChangeToSize,
- setGroupPlanToChangeToUsage,
- setModalIdShown,
- setPlanCodeToChangeTo,
- setRecurlyLoadError,
- showCancellation,
- setShowCancellation,
- leavingGroupId,
- setLeavingGroupId,
- userCanExtendTrial,
- getFormattedRenewalDate,
- }),
- [
- groupPlanToChangeToCode,
- groupPlanToChangeToPrice,
- groupPlanToChangeToPriceError,
- groupPlanToChangeToSize,
- groupPlanToChangeToUsage,
- handleCloseModal,
- handleOpenModal,
- hasDisplayedSubscription,
- hasValidActiveSubscription,
- institutionMemberships,
- managedGroupSubscriptions,
- memberGroupSubscriptions,
- managedInstitutions,
- managedPublishers,
- updateManagedInstitution,
- modalIdShown,
- personalSubscription,
- hasSubscription,
- plans,
- planCodeToChangeTo,
- queryingGroupPlanToChangeToPrice,
- queryingIndividualPlansData,
- recurlyLoadError,
- setGroupPlanToChangeToCode,
- setGroupPlanToChangeToSize,
- setGroupPlanToChangeToUsage,
- setModalIdShown,
- setPlanCodeToChangeTo,
- setRecurlyLoadError,
- showCancellation,
- setShowCancellation,
- leavingGroupId,
- setLeavingGroupId,
- userCanExtendTrial,
- getFormattedRenewalDate,
- ]
- )
- return (
- <SubscriptionDashboardContext.Provider value={value}>
- {children}
- </SubscriptionDashboardContext.Provider>
- )
- }
- export function useSubscriptionDashboardContext() {
- const context = useContext(SubscriptionDashboardContext)
- if (!context) {
- throw new Error(
- 'SubscriptionDashboardContext is only available inside SubscriptionDashboardProvider'
- )
- }
- return context
- }
|