use-tutorial.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  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 { deactivateTutorial, currentPopup, setCurrentPopup } =
  21. useTutorialContext()
  22. const completeTutorial = useCallback(
  23. async (
  24. {
  25. event = 'promo-click',
  26. action = 'complete',
  27. ...rest
  28. }: CompleteTutorialParams,
  29. options: CompleteTutorialOptions = {}
  30. ) => {
  31. eventTracking.sendMB(event, { ...eventData, ...rest })
  32. try {
  33. await postJSON(`/tutorial/${tutorialKey}/${action}`)
  34. } catch (err) {
  35. const failSilently = options.failSilently ?? true
  36. if (!failSilently) {
  37. throw err
  38. }
  39. debugConsole.error(err)
  40. }
  41. setShowPopup(false)
  42. deactivateTutorial(tutorialKey)
  43. },
  44. [deactivateTutorial, eventData, tutorialKey]
  45. )
  46. const dismissTutorial = useCallback(
  47. async (eventName: string = 'promo-dismiss') => {
  48. await completeTutorial({
  49. event: eventName,
  50. action: 'complete',
  51. })
  52. },
  53. [completeTutorial]
  54. )
  55. const maybeLater = useCallback(async () => {
  56. await completeTutorial({
  57. event: 'promo-click',
  58. action: 'postpone',
  59. button: 'maybe-later',
  60. })
  61. }, [completeTutorial])
  62. // try to show the popup if we don't already have one showing, returns true if it can show, false if it can't
  63. const tryShowingPopup = useCallback(
  64. (eventName: string = 'promo-prompt') => {
  65. if (currentPopup === null) {
  66. setCurrentPopup(tutorialKey)
  67. setShowPopup(true)
  68. eventTracking.sendMB(eventName, eventData)
  69. return true
  70. }
  71. return false
  72. },
  73. [currentPopup, setCurrentPopup, tutorialKey, eventData]
  74. )
  75. const clearPopup = useCallback(() => {
  76. // popups should only clear themselves, in cases they need to cleanup or shouldnt show anymore
  77. // allow forcing the clear if needed, eg: higher prio alert needs to show
  78. if (currentPopup === tutorialKey) {
  79. setCurrentPopup(null)
  80. setShowPopup(false)
  81. }
  82. }, [setCurrentPopup, setShowPopup, currentPopup, tutorialKey])
  83. const clearAndShow = useCallback(
  84. (eventName: string = 'promo-prompt') => {
  85. setCurrentPopup(tutorialKey)
  86. setShowPopup(true)
  87. eventTracking.sendMB(eventName, eventData)
  88. },
  89. [setCurrentPopup, setShowPopup, tutorialKey, eventData]
  90. )
  91. const hideUntilReload = useCallback(() => {
  92. clearPopup()
  93. deactivateTutorial(tutorialKey)
  94. }, [clearPopup, deactivateTutorial, tutorialKey])
  95. return {
  96. completeTutorial,
  97. dismissTutorial,
  98. maybeLater,
  99. tryShowingPopup,
  100. clearPopup,
  101. clearAndShow,
  102. showPopup,
  103. hideUntilReload,
  104. }
  105. }
  106. export default useTutorial