subscription-dashboard-context.tsx 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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. leavingGroupId?: string
  69. setLeavingGroupId: React.Dispatch<React.SetStateAction<string | undefined>>
  70. }
  71. export const SubscriptionDashboardContext = createContext<
  72. SubscriptionDashboardContextValue | undefined
  73. >(undefined)
  74. export function SubscriptionDashboardProvider({
  75. children,
  76. }: {
  77. children: ReactNode
  78. }) {
  79. const [modalIdShown, setModalIdShown] = useState<
  80. SubscriptionDashModalIds | undefined
  81. >()
  82. const [recurlyLoadError, setRecurlyLoadError] = useState(false)
  83. const [showCancellation, setShowCancellation] = useState(false)
  84. const [plans, setPlans] = useState([])
  85. const [queryingIndividualPlansData, setQueryingIndividualPlansData] =
  86. useState(true)
  87. const [planCodeToChangeTo, setPlanCodeToChangeTo] = useState<
  88. string | undefined
  89. >()
  90. const [groupPlanToChangeToSize, setGroupPlanToChangeToSize] = useState('10')
  91. const [groupPlanToChangeToCode, setGroupPlanToChangeToCode] =
  92. useState('collaborator')
  93. const [groupPlanToChangeToUsage, setGroupPlanToChangeToUsage] =
  94. useState('enterprise')
  95. const [
  96. queryingGroupPlanToChangeToPrice,
  97. setQueryingGroupPlanToChangeToPrice,
  98. ] = useState(false)
  99. const [groupPlanToChangeToPrice, setGroupPlanToChangeToPrice] =
  100. useState<PriceForDisplayData>()
  101. const [groupPlanToChangeToPriceError, setGroupPlanToChangeToPriceError] =
  102. useState(false)
  103. const [leavingGroupId, setLeavingGroupId] = useState<string | undefined>()
  104. const plansWithoutDisplayPrice = getMeta('ol-plans')
  105. const institutionMemberships: Institution[] = getMeta(
  106. 'ol-currentInstitutionsWithLicence'
  107. )
  108. const personalSubscription = getMeta('ol-subscription')
  109. const managedGroupSubscriptions: ManagedGroupSubscription[] = getMeta(
  110. 'ol-managedGroupSubscriptions'
  111. )
  112. const memberGroupSubscriptions: MemberGroupSubscription[] = getMeta(
  113. 'ol-memberGroupSubscriptions'
  114. )
  115. const [managedInstitutions, setManagedInstitutions] = useState<
  116. ManagedInstitution[]
  117. >(getMeta('ol-managedInstitutions'))
  118. const managedPublishers = getMeta('ol-managedPublishers')
  119. const hasSubscription = getMeta('ol-hasSubscription')
  120. const recurlyApiKey = getMeta('ol-recurlyApiKey')
  121. const hasDisplayedSubscription = Boolean(
  122. institutionMemberships?.length > 0 ||
  123. personalSubscription ||
  124. memberGroupSubscriptions?.length > 0 ||
  125. managedGroupSubscriptions?.length > 0 ||
  126. managedInstitutions?.length > 0 ||
  127. managedPublishers?.length > 0
  128. )
  129. useEffect(() => {
  130. if (!isRecurlyLoaded()) {
  131. setRecurlyLoadError(true)
  132. } else if (recurlyApiKey) {
  133. recurly.configure(recurlyApiKey)
  134. }
  135. }, [recurlyApiKey, setRecurlyLoadError])
  136. useEffect(() => {
  137. if (
  138. isRecurlyLoaded() &&
  139. plansWithoutDisplayPrice &&
  140. personalSubscription?.recurly
  141. ) {
  142. const { currency, taxRate } = personalSubscription.recurly
  143. const fetchPlansDisplayPrices = async () => {
  144. for (const plan of plansWithoutDisplayPrice) {
  145. try {
  146. const priceData = await loadDisplayPriceWithTaxPromise(
  147. plan.planCode,
  148. currency,
  149. taxRate
  150. )
  151. if (priceData?.totalForDisplay) {
  152. plan.displayPrice = priceData.totalForDisplay
  153. }
  154. } catch (error) {
  155. console.error(error)
  156. }
  157. }
  158. setPlans(plansWithoutDisplayPrice)
  159. setQueryingIndividualPlansData(false)
  160. }
  161. fetchPlansDisplayPrices().catch(console.error)
  162. }
  163. }, [personalSubscription, plansWithoutDisplayPrice])
  164. useEffect(() => {
  165. if (
  166. isRecurlyLoaded() &&
  167. groupPlanToChangeToCode &&
  168. groupPlanToChangeToSize &&
  169. groupPlanToChangeToUsage &&
  170. personalSubscription?.recurly
  171. ) {
  172. setQueryingGroupPlanToChangeToPrice(true)
  173. const { currency, taxRate } = personalSubscription.recurly
  174. const fetchGroupDisplayPrice = async () => {
  175. setGroupPlanToChangeToPriceError(false)
  176. let priceData
  177. try {
  178. priceData = await loadGroupDisplayPriceWithTaxPromise(
  179. groupPlanToChangeToCode,
  180. currency,
  181. taxRate,
  182. groupPlanToChangeToSize,
  183. groupPlanToChangeToUsage
  184. )
  185. } catch (e) {
  186. console.error(e)
  187. setGroupPlanToChangeToPriceError(true)
  188. }
  189. setQueryingGroupPlanToChangeToPrice(false)
  190. setGroupPlanToChangeToPrice(priceData)
  191. }
  192. fetchGroupDisplayPrice()
  193. }
  194. }, [
  195. groupPlanToChangeToUsage,
  196. groupPlanToChangeToSize,
  197. personalSubscription,
  198. groupPlanToChangeToCode,
  199. ])
  200. const updateManagedInstitution = useCallback(
  201. (institution: ManagedInstitution) => {
  202. setManagedInstitutions(institutions => {
  203. return [
  204. ...(institutions || []).map(i =>
  205. i.v1Id === institution.v1Id ? institution : i
  206. ),
  207. ]
  208. })
  209. },
  210. []
  211. )
  212. const handleCloseModal = useCallback(() => {
  213. setModalIdShown(undefined)
  214. setPlanCodeToChangeTo(undefined)
  215. }, [setModalIdShown, setPlanCodeToChangeTo])
  216. const handleOpenModal = useCallback(
  217. (id, planCode) => {
  218. setModalIdShown(id)
  219. setPlanCodeToChangeTo(planCode)
  220. },
  221. [setModalIdShown, setPlanCodeToChangeTo]
  222. )
  223. const value = useMemo<SubscriptionDashboardContextValue>(
  224. () => ({
  225. groupPlanToChangeToCode,
  226. groupPlanToChangeToPrice,
  227. groupPlanToChangeToPriceError,
  228. groupPlanToChangeToSize,
  229. groupPlanToChangeToUsage,
  230. handleCloseModal,
  231. handleOpenModal,
  232. hasDisplayedSubscription,
  233. institutionMemberships,
  234. managedGroupSubscriptions,
  235. memberGroupSubscriptions,
  236. managedInstitutions,
  237. managedPublishers,
  238. updateManagedInstitution,
  239. modalIdShown,
  240. personalSubscription,
  241. hasSubscription,
  242. plans,
  243. planCodeToChangeTo,
  244. queryingGroupPlanToChangeToPrice,
  245. queryingIndividualPlansData,
  246. recurlyLoadError,
  247. setGroupPlanToChangeToCode,
  248. setGroupPlanToChangeToSize,
  249. setGroupPlanToChangeToUsage,
  250. setModalIdShown,
  251. setPlanCodeToChangeTo,
  252. setRecurlyLoadError,
  253. showCancellation,
  254. setShowCancellation,
  255. leavingGroupId,
  256. setLeavingGroupId,
  257. }),
  258. [
  259. groupPlanToChangeToCode,
  260. groupPlanToChangeToPrice,
  261. groupPlanToChangeToPriceError,
  262. groupPlanToChangeToSize,
  263. groupPlanToChangeToUsage,
  264. handleCloseModal,
  265. handleOpenModal,
  266. hasDisplayedSubscription,
  267. institutionMemberships,
  268. managedGroupSubscriptions,
  269. memberGroupSubscriptions,
  270. managedInstitutions,
  271. managedPublishers,
  272. updateManagedInstitution,
  273. modalIdShown,
  274. personalSubscription,
  275. hasSubscription,
  276. plans,
  277. planCodeToChangeTo,
  278. queryingGroupPlanToChangeToPrice,
  279. queryingIndividualPlansData,
  280. recurlyLoadError,
  281. setGroupPlanToChangeToCode,
  282. setGroupPlanToChangeToSize,
  283. setGroupPlanToChangeToUsage,
  284. setModalIdShown,
  285. setPlanCodeToChangeTo,
  286. setRecurlyLoadError,
  287. showCancellation,
  288. setShowCancellation,
  289. leavingGroupId,
  290. setLeavingGroupId,
  291. ]
  292. )
  293. return (
  294. <SubscriptionDashboardContext.Provider value={value}>
  295. {children}
  296. </SubscriptionDashboardContext.Provider>
  297. )
  298. }
  299. export function useSubscriptionDashboardContext() {
  300. const context = useContext(SubscriptionDashboardContext)
  301. if (!context) {
  302. throw new Error(
  303. 'SubscriptionDashboardContext is only available inside SubscriptionDashboardProvider'
  304. )
  305. }
  306. return context
  307. }