{"version":3,"file":"TOTPEnrollManualScreen.mjs","sources":["../../../../../../../../../web/src/ui/b2b/screens/TOTPEnrollManualScreen.tsx"],"sourcesContent":["import { useLingui } from '@lingui/react/macro';\nimport React, { CSSProperties, useLayoutEffect, useRef, useState } from 'react';\n\nimport Button from '../../components/atoms/Button';\nimport CodeContainer from '../../components/atoms/CodeContainer';\nimport Column from '../../components/atoms/Column';\nimport { errorToast } from '../../components/atoms/Toast';\nimport Typography from '../../components/atoms/Typography';\nimport VerticalTransition, { useTimedBoolean } from '../../components/atoms/VerticalTransition';\nimport ButtonColumn from '../../components/molecules/ButtonColumn';\nimport TextColumn from '../../components/molecules/TextColumn';\nimport { useGlobalReducer } from '../GlobalContextProvider';\nimport { AppScreens } from '../types/AppScreens';\nimport styles from './TOTPEnrollManualScreen.module.css';\n\nconst CHUNK_SIZE = 4;\n\nexport const TOTPEnrollManualScreen = () => {\n  const [state, dispatch] = useGlobalReducer();\n  const { t } = useLingui();\n\n  const [copied, setCopied] = useTimedBoolean(3);\n\n  // This screen should only be shown if TOTP enrollment is available\n  const { secret } = state.mfa.totp.enrollment!;\n\n  const displaySecret = secret.toLowerCase();\n  const secretChunked = displaySecret.split(new RegExp(`(.{${CHUNK_SIZE}})`, 'g')).filter(Boolean);\n\n  const { codeRef, columnCount } = useColumnCount(secretChunked.length);\n\n  const handleBack = () => dispatch({ type: 'navigate_back' });\n\n  const handleContinue = () => {\n    dispatch({ type: 'transition', history: 'push', screen: AppScreens.TOTPEntry });\n  };\n\n  const handleCodeCopy = async () => {\n    try {\n      await navigator.clipboard.writeText(displaySecret);\n      setCopied(true);\n    } catch {\n      errorToast({\n        message: t({ id: 'mfa.totpEnrollment.toast.copyFailed', message: \"Couldn't copy code to clipboard\" }),\n      });\n\n      // Select all text instead as fallback\n      const selection = window.getSelection();\n      if (codeRef.current && selection) {\n        selection.empty();\n        const range = document.createRange();\n        range.selectNodeContents(codeRef.current);\n        selection.addRange(range);\n      }\n    }\n  };\n\n  return (\n    <Column gap={6}>\n      <TextColumn\n        header={t({ id: 'mfa.totpEnrollment.title', message: 'Having trouble scanning the QR code?' })}\n        body={t({\n          id: 'mfa.totpEnrollment.content',\n          message: 'Click to copy the code below. Enter into your authenticator app to link your account.',\n        })}\n      />\n\n      {/* No additional keyboard support since there's a copy code button below */}\n      <CodeContainer className={styles.codeContainer} onClick={handleCodeCopy}>\n        {/* This Typography element is on the outside because we need the 1ch gap to also be in monospace */}\n        <Typography\n          ref={codeRef}\n          className={styles.code}\n          style={\n            {\n              '--st-column-count': columnCount,\n              '--st-chunk-size': CHUNK_SIZE + 'ch',\n            } as CSSProperties\n          }\n          font=\"mono\"\n        >\n          {secretChunked.map((part, i) => (\n            <span key={i}>{part}</span>\n          ))}\n        </Typography>\n      </CodeContainer>\n\n      <ButtonColumn>\n        <Button variant=\"primary\" onClick={handleCodeCopy}>\n          <VerticalTransition\n            primary={t({ id: 'mfa.totpEnrollment.copyCode.label', message: 'Copy to clipboard' })}\n            secondary={t({ id: 'mfa.totpEnrollment.copyCode.codeCopied', message: 'Copied!' })}\n            triggered={copied}\n          />\n        </Button>\n        <Button variant=\"outline\" onClick={handleContinue}>\n          {t({ id: 'button.continue', message: 'Continue' })}\n        </Button>\n        <Button variant=\"ghost\" onClick={handleBack}>\n          {t({ id: 'button.totpQrRetry', message: 'Try to scan the QR code again' })}\n        </Button>\n      </ButtonColumn>\n    </Column>\n  );\n};\n\n// Balance out the rows - this is fairly complex, but there's not any way to really do this in CSS only\n// so instead we read the width of the container and the chunks below it, then use that to calculate\n// first the number of rows, then how many chunks can evenly fit per row\nfunction useColumnCount(chunksCount: number) {\n  const codeRef = useRef<HTMLDivElement>(null);\n  const [columnCount, setColumnCount] = useState<number>();\n\n  useLayoutEffect(() => {\n    const updateRowCount = () => {\n      if (!codeRef.current) return;\n\n      // Get the width of the container and inner spans\n      const containerWidth = codeRef.current.clientWidth;\n      const spanWidth = Math.max(\n        ...Array.from(codeRef.current.querySelectorAll('span')).map((elem) => elem.clientWidth),\n      );\n\n      // Find the number of columns to display. First compute the raw number\n      // Note we don't check how many rows are currently rendered because this number is already affected by\n      // the current row count.\n      const fittedColumns = Math.floor(containerWidth / spanWidth);\n      // Then account for gaps (1ch each, so 1/CHUNK_SIZE per gap)\n      const fittedColumnsWithGaps = fittedColumns - Math.floor(fittedColumns / CHUNK_SIZE);\n      // Number of rows is count / columns\n      const rows = Math.ceil(chunksCount / fittedColumnsWithGaps);\n      // And finally the closest even column count is count / rows\n      const evenColumns = Math.ceil(chunksCount / rows);\n\n      setColumnCount(evenColumns);\n    };\n\n    updateRowCount();\n\n    if (!window.ResizeObserver || !codeRef.current) return;\n    const observer = new ResizeObserver(updateRowCount);\n    observer.observe(codeRef.current);\n    return () => observer.disconnect();\n  }, [chunksCount]);\n\n  return {\n    codeRef,\n    columnCount,\n  };\n}\n"],"names":["CHUNK_SIZE","TOTPEnrollManualScreen","state","dispatch","useGlobalReducer","useLingui","copied","setCopied","useTimedBoolean","secret","mfa","totp","enrollment","displaySecret","toLowerCase","secretChunked","split","RegExp","filter","Boolean","codeRef","columnCount","useColumnCount","length","handleBack","type","handleContinue","history","screen","AppScreens","TOTPEntry","handleCodeCopy","navigator","clipboard","writeText","errorToast","message","selection","window","getSelection","current","empty","range","document","createRange","selectNodeContents","addRange","React","Column","gap","TextColumn","header","body","CodeContainer","className","styles","codeContainer","onClick","Typography","ref","code","style","font","map","part","i","span","key","ButtonColumn","Button","variant","VerticalTransition","primary","secondary","triggered","chunksCount","useRef","setColumnCount","useState","useLayoutEffect","updateRowCount","containerWidth","clientWidth","spanWidth","Math","max","Array","from","querySelectorAll","elem","fittedColumns","floor","fittedColumnsWithGaps","rows","ceil","evenColumns","ResizeObserver","observer","observe","disconnect"],"mappings":"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"}