{"version":3,"sources":["../../../src/lib/elevated-access.tsx"],"sourcesContent":["import { Form } from \"radix-ui\";\nimport { Callout, Flex, Text } from \"@radix-ui/themes\";\nimport { useMe, useSendVerification, useVerify } from \"../api/endpoint.js\";\nimport { useElevatedAccessToken } from \"../api/api-provider.js\";\nimport { PropsWithChildren, useEffect, useRef, useState } from \"react\";\nimport { AlertDialog, Dialog, Button } from \"./elements.js\";\nimport * as Otp from \"./otp-input.js\";\nimport { Translation } from \"./i18n/translation.js\";\n\ninterface ElevatedAccessProps extends PropsWithChildren {\n  onVerified?: () => Promise<unknown>;\n  type?: \"dialog\" | \"alert\";\n}\n\nexport function ElevatedAccess({\n  type = \"dialog\",\n  children,\n  onVerified,\n}: ElevatedAccessProps) {\n  const { elevatedAccess } = useElevatedAccessToken();\n  const [authenticationChallengeId, setAuthenticationChallengeId] =\n    useState<string>();\n\n  const prevAccessToken = useRef(elevatedAccess);\n\n  useEffect(() => {\n    prevAccessToken.current = elevatedAccess;\n  }, [elevatedAccess]);\n\n  if (elevatedAccess) {\n    return <>{children}</>;\n  }\n\n  if (!authenticationChallengeId) {\n    // FIXME: This should be refactored\n    // eslint-disable-next-line react-hooks/refs\n    const hasTokenExpired = !!prevAccessToken.current;\n\n    return (\n      <SendVerificationEmailForm\n        type={type}\n        hasTokenExpired={hasTokenExpired}\n        onSuccess={(challengeId) => {\n          setAuthenticationChallengeId(challengeId);\n        }}\n      />\n    );\n  }\n\n  if (authenticationChallengeId) {\n    return (\n      <VerificationIdentityForm\n        type={type}\n        authenticationChallengeId={authenticationChallengeId}\n        onSuccess={() => {\n          // Reset the challenge id\n          setAuthenticationChallengeId(undefined);\n\n          return onVerified?.();\n        }}\n      />\n    );\n  }\n\n  return null;\n}\n\ninterface SendVerificationEmailFormProps {\n  onSuccess: (challengeId: string) => unknown | Promise<unknown>;\n  type: \"dialog\" | \"alert\";\n  hasTokenExpired: boolean;\n}\n\nfunction SendVerificationEmailForm({\n  onSuccess,\n  type,\n  hasTokenExpired,\n}: SendVerificationEmailFormProps) {\n  const { data: me } = useMe();\n  const sendVerification = useSendVerification({\n    mutation: { onSuccess: (data) => onSuccess(data.authenticationChallenge) },\n  });\n\n  const Title = type === \"dialog\" ? Dialog.Title : AlertDialog.Title;\n  const Description =\n    type === \"dialog\" ? Dialog.Description : AlertDialog.Description;\n  const Close = type === \"dialog\" ? Dialog.Close : AlertDialog.Cancel;\n\n  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n    event.preventDefault();\n\n    sendVerification.mutate();\n  };\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <Title>\n        {hasTokenExpired ? (\n          <Translation\n            defaultMessage=\"Your verification token has expired\"\n            id=\"lb/AsC\"\n            description=\"Dialog title when verification token has expired\"\n          />\n        ) : (\n          <Translation\n            defaultMessage=\"Verify your identity\"\n            id=\"7tGCOh\"\n            description=\"Dialog title for identity verification\"\n          />\n        )}\n      </Title>\n\n      <Description mb=\"5\">\n        <Translation\n          defaultMessage=\"To continue, we need to confirm your identity. We'll send a temporary verification code to {email}.\"\n          id=\"FTUSRM\"\n          description=\"Description explaining verification code will be sent\"\n          values={{\n            email: (\n              <Text weight=\"bold\" highContrast>\n                {me?.email}\n              </Text>\n            ),\n          }}\n        />\n      </Description>\n\n      {sendVerification.error && (\n        <Callout.Root color=\"red\" mt=\"-2\" mb=\"0\">\n          <Callout.Text>\n            {getMutationErrorMessage(sendVerification.error)}\n          </Callout.Text>\n        </Callout.Root>\n      )}\n\n      <Flex justify=\"end\" align=\"center\" gap=\"3\" mt=\"5\">\n        <Close>\n          <Button\n            variant=\"secondary\"\n            type=\"button\"\n            disabled={sendVerification.isPending}\n          >\n            <Translation\n              defaultMessage=\"Cancel\"\n              id=\"hHNj31\"\n              description=\"Cancel button text\"\n            />\n          </Button>\n        </Close>\n        <Button type=\"submit\" loading={sendVerification.isPending}>\n          <Translation\n            defaultMessage=\"Send verification code\"\n            id=\"XsTeXJ\"\n            description=\"Button text to send verification code\"\n          />\n        </Button>\n      </Flex>\n    </form>\n  );\n}\n\ninterface VerificationIdentityFormProps {\n  onSuccess?: () => unknown | Promise<unknown>;\n  authenticationChallengeId: string;\n  type: \"dialog\" | \"alert\";\n}\n\nfunction VerificationIdentityForm({\n  onSuccess,\n  authenticationChallengeId,\n  type,\n}: VerificationIdentityFormProps) {\n  const { data: me } = useMe();\n  const { setElevatedAccess } = useElevatedAccessToken();\n  const verifyIdentity = useVerify();\n  const [isSubmitting, setIsSubmitting] = useState(false);\n\n  const Title = type === \"dialog\" ? Dialog.Title : AlertDialog.Title;\n  const Description =\n    type === \"dialog\" ? Dialog.Description : AlertDialog.Description;\n  const Close = type === \"dialog\" ? Dialog.Close : AlertDialog.Cancel;\n  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n    event.preventDefault();\n\n    const formData = new FormData(event.currentTarget);\n    const code = formData.get(\"otp-code\")?.toString() ?? \"\";\n\n    setIsSubmitting(true);\n\n    try {\n      // Mutate async so we can wait for the onSuccess callback as well\n      const newAuthState = await verifyIdentity.mutateAsync({\n        data: {\n          code,\n          authenticationChallengeId,\n        },\n      });\n\n      const in10Seconds = new Date(Date.now() + 5 * 1000);\n\n      setElevatedAccess({\n        token: newAuthState.elevatedAccessToken,\n        // expiresAt: newAuthState.expiresAt,\n        expiresAt: in10Seconds.toISOString(),\n      });\n\n      if (onSuccess) {\n        await new Promise((resolve) => setTimeout(resolve, 200));\n        await onSuccess();\n      }\n    } catch (error) {\n      console.error(error);\n    }\n\n    setIsSubmitting(false);\n  };\n\n  return (\n    <Form.Root onSubmit={handleSubmit}>\n      <Title>\n        <Translation\n          defaultMessage=\"Verify your identity\"\n          id=\"7tGCOh\"\n          description=\"Dialog title for identity verification\"\n        />\n      </Title>\n\n      <Description>\n        <Translation\n          defaultMessage=\"A verification code was sent to {email}. Please enter it below.\"\n          id=\"kvxE0S\"\n          description=\"Description explaining where verification code was sent\"\n          values={{\n            email: (\n              <Text weight=\"bold\" highContrast>\n                {me?.email}\n              </Text>\n            ),\n          }}\n        />\n      </Description>\n\n      <Flex direction=\"column\" gap=\"2\" mt=\"5\" mx=\"auto\" width=\"fit-content\">\n        <Otp.Root\n          autoSubmit\n          gap=\"2\"\n          justify=\"center\"\n          columns=\"repeat(6, 48px)\"\n          width=\"fit-content\"\n          rows=\"48px\"\n          name=\"otp-code\"\n          readOnly={isSubmitting}\n        >\n          <Otp.Input required autoFocus autoComplete=\"off\" />\n          <Otp.Input required />\n          <Otp.Input required />\n          <Otp.Input required />\n          <Otp.Input required />\n          <Otp.Input required />\n        </Otp.Root>\n\n        {verifyIdentity.error && (\n          <Text color=\"red\" size=\"2\" as=\"p\">\n            {getMutationErrorMessage(verifyIdentity.error)}\n          </Text>\n        )}\n      </Flex>\n\n      <Flex justify=\"end\" align=\"center\" gap=\"3\" mt=\"5\">\n        <Close>\n          <Button variant=\"secondary\" type=\"button\">\n            <Translation\n              defaultMessage=\"Cancel\"\n              id=\"hHNj31\"\n              description=\"Cancel button text\"\n            />\n          </Button>\n        </Close>\n        <Button type=\"submit\" loading={isSubmitting}>\n          <Translation\n            defaultMessage=\"Confirm\"\n            id=\"EmYENK\"\n            description=\"Confirm button text for verification\"\n          />\n        </Button>\n      </Flex>\n    </Form.Root>\n  );\n}\n\nfunction getMutationErrorMessage(error: unknown) {\n  let message = typeof error === \"string\" ? error : \"\";\n\n  if (error instanceof Error) {\n    message = error.message;\n  } else if (\n    typeof error === \"object\" &&\n    error !== null &&\n    \"message\" in error &&\n    typeof error.message === \"string\"\n  ) {\n    message = error.message;\n  }\n\n  if (!message || message === \"Bad Request\") {\n    message = \"Invalid code, please try again.\";\n  }\n\n  return message;\n}\n\n// Note: Error messages in getMutationErrorMessage are kept as plain strings\n// since they are displayed dynamically\n"],"mappings":"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","names":[]}