subscription-dashboard-context.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. import {
  2. createContext,
  3. ReactNode,
  4. useCallback,
  5. useContext,
  6. useEffect,
  7. useMemo,
  8. useState,
  9. } from 'react'
  10. import {
  11. CustomSubscription,
  12. ManagedGroupSubscription,
  13. MemberGroupSubscription,
  14. RecurlySubscription,
  15. } from '../../../../../types/subscription/dashboard/subscription'
  16. import {
  17. Plan,
  18. PriceForDisplayData,
  19. } from '../../../../../types/subscription/plan'
  20. import { Institution } from '../../../../../types/institution'
  21. import { Institution as ManagedInstitution } from '../components/dashboard/managed-institutions'
  22. import { Publisher as ManagedPublisher } from '../components/dashboard/managed-publishers'
  23. import getMeta from '../../../utils/meta'
  24. import {
  25. loadDisplayPriceWithTaxPromise,
  26. loadGroupDisplayPriceWithTaxPromise,
  27. } from '../util/recurly-pricing'
  28. import { isRecurlyLoaded } from '../util/is-recurly-loaded'
  29. import { SubscriptionDashModalIds } from '../../../../../types/subscription/dashboard/modal-ids'
  30. type SubscriptionDashboardContextValue = {
  31. groupPlanToChangeToCode: string
  32. groupPlanToChangeToSize: string
  33. groupPlanToChangeToUsage: string
  34. groupPlanToChangeToPrice?: PriceForDisplayData
  35. groupPlanToChangeToPriceError?: boolean
  36. handleCloseModal: () => void
  37. handleOpenModal: (
  38. modalIdToOpen: SubscriptionDashModalIds,
  39. planCode?: string
  40. ) => void
  41. hasDisplayedSubscription: boolean
  42. institutionMemberships?: Institution[]
  43. managedGroupSubscriptions: ManagedGroupSubscription[]
  44. memberGroupSubscriptions: MemberGroupSubscription[]
  45. managedInstitutions: ManagedInstitution[]
  46. managedPublishers: ManagedPublisher[]
  47. updateManagedInstitution: (institution: ManagedInstitution) => void
  48. modalIdShown?: SubscriptionDashModalIds
  49. personalSubscription?: RecurlySubscription | CustomSubscription
  50. hasSubscription: boolean
  51. plans: Plan[]
  52. planCodeToChangeTo?: string
  53. queryingGroupPlanToChangeToPrice: boolean
  54. queryingIndividualPlansData: boolean
  55. recurlyLoadError: boolean
  56. setGroupPlanToChangeToCode: React.Dispatch<React.SetStateAction<string>>
  57. setGroupPlanToChangeToSize: React.Dispatch<React.SetStateAction<string>>
  58. setGroupPlanToChangeToUsage: React.Dispatch<React.SetStateAction<string>>
  59. setModalIdShown: React.Dispatch<
  60. React.SetStateAction<SubscriptionDashModalIds | undefined>
  61. >
  62. setPlanCodeToChangeTo: React.Dispatch<
  63. React.SetStateAction<string | undefined>
  64. >
  65. setRecurlyLoadError: React.Dispatch<React.SetStateAction<boolean>>
  66. showCancellation: boolean
  67. setShowCancellation: React.Dispatch<React.SetStateAction<boolean>>
  68. showChangePersonalPlan: boolean
  69. setShowChangePersonalPlan: React.Dispatch<React.SetStateAction<boolean>>
  70. leavingGroupId?: string
  71. setLeavingGroupId: React.Dispatch<React.SetStateAction<string | undefined>>
  72. }
  73. export const SubscriptionDashboardContext = createContext<
  74. SubscriptionDashboardContextValue | undefined
  75. >(undefined)
  76. export function SubscriptionDashboardProvider({
  77. children,
  78. }: {
  79. children: ReactNode
  80. }) {
  81. const [modalIdShown, setModalIdShown] = useState<
  82. SubscriptionDashModalIds | undefined
  83. >()
  84. const [recurlyLoadError, setRecurlyLoadError] = useState(false)
  85. const [showCancellation, setShowCancellation] = useState(false)
  86. const [showChangePersonalPlan, setShowChangePersonalPlan] = useState(false)
  87. const [plans, setPlans] = useState([])
  88. const [queryingIndividualPlansData, setQueryingIndividualPlansData] =
  89. useState(true)
  90. const [planCodeToChangeTo, setPlanCodeToChangeTo] = useState<
  91. string | undefined
  92. >()
  93. const [groupPlanToChangeToSize, setGroupPlanToChangeToSize] = useState('10')
  94. const [groupPlanToChangeToCode, setGroupPlanToChangeToCode] =
  95. useState('collaborator')
  96. const [groupPlanToChangeToUsage, setGroupPlanToChangeToUsage] =
  97. useState('enterprise')
  98. const [
  99. queryingGroupPlanToChangeToPrice,
  100. setQueryingGroupPlanToChangeToPrice,
  101. ] = useState(false)
  102. const [groupPlanToChangeToPrice, setGroupPlanToChangeToPrice] =
  103. useState<PriceForDisplayData>()
  104. const [groupPlanToChangeToPriceError, setGroupPlanToChangeToPriceError] =
  105. useState(false)
  106. const [leavingGroupId, setLeavingGroupId] = useState<string | undefined>()
  107. const plansWithoutDisplayPrice = getMeta('ol-plans')
  108. const institutionMemberships: Institution[] = getMeta(
  109. 'ol-currentInstitutionsWithLicence'
  110. )
  111. const personalSubscription = getMeta('ol-subscription')
  112. const managedGroupSubscriptions: ManagedGroupSubscription[] = getMeta(
  113. 'ol-managedGroupSubscriptions'
  114. )
  115. const memberGroupSubscriptions: MemberGroupSubscription[] = getMeta(
  116. 'ol-memberGroupSubscriptions'
  117. )
  118. const [managedInstitutions, setManagedInstitutions] = useState<
  119. ManagedInstitution[]
  120. >(getMeta('ol-managedInstitutions'))
  121. const managedPublishers = getMeta('ol-managedPublishers')
  122. const hasSubscription = getMeta('ol-hasSubscription')
  123. const recurlyApiKey = getMeta('ol-recurlyApiKey')
  124. const hasDisplayedSubscription = Boolean(
  125. institutionMemberships?.length > 0 ||
  126. personalSubscription ||
  127. memberGroupSubscriptions?.length > 0 ||
  128. managedGroupSubscriptions?.length > 0 ||
  129. managedInstitutions?.length > 0 ||
  130. managedPublishers?.length > 0
  131. )
  132. useEffect(() => {
  133. if (!isRecurlyLoaded()) {
  134. setRecurlyLoadError(true)
  135. } else if (recurlyApiKey) {
  136. recurly.configure(recurlyApiKey)
  137. }
  138. }, [recurlyApiKey, setRecurlyLoadError])
  139. useEffect(() => {
  140. if (
  141. isRecurlyLoaded() &&
  142. plansWithoutDisplayPrice &&
  143. personalSubscription?.recurly
  144. ) {
  145. const { currency, taxRate } = personalSubscription.recurly
  146. const fetchPlansDisplayPrices = async () => {
  147. for (const plan of plansWithoutDisplayPrice) {
  148. try {
  149. const priceData = await loadDisplayPriceWithTaxPromise(
  150. plan.planCode,
  151. currency,
  152. taxRate
  153. )
  154. if (priceData?.totalForDisplay) {
  155. plan.displayPrice = priceData.totalForDisplay
  156. }
  157. } catch (error) {
  158. console.error(error)
  159. }
  160. }
  161. setPlans(plansWithoutDisplayPrice)
  162. setQueryingIndividualPlansData(false)
  163. }
  164. fetchPlansDisplayPrices().catch(console.error)
  165. }
  166. }, [personalSubscription, plansWithoutDisplayPrice])
  167. useEffect(() => {
  168. if (
  169. isRecurlyLoaded() &&
  170. groupPlanToChangeToCode &&
  171. groupPlanToChangeToSize &&
  172. groupPlanToChangeToUsage &&
  173. personalSubscription?.recurly
  174. ) {
  175. setQueryingGroupPlanToChangeToPrice(true)
  176. const { currency, taxRate } = personalSubscription.recurly
  177. const fetchGroupDisplayPrice = async () => {
  178. setGroupPlanToChangeToPriceError(false)
  179. let priceData
  180. try {
  181. priceData = await loadGroupDisplayPriceWithTaxPromise(
  182. groupPlanToChangeToCode,
  183. currency,
  184. taxRate,
  185. groupPlanToChangeToSize,
  186. groupPlanToChangeToUsage
  187. )
  188. } catch (e) {
  189. console.error(e)
  190. setGroupPlanToChangeToPriceError(true)
  191. }
  192. setQueryingGroupPlanToChangeToPrice(false)
  193. setGroupPlanToChangeToPrice(priceData)
  194. }
  195. fetchGroupDisplayPrice()
  196. }
  197. }, [
  198. groupPlanToChangeToUsage,
  199. groupPlanToChangeToSize,
  200. personalSubscription,
  201. groupPlanToChangeToCode,
  202. ])
  203. const updateManagedInstitution = useCallback(
  204. (institution: ManagedInstitution) => {
  205. setManagedInstitutions(institutions => {
  206. return [
  207. ...(institutions || []).map(i =>
  208. i.v1Id === institution.v1Id ? institution : i
  209. ),
  210. ]
  211. })
  212. },
  213. []
  214. )
  215. const handleCloseModal = useCallback(() => {
  216. setModalIdShown(undefined)
  217. setPlanCodeToChangeTo(undefined)
  218. }, [setModalIdShown, setPlanCodeToChangeTo])
  219. const handleOpenModal = useCallback(
  220. (id, planCode) => {
  221. setModalIdShown(id)
  222. setPlanCodeToChangeTo(planCode)
  223. },
  224. [setModalIdShown, setPlanCodeToChangeTo]
  225. )
  226. const value = useMemo<SubscriptionDashboardContextValue>(
  227. () => ({
  228. groupPlanToChangeToCode,
  229. groupPlanToChangeToPrice,
  230. groupPlanToChangeToPriceError,
  231. groupPlanToChangeToSize,
  232. groupPlanToChangeToUsage,
  233. handleCloseModal,
  234. handleOpenModal,
  235. hasDisplayedSubscription,
  236. institutionMemberships,
  237. managedGroupSubscriptions,
  238. memberGroupSubscriptions,
  239. managedInstitutions,
  240. managedPublishers,
  241. updateManagedInstitution,
  242. modalIdShown,
  243. personalSubscription,
  244. hasSubscription,
  245. plans,
  246. planCodeToChangeTo,
  247. queryingGroupPlanToChangeToPrice,
  248. queryingIndividualPlansData,
  249. recurlyLoadError,
  250. setGroupPlanToChangeToCode,
  251. setGroupPlanToChangeToSize,
  252. setGroupPlanToChangeToUsage,
  253. setModalIdShown,
  254. setPlanCodeToChangeTo,
  255. setRecurlyLoadError,
  256. showCancellation,
  257. setShowCancellation,
  258. showChangePersonalPlan,
  259. setShowChangePersonalPlan,
  260. leavingGroupId,
  261. setLeavingGroupId,
  262. }),
  263. [
  264. groupPlanToChangeToCode,
  265. groupPlanToChangeToPrice,
  266. groupPlanToChangeToPriceError,
  267. groupPlanToChangeToSize,
  268. groupPlanToChangeToUsage,
  269. handleCloseModal,
  270. handleOpenModal,
  271. hasDisplayedSubscription,
  272. institutionMemberships,
  273. managedGroupSubscriptions,
  274. memberGroupSubscriptions,
  275. managedInstitutions,
  276. managedPublishers,
  277. updateManagedInstitution,
  278. modalIdShown,
  279. personalSubscription,
  280. hasSubscription,
  281. plans,
  282. planCodeToChangeTo,
  283. queryingGroupPlanToChangeToPrice,
  284. queryingIndividualPlansData,
  285. recurlyLoadError,
  286. setGroupPlanToChangeToCode,
  287. setGroupPlanToChangeToSize,
  288. setGroupPlanToChangeToUsage,
  289. setModalIdShown,
  290. setPlanCodeToChangeTo,
  291. setRecurlyLoadError,
  292. showCancellation,
  293. setShowCancellation,
  294. showChangePersonalPlan,
  295. setShowChangePersonalPlan,
  296. leavingGroupId,
  297. setLeavingGroupId,
  298. ]
  299. )
  300. return (
  301. <SubscriptionDashboardContext.Provider value={value}>
  302. {children}
  303. </SubscriptionDashboardContext.Provider>
  304. )
  305. }
  306. export function useSubscriptionDashboardContext() {
  307. const context = useContext(SubscriptionDashboardContext)
  308. if (!context) {
  309. throw new Error(
  310. 'SubscriptionDashboardContext is only available inside SubscriptionDashboardProvider'
  311. )
  312. }
  313. return context
  314. }