{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "device-authorization",
  "title": "Device Authorization",
  "description": "Device authorization plugin: code entry, sign-in continuation, and approve or deny actions for Better Auth device requests.",
  "dependencies": [
    "@better-auth-ui/core@latest",
    "@better-auth-ui/react@latest",
    "@tanstack/react-form",
    "better-auth",
    "cn",
    "date-fns",
    "input-otp",
    "lucide-react"
  ],
  "registryDependencies": [
    "avatar",
    "button",
    "calendar",
    "card",
    "checkbox",
    "combobox",
    "field",
    "input",
    "input-group",
    "input-otp",
    "popover",
    "select",
    "skeleton",
    "slider",
    "sonner",
    "spinner",
    "switch",
    "textarea"
  ],
  "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/device-authorization/device-authorization.tsx",
      "content": "\"use client\"\n\nimport type { User } from \"better-auth\"\n\nimport type {\n  DeviceAuthorizationAuthClient,\n  DeviceAuthorizationLocalization\n} from \"@better-auth-ui/core/plugins/device-authorization\"\nimport { useAuth, useAuthPlugin, useSession } from \"@better-auth-ui/react\"\nimport {\n  useApproveDevice,\n  useDenyDevice,\n  useVerifyDeviceCode\n} from \"@better-auth-ui/react/plugins/device-authorization\"\nimport { useSelector } from \"@tanstack/react-form\"\nimport { REGEXP_ONLY_DIGITS_AND_CHARS } from \"input-otp\"\nimport { CircleCheckIcon, CircleXIcon } from \"lucide-react\"\nimport {\n  type ReactNode,\n  useCallback,\n  useEffect,\n  useReducer,\n  useRef,\n  useState\n} from \"react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle\n} from \"@/components/ui/card\"\nimport {\n  Field,\n  FieldError,\n  FieldGroup,\n  FieldLabel\n} from \"@/components/ui/field\"\nimport {\n  InputOTP,\n  InputOTPGroup,\n  InputOTPSeparator,\n  InputOTPSlot\n} from \"@/components/ui/input-otp\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { deviceAuthorizationPlugin } from \"@/lib/auth/device-authorization-plugin\"\nimport { UserAvatar } from \"../user/user-avatar\"\nimport { cn } from \"cn\"\nimport { useAuthForm } from \"../auth-form\"\n\ntype DeviceAuthorizationStep = \"code\" | \"approval\" | \"approved\" | \"denied\"\n\ntype DeviceAuthorizationState = {\n  step: DeviceAuthorizationStep\n  codeError: string\n}\n\ntype DeviceAuthorizationAction =\n  | { type: \"codeChanged\" }\n  | { type: \"verificationFailed\"; message: string }\n  | { type: \"verificationSucceeded\"; status: string }\n  | { type: \"approved\" }\n  | { type: \"denied\" }\n\nconst initialDeviceAuthorizationState: DeviceAuthorizationState = {\n  step: \"code\",\n  codeError: \"\"\n}\n\nfunction deviceAuthorizationReducer(\n  state: DeviceAuthorizationState,\n  action: DeviceAuthorizationAction\n): DeviceAuthorizationState {\n  switch (action.type) {\n    case \"codeChanged\":\n      return state.codeError ? { ...state, codeError: \"\" } : state\n    case \"verificationFailed\":\n      return { step: \"code\", codeError: action.message }\n    case \"verificationSucceeded\":\n      if (action.status === \"approved\") {\n        return { step: \"approved\", codeError: \"\" }\n      }\n      if (action.status === \"denied\") {\n        return { step: \"denied\", codeError: \"\" }\n      }\n      return { step: \"approval\", codeError: \"\" }\n    case \"approved\":\n      return { step: \"approved\", codeError: \"\" }\n    case \"denied\":\n      return { step: \"denied\", codeError: \"\" }\n  }\n}\n\nfunction normalizeDeviceCode(value: string) {\n  return value.replace(/-/g, \"\").trim().toUpperCase()\n}\n\nfunction createDeviceCodeSlots(length: number) {\n  return Array.from({ length }, (_, slotIndex) => ({\n    id: `device-code-character-${String(slotIndex + 1)}`,\n    index: slotIndex\n  }))\n}\n\nexport type DeviceAuthorizationProps = {\n  className?: string\n}\n\n/**\n * Render Better Auth's browser-side device authorization ceremony.\n *\n * The view accepts a user code, sends unauthenticated users through sign-in\n * with a return URL, verifies and claims the code for the current session,\n * and lets the user approve or deny the device.\n *\n * @param className - Additional CSS classes applied to the card.\n */\nexport function DeviceAuthorization({ className }: DeviceAuthorizationProps) {\n  const { authClient, basePaths, navigate, redirectTo, viewPaths } = useAuth()\n  const {\n    localization,\n    userCodeLength,\n    viewPaths: deviceAuthorizationViewPaths\n  } = useAuthPlugin(deviceAuthorizationPlugin)\n  const deviceAuthClient = authClient as DeviceAuthorizationAuthClient\n  const { data: session, isPending: isSessionPending } =\n    useSession(deviceAuthClient)\n  const [userCode, setUserCode] = useState(\"\")\n  const [state, dispatch] = useReducer(\n    deviceAuthorizationReducer,\n    initialDeviceAuthorizationState\n  )\n  const submittedCodeRef = useRef<string | null>(null)\n\n  const handleAuthorizationError = () => {\n    dispatch({\n      type: \"verificationFailed\",\n      message: localization.invalidDeviceCode\n    })\n  }\n\n  useEffect(() => {\n    const code = new URLSearchParams(window.location.search).get(\"user_code\")\n    if (!code) return\n\n    setUserCode(\n      normalizeDeviceCode(code)\n        .replace(/[^A-Z0-9]/g, \"\")\n        .slice(0, userCodeLength)\n    )\n  }, [userCodeLength])\n\n  const { mutateAsync: verifyDeviceCode, isPending: isVerifying } =\n    useVerifyDeviceCode(deviceAuthClient, {\n      onError: handleAuthorizationError,\n      onSuccess: ({ status }) => {\n        dispatch({ type: \"verificationSucceeded\", status })\n      }\n    })\n\n  const { mutate: approveDevice, isPending: isApproving } = useApproveDevice(\n    deviceAuthClient,\n    {\n      onError: handleAuthorizationError,\n      onSuccess: () => dispatch({ type: \"approved\" })\n    }\n  )\n\n  const { mutate: denyDevice, isPending: isDenying } = useDenyDevice(\n    deviceAuthClient,\n    {\n      onError: handleAuthorizationError,\n      onSuccess: () => dispatch({ type: \"denied\" })\n    }\n  )\n\n  const submitCode = useCallback(\n    async (completedCode: string) => {\n      const normalizedCode = normalizeDeviceCode(completedCode)\n\n      if (\n        isSessionPending ||\n        isVerifying ||\n        normalizedCode.length !== userCodeLength ||\n        normalizedCode === submittedCodeRef.current\n      ) {\n        return\n      }\n\n      submittedCodeRef.current = normalizedCode\n\n      if (!session) {\n        const verificationPath = `${basePaths.auth}/${deviceAuthorizationViewPaths.auth.deviceAuthorization}?user_code=${encodeURIComponent(normalizedCode)}`\n        const signInPath = `${basePaths.auth}/${viewPaths.auth.signIn}?redirectTo=${encodeURIComponent(verificationPath)}`\n        navigate({ to: signInPath })\n        return\n      }\n\n      await verifyDeviceCode({\n        query: { user_code: normalizedCode }\n      })\n    },\n    [\n      basePaths.auth,\n      deviceAuthorizationViewPaths.auth.deviceAuthorization,\n      isSessionPending,\n      isVerifying,\n      navigate,\n      session,\n      userCodeLength,\n      verifyDeviceCode,\n      viewPaths.auth.signIn\n    ]\n  )\n\n  const handleSubmit = useCallback(\n    async (code: string) => {\n      const normalizedCode = normalizeDeviceCode(code)\n      if (normalizedCode.length !== userCodeLength) {\n        dispatch({\n          type: \"verificationFailed\",\n          message: localization.invalidDeviceCode\n        })\n        return\n      }\n      await submitCode(normalizedCode)\n    },\n    [localization.invalidDeviceCode, submitCode, userCodeLength]\n  )\n\n  const authorizedCode = submittedCodeRef.current ?? \"\"\n\n  const cardClassName = cn(\"w-full max-w-sm\", className)\n\n  if (state.step === \"approval\" && session) {\n    return (\n      <DeviceApproval\n        className={className ?? \"\"}\n        localization={localization}\n        userCode={authorizedCode}\n        user={session.user}\n        isApproving={isApproving}\n        isDenying={isDenying}\n        onApprove={() => approveDevice({ userCode: authorizedCode })}\n        onDeny={() => denyDevice({ userCode: authorizedCode })}\n      />\n    )\n  }\n\n  if (state.step === \"approved\" || state.step === \"denied\") {\n    return (\n      <DeviceAuthorizationResult\n        className={cardClassName}\n        localization={localization}\n        status={state.step}\n        action={\n          <Button\n            className=\"w-full\"\n            onClick={() => navigate({ to: redirectTo })}\n          >\n            {localization.returnToApplication}\n          </Button>\n        }\n      />\n    )\n  }\n\n  return (\n    <DeviceCodeForm\n      className={cardClassName}\n      codeError={state.codeError}\n      isSessionPending={isSessionPending}\n      isVerifying={isVerifying}\n      localization={localization}\n      initialUserCode={userCode}\n      userCodeLength={userCodeLength}\n      onCodeChange={() => dispatch({ type: \"codeChanged\" })}\n      onSubmitCode={handleSubmit}\n    />\n  )\n}\n\ntype DeviceCodeFormProps = {\n  className: string\n  codeError: string\n  isSessionPending: boolean\n  isVerifying: boolean\n  localization: DeviceAuthorizationLocalization\n  initialUserCode: string\n  userCodeLength: number\n  onCodeChange: (value: string) => void\n  onSubmitCode: (code: string) => Promise<void>\n}\n\nfunction DeviceCodeForm({\n  className,\n  codeError,\n  isSessionPending,\n  isVerifying,\n  localization,\n  initialUserCode,\n  userCodeLength,\n  onCodeChange,\n  onSubmitCode\n}: DeviceCodeFormProps) {\n  const slots = createDeviceCodeSlots(userCodeLength)\n  const groupBreak = Math.ceil(userCodeLength / 2)\n  const firstGroup = slots.slice(0, groupBreak)\n  const secondGroup = slots.slice(groupBreak)\n  const errorId = \"device-code-error\"\n  const form = useAuthForm({\n    defaultValues: { userCode: initialUserCode },\n    onSubmit: async ({ value }) => {\n      await onSubmitCode(value.userCode)\n    }\n  })\n  const userCodeComplete = useSelector(\n    form.store,\n    (state) => state.values.userCode.length === userCodeLength\n  )\n\n  useEffect(() => {\n    form.setFieldValue(\"userCode\", initialUserCode)\n    if (initialUserCode.length === userCodeLength) {\n      void onSubmitCode(initialUserCode).catch(() => undefined)\n    }\n  }, [form.setFieldValue, initialUserCode, onSubmitCode, userCodeLength])\n\n  return (\n    <Card className={className}>\n      <CardHeader>\n        <CardTitle className=\"text-xl\">\n          {localization.deviceAuthorization}\n        </CardTitle>\n        <CardDescription>\n          {localization.deviceAuthorizationDescription}\n        </CardDescription>\n      </CardHeader>\n\n      <CardContent>\n        <form.AppForm>\n          <form.AuthFormRoot aria-label={localization.deviceAuthorization}>\n            <FieldGroup>\n              <form.AppField name=\"userCode\">\n                {(field) => (\n                  <Field data-invalid={Boolean(codeError)}>\n                    <FieldLabel htmlFor=\"device-code\">\n                      {localization.deviceCode}\n                    </FieldLabel>\n\n                    <InputOTP\n                      id=\"device-code\"\n                      aria-describedby={codeError ? errorId : undefined}\n                      aria-invalid={Boolean(codeError)}\n                      autoComplete=\"one-time-code\"\n                      containerClassName=\"w-full justify-center\"\n                      disabled={isVerifying}\n                      inputMode=\"text\"\n                      maxLength={userCodeLength}\n                      name={field.name}\n                      pasteTransformer={normalizeDeviceCode}\n                      pattern={REGEXP_ONLY_DIGITS_AND_CHARS}\n                      value={field.state.value}\n                      onChange={(value) => {\n                        const nextCode = normalizeDeviceCode(value)\n                          .replace(/[^A-Z0-9]/g, \"\")\n                          .slice(0, userCodeLength)\n                        field.handleChange(nextCode)\n                        onCodeChange(nextCode)\n                        if (nextCode.length === userCodeLength)\n                          void onSubmitCode(nextCode).catch(() => undefined)\n                      }}\n                    >\n                      <InputOTPGroup>\n                        {firstGroup.map((slot) => (\n                          <InputOTPSlot key={slot.id} index={slot.index} />\n                        ))}\n                      </InputOTPGroup>\n\n                      {secondGroup.length > 0 ? (\n                        <>\n                          <InputOTPSeparator />\n                          <InputOTPGroup>\n                            {secondGroup.map((slot) => (\n                              <InputOTPSlot key={slot.id} index={slot.index} />\n                            ))}\n                          </InputOTPGroup>\n                        </>\n                      ) : null}\n                    </InputOTP>\n\n                    <FieldError id={errorId}>{codeError}</FieldError>\n                  </Field>\n                )}\n              </form.AppField>\n\n              <form.AuthFormSubmitButton\n                isPending={isVerifying}\n                className=\"w-full\"\n                disabled={!userCodeComplete || isSessionPending || isVerifying}\n                type=\"submit\"\n              >\n                {localization.continue}\n              </form.AuthFormSubmitButton>\n            </FieldGroup>\n          </form.AuthFormRoot>\n        </form.AppForm>\n      </CardContent>\n    </Card>\n  )\n}\n\ntype DeviceApprovalProps = {\n  className: string\n  isApproving: boolean\n  isDenying: boolean\n  localization: DeviceAuthorizationLocalization\n  user: User\n  userCode: string\n  onApprove: () => void\n  onDeny: () => void\n}\n\nfunction DeviceApproval({\n  className,\n  isApproving,\n  isDenying,\n  localization,\n  user,\n  userCode,\n  onApprove,\n  onDeny\n}: DeviceApprovalProps) {\n  const isPending = isApproving || isDenying\n\n  return (\n    <Card\n      className={cn(\"w-full max-w-lg gap-0 overflow-hidden p-0\", className)}\n    >\n      <CardHeader className=\"grid justify-items-center gap-5 p-6 text-center\">\n        <UserAvatar className=\"size-16\" user={user} />\n        <div className=\"grid gap-1\">\n          <CardTitle className=\"text-xl font-semibold\">\n            {localization.approveDevice}\n          </CardTitle>\n          <CardDescription>\n            {localization.approveDeviceDescription}\n          </CardDescription>\n        </div>\n        <div className=\"grid max-w-full gap-1 text-sm text-muted-foreground\">\n          <span>{localization.signedInAs}</span>\n          <span className=\"break-all font-medium text-foreground\">\n            {user.name || user.email}\n          </span>\n          {user.name ? <span className=\"break-all\">{user.email}</span> : null}\n        </div>\n      </CardHeader>\n      <CardContent className=\"px-4 pb-6 sm:px-6\">\n        <div className=\"grid justify-items-center gap-2 rounded-lg bg-muted/50 p-5\">\n          <p className=\"text-sm text-muted-foreground\">\n            {localization.deviceCode}\n          </p>\n          <p className=\"font-mono text-2xl font-semibold tracking-widest\">\n            {userCode}\n          </p>\n        </div>\n      </CardContent>\n      <CardFooter className=\"grid grid-cols-2 gap-3 p-4 sm:p-6\">\n        <Button\n          className=\"h-11 w-full\"\n          disabled={isPending}\n          variant=\"secondary\"\n          onClick={onDeny}\n        >\n          {isDenying ? <Spinner /> : null}\n          {localization.deny}\n        </Button>\n        <Button\n          className=\"h-11 w-full\"\n          disabled={isPending}\n          onClick={onApprove}\n        >\n          {isApproving ? <Spinner /> : null}\n          {localization.approve}\n        </Button>\n      </CardFooter>\n    </Card>\n  )\n}\n\ntype DeviceAuthorizationResultProps = {\n  action: ReactNode\n  className: string\n  localization: DeviceAuthorizationLocalization\n  status: \"approved\" | \"denied\"\n}\n\nfunction DeviceAuthorizationResult({\n  action,\n  className,\n  localization,\n  status\n}: DeviceAuthorizationResultProps) {\n  const approved = status === \"approved\"\n  const Icon = approved ? CircleCheckIcon : CircleXIcon\n\n  return (\n    <Card className={className}>\n      <CardHeader className=\"justify-items-center text-center\">\n        <Icon\n          aria-hidden=\"true\"\n          className={cn(\n            \"mb-1 size-10\",\n            approved ? \"text-primary\" : \"text-destructive\"\n          )}\n        />\n        <CardTitle className=\"text-xl\">\n          {approved ? localization.deviceApproved : localization.deviceDenied}\n        </CardTitle>\n        <CardDescription>\n          {approved\n            ? localization.deviceApprovedDescription\n            : localization.deviceDeniedDescription}\n        </CardDescription>\n      </CardHeader>\n\n      <CardFooter>{action}</CardFooter>\n    </Card>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/device-authorization/device-authorization.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/user/user-avatar.tsx",
      "content": "\"use client\"\n\nimport type { UsernameAuthClient } from \"@better-auth-ui/core/plugins/username\"\nimport { useAuth, useSession } from \"@better-auth-ui/react\"\nimport type { User } from \"better-auth\"\nimport { User2 } from \"lucide-react\"\nimport type { ReactNode } from \"react\"\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { cn } from \"cn\"\n\nexport type UserAvatarProps = {\n  className?: string\n  fallback?: ReactNode\n  isPending?: boolean\n  /** @remarks `User` */\n  user?: User & { username?: string | null; displayUsername?: string | null }\n}\n\n/**\n * Display a user's avatar using session information or an explicit user prop.\n *\n * Renders a circular avatar that shows the user's image when available, a fallback node if provided, or the user's first two initials; while the session is loading (or when `isPending` is true) and no `user` prop is supplied, renders a skeleton placeholder.\n *\n * @param className - Additional CSS classes applied to the avatar container\n * @param user - Optional user object to display instead of the session user\n * @param isPending - When true, treat the component as loading and show the skeleton if no `user` is provided\n * @param fallback - Node to render inside the avatar fallback area before initials or the default icon\n * @returns The avatar element to render (JSX)\n */\nexport function UserAvatar({\n  className,\n  user,\n  isPending,\n  fallback\n}: UserAvatarProps) {\n  const { authClient } = useAuth<UsernameAuthClient>()\n  const { data: session, isPending: sessionPending } = useSession(authClient, {\n    enabled: !user && !isPending\n  })\n\n  if ((isPending || sessionPending) && !user) {\n    return <Skeleton className={cn(\"size-8 rounded-full\", className)} />\n  }\n\n  const resolvedUser = user ?? session?.user\n\n  const initials = (\n    resolvedUser?.username ||\n    resolvedUser?.name ||\n    resolvedUser?.email\n  )\n    ?.slice(0, 2)\n    .toUpperCase()\n\n  return (\n    <Avatar\n      className={cn(\n        \"size-8 bg-muted text-foreground text-sm rounded-full\",\n        className\n      )}\n    >\n      <AvatarImage\n        src={resolvedUser?.image ?? undefined}\n        alt={\n          resolvedUser?.displayUsername ||\n          resolvedUser?.name ||\n          resolvedUser?.email\n        }\n      />\n\n      <AvatarFallback className=\"text-muted-foreground!\">\n        {fallback || initials || <User2 className=\"size-4\" />}\n      </AvatarFallback>\n    </Avatar>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/user/user-avatar.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/lib/auth/device-authorization-plugin.ts",
      "content": "import { createAuthPlugin } from \"@better-auth-ui/core\"\nimport {\n  deviceAuthorizationPlugin as coreDeviceAuthorizationPlugin,\n  type DeviceAuthorizationPluginOptions\n} from \"@better-auth-ui/core/plugins/device-authorization\"\n\nimport { DeviceAuthorization } from \"@/components/auth/device-authorization/device-authorization\"\n\nexport const deviceAuthorizationPlugin = createAuthPlugin(\n  coreDeviceAuthorizationPlugin.id,\n  (options: DeviceAuthorizationPluginOptions = {}) => ({\n    ...coreDeviceAuthorizationPlugin(options),\n    views: {\n      auth: {\n        deviceAuthorization: DeviceAuthorization\n      }\n    }\n  })\n)\n",
      "type": "registry:lib",
      "target": "@lib/auth/device-authorization-plugin.ts"
    }
  ],
  "type": "registry:component"
}