{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "security-settings",
  "title": "Security Settings",
  "description": "A container component that displays security settings including password management, linked accounts, plugin-contributed security cards (e.g. passkeys), active sessions, and danger zone.",
  "dependencies": [
    "@better-auth-ui/core@latest",
    "@better-auth-ui/react@latest",
    "@tanstack/react-form",
    "better-auth",
    "bowser",
    "class-variance-authority",
    "cn",
    "date-fns",
    "lucide-react"
  ],
  "registryDependencies": [
    "alert",
    "alert-dialog",
    "badge",
    "button",
    "calendar",
    "card",
    "checkbox",
    "combobox",
    "dialog",
    "field",
    "https://better-auth-ui.com/r/radix-nova/active-sessions.json",
    "https://better-auth-ui.com/r/radix-nova/change-password.json",
    "https://better-auth-ui.com/r/radix-nova/linked-accounts.json",
    "input",
    "input-group",
    "item",
    "popover",
    "select",
    "skeleton",
    "slider",
    "sonner",
    "spinner",
    "switch",
    "textarea",
    "tooltip"
  ],
  "files": [
    {
      "path": "examples/start-shadcn-example/src/components/auth/additional-field.tsx",
      "content": "\"use client\"\n\nimport {\n  type AdditionalField as AdditionalFieldConfig,\n  type AdditionalFieldFormValue,\n  getFormFieldErrors,\n  resolveInputType\n} from \"@better-auth-ui/core\"\nimport { useAuth, useCopyToClipboard } from \"@better-auth-ui/react\"\nimport { format } from \"date-fns\"\nimport { CalendarIcon, Check, ChevronDownIcon, Copy } from \"lucide-react\"\nimport { type ComponentType, useRef, useState } from \"react\"\nimport { toast } from \"sonner\"\n\nimport { buttonVariants } from \"@/components/ui/button\"\nimport { Calendar } from \"@/components/ui/calendar\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport {\n  Combobox,\n  ComboboxContent,\n  ComboboxEmpty,\n  ComboboxInput,\n  ComboboxItem,\n  ComboboxList\n} from \"@/components/ui/combobox\"\nimport {\n  Field,\n  FieldContent,\n  FieldError,\n  FieldLabel\n} from \"@/components/ui/field\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupInput\n} from \"@/components/ui/input-group\"\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger\n} from \"@/components/ui/popover\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue\n} from \"@/components/ui/select\"\nimport { Slider } from \"@/components/ui/slider\"\nimport { Switch } from \"@/components/ui/switch\"\nimport { Textarea } from \"@/components/ui/textarea\"\nimport { cn } from \"cn\"\n\nexport type AdditionalFieldProps = {\n  name: string\n  field: AdditionalFieldConfig\n  value: AdditionalFieldFormValue\n  onBlur: () => void\n  onChange: (value: AdditionalFieldFormValue) => void\n  isInvalid?: boolean\n  errors?: unknown[]\n  isPending?: boolean\n  /** Complete suffix appended to labels for fields that are not required. */\n  optionalLabel?: string\n}\n\nfunction valueToString(value: AdditionalFieldFormValue) {\n  if (value == null) return \"\"\n  return value instanceof Date ? value.toISOString() : String(value)\n}\n\n/** Convert a `defaultValue` into a `Date` for the calendar. */\nfunction toDate(value: unknown): Date | undefined {\n  if (value instanceof Date) return value\n  if (typeof value === \"string\") {\n    const parsed = new Date(value)\n    return Number.isNaN(parsed.getTime()) ? undefined : parsed\n  }\n  return undefined\n}\n\n/** Format a Date as `HH:mm:ss` for an `<input type=\"time\">`. */\nfunction formatTime(date: Date) {\n  const pad = (n: number) => n.toString().padStart(2, \"0\")\n  return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`\n}\n\n/**\n * Icon-only copy button used as an `InputGroupAddon`. `getValue` is invoked\n * lazily on click so the button copies the input's *live* value rather than a\n * stale snapshot — important when paired with editable inputs.\n */\nfunction CopyButton({\n  getValue,\n  isDisabled\n}: {\n  getValue: () => string | undefined\n  isDisabled?: boolean\n}) {\n  const { localization } = useAuth()\n  const { copied, copy } = useCopyToClipboard({\n    onError: (error) => {\n      console.error(\"[Better Auth UI] Copy failed\", error)\n      toast.error(localization.errors.copyFailed)\n    }\n  })\n\n  async function handleCopy() {\n    const value = getValue()\n    if (!value) return\n\n    await copy(value)\n  }\n\n  return (\n    <InputGroupButton\n      aria-label={\n        copied\n          ? localization.settings.copiedToClipboard\n          : localization.settings.copyToClipboard\n      }\n      title={\n        copied\n          ? localization.settings.copiedToClipboard\n          : localization.settings.copyToClipboard\n      }\n      onClick={handleCopy}\n      disabled={isDisabled}\n    >\n      {copied ? <Check /> : <Copy />}\n    </InputGroupButton>\n  )\n}\n\n/** Renders a single additional user field via shadcn primitives. */\nexport function AdditionalField({\n  name,\n  field: configuredField,\n  value,\n  onBlur,\n  onChange,\n  isInvalid,\n  errors,\n  isPending,\n  optionalLabel\n}: AdditionalFieldProps) {\n  const field =\n    optionalLabel && !configuredField.required\n      ? {\n          ...configuredField,\n          label: (\n            <>\n              {configuredField.label}\n              {optionalLabel}\n            </>\n          )\n        }\n      : configuredField\n  const inputType = resolveInputType(field)\n  const fieldErrors = getFormFieldErrors(errors ?? [])\n\n  if (field.render) {\n    const FieldRenderer = field.render as ComponentType<AdditionalFieldProps>\n    return (\n      <FieldRenderer\n        name={name}\n        field={field}\n        value={value}\n        onBlur={onBlur}\n        onChange={onChange}\n        isInvalid={isInvalid}\n        errors={errors}\n        isPending={isPending}\n        optionalLabel={optionalLabel}\n      />\n    )\n  }\n\n  if (inputType === \"hidden\") {\n    return (\n      <input type=\"hidden\" name={name} value={valueToString(value)} readOnly />\n    )\n  }\n\n  if (inputType === \"textarea\") {\n    return (\n      <Field data-invalid={isInvalid}>\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n        <Textarea\n          id={name}\n          name={name}\n          value={valueToString(value)}\n          onBlur={onBlur}\n          onChange={(event) => onChange(event.target.value || null)}\n          placeholder={field.placeholder}\n          required={field.required}\n          readOnly={field.readOnly}\n          disabled={isPending}\n          aria-invalid={isInvalid}\n        />\n\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"number\") {\n    const maxFractionDigits = field.formatOptions?.maximumFractionDigits\n\n    return (\n      <Field data-invalid={isInvalid}>\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n        <Input\n          id={name}\n          name={name}\n          type=\"number\"\n          inputMode={maxFractionDigits ? \"decimal\" : \"numeric\"}\n          min={field.min}\n          max={field.max}\n          step={\n            field.step ??\n            (maxFractionDigits ? 1 / 10 ** maxFractionDigits : undefined)\n          }\n          value={typeof value === \"number\" ? value : \"\"}\n          onBlur={onBlur}\n          onChange={(event) =>\n            onChange(\n              event.target.value === \"\" ? null : event.target.valueAsNumber\n            )\n          }\n          placeholder={field.placeholder}\n          required={field.required}\n          readOnly={field.readOnly}\n          disabled={isPending}\n          aria-invalid={isInvalid}\n        />\n\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"slider\") {\n    return (\n      <SliderField\n        name={name}\n        field={field}\n        value={value}\n        onBlur={onBlur}\n        onChange={onChange}\n        isInvalid={isInvalid}\n        errors={errors}\n        isPending={isPending}\n      />\n    )\n  }\n\n  if (inputType === \"switch\") {\n    return (\n      <Field data-invalid={isInvalid} orientation=\"horizontal\">\n        <Switch\n          id={name}\n          name={name}\n          checked={value === true}\n          onBlur={onBlur}\n          onCheckedChange={onChange}\n          disabled={isPending || field.readOnly}\n          aria-invalid={isInvalid}\n        />\n\n        <FieldContent>\n          <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n        </FieldContent>\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"checkbox\") {\n    return (\n      <Field data-invalid={isInvalid} orientation=\"horizontal\">\n        <Checkbox\n          id={name}\n          name={name}\n          checked={value === true}\n          onBlur={onBlur}\n          onCheckedChange={(checked) => onChange(checked === true)}\n          required={field.required}\n          disabled={isPending || field.readOnly}\n          aria-invalid={isInvalid}\n        />\n\n        <FieldContent>\n          <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n        </FieldContent>\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"select\") {\n    return (\n      <Field data-invalid={isInvalid}>\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n        <Select\n          name={name}\n          value={valueToString(value) || undefined}\n          onValueChange={(nextValue) => onChange(nextValue)}\n          required={field.required}\n          disabled={isPending || field.readOnly}\n        >\n          <SelectTrigger\n            id={name}\n            className=\"w-full\"\n            onBlur={onBlur}\n            aria-invalid={isInvalid}\n          >\n            <SelectValue placeholder={field.placeholder} />\n          </SelectTrigger>\n\n          <SelectContent>\n            {field.options?.map((option) => (\n              <SelectItem key={option.value} value={option.value}>\n                {option.label}\n              </SelectItem>\n            ))}\n          </SelectContent>\n        </Select>\n\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"combobox\") {\n    const selectedOption = field.options?.find(\n      (option) => option.value === valueToString(value)\n    )\n\n    return (\n      <Field data-invalid={isInvalid}>\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n        <Combobox\n          items={field.options ?? []}\n          name={name}\n          value={selectedOption ?? null}\n          onValueChange={(option) => onChange(option?.value ?? null)}\n          required={field.required}\n          disabled={isPending || field.readOnly}\n        >\n          <ComboboxInput\n            placeholder={field.placeholder}\n            id={name}\n            onBlur={onBlur}\n            aria-invalid={isInvalid}\n          />\n\n          <ComboboxContent>\n            <ComboboxEmpty>No items found.</ComboboxEmpty>\n\n            <ComboboxList>\n              {(option) => (\n                <ComboboxItem key={option.value} value={option}>\n                  {option.label}\n                </ComboboxItem>\n              )}\n            </ComboboxList>\n          </ComboboxContent>\n        </Combobox>\n\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"date\" || inputType === \"datetime\") {\n    return (\n      <DateInput\n        name={name}\n        field={field}\n        value={value}\n        onBlur={onBlur}\n        onChange={onChange}\n        isInvalid={isInvalid}\n        errors={errors}\n        isPending={isPending}\n      />\n    )\n  }\n\n  return (\n    <InputField\n      name={name}\n      field={field}\n      value={value}\n      onBlur={onBlur}\n      onChange={onChange}\n      isInvalid={isInvalid}\n      errors={errors}\n      isPending={isPending}\n    />\n  )\n}\n\nfunction InputField({\n  name,\n  field,\n  value,\n  onBlur,\n  onChange,\n  isInvalid,\n  errors,\n  isPending\n}: AdditionalFieldProps) {\n  const inputRef = useRef<HTMLInputElement>(null)\n  const fieldErrors = getFormFieldErrors(errors ?? [])\n\n  const hasPrefix = field.prefix != null\n  const hasSuffix = field.suffix != null || field.copyable\n\n  const isNumeric = field.type === \"number\"\n  const maxFractionDigits = field.formatOptions?.maximumFractionDigits\n  const nativeInputType = isNumeric ? \"number\" : undefined\n  const nativeInputMode = isNumeric\n    ? maxFractionDigits\n      ? \"decimal\"\n      : \"numeric\"\n    : undefined\n  const nativeStep = maxFractionDigits ? 1 / 10 ** maxFractionDigits : undefined\n\n  if (hasPrefix || hasSuffix) {\n    return (\n      <Field data-invalid={isInvalid}>\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n        <InputGroup>\n          {hasPrefix && (\n            <InputGroupAddon align=\"inline-start\">\n              {field.prefix}\n            </InputGroupAddon>\n          )}\n\n          <InputGroupInput\n            ref={inputRef}\n            id={name}\n            name={name}\n            type={nativeInputType}\n            inputMode={nativeInputMode}\n            step={nativeStep}\n            value={valueToString(value)}\n            onBlur={onBlur}\n            onChange={(event) => onChange(event.target.value || null)}\n            placeholder={field.placeholder}\n            required={field.required}\n            readOnly={field.readOnly}\n            disabled={isPending}\n            aria-invalid={isInvalid}\n          />\n\n          {field.copyable ? (\n            <InputGroupAddon align=\"inline-end\">\n              <CopyButton\n                getValue={() => inputRef.current?.value}\n                isDisabled={isPending}\n              />\n            </InputGroupAddon>\n          ) : (\n            field.suffix != null && (\n              <InputGroupAddon align=\"inline-end\">\n                {field.suffix}\n              </InputGroupAddon>\n            )\n          )}\n        </InputGroup>\n\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  return (\n    <Field data-invalid={isInvalid}>\n      <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n      <Input\n        id={name}\n        name={name}\n        type={nativeInputType}\n        inputMode={nativeInputMode}\n        step={nativeStep}\n        value={valueToString(value)}\n        onBlur={onBlur}\n        onChange={(event) => onChange(event.target.value || null)}\n        placeholder={field.placeholder}\n        required={field.required}\n        readOnly={field.readOnly}\n        disabled={isPending}\n        aria-invalid={isInvalid}\n      />\n\n      <FieldError errors={fieldErrors} />\n    </Field>\n  )\n}\n\n/**\n * Slider field. Radix Slider doesn't render the current value, so we render\n * it next to the label and control the state to keep the displayed value in\n * sync. The selected value is submitted via the underlying Radix `name` prop.\n */\nfunction SliderField({\n  name,\n  field,\n  value,\n  onBlur,\n  onChange,\n  isInvalid,\n  errors,\n  isPending\n}: AdditionalFieldProps) {\n  const maxFractionDigits = field.formatOptions?.maximumFractionDigits\n  const min = field.min ?? 0\n  const max = field.max ?? 100\n  const step =\n    field.step ?? (maxFractionDigits ? 1 / 10 ** maxFractionDigits : 1)\n  const numericValue = typeof value === \"number\" ? value : min\n  const fieldErrors = getFormFieldErrors(errors ?? [])\n\n  const formatter = new Intl.NumberFormat(undefined, field.formatOptions)\n\n  return (\n    <Field data-invalid={isInvalid}>\n      <div className=\"flex items-center justify-between gap-2\">\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n        <span className=\"text-sm text-muted-foreground tabular-nums\">\n          {formatter.format(numericValue)}\n        </span>\n      </div>\n\n      <Slider\n        id={name}\n        name={name}\n        value={[numericValue]}\n        onBlur={onBlur}\n        onValueChange={(nextValue) =>\n          onChange((Array.isArray(nextValue) ? nextValue[0] : nextValue) ?? min)\n        }\n        min={min}\n        max={max}\n        step={step}\n        disabled={isPending || field.readOnly}\n        aria-invalid={isInvalid}\n      />\n\n      <FieldError errors={fieldErrors} />\n    </Field>\n  )\n}\n\n/**\n * Date / datetime input. Composes `Popover` + `Calendar` for the date and\n * (optionally) `<input type=\"time\">` for the time. Submits the combined ISO\n * value via a hidden `<input>` so it shows up in `FormData`.\n */\nfunction DateInput({\n  name,\n  field,\n  value,\n  onBlur,\n  onChange,\n  isInvalid,\n  errors,\n  isPending\n}: AdditionalFieldProps) {\n  const { localization } = useAuth()\n  const inputType = resolveInputType(field)\n  const isDateTime = inputType === \"datetime\"\n  const fieldErrors = getFormFieldErrors(errors ?? [])\n\n  const date = toDate(value)\n  const [time, setTime] = useState<string>(\n    isDateTime && date ? formatTime(date) : \"\"\n  )\n  const [open, setOpen] = useState(false)\n\n  // Compose the hidden form value: ISO date for \"date\", ISO datetime for\n  // \"datetime\" (date + time).\n  return (\n    <Field data-invalid={isInvalid}>\n      <FieldLabel htmlFor={`${name}-date`}>{field.label}</FieldLabel>\n\n      <div className=\"relative flex gap-2\">\n        <Popover open={open} onOpenChange={setOpen}>\n          <PopoverTrigger\n            type=\"button\"\n            id={`${name}-date`}\n            data-empty={!date}\n            aria-invalid={isInvalid}\n            aria-required={field.required}\n            onBlur={onBlur}\n            disabled={isPending || field.readOnly}\n            className={cn(\n              buttonVariants({ variant: \"outline\" }),\n              \"flex-1 justify-between font-normal\",\n              \"data-[empty=true]:text-muted-foreground\"\n            )}\n          >\n            {date ? format(date, \"PPP\") : <span>{field.placeholder}</span>}\n\n            {isDateTime ? <ChevronDownIcon /> : <CalendarIcon />}\n          </PopoverTrigger>\n\n          <PopoverContent className=\"w-auto overflow-hidden p-0\" align=\"start\">\n            <Calendar\n              mode=\"single\"\n              selected={date}\n              defaultMonth={date}\n              captionLayout=\"dropdown\"\n              onSelect={(value) => {\n                if (!value) {\n                  onChange(null)\n                } else {\n                  const nextValue = new Date(value)\n                  if (isDateTime && time.trim()) {\n                    const [hours = \"0\", minutes = \"0\", seconds = \"0\"] =\n                      time.split(\":\")\n                    nextValue.setHours(\n                      Number(hours),\n                      Number(minutes),\n                      Number(seconds),\n                      0\n                    )\n                  } else {\n                    nextValue.setHours(0, 0, 0, 0)\n                  }\n                  onChange(nextValue)\n                }\n                if (!isDateTime) setOpen(false)\n              }}\n            />\n          </PopoverContent>\n        </Popover>\n\n        {isDateTime && (\n          <Field className=\"w-32\">\n            <FieldLabel htmlFor={`${name}-time`} className=\"sr-only\">\n              {localization.settings.time}\n            </FieldLabel>\n\n            <Input\n              type=\"time\"\n              id={`${name}-time`}\n              step=\"1\"\n              value={time}\n              onChange={(event) => {\n                const nextTime = event.target.value\n                setTime(nextTime)\n                if (!date) return\n                const nextValue = new Date(date)\n                const [hours = \"0\", minutes = \"0\", seconds = \"0\"] =\n                  nextTime.split(\":\")\n                nextValue.setHours(\n                  Number(hours),\n                  Number(minutes),\n                  Number(seconds),\n                  0\n                )\n                onChange(nextValue)\n              }}\n              disabled={isPending || field.readOnly}\n              className=\"appearance-none bg-background [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none\"\n            />\n          </Field>\n        )}\n      </div>\n\n      <FieldError errors={fieldErrors} />\n    </Field>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/additional-field.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/auth-form.tsx",
      "content": "\"use client\"\n\nimport {\n  type AdditionalField as AdditionalFieldConfig,\n  type AdditionalFieldFormValue,\n  DEFAULT_ADDITIONAL_FIELD_VALIDATION_DEBOUNCE_MS,\n  getFormFieldErrors,\n  normalizeAuthFormServerError,\n  validateAdditionalFieldRequired,\n  validateAdditionalFieldValue\n} from \"@better-auth-ui/core\"\nimport {\n  type AnyFormApi,\n  createFormHook,\n  createFormHookContexts\n} from \"@tanstack/react-form\"\nimport {\n  type ComponentProps,\n  type FormEvent,\n  type ReactNode,\n  useRef\n} from \"react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Field,\n  FieldDescription,\n  FieldError,\n  FieldLabel\n} from \"@/components/ui/field\"\nimport { Input } from \"@/components/ui/input\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { AdditionalField, type AdditionalFieldProps } from \"./additional-field\"\n\nconst { fieldContext, formContext, useFieldContext, useFormContext } =\n  createFormHookContexts()\n\nconst DEFAULT_AUTH_FORM_SERVER_ERROR = \"Unable to submit this form. Try again.\"\n\nexport function focusFirstInvalidAuthFormControl(form: HTMLFormElement) {\n  requestAnimationFrame(() => {\n    form\n      .querySelector<HTMLElement>(\n        '[aria-invalid=\"true\"]:not([disabled]), :invalid:not([disabled])'\n      )\n      ?.focus()\n  })\n}\n\nfunction AuthFormFieldError() {\n  const field = useFieldContext<unknown>()\n  const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid\n\n  if (!isInvalid) return null\n\n  const errors = getFormFieldErrors(field.state.meta.errors)\n\n  return errors.length > 0 ? <FieldError errors={errors} /> : null\n}\n\nfunction AuthFormServerError() {\n  const form = useFormContext()\n\n  return (\n    <form.Subscribe selector={(state) => state.errorMap.onServer}>\n      {(error) => {\n        const formError =\n          error && typeof error === \"object\" && \"form\" in error\n            ? error.form\n            : error\n        const errors = getFormFieldErrors(formError ? [formError] : [])\n        return errors.length > 0 ? <FieldError errors={errors} /> : null\n      }}\n    </form.Subscribe>\n  )\n}\n\nexport function setAuthFormServerError(\n  form: AnyFormApi,\n  error: unknown,\n  fallbackMessage: string\n) {\n  const normalized = normalizeAuthFormServerError(error, fallbackMessage)\n  form.setErrorMap({\n    onServer: {\n      fields: normalized.fields ?? {},\n      form: normalized.form\n    }\n  })\n}\n\nexport function clearAuthFormServerError(form: AnyFormApi) {\n  form.setErrorMap({ onServer: { fields: {} } })\n}\n\nexport function clearAuthFormFieldServerError(\n  form: AnyFormApi,\n  fieldName: string\n) {\n  form.setErrorMap({ onServer: undefined })\n  if (!fieldName) return\n\n  const fieldMeta = form.getFieldMeta(fieldName as never)\n  if (!fieldMeta?.errorMap.onServer) return\n\n  form.setFieldMeta(fieldName as never, (current = fieldMeta) => ({\n    ...current,\n    errorMap: { ...current.errorMap, onServer: undefined },\n    errorSourceMap: { ...current.errorSourceMap, onServer: undefined }\n  }))\n}\n\nexport async function runAuthFormAction(\n  form: AnyFormApi,\n  action: () => Promise<unknown>,\n  serverErrorMessage = DEFAULT_AUTH_FORM_SERVER_ERROR\n) {\n  clearAuthFormServerError(form)\n  try {\n    await action()\n    return true\n  } catch (error) {\n    if (!form.state.errorMap.onServer) {\n      setAuthFormServerError(form, error, serverErrorMessage)\n    }\n    return false\n  }\n}\n\nexport async function submitAuthForm(\n  form: AnyFormApi,\n  serverErrorMessage = DEFAULT_AUTH_FORM_SERVER_ERROR\n) {\n  clearAuthFormServerError(form)\n  try {\n    await form.handleSubmit()\n    return form.state.isValid\n  } catch (error) {\n    if (!form.state.errorMap.onServer) {\n      setAuthFormServerError(form, error, serverErrorMessage)\n    }\n    return false\n  }\n}\n\ntype AuthFormRootProps = Omit<ComponentProps<\"form\">, \"onSubmit\"> & {\n  onBeforeSubmit?: () => void\n  serverErrorMessage?: string\n}\n\nfunction AuthFormRoot({\n  children,\n  onBeforeSubmit,\n  onInput,\n  serverErrorMessage = DEFAULT_AUTH_FORM_SERVER_ERROR,\n  ...props\n}: AuthFormRootProps) {\n  const form = useFormContext()\n  const submittingRef = useRef(false)\n\n  async function submit(event: FormEvent<HTMLFormElement>) {\n    event.preventDefault()\n    if (submittingRef.current || form.state.isSubmitting) return\n\n    const formElement = event.currentTarget\n    onBeforeSubmit?.()\n    submittingRef.current = true\n    try {\n      const isValid = await submitAuthForm(form, serverErrorMessage)\n      if (!isValid) focusFirstInvalidAuthFormControl(formElement)\n    } finally {\n      submittingRef.current = false\n    }\n  }\n\n  return (\n    <form\n      {...props}\n      onInvalid={(event) =>\n        focusFirstInvalidAuthFormControl(event.currentTarget)\n      }\n      onInput={(event) => {\n        const target = event.target\n        const fieldName =\n          target instanceof HTMLInputElement ||\n          target instanceof HTMLSelectElement ||\n          target instanceof HTMLTextAreaElement\n            ? target.name\n            : \"\"\n        clearAuthFormFieldServerError(form, fieldName)\n        onInput?.(event)\n      }}\n      onSubmit={submit}\n    >\n      {children}\n    </form>\n  )\n}\n\ntype AuthFormTextFieldProps = Omit<\n  ComponentProps<typeof Input>,\n  \"name\" | \"onBlur\" | \"onChange\" | \"value\"\n> & {\n  description?: ReactNode\n  label: ReactNode\n}\n\nfunction AuthFormTextField({\n  description,\n  id,\n  label,\n  ...props\n}: AuthFormTextFieldProps) {\n  const field = useFieldContext<string>()\n  const form = useFormContext()\n  const isInvalid = isAuthFormFieldInvalid(field.state.meta)\n  const inputId = id ?? field.name\n\n  return (\n    <Field data-invalid={isInvalid}>\n      <FieldLabel htmlFor={inputId}>{label}</FieldLabel>\n      <Input\n        {...props}\n        aria-busy={field.state.meta.isValidating || undefined}\n        aria-invalid={isInvalid}\n        id={inputId}\n        name={field.name}\n        onBlur={field.handleBlur}\n        onChange={(event) => {\n          clearAuthFormFieldServerError(form, field.name)\n          field.handleChange(event.target.value)\n        }}\n        value={field.state.value}\n      />\n      {description ? <FieldDescription>{description}</FieldDescription> : null}\n      <AuthFormFieldError />\n    </Field>\n  )\n}\n\nfunction AuthFormSubmitButton({\n  children,\n  disabled,\n  isPending,\n  ...props\n}: ComponentProps<typeof Button> & { isPending?: boolean }) {\n  const form = useFormContext()\n\n  return (\n    <form.Subscribe\n      selector={(state) => [state.isSubmitting, state.isValidating] as const}\n    >\n      {([isSubmitting, isValidating]) => (\n        <Button\n          {...props}\n          aria-busy={isPending || isSubmitting || undefined}\n          aria-disabled={\n            disabled || isPending || isSubmitting || isValidating || undefined\n          }\n          disabled={disabled || isPending || isSubmitting || isValidating}\n          type=\"submit\"\n        >\n          {isPending || isSubmitting ? (\n            <Spinner data-icon=\"inline-start\" />\n          ) : null}\n          {children}\n        </Button>\n      )}\n    </form.Subscribe>\n  )\n}\n\ntype AuthFormAdditionalFieldProps = Omit<\n  AdditionalFieldProps,\n  \"errors\" | \"isInvalid\" | \"name\" | \"onBlur\" | \"onChange\" | \"value\"\n>\n\nfunction AuthFormAdditionalField(props: AuthFormAdditionalFieldProps) {\n  const field = useFieldContext<AdditionalFieldFormValue>()\n  const form = useFormContext()\n  const isInvalid = isAuthFormFieldInvalid(field.state.meta)\n\n  return (\n    <AdditionalField\n      {...props}\n      errors={\n        isInvalid ? getFormFieldErrors(field.state.meta.errors) : undefined\n      }\n      isInvalid={isInvalid}\n      name={field.name}\n      onBlur={field.handleBlur}\n      onChange={(value) => {\n        clearAuthFormFieldServerError(form, field.name)\n        field.handleChange(value)\n      }}\n      value={field.state.value}\n    />\n  )\n}\n\nexport const {\n  useAppForm: useAuthForm,\n  withFieldGroup: withAuthFieldGroup,\n  withForm: withAuthForm\n} = createFormHook({\n  fieldComponents: {\n    AuthFormAdditionalField,\n    AuthFormFieldError,\n    AuthFormTextField\n  },\n  fieldContext,\n  formComponents: {\n    AuthFormRoot,\n    AuthFormServerError,\n    AuthFormSubmitButton\n  },\n  formContext\n})\n\nexport function isAuthFormFieldInvalid({\n  isTouched,\n  isValid\n}: {\n  isTouched: boolean\n  isValid: boolean\n}) {\n  return isTouched && !isValid\n}\n\nexport function getAuthAdditionalFieldValidators(\n  field: AdditionalFieldConfig,\n  requiredMessage: string\n) {\n  return {\n    onChange: ({ value }: { value: AdditionalFieldFormValue }) =>\n      validateAdditionalFieldRequired(field, value, requiredMessage),\n    onChangeAsync: field.validate\n      ? ({ value }: { value: AdditionalFieldFormValue }) =>\n          validateAdditionalFieldValue(field, value)\n      : undefined,\n    onChangeAsyncDebounceMs: field.validate\n      ? (field.validateDebounceMs ??\n        DEFAULT_ADDITIONAL_FIELD_VALIDATION_DEBOUNCE_MS)\n      : undefined\n  }\n}\n",
      "type": "registry:component",
      "target": "@components/auth/auth-form.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/open-email-button.tsx",
      "content": "\"use client\"\n\nimport { createQrCodeSvgData, getEmailProviderLink } from \"@better-auth-ui/core\"\nimport { useAuth } from \"@better-auth-ui/react\"\nimport type { VariantProps } from \"class-variance-authority\"\nimport { QrCode } from \"lucide-react\"\nimport { useMemo } from \"react\"\n\nimport { buttonVariants } from \"@/components/ui/button\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger\n} from \"@/components/ui/tooltip\"\nimport { cn } from \"cn\"\n\nexport type OpenEmailButtonProps = {\n  /** Email address used to detect the provider, e.g. from the verify-email flow. */\n  email: string\n  className?: string\n  /**\n   * Button variant. Defaults to the primary style for dead-end views where\n   * opening the inbox is the only action; pass `\"secondary\"` where it sits\n   * beside a submit button that should stay the primary call to action.\n   */\n  variant?: VariantProps<typeof buttonVariants>[\"variant\"]\n}\n\n/**\n * Render a button that opens the user's email provider login page in a new\n * tab. Hovering or focusing the button reveals a QR code for opening the same\n * provider on another device.\n *\n * The provider is resolved from the email domain via the curated\n * `@mikkelscheike/email-provider-links` dataset (Gmail, Outlook, GMX, etc.).\n * Renders nothing when the domain is empty or not a known provider.\n *\n * @param email - Email address to resolve the provider from.\n * @param className - Additional CSS classes applied to the button.\n * @param variant - Button variant. Defaults to the primary style.\n * @returns The open-email button, or `null` when no provider matches.\n */\nexport function OpenEmailButton({\n  email,\n  className,\n  variant\n}: OpenEmailButtonProps) {\n  const { localization } = useAuth()\n\n  const provider = getEmailProviderLink(email)\n  const loginUrl = provider?.loginUrl\n  const qrCode = useMemo(\n    () => (loginUrl ? createQrCodeSvgData(loginUrl) : null),\n    [loginUrl]\n  )\n\n  if (!provider || !qrCode) return null\n\n  const scanLabel = localization.auth.scanToOpenEmailProvider.replace(\n    \"{{provider}}\",\n    provider.companyProvider\n  )\n\n  return (\n    <TooltipProvider>\n      <Tooltip>\n        <TooltipTrigger\n          type=\"button\"\n          className={cn(buttonVariants({ variant }), \"w-full\", className)}\n          onClick={() =>\n            window.open(provider.loginUrl, \"_blank\", \"noopener,noreferrer\")\n          }\n        >\n          {localization.auth.openEmailProvider.replace(\n            \"{{provider}}\",\n            provider.companyProvider\n          )}\n          <QrCode data-icon=\"inline-end\" />\n        </TooltipTrigger>\n        <TooltipContent\n          sideOffset={8}\n          className=\"flex-col items-center gap-2 p-3\"\n        >\n          <svg\n            viewBox={`0 0 ${qrCode.size} ${qrCode.size}`}\n            aria-hidden=\"true\"\n            focusable=\"false\"\n            className=\"size-40\"\n          >\n            <path fill=\"white\" d={`M0 0h${qrCode.size}v${qrCode.size}H0z`} />\n            <path fill=\"black\" d={qrCode.path} shapeRendering=\"crispEdges\" />\n          </svg>\n          <p className=\"max-w-40 text-center leading-snug\">{scanLabel}</p>\n        </TooltipContent>\n      </Tooltip>\n    </TooltipProvider>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/open-email-button.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/password-strength-meter.tsx",
      "content": "\"use client\"\n\nimport {\n  evaluatePasswordStrength,\n  type PasswordStrengthLevel\n} from \"@better-auth-ui/core\"\nimport { useAuth } from \"@better-auth-ui/react\"\n\nimport { cn } from \"cn\"\n\n/** Fixed segment identities, so the bars keep their own React keys. */\nconst STRENGTH_SEGMENTS = [1, 2, 3, 4] as const\n\ntype FilledLevel = Exclude<PasswordStrengthLevel, \"empty\">\n\nconst segmentColors: Record<FilledLevel, string> = {\n  weak: \"bg-destructive\",\n  fair: \"bg-amber-500\",\n  good: \"bg-sky-500\",\n  strong: \"bg-emerald-500\"\n}\n\nexport type PasswordStrengthMeterProps = {\n  /** The password as typed. Renders nothing while it is empty. */\n  password: string\n  className?: string\n}\n\n/**\n * Four-segment strength hint shown while someone picks a new password.\n *\n * Renders nothing when `emailAndPassword.strengthMeter` is off or the field is\n * empty. The score never gates submission: your server rules stay the\n * authority on what is acceptable.\n */\nexport function PasswordStrengthMeter({\n  password,\n  className\n}: PasswordStrengthMeterProps) {\n  const { emailAndPassword, localization } = useAuth()\n\n  if (!emailAndPassword?.strengthMeter) return null\n\n  const { score, level } = evaluatePasswordStrength(password, {\n    minLength: emailAndPassword.minPasswordLength\n  })\n\n  if (level === \"empty\") return null\n\n  const levelLabels: Record<FilledLevel, string> = {\n    weak: localization.auth.passwordWeak,\n    fair: localization.auth.passwordFair,\n    good: localization.auth.passwordGood,\n    strong: localization.auth.passwordStrong\n  }\n\n  return (\n    <div className={cn(\"flex flex-col gap-1.5\", className)}>\n      {/* Decorative: the live region below is what gets announced. */}\n      <div aria-hidden=\"true\" className=\"flex gap-1\">\n        {STRENGTH_SEGMENTS.map((segment) => (\n          <span\n            key={segment}\n            className={cn(\n              \"h-1 flex-1 rounded-full bg-muted transition-colors\",\n              segment <= score && segmentColors[level]\n            )}\n          />\n        ))}\n      </div>\n\n      <p aria-live=\"polite\" className=\"text-muted-foreground text-xs\">\n        {localization.auth.passwordStrength}:{\" \"}\n        <span className=\"font-medium text-foreground\">\n          {levelLabels[level]}\n        </span>\n      </p>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/password-strength-meter.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/reauthentication.tsx",
      "content": "\"use client\"\n\nimport {\n  getReauthenticationSignInURL,\n  isReauthenticationSignInURL\n} from \"@better-auth-ui/core\"\nimport { useAuth, useSignOut } from \"@better-auth-ui/react\"\nimport { useSyncExternalStore } from \"react\"\nimport { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\"\nimport { Button } from \"@/components/ui/button\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { cn } from \"cn\"\n\nconst subscribeToLocation = () => () => undefined\n\nfunction useIsReauthenticationSignIn() {\n  return useSyncExternalStore(\n    subscribeToLocation,\n    () => isReauthenticationSignInURL(new URL(window.location.href)),\n    () => false\n  )\n}\n\nexport type ReauthenticationActionProps = {\n  className?: string\n  showTitle?: boolean\n}\n\nexport function ReauthenticationAction({\n  className,\n  showTitle = true\n}: ReauthenticationActionProps) {\n  const auth = useAuth()\n  const signOut = useSignOut(auth.authClient)\n\n  const handleReauthentication = () => {\n    const signInURL = getReauthenticationSignInURL(\n      new URL(window.location.href),\n      `${auth.basePaths.auth}/${auth.viewPaths.auth.signIn}`\n    )\n    signOut.mutate(undefined, {\n      onSuccess: () => auth.navigate({ to: signInURL })\n    })\n  }\n\n  return (\n    <div className={cn(\"flex flex-col items-start gap-3 p-4\", className)}>\n      <div className=\"flex flex-col gap-1\">\n        {showTitle ? (\n          <h3 className=\"text-sm font-medium\">\n            {auth.localization.settings.reauthenticationTitle}\n          </h3>\n        ) : null}\n        <p className=\"text-sm text-muted-foreground\">\n          {auth.localization.settings.reauthenticationDescription}\n        </p>\n      </div>\n      <Button\n        disabled={signOut.isPending}\n        onClick={handleReauthentication}\n        size=\"sm\"\n      >\n        {signOut.isPending ? <Spinner data-icon=\"inline-start\" /> : null}\n        {auth.localization.settings.reauthenticationAction}\n      </Button>\n    </div>\n  )\n}\n\nexport function ReauthenticationNotice() {\n  const auth = useAuth()\n  const isReauthenticationSignIn = useIsReauthenticationSignIn()\n  if (!isReauthenticationSignIn) return null\n\n  return (\n    <Alert className=\"mx-4 w-auto group-data-[size=sm]/card:mx-3\">\n      <AlertTitle>\n        {auth.localization.settings.reauthenticationTitle}\n      </AlertTitle>\n      <AlertDescription>\n        {auth.localization.settings.reauthenticationDescription}\n      </AlertDescription>\n    </Alert>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/reauthentication.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/settings/security/active-session.tsx",
      "content": "\"use client\"\n\nimport { useAuth, useRevokeSession, useSession } from \"@better-auth-ui/react\"\nimport type { Session } from \"better-auth\"\nimport Bowser from \"bowser\"\nimport { LogOut, Monitor, Smartphone, X } from \"lucide-react\"\nimport { toast } from \"sonner\"\n\nimport { Badge } from \"@/components/ui/badge\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Item,\n  ItemActions,\n  ItemContent,\n  ItemDescription,\n  ItemMedia,\n  ItemTitle\n} from \"@/components/ui/item\"\nimport { Spinner } from \"@/components/ui/spinner\"\n\nfunction timeAgo(date: Date) {\n  const seconds = Math.floor((Date.now() - date.getTime()) / 1000)\n  const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: \"auto\" })\n\n  const UNITS: [Intl.RelativeTimeFormatUnit, number][] = [\n    [\"year\", 31536000],\n    [\"month\", 2592000],\n    [\"week\", 604800],\n    [\"day\", 86400],\n    [\"hour\", 3600],\n    [\"minute\", 60],\n    [\"second\", 1]\n  ]\n\n  for (const [unit, threshold] of UNITS) {\n    if (seconds >= threshold) {\n      return rtf.format(-Math.floor(seconds / threshold), unit)\n    }\n  }\n\n  return rtf.format(0, \"second\")\n}\n\nexport type ActiveSessionProps = {\n  activeSession: Session\n}\n\n/**\n * Render a single active session row with device info and revoke control.\n *\n * Shows the session's browser, OS, and creation time. The current session is marked\n * and navigates to sign-out on click, while other sessions can be revoked individually.\n *\n * @param session - The session object containing id, token, userAgent, ipAddress, and createdAt\n * @returns A JSX element containing the active session row\n */\nexport function ActiveSession({ activeSession }: ActiveSessionProps) {\n  const { authClient, basePaths, localization, viewPaths, navigate } = useAuth()\n  const { data: session } = useSession(authClient, { refetchOnMount: false })\n\n  const { mutate: revokeSession, isPending: isRevoking } = useRevokeSession(\n    authClient,\n    {\n      onSuccess: () => toast.success(localization.settings.revokeSessionSuccess)\n    }\n  )\n\n  const isCurrentSession = activeSession.token === session?.session.token\n  const ua = activeSession.userAgent\n    ? Bowser.parse(activeSession.userAgent)\n    : undefined\n  const isMobile =\n    ua?.platform.type === \"mobile\" || ua?.platform.type === \"tablet\"\n\n  return (\n    <Item>\n      <ItemMedia variant=\"icon\">\n        {isMobile ? <Smartphone /> : <Monitor />}\n      </ItemMedia>\n      <ItemContent>\n        <ItemTitle>\n          {ua?.browser.name || \"Unknown Browser\"}\n          {ua?.os.name ? `, ${ua.os.name}` : \"\"}\n        </ItemTitle>\n        {isCurrentSession ? (\n          <Badge variant=\"secondary\">\n            {localization.settings.currentSession}\n          </Badge>\n        ) : (\n          activeSession.createdAt && (\n            <ItemDescription className=\"capitalize\">\n              {timeAgo(activeSession.createdAt)}\n            </ItemDescription>\n          )\n        )}\n      </ItemContent>\n      <ItemActions>\n        <Button\n          variant=\"outline\"\n          size=\"sm\"\n          onClick={() =>\n            isCurrentSession\n              ? navigate({\n                  to: `${basePaths.auth}/${viewPaths.auth.signOut}`\n                })\n              : revokeSession(activeSession)\n          }\n          disabled={isRevoking}\n          aria-label={\n            isCurrentSession\n              ? localization.auth.signOut\n              : localization.settings.revokeSession\n          }\n        >\n          {isRevoking ? <Spinner /> : isCurrentSession ? <LogOut /> : <X />}\n\n          {isCurrentSession\n            ? localization.auth.signOut\n            : localization.settings.revoke}\n        </Button>\n      </ItemActions>\n    </Item>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/settings/security/active-session.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/settings/security/active-sessions.tsx",
      "content": "\"use client\"\n\nimport { isReauthenticationRequiredError } from \"@better-auth-ui/core\"\nimport { useAuth, useListSessions, useSession } from \"@better-auth-ui/react\"\nimport { Fragment } from \"react\"\nimport { Card, CardContent } from \"@/components/ui/card\"\nimport {\n  Item,\n  ItemContent,\n  ItemGroup,\n  ItemMedia,\n  ItemSeparator\n} from \"@/components/ui/item\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { cn } from \"cn\"\nimport { ReauthenticationAction } from \"../../reauthentication\"\nimport { ActiveSession } from \"./active-session\"\nimport { SessionActions } from \"./session-actions\"\n\nexport type ActiveSessionsProps = {\n  className?: string\n}\n\n/**\n * Render a card listing all active sessions for the current user with revoke controls.\n *\n * Shows each session's browser, OS, IP address, and creation time. The current session is marked\n * and navigates to sign-out on click, while other sessions can be revoked individually.\n *\n * @returns A JSX element containing the sessions card\n */\nexport function ActiveSessions({ className }: ActiveSessionsProps) {\n  const { authClient, localization } = useAuth()\n  const { data: session } = useSession(authClient)\n\n  const sessionsQuery = useListSessions(authClient, {\n    meta: { errorPresentation: \"inline\" }\n  })\n  const { data: sessions, error, isPending } = sessionsQuery\n\n  const activeSessions = [...(sessions ?? [])].sort((activeSession) =>\n    activeSession.id === session?.session.id ? -1 : 1\n  )\n\n  return (\n    <div>\n      <h2 className=\"text-sm font-semibold mb-3\">\n        {localization.settings.activeSessions}\n      </h2>\n\n      <Card className={cn(\"gap-0 p-0\", className)}>\n        <CardContent className=\"p-0\">\n          {isReauthenticationRequiredError(error) ? (\n            <ReauthenticationAction />\n          ) : error ? (\n            <div className=\"p-4 text-destructive text-sm\">{error.message}</div>\n          ) : isPending ? (\n            <SessionRowSkeleton />\n          ) : (\n            <ItemGroup className=\"gap-0!\">\n              {activeSessions?.map((activeSession, index) => (\n                <Fragment key={activeSession.id}>\n                  {index > 0 && <ItemSeparator className=\"my-0!\" />}\n                  <ActiveSession activeSession={activeSession} />\n                </Fragment>\n              ))}\n            </ItemGroup>\n          )}\n        </CardContent>\n        {!isPending && !error && (\n          <SessionActions\n            hasOtherSessions={activeSessions.some(\n              (activeSession) => activeSession.id !== session?.session.id\n            )}\n          />\n        )}\n      </Card>\n    </div>\n  )\n}\n\nfunction SessionRowSkeleton() {\n  return (\n    <Item>\n      <ItemMedia>\n        <Skeleton className=\"size-10 rounded-md\" />\n      </ItemMedia>\n      <ItemContent>\n        <Skeleton className=\"h-4 w-20\" />\n        <Skeleton className=\"h-3 w-32\" />\n      </ItemContent>\n    </Item>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/settings/security/active-sessions.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/settings/security/change-password.tsx",
      "content": "\"use client\"\n\nimport {\n  getViewURL,\n  isPasswordCompromisedError,\n  validateMatchingValue,\n  validateStringLength\n} from \"@better-auth-ui/core\"\nimport {\n  useAuth,\n  useChangePassword,\n  useFetchOptions,\n  useListAccounts,\n  useRequestPasswordReset,\n  useSession\n} from \"@better-auth-ui/react\"\nimport { Eye, EyeOff } from \"lucide-react\"\nimport { useState } from \"react\"\nimport { toast } from \"sonner\"\nimport { Button } from \"@/components/ui/button\"\nimport { Card, CardContent, CardFooter } from \"@/components/ui/card\"\nimport { Field, FieldError, FieldLabel } from \"@/components/ui/field\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupInput\n} from \"@/components/ui/input-group\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { cn } from \"cn\"\nimport { isAuthFormFieldInvalid, useAuthForm } from \"../../auth-form\"\nimport { OpenEmailButton } from \"../../open-email-button\"\nimport { PasswordStrengthMeter } from \"../../password-strength-meter\"\n\nexport type ChangePasswordProps = {\n  className?: string\n}\n\n/**\n * Render a card form for changing the authenticated user's password.\n *\n * When the user has a credential account, displays fields for current password,\n * new password, and optionally confirm password. When the user only has social\n * accounts, displays a prompt to set a password via the reset flow.\n *\n * @returns A JSX element containing the change-password or set-password card\n */\nexport function ChangePassword({ className }: ChangePasswordProps) {\n  const { authClient, emailAndPassword, localization } = useAuth()\n  const { data: session } = useSession(authClient)\n  const { data: accounts, isPending: isAccountsPending } =\n    useListAccounts(authClient)\n\n  const hasCredentialAccount = accounts?.some(\n    (account) => account.providerId === \"credential\"\n  )\n\n  if (!isAccountsPending && !hasCredentialAccount) {\n    return <SetPassword className={className} />\n  }\n\n  return (\n    <ChangePasswordForm\n      className={className}\n      emailAndPassword={emailAndPassword}\n      localization={localization}\n      session={isAccountsPending ? undefined : session}\n    />\n  )\n}\n\nfunction SetPassword({ className }: { className?: string }) {\n  const { authClient, basePaths, baseURL, localization, plugins, viewPaths } =\n    useAuth()\n  const { data: session } = useSession(authClient)\n  const { fetchOptions, resetFetchOptions } = useFetchOptions()\n  const [sentEmail, setSentEmail] = useState(\"\")\n\n  const { mutate: requestPasswordReset, isPending } = useRequestPasswordReset(\n    authClient,\n    {\n      onError: () => {\n        resetFetchOptions()\n      },\n      onSuccess: (_data, { email }) => {\n        setSentEmail(email)\n      }\n    }\n  )\n\n  const Captcha = plugins.find(\n    (plugin) => plugin.captchaComponent\n  )?.captchaComponent\n\n  const handleSetPassword = () => {\n    if (!session) return\n\n    requestPasswordReset({\n      email: session.user.email,\n      redirectTo: getViewURL(\n        baseURL,\n        basePaths.auth,\n        viewPaths.auth.resetPassword\n      ),\n      fetchOptions\n    })\n  }\n\n  return (\n    <div>\n      <h2 className=\"text-sm font-semibold mb-3\">\n        {localization.settings.changePassword}\n      </h2>\n\n      <Card className={cn(className)}>\n        <CardContent className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n          <div>\n            <p className=\"text-sm font-medium leading-tight\">\n              {localization.settings.setPassword}\n            </p>\n\n            <p className=\"text-muted-foreground text-xs mt-0.5\">\n              {localization.settings.setPasswordDescription}\n            </p>\n          </div>\n\n          {sentEmail ? (\n            <div className=\"flex flex-col gap-3 items-start sm:items-end\">\n              <p className=\"text-sm\" role=\"status\">\n                {localization.auth.resetLinkSentTo.replace(\n                  \"{{email}}\",\n                  sentEmail\n                )}\n              </p>\n\n              <OpenEmailButton email={sentEmail} className=\"w-auto\" />\n            </div>\n          ) : (\n            <div className=\"flex flex-col gap-3 items-start sm:items-end\">\n              {Captcha && <div>{Captcha}</div>}\n\n              <Button\n                size=\"sm\"\n                disabled={isPending || !session?.user.email}\n                onClick={handleSetPassword}\n              >\n                {isPending && <Spinner />}\n\n                {localization.auth.sendResetLink}\n              </Button>\n            </div>\n          )}\n        </CardContent>\n      </Card>\n    </div>\n  )\n}\n\nfunction ChangePasswordForm({\n  className,\n  emailAndPassword,\n  localization,\n  session\n}: {\n  className?: string\n  emailAndPassword: ReturnType<typeof useAuth>[\"emailAndPassword\"]\n  localization: ReturnType<typeof useAuth>[\"localization\"]\n  session: ReturnType<typeof useSession>[\"data\"]\n}) {\n  const { authClient } = useAuth()\n  const { mutateAsync: changePassword, isPending } = useChangePassword(\n    authClient,\n    {\n      onError: (error) => {\n        // The haveIBeenPwned plugin rejects on the password itself, so it\n        // belongs against the field rather than in a toast.\n        setIsCompromised(isPasswordCompromisedError(error))\n      },\n      onSuccess: () => {\n        form.reset()\n        toast.success(localization.settings.changePasswordSuccess)\n      }\n    }\n  )\n\n  const [isCurrentPasswordVisible, setIsCurrentPasswordVisible] =\n    useState(false)\n  const [isNewPasswordVisible, setIsNewPasswordVisible] = useState(false)\n  const [isConfirmPasswordVisible, setIsConfirmPasswordVisible] =\n    useState(false)\n\n  const [isCompromised, setIsCompromised] = useState(false)\n\n  const form = useAuthForm({\n    defaultValues: {\n      confirmPassword: \"\",\n      currentPassword: \"\",\n      newPassword: \"\"\n    },\n    onSubmit: async ({ value }) => {\n      try {\n        await changePassword({\n          currentPassword: value.currentPassword,\n          newPassword: value.newPassword,\n          revokeOtherSessions: true\n        })\n      } catch {\n        // The mutation reports the error through its configured handler.\n      }\n    }\n  })\n\n  return (\n    <div>\n      <h2 className=\"text-sm font-semibold mb-3\">\n        {localization.settings.changePassword}\n      </h2>\n\n      <form.AppForm>\n        <form.AuthFormRoot>\n          <Card className={cn(className)}>\n            <CardContent className=\"flex flex-col gap-6\">\n              <form.AppField\n                name=\"currentPassword\"\n                validators={{\n                  onChange: ({ value }) =>\n                    validateStringLength(value, {\n                      requiredMessage: localization.auth.fieldRequired\n                    })\n                }}\n              >\n                {(field) => (\n                  <Field\n                    data-invalid={isAuthFormFieldInvalid(field.state.meta)}\n                  >\n                    <FieldLabel htmlFor=\"currentPassword\">\n                      {localization.settings.currentPassword}\n                    </FieldLabel>\n\n                    {session ? (\n                      <InputGroup>\n                        <InputGroupInput\n                          id=\"currentPassword\"\n                          name={field.name}\n                          type={isCurrentPasswordVisible ? \"text\" : \"password\"}\n                          autoComplete=\"current-password\"\n                          placeholder={\n                            localization.settings.currentPasswordPlaceholder\n                          }\n                          value={field.state.value}\n                          onBlur={field.handleBlur}\n                          onChange={(e) => field.handleChange(e.target.value)}\n                          disabled={isPending}\n                          required\n                          aria-invalid={isAuthFormFieldInvalid(\n                            field.state.meta\n                          )}\n                        />\n\n                        <InputGroupAddon align=\"inline-end\">\n                          <InputGroupButton\n                            size=\"icon-xs\"\n                            aria-label={\n                              isCurrentPasswordVisible\n                                ? localization.auth.hidePassword\n                                : localization.auth.showPassword\n                            }\n                            title={\n                              isCurrentPasswordVisible\n                                ? localization.auth.hidePassword\n                                : localization.auth.showPassword\n                            }\n                            onClick={() => {\n                              setIsCurrentPasswordVisible((visible) => !visible)\n                            }}\n                          >\n                            {isCurrentPasswordVisible ? <EyeOff /> : <Eye />}\n                          </InputGroupButton>\n                        </InputGroupAddon>\n                      </InputGroup>\n                    ) : (\n                      <Skeleton>\n                        <Input className=\"invisible\" />\n                      </Skeleton>\n                    )}\n\n                    <field.AuthFormFieldError />\n                  </Field>\n                )}\n              </form.AppField>\n\n              <form.AppField\n                name=\"newPassword\"\n                validators={{\n                  onChange: ({ value }) =>\n                    validateStringLength(value, {\n                      maxLength: emailAndPassword.maxPasswordLength,\n                      maxLengthMessage: localization.auth.tooLong.replace(\n                        \"{{max}}\",\n                        String(emailAndPassword.maxPasswordLength)\n                      ),\n                      minLength: emailAndPassword.minPasswordLength,\n                      minLengthMessage: localization.auth.tooShort.replace(\n                        \"{{min}}\",\n                        String(emailAndPassword.minPasswordLength)\n                      ),\n                      requiredMessage: localization.auth.fieldRequired\n                    })\n                }}\n              >\n                {(field) => {\n                  const isInvalid =\n                    isAuthFormFieldInvalid(field.state.meta) || isCompromised\n\n                  return (\n                    <Field data-invalid={isInvalid}>\n                      <FieldLabel htmlFor=\"newPassword\">\n                        {localization.auth.newPassword}\n                      </FieldLabel>\n\n                      {session ? (\n                        <InputGroup>\n                          <InputGroupInput\n                            id=\"newPassword\"\n                            name={field.name}\n                            type={isNewPasswordVisible ? \"text\" : \"password\"}\n                            autoComplete=\"new-password\"\n                            placeholder={\n                              localization.auth.newPasswordPlaceholder\n                            }\n                            value={field.state.value}\n                            onBlur={field.handleBlur}\n                            onChange={(e) => {\n                              field.handleChange(e.target.value)\n                              setIsCompromised(false)\n                            }}\n                            minLength={emailAndPassword.minPasswordLength}\n                            maxLength={emailAndPassword.maxPasswordLength}\n                            disabled={isPending}\n                            required\n                            aria-invalid={isInvalid}\n                          />\n\n                          <InputGroupAddon align=\"inline-end\">\n                            <InputGroupButton\n                              size=\"icon-xs\"\n                              aria-label={\n                                isNewPasswordVisible\n                                  ? localization.auth.hidePassword\n                                  : localization.auth.showPassword\n                              }\n                              onClick={() =>\n                                setIsNewPasswordVisible((visible) => !visible)\n                              }\n                            >\n                              {isNewPasswordVisible ? <EyeOff /> : <Eye />}\n                            </InputGroupButton>\n                          </InputGroupAddon>\n                        </InputGroup>\n                      ) : (\n                        <Skeleton>\n                          <Input className=\"invisible\" />\n                        </Skeleton>\n                      )}\n\n                      {isCompromised ? (\n                        <FieldError>\n                          {localization.auth.passwordCompromised}\n                        </FieldError>\n                      ) : (\n                        <field.AuthFormFieldError />\n                      )}\n\n                      <PasswordStrengthMeter password={field.state.value} />\n                    </Field>\n                  )\n                }}\n              </form.AppField>\n\n              {emailAndPassword.confirmPassword && (\n                <form.AppField\n                  name=\"confirmPassword\"\n                  validators={{\n                    onChangeListenTo: [\"newPassword\"],\n                    onChange: ({ fieldApi, value }) =>\n                      validateStringLength(value, {\n                        maxLength: emailAndPassword.maxPasswordLength,\n                        maxLengthMessage: localization.auth.tooLong.replace(\n                          \"{{max}}\",\n                          String(emailAndPassword.maxPasswordLength)\n                        ),\n                        minLength: emailAndPassword.minPasswordLength,\n                        minLengthMessage: localization.auth.tooShort.replace(\n                          \"{{min}}\",\n                          String(emailAndPassword.minPasswordLength)\n                        ),\n                        requiredMessage: localization.auth.fieldRequired\n                      }) ??\n                      validateMatchingValue(\n                        value,\n                        fieldApi.form.getFieldValue(\"newPassword\"),\n                        localization.auth.passwordsDoNotMatch\n                      )\n                  }}\n                >\n                  {(field) => (\n                    <Field\n                      data-invalid={isAuthFormFieldInvalid(field.state.meta)}\n                    >\n                      <FieldLabel htmlFor=\"confirmPassword\">\n                        {localization.auth.confirmPassword}\n                      </FieldLabel>\n\n                      {session ? (\n                        <InputGroup>\n                          <InputGroupInput\n                            id=\"confirmPassword\"\n                            name={field.name}\n                            type={\n                              isConfirmPasswordVisible ? \"text\" : \"password\"\n                            }\n                            autoComplete=\"new-password\"\n                            placeholder={\n                              localization.auth.confirmPasswordPlaceholder\n                            }\n                            value={field.state.value}\n                            onBlur={field.handleBlur}\n                            onChange={(e) => field.handleChange(e.target.value)}\n                            minLength={emailAndPassword.minPasswordLength}\n                            maxLength={emailAndPassword.maxPasswordLength}\n                            disabled={isPending}\n                            required\n                            aria-invalid={isAuthFormFieldInvalid(\n                              field.state.meta\n                            )}\n                          />\n\n                          <InputGroupAddon align=\"inline-end\">\n                            <InputGroupButton\n                              size=\"icon-xs\"\n                              aria-label={\n                                isConfirmPasswordVisible\n                                  ? localization.auth.hidePassword\n                                  : localization.auth.showPassword\n                              }\n                              onClick={() =>\n                                setIsConfirmPasswordVisible(\n                                  (visible) => !visible\n                                )\n                              }\n                            >\n                              {isConfirmPasswordVisible ? <EyeOff /> : <Eye />}\n                            </InputGroupButton>\n                          </InputGroupAddon>\n                        </InputGroup>\n                      ) : (\n                        <Skeleton>\n                          <Input className=\"invisible\" />\n                        </Skeleton>\n                      )}\n\n                      <field.AuthFormFieldError />\n                    </Field>\n                  )}\n                </form.AppField>\n              )}\n            </CardContent>\n\n            <CardFooter>\n              <form.AuthFormSubmitButton\n                disabled={isPending || !session}\n                size=\"sm\"\n              >\n                {localization.settings.updatePassword}\n              </form.AuthFormSubmitButton>\n            </CardFooter>\n          </Card>\n        </form.AuthFormRoot>\n      </form.AppForm>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/settings/security/change-password.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/settings/security/linked-account.tsx",
      "content": "\"use client\"\n\nimport {\n  type AuthSocialProvider,\n  getAuthErrorMessage,\n  getProviderId,\n  getProviderName,\n  isReauthenticationRequiredError\n} from \"@better-auth-ui/core\"\nimport {\n  renderProviderIcon,\n  useAccountInfo,\n  useAuth,\n  useLinkSocial,\n  useUnlinkAccount\n} from \"@better-auth-ui/react\"\nimport type { Account } from \"better-auth\"\nimport { Link2, Link2Off, Plug } from \"lucide-react\"\nimport { toast } from \"sonner\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle\n} from \"@/components/ui/dialog\"\nimport {\n  Item,\n  ItemActions,\n  ItemContent,\n  ItemDescription,\n  ItemMedia,\n  ItemTitle\n} from \"@/components/ui/item\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { cn } from \"cn\"\nimport { ReauthenticationAction } from \"../../reauthentication\"\n\nexport type LinkedAccountProps = {\n  account?: Account\n  canUnlink?: boolean\n  provider: AuthSocialProvider | string\n}\n\n/**\n * Render a single linked social account row with provider info and link/unlink control.\n *\n * Fetches additional account information from the provider using the accountInfo API\n * and displays the provider name, account details, and a link/unlink button.\n *\n * @param account - The account object containing id, accountId, and providerId\n * @param provider - The provider id\n * @returns A JSX element containing the linked account row\n */\nexport function LinkedAccount({\n  account,\n  canUnlink = true,\n  provider\n}: LinkedAccountProps) {\n  const { authClient, baseURL, localization } = useAuth()\n\n  const { data: accountInfo, isPending: isLoadingInfo } = useAccountInfo(\n    authClient,\n    { query: { accountId: account?.id ?? \"\" } }\n  )\n\n  const { mutate: linkSocial, isPending: isLinking } = useLinkSocial(authClient)\n\n  const unlinkAccount = useUnlinkAccount(authClient, {\n    meta: { errorPresentation: \"inline\" },\n    onError: (error) => {\n      if (!isReauthenticationRequiredError(error)) {\n        const message = getAuthErrorMessage(error, localization)\n        if (message) {\n          console.error(\"[Better Auth UI]\", error)\n          toast.error(message)\n        }\n      }\n    },\n    onSuccess: () => toast.success(localization.settings.accountUnlinked)\n  })\n\n  const providerId = getProviderId(provider)\n  const providerIcon = renderProviderIcon(provider)\n  const providerName = getProviderName(provider)\n  const accountData = accountInfo?.data as\n    | { login?: string; username?: string }\n    | undefined\n\n  const displayName =\n    accountData?.login ||\n    accountData?.username ||\n    accountInfo?.user?.email ||\n    accountInfo?.user?.name ||\n    account?.accountId\n\n  const needsReauthentication = isReauthenticationRequiredError(\n    unlinkAccount.error\n  )\n\n  return (\n    <>\n      <Item>\n        <ItemMedia variant=\"icon\" className={cn(!account && \"opacity-50\")}>\n          {providerIcon ? providerIcon : <Plug />}\n        </ItemMedia>\n        <ItemContent>\n          <ItemTitle>{providerName}</ItemTitle>\n          {account && isLoadingInfo ? (\n            <Skeleton className=\"my-0.5 h-3 w-24\" />\n          ) : (\n            <ItemDescription>\n              {account\n                ? displayName\n                : localization.settings.linkProvider.replace(\n                    \"{{provider}}\",\n                    providerName\n                  )}\n            </ItemDescription>\n          )}\n        </ItemContent>\n        <ItemActions>\n          {account ? (\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() => unlinkAccount.mutate({ accountId: account.id })}\n              disabled={unlinkAccount.isPending || !canUnlink}\n              title={\n                canUnlink\n                  ? undefined\n                  : localization.settings.lastAccountUnlinkingDisabled\n              }\n              aria-label={localization.settings.unlinkProvider.replace(\n                \"{{provider}}\",\n                providerName\n              )}\n            >\n              {unlinkAccount.isPending ? <Spinner /> : <Link2Off />}\n              {localization.settings.unlinkProvider\n                .replace(\"{{provider}}\", \"\")\n                .trim()}\n            </Button>\n          ) : (\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() =>\n                linkSocial({\n                  provider: providerId,\n                  callbackURL: `${baseURL}${window.location.pathname}`\n                })\n              }\n              disabled={isLinking}\n              aria-label={localization.settings.linkProvider.replace(\n                \"{{provider}}\",\n                providerName\n              )}\n            >\n              {isLinking ? <Spinner /> : <Link2 />}\n              {localization.settings.link}\n            </Button>\n          )}\n        </ItemActions>\n      </Item>\n      {account && (\n        <Dialog\n          open={needsReauthentication}\n          onOpenChange={(nextOpen) => {\n            if (!nextOpen) unlinkAccount.reset()\n          }}\n        >\n          <DialogContent>\n            <DialogHeader>\n              <DialogTitle className=\"sr-only\">\n                {localization.settings.reauthenticationTitle}\n              </DialogTitle>\n            </DialogHeader>\n            <ReauthenticationAction showTitle={false} />\n          </DialogContent>\n        </Dialog>\n      )}\n    </>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/settings/security/linked-account.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/settings/security/linked-accounts.tsx",
      "content": "\"use client\"\n\nimport { getProviderId } from \"@better-auth-ui/core\"\nimport { useAuth, useListAccounts } from \"@better-auth-ui/react\"\nimport { Fragment } from \"react\"\nimport { Card, CardContent } from \"@/components/ui/card\"\nimport {\n  Item,\n  ItemContent,\n  ItemGroup,\n  ItemMedia,\n  ItemSeparator\n} from \"@/components/ui/item\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { cn } from \"cn\"\nimport { LinkedAccount } from \"./linked-account\"\n\nexport type LinkedAccountsProps = {\n  className?: string\n}\n\n/**\n * Render a card showing linked social accounts and available social providers to link.\n *\n * Linked accounts (excluding the \"credential\" provider) are shown with an unlink control;\n * available providers are shown with a link control. Button states and labels reflect\n * ongoing link/unlink activity and use localization for provider-specific text.\n *\n * @returns A JSX element containing the linked accounts card\n */\nexport function LinkedAccounts({ className }: LinkedAccountsProps) {\n  const {\n    authClient,\n    allowUnlinkingAllAccounts,\n    localization,\n    multipleAccountsPerProvider,\n    socialProviders\n  } = useAuth()\n\n  const { data: accounts, isPending } = useListAccounts(authClient)\n\n  const linkedAccounts = accounts?.filter(\n    (account) => account.providerId !== \"credential\"\n  )\n  const canUnlink =\n    allowUnlinkingAllAccounts === true || (accounts?.length ?? 0) > 1\n\n  const linkedProviderIds = new Set(linkedAccounts?.map((a) => a.providerId))\n\n  const availableProviders =\n    multipleAccountsPerProvider === false\n      ? socialProviders?.filter(\n          (provider) => !linkedProviderIds.has(getProviderId(provider))\n        )\n      : socialProviders\n\n  const allRows = [\n    ...(linkedAccounts?.map((account) => ({\n      key: account.id,\n      account,\n      provider:\n        socialProviders?.find(\n          (provider) => getProviderId(provider) === account.providerId\n        ) ?? account.providerId\n    })) ?? []),\n    ...(availableProviders?.map((provider) => ({\n      key: getProviderId(provider),\n      account: undefined,\n      provider\n    })) ?? [])\n  ]\n\n  return (\n    <div>\n      <h2 className=\"text-sm font-semibold mb-3\">\n        {localization.settings.linkedAccounts}\n      </h2>\n\n      <Card className={cn(\"p-0\", className)}>\n        <CardContent className=\"p-0\">\n          <ItemGroup className=\"gap-0\">\n            {isPending\n              ? socialProviders?.map((provider, index) => (\n                  <Fragment key={getProviderId(provider)}>\n                    {index > 0 && <ItemSeparator />}\n                    <AccountRowSkeleton />\n                  </Fragment>\n                ))\n              : allRows.map((row, index) => (\n                  <Fragment key={row.key}>\n                    {index > 0 && <ItemSeparator />}\n                    <LinkedAccount\n                      account={row.account}\n                      canUnlink={canUnlink}\n                      provider={row.provider}\n                    />\n                  </Fragment>\n                ))}\n          </ItemGroup>\n        </CardContent>\n      </Card>\n    </div>\n  )\n}\n\nfunction AccountRowSkeleton() {\n  return (\n    <Item>\n      <ItemMedia>\n        <Skeleton className=\"size-10 rounded-md\" />\n      </ItemMedia>\n      <ItemContent>\n        <Skeleton className=\"h-4 w-20\" />\n        <Skeleton className=\"h-3 w-32\" />\n      </ItemContent>\n    </Item>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/settings/security/linked-accounts.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/settings/security/security-settings.tsx",
      "content": "\"use client\"\n\nimport { useAuth } from \"@better-auth-ui/react\"\nimport { cn } from \"cn\"\nimport { ActiveSessions } from \"./active-sessions\"\nimport { ChangePassword } from \"./change-password\"\nimport { LinkedAccounts } from \"./linked-accounts\"\n\nexport type SecuritySettingsProps = {\n  className?: string\n}\n\n/**\n * Renders the security settings layout including password management, linked accounts, and active sessions.\n *\n * ChangePassword is rendered when password authentication is enabled; LinkedAccounts is rendered when social providers are present.\n * Each registered auth plugin may contribute `securityCards` (for example passkeys, delete-user).\n *\n * @param className - Optional additional CSS class names for the outer container.\n * @returns The security settings container as a JSX element.\n */\nexport function SecuritySettings({ className }: SecuritySettingsProps) {\n  const { emailAndPassword, plugins, socialProviders } = useAuth()\n\n  return (\n    <div className={cn(\"flex w-full flex-col gap-4 md:gap-6\", className)}>\n      {emailAndPassword?.enabled && <ChangePassword />}\n      {!!socialProviders?.length && <LinkedAccounts />}\n      <ActiveSessions />\n      {plugins.flatMap(\n        (plugin) =>\n          plugin.securityCards?.map((Card, index) => (\n            <Card key={`${plugin.id}-${index.toString()}`} />\n          )) ?? []\n      )}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/settings/security/security-settings.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/settings/security/session-actions.tsx",
      "content": "\"use client\"\n\nimport {\n  useAuth,\n  useRevokeOtherSessions,\n  useRevokeSessions\n} from \"@better-auth-ui/react\"\nimport { LogOut } from \"lucide-react\"\nimport { useState } from \"react\"\nimport { toast } from \"sonner\"\n\nimport {\n  AlertDialog,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogMedia,\n  AlertDialogTitle\n} from \"@/components/ui/alert-dialog\"\nimport { Button } from \"@/components/ui/button\"\nimport { Spinner } from \"@/components/ui/spinner\"\n\ntype PendingAction = \"other\" | \"all\"\n\nexport function SessionActions(props: { hasOtherSessions: boolean }) {\n  const { authClient, basePaths, localization, navigate, viewPaths } = useAuth()\n  const [action, setAction] = useState<PendingAction | null>(null)\n\n  const revokeOtherSessions = useRevokeOtherSessions(authClient, {\n    onSuccess: () => {\n      toast.success(localization.settings.signOutOtherDevicesSuccess)\n      setAction(null)\n    }\n  })\n  const revokeSessions = useRevokeSessions(authClient, {\n    onSuccess: () =>\n      navigate({\n        to: `${basePaths.auth}/${viewPaths.auth.signIn}`,\n        replace: true\n      })\n  })\n\n  const isPending = revokeOtherSessions.isPending || revokeSessions.isPending\n  const isEverywhere = action === \"all\"\n\n  return (\n    <>\n      <div className=\"flex flex-wrap justify-end gap-2 border-t p-4\">\n        <Button\n          disabled={!props.hasOtherSessions || isPending}\n          onClick={() => setAction(\"other\")}\n          size=\"sm\"\n          type=\"button\"\n          variant=\"outline\"\n        >\n          {localization.settings.signOutOtherDevices}\n        </Button>\n        <Button\n          disabled={isPending}\n          onClick={() => setAction(\"all\")}\n          size=\"sm\"\n          type=\"button\"\n          variant=\"destructive\"\n        >\n          {localization.settings.signOutEverywhere}\n        </Button>\n      </div>\n\n      <AlertDialog\n        open={action !== null}\n        onOpenChange={(open) => !open && !isPending && setAction(null)}\n      >\n        <AlertDialogContent>\n          <AlertDialogHeader>\n            <AlertDialogMedia className=\"bg-destructive/10 text-destructive dark:bg-destructive/20 dark:text-destructive\">\n              <LogOut />\n            </AlertDialogMedia>\n            <AlertDialogTitle>\n              {isEverywhere\n                ? localization.settings.signOutEverywhere\n                : localization.settings.signOutOtherDevices}\n            </AlertDialogTitle>\n            <AlertDialogDescription>\n              {isEverywhere\n                ? localization.settings.signOutEverywhereDescription\n                : localization.settings.signOutOtherDevicesDescription}\n            </AlertDialogDescription>\n          </AlertDialogHeader>\n          <AlertDialogFooter>\n            <AlertDialogCancel disabled={isPending}>\n              {localization.settings.cancel}\n            </AlertDialogCancel>\n            <Button\n              disabled={isPending}\n              onClick={() =>\n                isEverywhere\n                  ? revokeSessions.mutate()\n                  : revokeOtherSessions.mutate()\n              }\n              type=\"button\"\n              variant={isEverywhere ? \"destructive\" : \"default\"}\n            >\n              {isPending && <Spinner />}\n              {isEverywhere\n                ? localization.settings.signOutEverywhere\n                : localization.settings.signOutOtherDevices}\n            </Button>\n          </AlertDialogFooter>\n        </AlertDialogContent>\n      </AlertDialog>\n    </>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/settings/security/session-actions.tsx"
    }
  ],
  "type": "registry:component"
}