{"version":3,"sources":["../../../src/lib/invite-user-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { Callout, Flex, Text, VisuallyHidden } from \"@radix-ui/themes\";\nimport * as React from \"react\";\nimport { Dialog, Button, Select, TextField } from \"./elements.js\";\nimport { Label } from \"./elements.js\";\nimport { isErrorLike } from \"./utils.js\";\nimport { useInviteUser } from \"./api/user.js\";\nimport { InviteMemberInput, MemberRole, useRoles } from \"../api/endpoint.js\";\nimport { Translation } from \"./i18n/translation.js\";\nimport { useTranslation } from \"./i18n/use-translation.js\";\n\n/**\n * Used to stub a fake value for the role select. It will be selected by default\n * before the role query resolves, or if the query results in an error. We do\n * this because we need to provide _any_ value to the select to avoid\n * controlled/uncontrolled bugs.\n */\nconst PLACEHOLDER_ROLE = \"_rolePlaceholder\";\n\ninterface InviteUserDialogProps {\n  children?: React.ReactNode;\n}\n\nexport function InviteUserDialog({ children }: InviteUserDialogProps) {\n  const translate = useTranslation();\n  const [open, setOpen] = React.useState(false);\n  const dialogId = toId(\"invite-user\", React.useId());\n  const formId = toId(dialogId, \"form\");\n\n  const inviteUser = useInviteUser();\n  const rolesQuery = useRoles({\n    query: { initialData: [] },\n  });\n  const roles = rolesQuery.data;\n  const [selectedRole, setSelectedRole] = React.useState(\n    () => getDefaultRole(roles)?.slug || PLACEHOLDER_ROLE,\n  );\n  React.useEffect(() => {\n    // Update the selected role if it's not in the list (eg if the list was\n    // previously empty and the query resolved)\n    setSelectedRole((selectedRole) => {\n      if (roles.find((role) => role.slug === selectedRole)) {\n        // if current selected role is in the new list, don't change it\n        return selectedRole;\n      }\n      return getDefaultRole(roles)?.slug || PLACEHOLDER_ROLE;\n    });\n  }, [roles]);\n\n  const onSubmitForm = (data: InviteMemberInput) => {\n    if (inviteUser.isPending || rolesQuery.status !== \"success\") {\n      return;\n    }\n    inviteUser.mutate(\n      { data },\n      {\n        onSuccess: () => {\n          setOpen(false);\n        },\n      },\n    );\n  };\n\n  const formErrors = getFormErrors(inviteUser.error);\n  useFormFieldFocusOnError(dialogId, inviteUser.error);\n\n  return (\n    <Dialog.Root open={open} onOpenChange={setOpen}>\n      {children && <Dialog.Trigger>{children}</Dialog.Trigger>}\n      <Dialog.Content maxWidth=\"480px\" key={String(open)}>\n        <Dialog.Title>\n          <Translation\n            defaultMessage=\"Invite user\"\n            id=\"6+aYFi\"\n            description=\"Dialog title for inviting a user\"\n          />\n        </Dialog.Title>\n        <Dialog.Description>\n          <Translation\n            defaultMessage=\"An invitation will be sent to this email address with a link to complete their account.\"\n            id=\"IicELr\"\n            description=\"Dialog description explaining invitation process\"\n          />\n        </Dialog.Description>\n        <Flex direction=\"column\" gap=\"4\" mt=\"5\" asChild>\n          <form\n            id={formId}\n            onSubmit={async (event) => {\n              event.preventDefault();\n              onSubmitForm({\n                email: event.currentTarget.email.value,\n                roles: [selectedRole],\n              });\n            }}\n          >\n            <FormField\n              rootId={dialogId}\n              name=\"email\"\n              label={translate({\n                defaultMessage: \"Email address\",\n                id: \"ACNqhk\",\n                description: \"Label for email address field\",\n              })}\n              error={formErrors.fields.email}\n              required\n              control={(props) => (\n                <TextField\n                  {...props}\n                  data-1p-ignore=\"true\"\n                  data-lpignore=\"true\"\n                  type=\"email\"\n                  autoComplete=\"off\"\n                  placeholder={translate({\n                    defaultMessage: \"Enter an email address\",\n                    id: \"0ulARV\",\n                    description: \"Placeholder for email address field\",\n                  })}\n                />\n              )}\n            />\n\n            <FormField\n              rootId={dialogId}\n              name=\"role\"\n              label={translate({\n                defaultMessage: \"Role\",\n                id: \"1Htc3a\",\n                description: \"Label for role selection field\",\n              })}\n              error={formErrors.fields.role}\n              disabled={rolesQuery.isPending || roles.length <= 1}\n              info={\n                roles.length === 1 ? (\n                  <Translation\n                    defaultMessage=\"New users will be invited with the {roleName} role, as it is the only one available.\"\n                    id=\"/CNKkm\"\n                    description=\"Info message when only one role is available\"\n                    values={{\n                      roleName: <Text weight=\"bold\">{roles[0].name}</Text>,\n                    }}\n                  />\n                ) : undefined\n              }\n              control={({\n                id,\n                \"aria-invalid\": ariaInvalid,\n                \"aria-describedby\": ariaDescribedBy,\n                ...props\n              }) => (\n                <Select.Root\n                  {...props}\n                  value={selectedRole}\n                  onValueChange={setSelectedRole}\n                >\n                  <Select.Trigger\n                    id={id}\n                    aria-invalid={ariaInvalid}\n                    aria-describedby={ariaDescribedBy}\n                  />\n                  <Select.Content>\n                    <Select.Item value={PLACEHOLDER_ROLE} disabled>\n                      <Translation\n                        defaultMessage=\"Select a role\"\n                        id=\"0VMIWs\"\n                        description=\"Placeholder option for role selection\"\n                      />\n                    </Select.Item>\n                    {roles.map((role) => (\n                      <Select.Item key={role.slug} value={role.slug}>\n                        {role.name}\n                      </Select.Item>\n                    ))}\n                  </Select.Content>\n                </Select.Root>\n              )}\n            />\n          </form>\n        </Flex>\n\n        {formErrors.form ? (\n          <Callout.Root color=\"red\" mt=\"4\" mb=\"-2\">\n            <Callout.Text>{formErrors.form}</Callout.Text>\n          </Callout.Root>\n        ) : null}\n\n        <Flex mt=\"5\" gap=\"3\" justify=\"end\">\n          <Dialog.Close>\n            <Button variant=\"secondary\" disabled={inviteUser.isPending}>\n              <Translation\n                defaultMessage=\"Cancel\"\n                id=\"hHNj31\"\n                description=\"Cancel button text\"\n              />\n            </Button>\n          </Dialog.Close>\n          <Button\n            type=\"submit\"\n            form={formId}\n            loading={inviteUser.isPending}\n            disabled={rolesQuery.isPending || undefined}\n          >\n            <Translation\n              defaultMessage=\"Invite\"\n              id=\"JIQROV\"\n              description=\"Invite button text to send invitation\"\n            />\n          </Button>\n        </Flex>\n        {/* mirror errors in a live region */}\n        <VisuallyHidden asChild>\n          <section aria-live=\"polite\">{formErrors.form}</section>\n        </VisuallyHidden>\n      </Dialog.Content>\n    </Dialog.Root>\n  );\n}\n\ninterface FormControlRenderProps {\n  id: string;\n  name: string;\n  \"aria-describedby\": string | undefined;\n  \"aria-invalid\"?: boolean;\n  required: boolean | undefined;\n  disabled: boolean | undefined;\n}\n\nfunction FormField({\n  rootId,\n  name,\n  label,\n  error,\n  info,\n  control,\n  required,\n  disabled,\n}: {\n  rootId: string;\n  name: string;\n  label: string;\n  error?: React.ReactNode;\n  info?: React.ReactNode;\n  control: (props: FormControlRenderProps) => React.ReactNode;\n  required?: boolean;\n  disabled?: boolean;\n}) {\n  const fieldId = toId(rootId, name);\n  const errorId = toId(rootId, name, \"error\");\n  const infoId = toId(rootId, name, \"info\");\n  return (\n    <Flex direction=\"column\" gap=\"1\">\n      <Label htmlFor={fieldId}>{label}</Label>\n      {control({\n        id: fieldId,\n        name,\n        \"aria-describedby\": (() => {\n          const tags: string[] = [];\n          if (error) {\n            tags.push(errorId);\n          }\n          if (info) {\n            tags.push(infoId);\n          }\n          if (tags.length === 0) {\n            return undefined;\n          }\n          return tags.join(\" \");\n        })(),\n        \"aria-invalid\": !!error || undefined,\n        required: required || undefined,\n        disabled: disabled || undefined,\n      })}\n\n      {error ? (\n        <Text color=\"red\" size=\"2\" id={errorId}>\n          {error}\n        </Text>\n      ) : null}\n      {info ? (\n        <Text color=\"gray\" size=\"2\" id={infoId} mt=\"1\">\n          {info}\n        </Text>\n      ) : null}\n    </Flex>\n  );\n}\n\nfunction toId(...parts: string[]) {\n  return parts.join(\"-\");\n}\n\nfunction getFormErrors(queryError: unknown) {\n  const formErrors = {\n    form: null as string | null,\n    fields: {\n      email: null as string | null,\n      role: null as string | null,\n    },\n  };\n\n  if (queryError) {\n    if (!isErrorLike(queryError)) {\n      return {\n        ...formErrors,\n        form: \"An unexpected error occurred. Please try again.\",\n      };\n    }\n\n    switch (queryError.message.toLowerCase()) {\n      case \"user already exists\":\n      case \"user already invited\":\n      case \"invalid email\":\n        formErrors.fields.email = queryError.message;\n        break;\n      case \"invalid role\":\n        formErrors.fields.role = queryError.message;\n        break;\n      default:\n        // TODO handle more cases for various server errors\n        formErrors.form =\n          \"There was an error inviting this user. Please refresh the page and try again.\";\n        break;\n    }\n  }\n\n  return formErrors;\n}\n\n// Note: Error messages in getFormErrors are kept as plain strings\n// since they are displayed dynamically\n\nfunction useFormFieldFocusOnError(dialogId: string, queryError: unknown) {\n  React.useEffect(() => {\n    const fieldErrors = getFormErrors(queryError).fields;\n    for (const [name, error] of Object.entries(fieldErrors)) {\n      if (error) {\n        const fieldElement = document.getElementById(toId(dialogId, name)) as\n          | HTMLInputElement\n          | HTMLButtonElement\n          | null;\n        if (fieldElement) {\n          fieldElement?.focus();\n          if (\"select\" in fieldElement) {\n            fieldElement.select();\n          }\n        }\n        break;\n      }\n    }\n  }, [dialogId, queryError]);\n}\n\nfunction getDefaultRole(roles: MemberRole[]) {\n  return roles.find((role) => role.default) || roles[0];\n}\n"],"mappings":"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","names":["import_elements","selectedRole"]}