subscription-dashboard-context.tsx 10 KB

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