use-tutorial.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import { useCallback, useState } from 'react'
  2. import * as eventTracking from '@/infrastructure/event-tracking'
  3. import { postJSON } from '@/infrastructure/fetch-json'
  4. import { debugConsole } from '@/utils/debugging'
  5. import { useTutorialContext } from '@/shared/context/tutorial-context'
  6. type CompleteTutorialParams = {
  7. event: string
  8. action: 'complete' | 'postpone'
  9. } & Record<string, any>
  10. type CompleteTutorialOptions = {
  11. // Whether to ignore errors if the request fails. Defaults to true. If
  12. // successfull completion is required, set this to false.
  13. failSilently?: boolean
  14. }
  15. const useTutorial = (
  16. tutorialKey: string,
  17. eventData: Record<string, any> = {}
  18. ) => {
  19. const [showPopup, setShowPopup] = useState(false)
  20. const {
  21. deactivateTutorial,
  22. currentPopup,
  23. setCurrentPopup,
  24. inactiveTutorials,
  25. } = useTutorialContext()
  26. const checkCompletion = useCallback(
  27. () => inactiveTutorials.includes(tutorialKey),
  28. [inactiveTutorials, tutorialKey]
  29. )
  30. const completeTutorial = useCallback(
  31. async (
  32. {
  33. event = 'promo-click',
  34. action = 'complete',
  35. ...rest
  36. }: CompleteTutorialParams,
  37. options: CompleteTutorialOptions = {}
  38. ) => {
  39. eventTracking.sendMB(event, { ...eventData, ...rest })
  40. try {
  41. await postJSON(`/tutorial/${tutorialKey}/${action}`)
  42. } catch (err) {
  43. const failSilently = options.failSilently ?? true
  44. if (!failSilently) {
  45. throw err
  46. }
  47. debugConsole.error(err)
  48. }
  49. setShowPopup(false)
  50. deactivateTutorial(tutorialKey)
  51. },
  52. [deactivateTutorial, eventData, tutorialKey]
  53. )
  54. const dismissTutorial = useCallback(
  55. async (eventName: string = 'promo-dismiss') => {
  56. await completeTutorial({
  57. event: eventName,
  58. action: 'complete',
  59. })
  60. },
  61. [completeTutorial]
  62. )
  63. const maybeLater = useCallback(async () => {
  64. await completeTutorial({
  65. event: 'promo-click',
  66. action: 'postpone',
  67. button: 'maybe-later',
  68. })
  69. }, [completeTutorial])
  70. // try to show the popup if we don't already have one showing, returns true if it can show, false if it can't
  71. const tryShowingPopup = useCallback(
  72. (eventName: string = 'promo-prompt') => {
  73. if (currentPopup === null) {
  74. setCurrentPopup(tutorialKey)
  75. setShowPopup(true)
  76. eventTracking.sendMB(eventName, eventData)
  77. return true
  78. }
  79. return false
  80. },
  81. [currentPopup, setCurrentPopup, tutorialKey, eventData]
  82. )
  83. const clearPopup = useCallback(() => {
  84. // popups should only clear themselves, in cases they need to cleanup or shouldnt show anymore
  85. // allow forcing the clear if needed, eg: higher prio alert needs to show
  86. if (currentPopup === tutorialKey) {
  87. setCurrentPopup(null)
  88. setShowPopup(false)
  89. }
  90. }, [setCurrentPopup, setShowPopup, currentPopup, tutorialKey])
  91. const clearAndShow = useCallback(
  92. (eventName: string = 'promo-prompt') => {
  93. setCurrentPopup(tutorialKey)
  94. setShowPopup(true)
  95. eventTracking.sendMB(eventName, eventData)
  96. },
  97. [setCurrentPopup, setShowPopup, tutorialKey, eventData]
  98. )
  99. const hideUntilReload = useCallback(() => {
  100. clearPopup()
  101. deactivateTutorial(tutorialKey)
  102. }, [clearPopup, deactivateTutorial, tutorialKey])
  103. return {
  104. completeTutorial,
  105. dismissTutorial,
  106. maybeLater,
  107. tryShowingPopup,
  108. clearPopup,
  109. clearAndShow,
  110. showPopup,
  111. hideUntilReload,
  112. checkCompletion,
  113. }
  114. }
  115. export default useTutorial