token-access-root.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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 { ElementType, 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 { AccessAttemptScreen } from '@/features/token-access/components/access-attempt-screen'
  10. import {
  11. RequireAcceptData,
  12. RequireAcceptScreen,
  13. } from '@/features/token-access/components/require-accept-screen'
  14. import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
  15. import MaterialIcon from '@/shared/components/material-icon'
  16. type Mode = 'access-attempt' | 'v1Import' | 'requireAccept'
  17. const [v1ImportDataScreenModule] = importOverleafModules(
  18. 'v1ImportDataScreen'
  19. ) as {
  20. import: { default: ElementType }
  21. }[]
  22. const V1ImportDataScreen = v1ImportDataScreenModule?.import.default
  23. export type V1ImportData = {
  24. name?: string
  25. status: string
  26. projectId: string
  27. }
  28. function TokenAccessRoot() {
  29. const [mode, setMode] = useState<Mode>('access-attempt')
  30. const [inflight, setInflight] = useState(false)
  31. const [accessError, setAccessError] = useState<string | boolean>(false)
  32. const [v1ImportData, setV1ImportData] = useState<V1ImportData>()
  33. const [requireAcceptData, setRequireAcceptData] =
  34. useState<RequireAcceptData>()
  35. const [loadingScreenBrandHeight, setLoadingScreenBrandHeight] =
  36. useState('0px')
  37. const location = useLocation()
  38. const sendPostRequest = useCallback(
  39. (confirmedByUser = false) => {
  40. setInflight(true)
  41. postJSON(getMeta('ol-postUrl'), {
  42. body: {
  43. confirmedByUser,
  44. tokenHashPrefix: document.location.hash,
  45. },
  46. })
  47. .then(async data => {
  48. setAccessError(false)
  49. if (data.redirect) {
  50. location.replace(data.redirect)
  51. } else if (data.v1Import) {
  52. setMode('v1Import')
  53. setV1ImportData(data.v1Import)
  54. } else if (data.requireAccept) {
  55. setMode('requireAccept')
  56. setRequireAcceptData(data.requireAccept)
  57. } else {
  58. debugConsole.warn(
  59. 'invalid data from server in success response',
  60. data
  61. )
  62. setAccessError(true)
  63. }
  64. })
  65. .catch(error => {
  66. debugConsole.warn('error response from server', error)
  67. setAccessError(error.response?.status === 404 ? 'not_found' : 'error')
  68. })
  69. .finally(() => {
  70. setInflight(false)
  71. })
  72. },
  73. [location]
  74. )
  75. const postedRef = useRef(false)
  76. useEffect(() => {
  77. if (!postedRef.current) {
  78. postedRef.current = true
  79. sendPostRequest()
  80. setTimeout(() => {
  81. setLoadingScreenBrandHeight('20%')
  82. }, 500)
  83. }
  84. }, [sendPostRequest])
  85. const { isReady } = useWaitForI18n()
  86. if (!isReady) {
  87. return null
  88. }
  89. // We don't want the full-size div and back link(?) on
  90. // the new page, but we do this so the original page
  91. // doesn't change.
  92. // TODO: clean up the DOM in the main return
  93. if (mode === 'requireAccept' && requireAcceptData) {
  94. return (
  95. <RequireAcceptScreen
  96. requireAcceptData={requireAcceptData}
  97. sendPostRequest={sendPostRequest}
  98. />
  99. )
  100. }
  101. return (
  102. <div>
  103. <div>
  104. <a
  105. href="/project"
  106. // TODO: class name
  107. style={{ fontSize: '2rem', marginLeft: '1rem', color: '#ddd' }}
  108. >
  109. <MaterialIcon type="arrow_left_alt" style={{ fontSize: 'inherit' }} />
  110. </a>
  111. </div>
  112. {mode === 'access-attempt' && (
  113. <AccessAttemptScreen
  114. accessError={accessError}
  115. inflight={inflight}
  116. loadingScreenBrandHeight={loadingScreenBrandHeight}
  117. />
  118. )}
  119. {V1ImportDataScreen && mode === 'v1Import' && v1ImportData && (
  120. <V1ImportDataScreen v1ImportData={v1ImportData} />
  121. )}
  122. </div>
  123. )
  124. }
  125. export default withErrorBoundary(TokenAccessRoot, () => (
  126. <GenericErrorBoundaryFallback />
  127. ))