token-access-root.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import useWaitForI18n from '@/shared/hooks/use-wait-for-i18n'
  2. import withErrorBoundary from '@/infrastructure/error-boundary'
  3. import { GenericErrorBoundaryFallback } from '@/shared/components/generic-error-boundary-fallback'
  4. import { useCallback, useEffect, useRef, useState } from 'react'
  5. import getMeta from '@/utils/meta'
  6. import { postJSON } from '@/infrastructure/fetch-json'
  7. import { debugConsole } from '@/utils/debugging'
  8. import { useLocation } from '@/shared/hooks/use-location'
  9. import {
  10. V1ImportData,
  11. V1ImportDataScreen,
  12. } from '@/features/token-access/components/v1-import-data-screen'
  13. import { AccessAttemptScreen } from '@/features/token-access/components/access-attempt-screen'
  14. import {
  15. RequireAcceptData,
  16. RequireAcceptScreen,
  17. } from '@/features/token-access/components/require-accept-screen'
  18. import Icon from '@/shared/components/icon'
  19. type Mode = 'access-attempt' | 'v1Import' | 'requireAccept'
  20. function TokenAccessRoot() {
  21. const [mode, setMode] = useState<Mode>('access-attempt')
  22. const [inflight, setInflight] = useState(false)
  23. const [accessError, setAccessError] = useState<string | boolean>(false)
  24. const [v1ImportData, setV1ImportData] = useState<V1ImportData>()
  25. const [requireAcceptData, setRequireAcceptData] =
  26. useState<RequireAcceptData>()
  27. const [loadingScreenBrandHeight, setLoadingScreenBrandHeight] =
  28. useState('0px')
  29. const location = useLocation()
  30. const sendPostRequest = useCallback(
  31. (confirmedByUser = false) => {
  32. setInflight(true)
  33. postJSON(getMeta('ol-postUrl'), {
  34. body: {
  35. confirmedByUser,
  36. tokenHashPrefix: document.location.hash,
  37. },
  38. })
  39. .then(async data => {
  40. setAccessError(false)
  41. if (data.redirect) {
  42. location.replace(data.redirect)
  43. } else if (data.v1Import) {
  44. setMode('v1Import')
  45. setV1ImportData(data.v1Import)
  46. } else if (data.requireAccept) {
  47. setMode('requireAccept')
  48. setRequireAcceptData(data.requireAccept)
  49. } else {
  50. debugConsole.warn(
  51. 'invalid data from server in success response',
  52. data
  53. )
  54. setAccessError(true)
  55. }
  56. })
  57. .catch(error => {
  58. debugConsole.warn('error response from server', error)
  59. setAccessError(error.response?.status === 404 ? 'not_found' : 'error')
  60. })
  61. .finally(() => {
  62. setInflight(false)
  63. })
  64. },
  65. [location]
  66. )
  67. const postedRef = useRef(false)
  68. useEffect(() => {
  69. if (!postedRef.current) {
  70. postedRef.current = true
  71. sendPostRequest()
  72. setTimeout(() => {
  73. setLoadingScreenBrandHeight('20%')
  74. }, 500)
  75. }
  76. }, [sendPostRequest])
  77. const { isReady } = useWaitForI18n()
  78. if (!isReady) {
  79. return null
  80. }
  81. return (
  82. <div className="full-size">
  83. <div>
  84. <a
  85. href="/project"
  86. // TODO: class name
  87. style={{ fontSize: '2rem', marginLeft: '1rem', color: '#ddd' }}
  88. >
  89. <Icon type="arrow-left" />
  90. </a>
  91. </div>
  92. {mode === 'access-attempt' && (
  93. <AccessAttemptScreen
  94. accessError={accessError}
  95. inflight={inflight}
  96. loadingScreenBrandHeight={loadingScreenBrandHeight}
  97. />
  98. )}
  99. {mode === 'v1Import' && v1ImportData && (
  100. <V1ImportDataScreen v1ImportData={v1ImportData} />
  101. )}
  102. {mode === 'requireAccept' && requireAcceptData && (
  103. <RequireAcceptScreen
  104. requireAcceptData={requireAcceptData}
  105. sendPostRequest={sendPostRequest}
  106. />
  107. )}
  108. </div>
  109. )
  110. }
  111. export default withErrorBoundary(TokenAccessRoot, GenericErrorBoundaryFallback)