{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "api-key",
  "title": "API Key",
  "description": "API Key plugin: list, create, copy, and revoke API keys. Registers an API Keys security card in account settings.",
  "dependencies": [
    "@better-auth-ui/core@latest",
    "@better-auth-ui/react@latest",
    "@tanstack/react-form",
    "@tanstack/react-pacer",
    "@tanstack/react-store",
    "@tanstack/react-table",
    "better-auth",
    "cn",
    "date-fns",
    "lucide-react"
  ],
  "registryDependencies": [
    "alert-dialog",
    "avatar",
    "badge",
    "button",
    "calendar",
    "card",
    "checkbox",
    "combobox",
    "dialog",
    "empty",
    "field",
    "input",
    "input-group",
    "item",
    "label",
    "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/api-key/api-key-skeleton.tsx",
      "content": "\"use client\"\n\nimport { Item, ItemContent, ItemMedia } from \"@/components/ui/item\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\n\nexport function ApiKeySkeleton() {\n  return (\n    <Item>\n      <ItemMedia>\n        <Skeleton className=\"size-10 rounded-md\" />\n      </ItemMedia>\n      <ItemContent>\n        <Skeleton className=\"h-4 w-28\" />\n        <Skeleton className=\"h-3 w-36\" />\n        <Skeleton className=\"h-3 w-32\" />\n      </ItemContent>\n    </Item>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/api-key/api-key-skeleton.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/api-key/api-key.tsx",
      "content": "\"use client\"\n\nimport type { ListedApiKey } from \"@better-auth-ui/core/plugins/api-key\"\nimport { useAuth, useAuthPlugin } from \"@better-auth-ui/react\"\nimport { Key, Pencil, X } from \"lucide-react\"\nimport { useState } from \"react\"\n\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 { apiKeyPlugin } from \"@/lib/auth/api-key-plugin\"\nimport { DeleteApiKeyDialog } from \"./delete-api-key-dialog\"\nimport { EditApiKeyDialog } from \"./edit-api-key-dialog\"\n\nexport type ApiKeyProps = {\n  apiKey: ListedApiKey\n  /** Hide the row's delete button (e.g., when caller lacks `apiKey:delete`). */\n  hideDelete?: boolean\n  /** Hide the row's edit button (e.g., when caller lacks `apiKey:update`). */\n  hideUpdate?: boolean\n  /** Scope the delete payload to an organization (sets `configId`). */\n  organizationId?: string\n}\n\nexport function ApiKey({\n  apiKey,\n  hideDelete,\n  hideUpdate,\n  organizationId\n}: ApiKeyProps) {\n  const { localization } = useAuth()\n  const { localization: apiKeyLocalization } = useAuthPlugin(apiKeyPlugin)\n  const [deleteOpen, setDeleteOpen] = useState(false)\n  const [editOpen, setEditOpen] = useState(false)\n\n  const preview = `${apiKey.start}${\"*\".repeat(16)}`\n\n  return (\n    <Item>\n      <ItemMedia variant=\"icon\">\n        <Key />\n      </ItemMedia>\n      <ItemContent>\n        <ItemTitle>{apiKey.name || apiKeyLocalization.apiKey}</ItemTitle>\n        <ItemDescription className=\"font-mono\">{preview}</ItemDescription>\n        <ItemDescription>\n          {apiKeyLocalization.created}{\" \"}\n          {new Date(apiKey.createdAt).toLocaleString(undefined, {\n            dateStyle: \"medium\",\n            timeStyle: \"short\"\n          })}\n        </ItemDescription>\n        <ItemDescription>\n          {apiKey.expiresAt\n            ? `${apiKeyLocalization.expires} ${new Date(\n                apiKey.expiresAt\n              ).toLocaleString(undefined, {\n                dateStyle: \"medium\",\n                timeStyle: \"short\"\n              })}`\n            : apiKeyLocalization.neverExpires}\n        </ItemDescription>\n        <ItemDescription>\n          {apiKey.enabled\n            ? apiKeyLocalization.enabled\n            : apiKeyLocalization.disabled}\n          {` · ${apiKeyLocalization.requests}: ${apiKey.requestCount}`}\n          {apiKey.remaining === null\n            ? \"\"\n            : ` · ${apiKeyLocalization.remaining}: ${apiKey.remaining}`}\n        </ItemDescription>\n        <ItemDescription>\n          {apiKeyLocalization.lastRequest}:{\" \"}\n          {apiKey.lastRequest\n            ? new Date(apiKey.lastRequest).toLocaleString()\n            : apiKeyLocalization.neverRequested}\n        </ItemDescription>\n      </ItemContent>\n      <ItemActions>\n        {!hideUpdate && (\n          <>\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() => setEditOpen(true)}\n            >\n              <Pencil />\n              {apiKeyLocalization.editApiKey}\n            </Button>\n            <EditApiKeyDialog\n              apiKey={apiKey}\n              open={editOpen}\n              onOpenChange={setEditOpen}\n            />\n          </>\n        )}\n        {!hideDelete && (\n          <>\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() => setDeleteOpen(true)}\n              aria-label={apiKeyLocalization.deleteApiKey}\n            >\n              <X />\n\n              {localization.settings.delete}\n            </Button>\n\n            <DeleteApiKeyDialog\n              open={deleteOpen}\n              onOpenChange={setDeleteOpen}\n              apiKey={apiKey}\n              organizationId={organizationId}\n            />\n          </>\n        )}\n      </ItemActions>\n    </Item>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/api-key/api-key.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/api-key/api-keys-empty.tsx",
      "content": "\"use client\"\n\nimport { useAuthPlugin } from \"@better-auth-ui/react\"\nimport { Key } from \"lucide-react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Empty,\n  EmptyContent,\n  EmptyDescription,\n  EmptyHeader,\n  EmptyMedia,\n  EmptyTitle\n} from \"@/components/ui/empty\"\nimport { apiKeyPlugin } from \"@/lib/auth/api-key-plugin\"\n\nexport type ApiKeysEmptyProps = {\n  onCreatePress: () => void\n  hideCreate?: boolean\n}\n\nexport function ApiKeysEmpty({ onCreatePress, hideCreate }: ApiKeysEmptyProps) {\n  const { localization: apiKeyLocalization } = useAuthPlugin(apiKeyPlugin)\n\n  return (\n    <Empty>\n      <EmptyHeader>\n        <EmptyMedia variant=\"icon\">\n          <Key />\n        </EmptyMedia>\n        <EmptyTitle>{apiKeyLocalization.noApiKeys}</EmptyTitle>\n        <EmptyDescription>\n          {apiKeyLocalization.apiKeysDescription}\n        </EmptyDescription>\n      </EmptyHeader>\n      <EmptyContent>\n        {!hideCreate && (\n          <Button size=\"sm\" onClick={onCreatePress}>\n            {apiKeyLocalization.createApiKey}\n          </Button>\n        )}\n      </EmptyContent>\n    </Empty>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/api-key/api-keys-empty.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/api-key/api-keys.tsx",
      "content": "\"use client\"\n\nimport { getLookaheadPage } from \"@better-auth-ui/core\"\nimport type {\n  ApiKeyAuthClient,\n  ListedApiKey\n} from \"@better-auth-ui/core/plugins/api-key\"\nimport { useAuth, useAuthPlugin } from \"@better-auth-ui/react\"\nimport { useListApiKeys } from \"@better-auth-ui/react/plugins/api-key\"\nimport {\n  createTableHook,\n  rowPaginationFeature,\n  rowSortingFeature,\n  type SortingState,\n  tableFeatures\n} from \"@tanstack/react-table\"\nimport { Fragment, useEffect, useMemo, useState } from \"react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { Card, CardContent } from \"@/components/ui/card\"\nimport { ItemGroup, ItemSeparator } from \"@/components/ui/item\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue\n} from \"@/components/ui/select\"\nimport { apiKeyPlugin } from \"@/lib/auth/api-key-plugin\"\nimport { cn } from \"cn\"\nimport { useServerTableState } from \"../server-table-state\"\nimport { ApiKey } from \"./api-key\"\nimport { ApiKeySkeleton } from \"./api-key-skeleton\"\nimport { ApiKeysEmpty } from \"./api-keys-empty\"\nimport { CreateApiKeyDialog } from \"./create-api-key-dialog\"\n\nconst { createAppColumnHelper, useAppTable: useApiKeyTable } = createTableHook({\n  enableMultiSort: false,\n  enableSortingRemoval: false,\n  sortDescFirst: false,\n  features: tableFeatures({ rowPaginationFeature, rowSortingFeature })\n})\n\nconst apiKeyColumnHelper = createAppColumnHelper<ListedApiKey>()\nconst apiKeyColumns = apiKeyColumnHelper.columns([\n  apiKeyColumnHelper.accessor(\"createdAt\", { id: \"createdAt\" }),\n  apiKeyColumnHelper.accessor(\"name\", { id: \"name\" })\n])\nconst EMPTY_API_KEYS: ListedApiKey[] = []\nconst INITIAL_API_KEY_SORTING: SortingState = [{ id: \"createdAt\", desc: true }]\n\nexport type ApiKeysProps = {\n  className?: string\n  /** Scope the list and create payload to an organization. */\n  organizationId?: string\n  /** Force the loading skeleton and disable the list query. */\n  isPending?: boolean\n  /** Hide the \"Create API key\" button (header + empty state). */\n  hideCreate?: boolean\n  /** Hide the per-row delete button on listed keys. */\n  hideDelete?: boolean\n  /** Hide the per-row edit button on listed keys. */\n  hideUpdate?: boolean\n}\n\nexport function ApiKeys({\n  className,\n  organizationId,\n  isPending: isPendingProp,\n  hideCreate,\n  hideDelete,\n  hideUpdate\n}: ApiKeysProps) {\n  const { authClient } = useAuth<ApiKeyAuthClient>()\n  const { localization: apiKeyLocalization, pageSize } =\n    useAuthPlugin(apiKeyPlugin)\n  const tableState = useServerTableState({\n    initialSorting: INITIAL_API_KEY_SORTING,\n    pageSize\n  })\n  const { pagination, setPagination, sorting } = tableState\n  const primarySort = sorting[0]\n  const sortBy = primarySort?.id === \"name\" ? \"name\" : \"createdAt\"\n  const sortDirection = primarySort?.desc ? \"desc\" : \"asc\"\n  const sort = `${sortBy}:${sortDirection}`\n\n  const {\n    data: listData,\n    isPending: isListPending,\n    isSuccess: isListSuccess\n  } = useListApiKeys(authClient, {\n    enabled: !isPendingProp,\n    query: {\n      limit: pagination.pageSize + 1,\n      offset: pagination.pageIndex * pagination.pageSize,\n      sortBy,\n      sortDirection,\n      ...(organizationId ? { organizationId, configId: \"organization\" } : {})\n    }\n  })\n\n  const isPending = isPendingProp || isListPending\n  const page = useMemo(\n    () =>\n      getLookaheadPage(\n        listData?.apiKeys ?? EMPTY_API_KEYS,\n        pagination.pageSize\n      ),\n    [listData?.apiKeys, pagination.pageSize]\n  )\n  useEffect(() => {\n    if (\n      isListSuccess &&\n      !isPendingProp &&\n      pagination.pageIndex > 0 &&\n      page.rows.length === 0\n    ) {\n      setPagination((current) => ({\n        ...current,\n        pageIndex: Math.max(0, current.pageIndex - 1)\n      }))\n    }\n  }, [\n    isListSuccess,\n    isPendingProp,\n    page.rows.length,\n    pagination.pageIndex,\n    setPagination\n  ])\n\n  const table = useApiKeyTable(\n    {\n      atoms: tableState.atoms,\n      columns: apiKeyColumns,\n      data: page.rows,\n      getRowId: (apiKey) => apiKey.id,\n      manualPagination: true,\n      pageCount: -1,\n      manualSorting: true\n    },\n    () => null\n  )\n\n  const [createOpen, setCreateOpen] = useState(false)\n\n  return (\n    <div className={cn(\"flex flex-col gap-3\", className)}>\n      <div className=\"flex items-end justify-between gap-3\">\n        <h2 className=\"truncate text-sm font-semibold\">\n          {apiKeyLocalization.apiKeys}\n        </h2>\n\n        {!hideCreate && (\n          <Button\n            className=\"shrink-0\"\n            size=\"sm\"\n            disabled={isPending}\n            onClick={() => setCreateOpen(true)}\n          >\n            {apiKeyLocalization.createApiKey}\n          </Button>\n        )}\n      </div>\n      <Select\n        value={sort}\n        onValueChange={(value) => {\n          const [id, direction] = value.split(\":\")\n          table.setSorting([{ id, desc: direction === \"desc\" }])\n        }}\n      >\n        <SelectTrigger aria-label={apiKeyLocalization.sortBy}>\n          <SelectValue />\n        </SelectTrigger>\n        <SelectContent>\n          <SelectItem value=\"createdAt:desc\">\n            {apiKeyLocalization.newest}\n          </SelectItem>\n          <SelectItem value=\"createdAt:asc\">\n            {apiKeyLocalization.oldest}\n          </SelectItem>\n          <SelectItem value=\"name:asc\">\n            {apiKeyLocalization.nameAscending}\n          </SelectItem>\n          <SelectItem value=\"name:desc\">\n            {apiKeyLocalization.nameDescending}\n          </SelectItem>\n        </SelectContent>\n      </Select>\n\n      <Card className=\"p-0\">\n        <CardContent className=\"p-0\">\n          {isPending ? (\n            <ApiKeySkeleton />\n          ) : page.rows.length === 0 ? (\n            <ApiKeysEmpty\n              onCreatePress={() => setCreateOpen(true)}\n              hideCreate={hideCreate}\n            />\n          ) : (\n            <ItemGroup className=\"gap-0\">\n              {table.getRowModel().rows.map((row, index) => (\n                <Fragment key={row.id}>\n                  {index > 0 && <ItemSeparator />}\n                  <ApiKey\n                    apiKey={row.original}\n                    hideDelete={hideDelete}\n                    hideUpdate={hideUpdate}\n                    organizationId={organizationId}\n                  />\n                </Fragment>\n              ))}\n            </ItemGroup>\n          )}\n        </CardContent>\n      </Card>\n      {(pagination.pageIndex > 0 || page.hasNextPage) && (\n        <div className=\"flex justify-end gap-2\">\n          <Button\n            variant=\"outline\"\n            size=\"sm\"\n            disabled={!table.getCanPreviousPage()}\n            onClick={() => table.previousPage()}\n          >\n            {apiKeyLocalization.previousPage}\n          </Button>\n          <Button\n            variant=\"outline\"\n            size=\"sm\"\n            disabled={!page.hasNextPage}\n            onClick={() => table.nextPage()}\n          >\n            {apiKeyLocalization.nextPage}\n          </Button>\n        </div>\n      )}\n\n      {!hideCreate && (\n        <CreateApiKeyDialog\n          open={createOpen}\n          onOpenChange={setCreateOpen}\n          organizationId={organizationId}\n        />\n      )}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/api-key/api-keys.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/api-key/create-api-key-dialog.tsx",
      "content": "\"use client\"\n\nimport {\n  type ApiKeyAuthClient,\n  apiKeyExpirationDaysToSeconds\n} from \"@better-auth-ui/core/plugins/api-key\"\nimport { useAuth, useAuthPlugin } from \"@better-auth-ui/react\"\nimport { useCreateApiKey } from \"@better-auth-ui/react/plugins/api-key\"\nimport { Key } from \"lucide-react\"\nimport { useState } from \"react\"\nimport { buttonVariants } from \"@/components/ui/button\"\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle\n} from \"@/components/ui/dialog\"\nimport {\n  Field,\n  FieldError,\n  FieldGroup,\n  FieldLabel\n} from \"@/components/ui/field\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectTrigger,\n  SelectValue\n} from \"@/components/ui/select\"\nimport { apiKeyPlugin } from \"@/lib/auth/api-key-plugin\"\nimport { useAuthForm } from \"../auth-form\"\nimport { NewApiKeyDialog } from \"./new-api-key-dialog\"\n\nexport type CreateApiKeyDialogProps = {\n  open: boolean\n  onOpenChange: (open: boolean) => void\n  /** Create an organization-owned key by passing the organization id. */\n  organizationId?: string\n}\n\nexport function CreateApiKeyDialog({\n  open,\n  onOpenChange,\n  organizationId\n}: CreateApiKeyDialogProps) {\n  const { authClient, localization } = useAuth<ApiKeyAuthClient>()\n  const {\n    configurations,\n    keyExpiration,\n    localization: apiKeyLocalization\n  } = useAuthPlugin(apiKeyPlugin)\n\n  const { mutateAsync: createApiKey, isPending: isCreating } =\n    useCreateApiKey(authClient)\n\n  const [isNewKeyDialogOpen, setIsNewKeyDialogOpen] = useState(false)\n  const [keyName, setKeyName] = useState<string | null>(null)\n  const [secretKey, setSecretKey] = useState<string | null>(null)\n  const availableConfigurations = configurations.filter(\n    (configuration) => configuration.organization === Boolean(organizationId)\n  )\n\n  const form = useAuthForm({\n    defaultValues: {\n      configId: availableConfigurations[0]?.id ?? \"\",\n      expiration:\n        keyExpiration === false || keyExpiration?.defaultInterval == null\n          ? \"never\"\n          : String(keyExpiration.defaultInterval),\n      name: \"\"\n    },\n    onSubmit: async ({ value }) => {\n      const name = value.name.trim()\n      const expirationDays =\n        value.expiration !== \"never\" ? Number(value.expiration) : undefined\n      const expiresIn = expirationDays\n        ? apiKeyExpirationDaysToSeconds(expirationDays)\n        : undefined\n      const configId = value.configId.trim()\n      const resolvedConfigId =\n        configId || (organizationId ? \"organization\" : undefined)\n      const payload = {\n        ...(name ? { name } : {}),\n        ...(expiresIn ? { expiresIn } : {}),\n        ...(resolvedConfigId ? { configId: resolvedConfigId } : {}),\n        ...(organizationId ? { organizationId } : {})\n      }\n      await createApiKey(\n        Object.keys(payload).length > 0 ? payload : undefined,\n        {\n          onSuccess: (result) => {\n            handleOpenChange(false)\n            setKeyName(name)\n            setSecretKey(result.key)\n            setIsNewKeyDialogOpen(true)\n          }\n        }\n      )\n    }\n  })\n\n  const handleOpenChange = (nextOpen: boolean) => {\n    if (!nextOpen) {\n      setKeyName(null)\n      setSecretKey(null)\n      form.reset()\n    }\n\n    onOpenChange(nextOpen)\n  }\n\n  const handleNewKeyDialogOpenChange = (nextOpen: boolean) => {\n    setIsNewKeyDialogOpen(nextOpen)\n\n    if (!nextOpen) {\n      setKeyName(null)\n      setSecretKey(null)\n    }\n  }\n\n  return (\n    <>\n      <Dialog open={open} onOpenChange={handleOpenChange}>\n        <DialogContent>\n          <form.AppForm>\n            <form.AuthFormRoot className=\"flex flex-col gap-6\">\n              <DialogHeader>\n                <DialogTitle className=\"flex items-center gap-2\">\n                  <Key />\n                  {apiKeyLocalization.createApiKey}\n                </DialogTitle>\n\n                <DialogDescription>\n                  {apiKeyLocalization.apiKeysDescription}\n                </DialogDescription>\n              </DialogHeader>\n\n              <FieldGroup>\n                <form.AppField name=\"name\">\n                  {(field) => (\n                    <Field>\n                      <FieldLabel htmlFor=\"api-key-name\">\n                        {apiKeyLocalization.name}\n                      </FieldLabel>\n\n                      <Input\n                        id=\"api-key-name\"\n                        name={field.name}\n                        autoFocus\n                        placeholder={localization.settings.optional}\n                        disabled={isCreating}\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChange={(event) =>\n                          field.handleChange(event.target.value)\n                        }\n                      />\n\n                      <FieldError />\n                    </Field>\n                  )}\n                </form.AppField>\n\n                {availableConfigurations.length > 0 && (\n                  <form.AppField name=\"configId\">\n                    {(field) => (\n                      <Field>\n                        <FieldLabel htmlFor=\"api-key-configuration\">\n                          {apiKeyLocalization.configuration}\n                        </FieldLabel>\n                        <Select\n                          name={field.name}\n                          value={field.state.value}\n                          onValueChange={(value) => field.handleChange(value)}\n                          disabled={isCreating}\n                        >\n                          <SelectTrigger\n                            id=\"api-key-configuration\"\n                            className=\"w-full\"\n                          >\n                            <SelectValue />\n                          </SelectTrigger>\n                          <SelectContent>\n                            <SelectGroup>\n                              {availableConfigurations.map((configuration) => (\n                                <SelectItem\n                                  key={configuration.id}\n                                  value={configuration.id}\n                                >\n                                  {configuration.label}\n                                </SelectItem>\n                              ))}\n                            </SelectGroup>\n                          </SelectContent>\n                        </Select>\n                      </Field>\n                    )}\n                  </form.AppField>\n                )}\n\n                {keyExpiration ? (\n                  <form.AppField name=\"expiration\">\n                    {(field) => (\n                      <Field>\n                        <FieldLabel htmlFor=\"api-key-expiration\">\n                          {apiKeyLocalization.expiration}\n                        </FieldLabel>\n\n                        <Select\n                          name={field.name}\n                          value={field.state.value}\n                          onValueChange={(value) => field.handleChange(value)}\n                          disabled={isCreating}\n                        >\n                          <SelectTrigger\n                            id=\"api-key-expiration\"\n                            className=\"w-full\"\n                          >\n                            <SelectValue />\n                          </SelectTrigger>\n\n                          <SelectContent>\n                            <SelectGroup>\n                              {keyExpiration.intervals.map((days) => (\n                                <SelectItem key={days} value={String(days)}>\n                                  {days.toLocaleString()}{\" \"}\n                                  {days === 1\n                                    ? apiKeyLocalization.day\n                                    : apiKeyLocalization.days}\n                                </SelectItem>\n                              ))}\n\n                              {keyExpiration.allowNever ? (\n                                <SelectItem value=\"never\">\n                                  {apiKeyLocalization.never}\n                                </SelectItem>\n                              ) : null}\n                            </SelectGroup>\n                          </SelectContent>\n                        </Select>\n                      </Field>\n                    )}\n                  </form.AppField>\n                ) : null}\n              </FieldGroup>\n\n              <DialogFooter>\n                <DialogClose\n                  className={buttonVariants({ variant: \"outline\" })}\n                  disabled={isCreating}\n                  type=\"button\"\n                >\n                  {localization.settings.cancel}\n                </DialogClose>\n\n                <form.AuthFormSubmitButton\n                  isPending={isCreating}\n                  disabled={isCreating}\n                >\n                  {apiKeyLocalization.createApiKey}\n                </form.AuthFormSubmitButton>\n              </DialogFooter>\n            </form.AuthFormRoot>\n          </form.AppForm>\n        </DialogContent>\n      </Dialog>\n\n      <NewApiKeyDialog\n        open={isNewKeyDialogOpen}\n        onOpenChange={handleNewKeyDialogOpenChange}\n        secretKey={secretKey}\n        name={keyName}\n      />\n    </>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/api-key/create-api-key-dialog.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/api-key/delete-api-key-dialog.tsx",
      "content": "\"use client\"\n\nimport type {\n  ApiKeyAuthClient,\n  ListedApiKey\n} from \"@better-auth-ui/core/plugins/api-key\"\nimport { useAuth, useAuthPlugin } from \"@better-auth-ui/react\"\nimport { useDeleteApiKey } from \"@better-auth-ui/react/plugins/api-key\"\nimport { Key } from \"lucide-react\"\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 { Field, FieldLabel } from \"@/components/ui/field\"\nimport { Input } from \"@/components/ui/input\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { apiKeyPlugin } from \"@/lib/auth/api-key-plugin\"\n\nexport type DeleteApiKeyDialogProps = {\n  open: boolean\n  onOpenChange: (open: boolean) => void\n  apiKey: ListedApiKey\n  /** Scope the delete payload to an organization (sets `configId`). */\n  organizationId?: string\n}\n\nexport function DeleteApiKeyDialog({\n  open,\n  onOpenChange,\n  apiKey,\n  organizationId\n}: DeleteApiKeyDialogProps) {\n  const { authClient, localization } = useAuth<ApiKeyAuthClient>()\n  const { localization: apiKeyLocalization } = useAuthPlugin(apiKeyPlugin)\n  const preview = `${apiKey.start}${\"*\".repeat(16)}`\n  const previewId = `delete-api-key-preview-${apiKey.id}`\n  const { mutate: deleteApiKey, isPending: isDeleting } = useDeleteApiKey(\n    authClient,\n    {\n      onSuccess: () => onOpenChange(false)\n    }\n  )\n\n  return (\n    <AlertDialog open={open} onOpenChange={onOpenChange}>\n      <AlertDialogContent>\n        <AlertDialogHeader>\n          <AlertDialogMedia>\n            <Key />\n          </AlertDialogMedia>\n\n          <AlertDialogTitle>{apiKeyLocalization.deleteApiKey}</AlertDialogTitle>\n\n          <AlertDialogDescription>\n            {apiKeyLocalization.deleteApiKeyWarning}\n          </AlertDialogDescription>\n        </AlertDialogHeader>\n\n        <Field>\n          <FieldLabel htmlFor={previewId}>\n            {apiKey.name || apiKeyLocalization.apiKey}\n          </FieldLabel>\n\n          <Input\n            id={previewId}\n            value={preview}\n            readOnly\n            className=\"font-mono text-xs\"\n            disabled\n          />\n        </Field>\n\n        <AlertDialogFooter>\n          <AlertDialogCancel disabled={isDeleting}>\n            {localization.settings.cancel}\n          </AlertDialogCancel>\n\n          <Button\n            type=\"button\"\n            variant=\"destructive\"\n            disabled={isDeleting}\n            onClick={() =>\n              deleteApiKey({\n                keyId: apiKey.id,\n                ...(organizationId ? { configId: \"organization\" } : {})\n              })\n            }\n          >\n            {isDeleting && <Spinner />}\n\n            {apiKeyLocalization.deleteApiKey}\n          </Button>\n        </AlertDialogFooter>\n      </AlertDialogContent>\n    </AlertDialog>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/api-key/delete-api-key-dialog.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/api-key/edit-api-key-dialog.tsx",
      "content": "\"use client\"\n\nimport type {\n  ApiKeyAuthClient,\n  ListedApiKey\n} from \"@better-auth-ui/core/plugins/api-key\"\nimport { useAuth, useAuthPlugin } from \"@better-auth-ui/react\"\nimport { useUpdateApiKey } from \"@better-auth-ui/react/plugins/api-key\"\nimport { useEffect } from \"react\"\nimport { buttonVariants } from \"@/components/ui/button\"\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle\n} from \"@/components/ui/dialog\"\nimport {\n  Field,\n  FieldError,\n  FieldGroup,\n  FieldLabel\n} from \"@/components/ui/field\"\nimport { Input } from \"@/components/ui/input\"\nimport { apiKeyPlugin } from \"@/lib/auth/api-key-plugin\"\nimport { useAuthForm } from \"../auth-form\"\n\nexport function EditApiKeyDialog({\n  apiKey,\n  open,\n  onOpenChange\n}: {\n  apiKey: ListedApiKey\n  open: boolean\n  onOpenChange: (open: boolean) => void\n}) {\n  const { authClient, localization } = useAuth<ApiKeyAuthClient>()\n  const { localization: labels } = useAuthPlugin(apiKeyPlugin)\n\n  const updateApiKey = useUpdateApiKey(authClient, {\n    onSuccess: () => onOpenChange(false)\n  })\n  const form = useAuthForm({\n    defaultValues: { name: apiKey.name ?? \"\" },\n    onSubmit: async ({ value }) =>\n      await updateApiKey.mutateAsync({\n        configId: apiKey.configId,\n        keyId: apiKey.id,\n        name: value.name.trim()\n      })\n  })\n  useEffect(() => {\n    if (open) form.reset({ name: apiKey.name ?? \"\" })\n  }, [apiKey.name, form, open])\n  return (\n    <Dialog open={open} onOpenChange={onOpenChange}>\n      <DialogContent>\n        <form.AppForm>\n          <form.AuthFormRoot className=\"flex flex-col gap-6\">\n            <DialogHeader>\n              <DialogTitle>{labels.editApiKey}</DialogTitle>\n            </DialogHeader>\n            <FieldGroup>\n              <form.AppField name=\"name\">\n                {(field) => (\n                  <Field>\n                    <FieldLabel htmlFor={`api-key-name-${apiKey.id}`}>\n                      {labels.name}\n                    </FieldLabel>\n                    <Input\n                      id={`api-key-name-${apiKey.id}`}\n                      name={field.name}\n                      value={field.state.value}\n                      onBlur={field.handleBlur}\n                      onChange={(event) =>\n                        field.handleChange(event.target.value)\n                      }\n                    />\n                  </Field>\n                )}\n              </form.AppField>\n              {updateApiKey.error && (\n                <FieldError>\n                  {updateApiKey.error.error?.message ??\n                    updateApiKey.error.message}\n                </FieldError>\n              )}\n            </FieldGroup>\n            <DialogFooter>\n              <DialogClose\n                className={buttonVariants({ variant: \"outline\" })}\n                type=\"button\"\n              >\n                {localization.settings.cancel}\n              </DialogClose>\n              <form.AuthFormSubmitButton\n                isPending={updateApiKey.isPending}\n                disabled={updateApiKey.isPending}\n              >\n                {localization.settings.saveChanges}\n              </form.AuthFormSubmitButton>\n            </DialogFooter>\n          </form.AuthFormRoot>\n        </form.AppForm>\n      </DialogContent>\n    </Dialog>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/api-key/edit-api-key-dialog.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/api-key/new-api-key-dialog.tsx",
      "content": "\"use client\"\n\nimport {\n  useAuth,\n  useAuthPlugin,\n  useCopyToClipboard\n} from \"@better-auth-ui/react\"\nimport { Check, Copy, Key } from \"lucide-react\"\nimport { toast } from \"sonner\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle\n} from \"@/components/ui/dialog\"\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupInput\n} from \"@/components/ui/input-group\"\nimport { Label } from \"@/components/ui/label\"\nimport { apiKeyPlugin } from \"@/lib/auth/api-key-plugin\"\n\nexport type NewApiKeyDialogProps = {\n  open: boolean\n  onOpenChange: (open: boolean) => void\n  name: string | null\n  secretKey: string | null\n}\n\nexport function NewApiKeyDialog({\n  open,\n  onOpenChange,\n  name,\n  secretKey\n}: NewApiKeyDialogProps) {\n  const { localization } = useAuth()\n  const { localization: apiKeyLocalization } = useAuthPlugin(apiKeyPlugin)\n\n  const { copied, copy, reset } = useCopyToClipboard({\n    onError: (error) => {\n      console.error(\"[Better Auth UI] Copy failed\", error)\n      toast.error(localization.errors.copyFailed)\n    }\n  })\n\n  const handleOpenChange = (nextOpen: boolean) => {\n    if (!nextOpen) {\n      reset()\n    }\n\n    onOpenChange(nextOpen)\n  }\n\n  const copySecretKey = async () => {\n    if (!secretKey) return\n\n    await copy(secretKey)\n  }\n\n  return (\n    <Dialog open={open} onOpenChange={handleOpenChange}>\n      <DialogContent showCloseButton={false}>\n        <DialogHeader>\n          <DialogTitle className=\"flex items-center gap-2\">\n            <Key />\n            {apiKeyLocalization.newApiKey}\n          </DialogTitle>\n\n          <DialogDescription>\n            {apiKeyLocalization.newApiKeyWarning}\n          </DialogDescription>\n        </DialogHeader>\n\n        <div className=\"flex flex-col gap-2\">\n          <Label htmlFor=\"new-api-key-secret\">\n            {name || apiKeyLocalization.apiKey}\n          </Label>\n\n          <InputGroup>\n            <InputGroupInput\n              id=\"new-api-key-secret\"\n              value={secretKey ?? \"\"}\n              readOnly\n              className=\"font-mono text-xs\"\n            />\n\n            <InputGroupAddon align=\"inline-end\">\n              <InputGroupButton\n                size=\"icon-xs\"\n                aria-label={\n                  copied\n                    ? localization.settings.copiedToClipboard\n                    : localization.settings.copyToClipboard\n                }\n                onClick={copySecretKey}\n              >\n                {copied ? <Check /> : <Copy />}\n              </InputGroupButton>\n            </InputGroupAddon>\n          </InputGroup>\n        </div>\n\n        <DialogFooter>\n          <Button type=\"button\" onClick={() => handleOpenChange(false)}>\n            {apiKeyLocalization.dismissNewKey}\n          </Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/api-key/new-api-key-dialog.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/api-key/organization-api-keys.tsx",
      "content": "\"use client\"\n\nimport {\n  hasMemberRole,\n  type OrganizationAuthClient\n} from \"@better-auth-ui/core/plugins/organization\"\nimport { useAuth, useAuthPlugin } from \"@better-auth-ui/react\"\nimport {\n  useActiveMemberRole,\n  useHasPermission\n} from \"@better-auth-ui/react/plugins/organization\"\nimport { organizationPlugin } from \"@/lib/auth/organization-plugin\"\nimport { ApiKeys } from \"./api-keys\"\n\nexport type OrganizationApiKeysProps = {\n  className?: string\n  organizationId: string\n  organizationSlug: string\n}\n\n/**\n * {@link ApiKeys} scoped to an explicit organization.\n *\n * Access is resolved per API-key action. The configured organization creator\n * role receives Better Auth's creator override.\n */\nexport function OrganizationApiKeys({\n  className,\n  organizationId\n}: OrganizationApiKeysProps) {\n  const { authClient } = useAuth<OrganizationAuthClient>()\n  const { creatorRole } = useAuthPlugin(organizationPlugin)\n  const memberRole = useActiveMemberRole(authClient, {\n    query: { organizationId }\n  })\n  const isCreator = hasMemberRole(memberRole.data?.role, creatorRole)\n  const permissionOptions = {\n    enabled: !memberRole.isPending && !isCreator,\n    organizationId\n  }\n  const canRead = useHasPermission(authClient, {\n    ...permissionOptions,\n    permissions: { apiKey: [\"read\"] } as Parameters<\n      OrganizationAuthClient[\"organization\"][\"hasPermission\"]\n    >[0][\"permissions\"]\n  })\n  const canCreate = useHasPermission(authClient, {\n    ...permissionOptions,\n    permissions: { apiKey: [\"create\"] } as Parameters<\n      OrganizationAuthClient[\"organization\"][\"hasPermission\"]\n    >[0][\"permissions\"]\n  })\n  const canUpdate = useHasPermission(authClient, {\n    ...permissionOptions,\n    permissions: { apiKey: [\"update\"] } as Parameters<\n      OrganizationAuthClient[\"organization\"][\"hasPermission\"]\n    >[0][\"permissions\"]\n  })\n  const canDelete = useHasPermission(authClient, {\n    ...permissionOptions,\n    permissions: { apiKey: [\"delete\"] } as Parameters<\n      OrganizationAuthClient[\"organization\"][\"hasPermission\"]\n    >[0][\"permissions\"]\n  })\n  const permissionPending =\n    !isCreator &&\n    !memberRole.isPending &&\n    (canRead.isPending ||\n      canCreate.isPending ||\n      canUpdate.isPending ||\n      canDelete.isPending)\n  const isPending = memberRole.isPending || permissionPending\n  const canReadKeys = isCreator || canRead.data?.success\n\n  if (!isPending && !canReadKeys) return null\n\n  return (\n    <ApiKeys\n      className={className}\n      hideCreate={!isCreator && !canCreate.data?.success}\n      hideDelete={!isCreator && !canDelete.data?.success}\n      hideUpdate={!isCreator && !canUpdate.data?.success}\n      isPending={isPending}\n      organizationId={organizationId}\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/api-key/organization-api-keys.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/organization/accept-invitation.tsx",
      "content": "\"use client\"\n\nimport { getSafeRedirectTo } from \"@better-auth-ui/core\"\nimport type { OrganizationAuthClient } from \"@better-auth-ui/core/plugins/organization\"\nimport { useAuth, useAuthenticate, useAuthPlugin } from \"@better-auth-ui/react\"\nimport {\n  useAcceptInvitation,\n  useInvitation,\n  useRejectInvitation\n} from \"@better-auth-ui/react/plugins/organization\"\nimport type { Invitation } from \"better-auth/client\"\nimport { BriefcaseBusiness } from \"lucide-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 { FieldDescription } from \"@/components/ui/field\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { organizationPlugin } from \"@/lib/auth/organization-plugin\"\nimport { UserAvatar } from \"../user/user-avatar\"\nimport { cn } from \"cn\"\nimport { useIsHydrated } from \"../use-is-hydrated\"\n\ntype UserInvitation = Invitation & { organizationName?: string }\n\nexport type AcceptInvitationProps = {\n  className?: string\n}\n\nfunction isPendingInvitation(invitation: UserInvitation | undefined) {\n  if (invitation?.status !== \"pending\") return false\n\n  return new Date(invitation.expiresAt).getTime() > Date.now()\n}\n\n/**\n * Render the organization invitation addressed by the `invitationId` query\n * parameter and let the signed-in recipient accept or reject it directly.\n */\nexport function AcceptInvitation({ className }: AcceptInvitationProps) {\n  const { authClient, navigate, redirectTo } = useAuth()\n  const { localization, roles } = useAuthPlugin(organizationPlugin)\n  const organizationAuthClient = authClient as OrganizationAuthClient\n  const isHydrated = useIsHydrated()\n  const invitationId = isHydrated\n    ? new URLSearchParams(window.location.search).get(\"invitationId\")\n    : null\n  const session = useAuthenticate(organizationAuthClient)\n  const invitationQuery = useInvitation(organizationAuthClient, {\n    query: { id: invitationId ?? \"\" },\n    enabled: Boolean(invitationId)\n  })\n  const invitation = invitationQuery.data as UserInvitation | undefined\n\n  const returnToApplication = () => {\n    navigate({\n      to: getSafeRedirectTo(redirectTo, window.location.origin),\n      replace: true\n    })\n  }\n\n  const { mutate: acceptInvitation, isPending: isAccepting } =\n    useAcceptInvitation(organizationAuthClient, {\n      onSuccess: returnToApplication\n    })\n  const { mutate: rejectInvitation, isPending: isRejecting } =\n    useRejectInvitation(organizationAuthClient, {\n      onSuccess: returnToApplication\n    })\n  const isLoading =\n    !isHydrated ||\n    session.isPending ||\n    !session.data ||\n    (Boolean(invitationId) && invitationQuery.isPending)\n  const isAvailable = isPendingInvitation(invitation)\n  const organizationName =\n    invitation?.organizationName || localization.organization\n  const role = invitation\n    ? (roles?.[invitation.role] ?? invitation.role)\n    : localization.member\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        <div className=\"flex size-16 items-center justify-center rounded-full bg-muted/50\">\n          <BriefcaseBusiness aria-hidden=\"true\" className=\"size-7\" />\n        </div>\n        <div className=\"grid gap-1\">\n          <CardTitle className=\"max-w-full break-words text-xl font-semibold\">\n            {isLoading ? (\n              <Skeleton className=\"h-6 w-48\" />\n            ) : isAvailable ? (\n              organizationName\n            ) : (\n              localization.invitationUnavailable\n            )}\n          </CardTitle>\n          <CardDescription>\n            {localization.acceptInvitationTitle}\n          </CardDescription>\n        </div>\n      </CardHeader>\n      <CardContent className=\"px-4 pb-6 sm:px-6\">\n        <div className=\"grid gap-5 rounded-lg bg-muted/50 p-5\">\n          {isLoading ? (\n            <Skeleton className=\"h-10 w-full\" />\n          ) : isAvailable ? (\n            <>\n              <FieldDescription>\n                {localization.acceptInvitationDescription\n                  .replace(\"{{organization}}\", organizationName)\n                  .replace(\"{{role}}\", role)}\n              </FieldDescription>\n              <dl className=\"grid gap-1\">\n                <dt className=\"text-sm text-muted-foreground\">\n                  {localization.role}\n                </dt>\n                <dd className=\"text-base font-medium\">{role}</dd>\n              </dl>\n            </>\n          ) : (\n            <FieldDescription>\n              {localization.invitationUnavailableDescription}\n            </FieldDescription>\n          )}\n          <div className=\"flex min-w-0 items-center gap-3\">\n            <UserAvatar\n              user={session.data?.user}\n              isPending={session.isPending}\n            />\n            {session.data ? (\n              <div className=\"grid min-w-0 gap-0.5\">\n                <p className=\"break-words text-sm font-medium\">\n                  {session.data.user.name || session.data.user.email}\n                </p>\n                {session.data.user.name ? (\n                  <p className=\"break-all text-sm text-muted-foreground\">\n                    {session.data.user.email}\n                  </p>\n                ) : null}\n              </div>\n            ) : (\n              <Skeleton className=\"h-4 w-40\" />\n            )}\n          </div>\n        </div>\n      </CardContent>\n      <CardFooter className=\"grid grid-cols-2 gap-3 p-4 sm:p-6\">\n        {isLoading ? (\n          <>\n            <Skeleton className=\"h-11 w-full\" />\n            <Skeleton className=\"h-11 w-full\" />\n          </>\n        ) : isAvailable && invitation ? (\n          <>\n            <Button\n              className=\"h-11 w-full\"\n              variant=\"secondary\"\n              disabled={isAccepting || isRejecting}\n              onClick={() => rejectInvitation({ invitationId: invitation.id })}\n            >\n              {isRejecting ? <Spinner /> : null}\n              {localization.rejectInvitation}\n            </Button>\n            <Button\n              className=\"h-11 w-full\"\n              disabled={isAccepting || isRejecting}\n              onClick={() => acceptInvitation({ invitationId: invitation.id })}\n            >\n              {isAccepting ? <Spinner /> : null}\n              {localization.accept}\n            </Button>\n          </>\n        ) : (\n          <Button\n            className=\"col-span-2 h-11 w-full\"\n            onClick={returnToApplication}\n          >\n            {localization.return}\n          </Button>\n        )}\n      </CardFooter>\n    </Card>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/organization/accept-invitation.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/organization/create-organization-dialog.tsx",
      "content": "\"use client\"\n\nimport {\n  getAdditionalFieldDefaultValues,\n  getAdditionalFieldSubmitValues,\n  validateStringLength\n} from \"@better-auth-ui/core\"\nimport type { OrganizationAuthClient } from \"@better-auth-ui/core/plugins/organization\"\nimport { useAuth, useAuthPlugin } from \"@better-auth-ui/react\"\nimport { useCreateOrganization } from \"@better-auth-ui/react/plugins/organization\"\nimport { Briefcase } from \"lucide-react\"\nimport { useEffect, useRef, useState } from \"react\"\nimport { buttonVariants } from \"@/components/ui/button\"\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle\n} from \"@/components/ui/dialog\"\nimport { Field, FieldLabel } from \"@/components/ui/field\"\nimport { Input } from \"@/components/ui/input\"\nimport { organizationPlugin } from \"@/lib/auth/organization-plugin\"\nimport {\n  getAuthAdditionalFieldValidators,\n  isAuthFormFieldInvalid,\n  useAuthForm\n} from \"../auth-form\"\nimport { SlugField, sanitizeSlug } from \"./slug-field\"\n\n/** Props for the `CreateOrganizationDialog` component. */\nexport type CreateOrganizationDialogProps = {\n  open: boolean\n  onOpenChange: (open: boolean) => void\n  hideSlug?: boolean\n}\n\nexport function CreateOrganizationDialog({\n  open,\n  onOpenChange,\n  hideSlug: hideSlugProp\n}: CreateOrganizationDialogProps) {\n  const { authClient, localization } = useAuth<OrganizationAuthClient>()\n  const {\n    additionalFields,\n    localization: organizationLocalization,\n    hideSlug: pluginHideSlug\n  } = useAuthPlugin(organizationPlugin)\n  const hideSlug = hideSlugProp ?? pluginHideSlug ?? false\n\n  const [slugEdited, setSlugEdited] = useState(false)\n  const submissionGeneration = useRef(0)\n  const submissionAttemptGeneration = useRef(0)\n\n  const { mutateAsync: createOrganization } = useCreateOrganization(authClient)\n\n  const form = useAuthForm({\n    defaultValues: {\n      additionalFields: getAdditionalFieldDefaultValues(additionalFields),\n      name: \"\",\n      slug: \"\"\n    },\n    onSubmit: async ({ value }) => {\n      const generation = submissionAttemptGeneration.current\n      if (generation !== submissionGeneration.current) return\n      await createOrganization({\n        ...getAdditionalFieldSubmitValues(\n          additionalFields,\n          value.additionalFields\n        ),\n        name: value.name,\n        slug: hideSlug ? undefined : value.slug\n      })\n      if (generation === submissionGeneration.current) onOpenChange(false)\n    }\n  })\n\n  useEffect(() => {\n    if (!open) {\n      submissionGeneration.current += 1\n      form.reset()\n      setSlugEdited(false)\n    }\n  }, [form, open])\n\n  return (\n    <Dialog open={open} onOpenChange={onOpenChange}>\n      <DialogContent>\n        <form.AppForm>\n          <form.AuthFormRoot\n            className=\"flex flex-col gap-6\"\n            onBeforeSubmit={() => {\n              submissionAttemptGeneration.current = submissionGeneration.current\n            }}\n          >\n            <DialogHeader>\n              <DialogTitle className=\"flex items-center gap-2\">\n                <Briefcase />\n                {organizationLocalization.createOrganization}\n              </DialogTitle>\n\n              <DialogDescription>\n                {organizationLocalization.organizationsDescription}\n              </DialogDescription>\n            </DialogHeader>\n\n            <div className=\"flex flex-col gap-4\">\n              <form.AppField\n                name=\"name\"\n                validators={{\n                  onChange: ({ value }) =>\n                    validateStringLength(value, {\n                      requiredMessage: localization.auth.fieldRequired,\n                      trim: true\n                    })\n                }}\n              >\n                {(field) => {\n                  const isInvalid = isAuthFormFieldInvalid(field.state.meta)\n\n                  return (\n                    <Field data-invalid={isInvalid}>\n                      <FieldLabel htmlFor=\"create-organization-name\">\n                        {organizationLocalization.name}\n                      </FieldLabel>\n\n                      <Input\n                        id=\"create-organization-name\"\n                        name={field.name}\n                        autoFocus\n                        placeholder={organizationLocalization.namePlaceholder}\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChange={(event) => {\n                          const value = event.target.value\n                          field.handleChange(value)\n                          if (!slugEdited) {\n                            form.setFieldValue(\"slug\", sanitizeSlug(value))\n                          }\n                        }}\n                        aria-invalid={isInvalid}\n                      />\n\n                      <field.AuthFormFieldError />\n                    </Field>\n                  )\n                }}\n              </form.AppField>\n\n              {!hideSlug && (\n                <form.AppField name=\"slug\">\n                  {(field) => (\n                    <SlugField\n                      id=\"create-organization-slug\"\n                      value={field.state.value}\n                      onChange={(value) => {\n                        field.handleChange(value)\n                        setSlugEdited(true)\n                      }}\n                    />\n                  )}\n                </form.AppField>\n              )}\n\n              {additionalFields.map((configuredField) => (\n                <form.AppField\n                  key={configuredField.name}\n                  name={`additionalFields.${configuredField.name}`}\n                  validators={getAuthAdditionalFieldValidators(\n                    configuredField,\n                    localization.auth.fieldRequired\n                  )}\n                >\n                  {(field) => (\n                    <field.AuthFormAdditionalField\n                      field={configuredField}\n                      optionalLabel={localization.settings.optional}\n                    />\n                  )}\n                </form.AppField>\n              ))}\n            </div>\n\n            <DialogFooter>\n              <DialogClose\n                className={buttonVariants({ variant: \"outline\" })}\n                type=\"button\"\n              >\n                {localization.settings.cancel}\n              </DialogClose>\n\n              <form.AuthFormSubmitButton>\n                {organizationLocalization.createOrganization}\n              </form.AuthFormSubmitButton>\n            </DialogFooter>\n          </form.AuthFormRoot>\n        </form.AppForm>\n      </DialogContent>\n    </Dialog>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/organization/create-organization-dialog.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/organization/organization-logo.tsx",
      "content": "\"use client\"\n\nimport type { Organization } from \"better-auth/client\"\nimport { Briefcase } from \"lucide-react\"\nimport type { ComponentProps, CSSProperties, 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 OrganizationLogoSize = \"sm\" | \"md\" | \"lg\"\n\nexport type OrganizationLogoProps = {\n  className?: string\n  fallback?: ReactNode\n  isPending?: boolean\n  organization?: Partial<Organization> | null\n  size?: OrganizationLogoSize\n}\n\nconst sizeClasses: Record<OrganizationLogoSize, string> = {\n  sm: \"size-8\",\n  md: \"size-10\",\n  lg: \"size-12\"\n}\n\nconst fallbackTextClasses: Record<OrganizationLogoSize, string> = {\n  sm: \"text-sm\",\n  md: \"text-base\",\n  lg: \"text-xl\"\n}\n\nexport function OrganizationLogo({\n  className,\n  fallback,\n  isPending,\n  organization,\n  size = \"sm\",\n  style,\n  ...props\n}: OrganizationLogoProps &\n  Omit<ComponentProps<typeof Avatar>, \"children\" | \"size\" | \"style\"> & {\n    style?: CSSProperties\n  }) {\n  if (isPending && !organization) {\n    return (\n      <Skeleton\n        className={cn(\"rounded-full\", sizeClasses[size], className)}\n        style={style}\n      />\n    )\n  }\n\n  const initials = organization?.name?.slice(0, 2).toUpperCase()\n  const normalizedLogo = organization?.logo?.trim() || undefined\n\n  return (\n    <Avatar\n      className={cn(\"rounded-full\", sizeClasses[size], className)}\n      style={style}\n      {...props}\n    >\n      <AvatarImage\n        alt={organization?.name ?? \"Organization\"}\n        src={normalizedLogo}\n      />\n\n      <AvatarFallback\n        className={cn(\"text-muted-foreground!\", fallbackTextClasses[size])}\n      >\n        {fallback || initials || <Briefcase className=\"size-4\" />}\n      </AvatarFallback>\n    </Avatar>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/organization/organization-logo.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/organization/organization-row.tsx",
      "content": "\"use client\"\n\nimport type { OrganizationAuthClient } from \"@better-auth-ui/core/plugins/organization\"\nimport { useAuth, useAuthPlugin } from \"@better-auth-ui/react\"\nimport { useSetActiveOrganization } from \"@better-auth-ui/react/plugins/organization\"\nimport type { Organization } from \"better-auth/client\"\nimport { Settings as SettingsIcon } from \"lucide-react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { Item, ItemActions } from \"@/components/ui/item\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { organizationPlugin } from \"@/lib/auth/organization-plugin\"\nimport { OrganizationView } from \"./organization-view\"\n\nexport type OrganizationRowProps = {\n  organization: Organization\n}\n\n/**\n * Single organization row: logo and labels via `OrganizationView`, plus a Manage action.\n */\nexport function OrganizationRow({ organization }: OrganizationRowProps) {\n  const { authClient, basePaths, navigate } = useAuth<OrganizationAuthClient>()\n  const {\n    localization: organizationLocalization,\n    viewPaths: organizationViewPaths,\n    slug,\n    slugPrefix\n  } = useAuthPlugin(organizationPlugin)\n\n  const { mutate: setActiveOrganization, isPending: setActivePending } =\n    useSetActiveOrganization(authClient, {\n      onSuccess: () => {\n        navigate({\n          to: `${basePaths.organization}/${organizationViewPaths.organization.settings}`\n        })\n      }\n    })\n\n  function manageOrganization() {\n    if (slug !== undefined) {\n      navigate({\n        to: `${basePaths.organization}/${slugPrefix}${organization.slug}/${organizationViewPaths.organization.settings}`\n      })\n    } else {\n      setActiveOrganization({ organizationId: organization.id })\n    }\n  }\n\n  return (\n    <Item>\n      <OrganizationView className=\"flex-1\" organization={organization} />\n      <ItemActions>\n        <Button\n          variant=\"outline\"\n          size=\"sm\"\n          disabled={setActivePending}\n          onClick={manageOrganization}\n          aria-label={organizationLocalization.manage}\n        >\n          {setActivePending ? <Spinner /> : <SettingsIcon />}\n\n          {organizationLocalization.manage}\n        </Button>\n      </ItemActions>\n    </Item>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/organization/organization-row.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/organization/organization-view-skeleton.tsx",
      "content": "\"use client\"\n\nimport type { ComponentProps } from \"react\"\n\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { cn } from \"cn\"\nimport {\n  OrganizationLogo,\n  type OrganizationLogoSize\n} from \"./organization-logo\"\n\nexport type OrganizationViewSkeletonProps = {\n  className?: string\n  hideSlug?: boolean\n  size?: OrganizationLogoSize\n}\n\n/**\n * Placeholder matching `OrganizationView` while organization data loads.\n */\nexport function OrganizationViewSkeleton({\n  className,\n  hideSlug,\n  size = \"md\",\n  ...props\n}: OrganizationViewSkeletonProps & ComponentProps<\"div\">) {\n  return (\n    <div\n      className={cn(\"flex min-w-0 items-center gap-2\", className)}\n      {...props}\n    >\n      <OrganizationLogo\n        isPending\n        className={size === \"sm\" ? \"size-5\" : undefined}\n        size={size === \"lg\" ? \"md\" : \"sm\"}\n      />\n\n      <div className=\"flex flex-col min-w-0 gap-1\">\n        <Skeleton className=\"h-3.5 w-20 rounded-md\" />\n\n        {!hideSlug && <Skeleton className=\"h-3 w-28 rounded-md\" />}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/organization/organization-view-skeleton.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/organization/organization-view.tsx",
      "content": "\"use client\"\n\nimport type { OrganizationAuthClient } from \"@better-auth-ui/core/plugins/organization\"\nimport { useAuth, useAuthPlugin, useSession } from \"@better-auth-ui/react\"\nimport {\n  useActiveOrganization,\n  useListOrganizationMembers\n} from \"@better-auth-ui/react/plugins/organization\"\nimport type { Organization } from \"better-auth/client\"\nimport type { ComponentProps } from \"react\"\n\nimport { Badge } from \"@/components/ui/badge\"\nimport { organizationPlugin } from \"@/lib/auth/organization-plugin\"\nimport { cn } from \"cn\"\nimport {\n  OrganizationLogo,\n  type OrganizationLogoSize\n} from \"./organization-logo\"\nimport { OrganizationViewSkeleton } from \"./organization-view-skeleton\"\n\nexport type OrganizationViewProps = {\n  className?: string\n  isPending?: boolean\n  size?: OrganizationLogoSize\n  hideRole?: boolean\n  hideSlug?: boolean\n  organization?: Partial<Organization>\n}\n\n/**\n * Compact organization row: logo, primary name, secondary slug — analogous to `UserView`.\n */\nexport function OrganizationView({\n  className,\n  isPending,\n  size = \"md\",\n  hideSlug: hideSlugProp,\n  hideRole,\n  organization,\n  ...props\n}: OrganizationViewProps & ComponentProps<\"div\">) {\n  const { authClient } = useAuth<OrganizationAuthClient>()\n  const {\n    roles,\n    slugPrefix,\n    hideSlug: pluginHideSlug\n  } = useAuthPlugin(organizationPlugin)\n  const hideSlug = hideSlugProp ?? pluginHideSlug ?? false\n\n  const { data: session } = useSession(authClient)\n\n  const { data: activeOrganization, isPending: activeOrganizationPending } =\n    useActiveOrganization(authClient, {\n      enabled: !organization && !isPending\n    })\n\n  const resolvedOrganization = organization ?? activeOrganization\n\n  const { data: membersList, isPending: membersPending } =\n    useListOrganizationMembers(authClient, {\n      query: {\n        organizationId: resolvedOrganization?.id\n      },\n      enabled: !!resolvedOrganization?.id && !hideRole\n    })\n\n  const membership = membersList?.members?.find(\n    (member) => member.userId === session?.user.id\n  )\n\n  if (\n    isPending ||\n    (!organization && activeOrganizationPending) ||\n    (!hideRole && !!resolvedOrganization?.id && membersPending)\n  ) {\n    return (\n      <OrganizationViewSkeleton\n        className={className}\n        hideSlug={hideSlug}\n        size={size}\n        {...props}\n      />\n    )\n  }\n\n  return (\n    <div\n      className={cn(\"flex min-w-0 items-center gap-2\", className)}\n      {...props}\n    >\n      <OrganizationLogo\n        organization={resolvedOrganization}\n        className={size === \"sm\" ? \"size-5\" : undefined}\n        size={size === \"lg\" ? \"md\" : \"sm\"}\n      />\n\n      <div className=\"flex min-w-0 flex-col\">\n        <div className=\"flex min-w-0 items-center gap-2\">\n          <p className=\"truncate text-sm font-medium leading-tight text-foreground\">\n            {resolvedOrganization?.name}\n          </p>\n\n          {!hideRole && !!membership && (\n            <Badge variant=\"secondary\" className=\"-my-0.5 shrink-0\">\n              {roles?.[membership.role] ?? membership.role}\n            </Badge>\n          )}\n        </div>\n\n        {!hideSlug && !!resolvedOrganization?.slug && (\n          <p className=\"truncate overflow-x-hidden text-muted-foreground text-xs font-mono leading-tight\">\n            {slugPrefix}\n            {resolvedOrganization.slug}\n          </p>\n        )}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/organization/organization-view.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/organization/organizations-empty.tsx",
      "content": "\"use client\"\n\nimport { useAuthPlugin } from \"@better-auth-ui/react\"\nimport { Briefcase } from \"lucide-react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Empty,\n  EmptyContent,\n  EmptyDescription,\n  EmptyHeader,\n  EmptyMedia,\n  EmptyTitle\n} from \"@/components/ui/empty\"\nimport { organizationPlugin } from \"@/lib/auth/organization-plugin\"\n\nexport type OrganizationsEmptyProps = {\n  onCreatePress: () => void\n  canCreate?: boolean\n}\n\nexport function OrganizationsEmpty({\n  onCreatePress,\n  canCreate = true\n}: OrganizationsEmptyProps) {\n  const { localization: organizationLocalization } =\n    useAuthPlugin(organizationPlugin)\n\n  return (\n    <Empty>\n      <EmptyHeader>\n        <EmptyMedia variant=\"icon\">\n          <Briefcase />\n        </EmptyMedia>\n        <EmptyTitle>{organizationLocalization.noOrganizations}</EmptyTitle>\n        <EmptyDescription>\n          {organizationLocalization.organizationsDescription}\n        </EmptyDescription>\n      </EmptyHeader>\n      <EmptyContent>\n        <Button size=\"sm\" disabled={!canCreate} onClick={onCreatePress}>\n          {organizationLocalization.createOrganization}\n        </Button>\n      </EmptyContent>\n    </Empty>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/organization/organizations-empty.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/organization/organizations-settings.tsx",
      "content": "\"use client\"\n\nimport type { ComponentProps } from \"react\"\n\nimport { cn } from \"cn\"\nimport { Organizations } from \"./organizations\"\nimport { UserInvitations } from \"./user-invitations\"\n\nexport type OrganizationsSettingsProps = {\n  className?: string\n}\n\n/**\n * Renders the organizations settings panel.\n *\n * Displays all organizations the user belongs to with an empty state and\n * create button, followed by a card for invitations to the user.\n */\nexport function OrganizationsSettings({\n  className,\n  ...props\n}: OrganizationsSettingsProps & ComponentProps<\"div\">) {\n  return (\n    <div\n      className={cn(\"flex w-full flex-col gap-4 md:gap-6\", className)}\n      {...props}\n    >\n      <Organizations />\n      <UserInvitations />\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/organization/organizations-settings.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/organization/organizations.tsx",
      "content": "\"use client\"\n\nimport type { OrganizationAuthClient } from \"@better-auth-ui/core/plugins/organization\"\nimport { useAuth, useAuthPlugin } from \"@better-auth-ui/react\"\nimport { useListOrganizations } from \"@better-auth-ui/react/plugins/organization\"\nimport { Fragment, useState } from \"react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { Card, CardContent } from \"@/components/ui/card\"\nimport { Item, ItemGroup, ItemSeparator } from \"@/components/ui/item\"\nimport { organizationPlugin } from \"@/lib/auth/organization-plugin\"\nimport { CreateOrganizationDialog } from \"./create-organization-dialog\"\nimport { OrganizationRow } from \"./organization-row\"\nimport { OrganizationViewSkeleton } from \"./organization-view-skeleton\"\nimport { OrganizationsEmpty } from \"./organizations-empty\"\n\nexport type OrganizationsProps = {\n  className?: string\n}\n\n/**\n * Lists organizations the user belongs to (via `useListOrganizations`): loading skeleton,\n * empty state with create, or a card of rows with a Manage control per organization.\n * Owns `CreateOrganizationDialog` open state and the create actions.\n */\nexport function Organizations({ className }: OrganizationsProps) {\n  const { authClient } = useAuth<OrganizationAuthClient>()\n  const {\n    allowOrganizationCreation,\n    localization: organizationLocalization,\n    organizationLimit\n  } = useAuthPlugin(organizationPlugin)\n\n  const [createOpen, setCreateOpen] = useState(false)\n\n  const { data: organizations, isPending: organizationsPending } =\n    useListOrganizations(authClient)\n  const canCreate =\n    allowOrganizationCreation &&\n    (organizationLimit === undefined ||\n      (organizations?.length ?? 0) < organizationLimit)\n\n  return (\n    <>\n      <div className={className}>\n        <div className=\"flex flex-col gap-3\">\n          <div className=\"flex items-end justify-between gap-3\">\n            <h2 className=\"truncate text-sm font-semibold\">\n              {organizationLocalization.organizations}\n            </h2>\n\n            {allowOrganizationCreation && (\n              <Button\n                className=\"shrink-0\"\n                size=\"sm\"\n                disabled={organizationsPending || !canCreate}\n                onClick={() => setCreateOpen(true)}\n              >\n                {organizationLocalization.createOrganization}\n              </Button>\n            )}\n          </div>\n\n          <Card className=\"p-0\">\n            <CardContent className=\"p-0\">\n              {organizationsPending ? (\n                <ItemGroup>\n                  <Item>\n                    <OrganizationViewSkeleton />\n                  </Item>\n                </ItemGroup>\n              ) : !organizations?.length ? (\n                <OrganizationsEmpty\n                  canCreate={canCreate}\n                  onCreatePress={() => setCreateOpen(true)}\n                />\n              ) : (\n                <ItemGroup className=\"gap-0\">\n                  {organizations.map((organization, index) => (\n                    <Fragment key={organization.id}>\n                      {index > 0 && <ItemSeparator />}\n                      <OrganizationRow organization={organization} />\n                    </Fragment>\n                  ))}\n                </ItemGroup>\n              )}\n            </CardContent>\n          </Card>\n        </div>\n      </div>\n\n      {canCreate && (\n        <CreateOrganizationDialog\n          open={createOpen}\n          onOpenChange={setCreateOpen}\n        />\n      )}\n    </>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/organization/organizations.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/organization/slug-field.tsx",
      "content": "\"use client\"\n\nimport type { OrganizationAuthClient } from \"@better-auth-ui/core/plugins/organization\"\nimport { useAuth, useAuthPlugin } from \"@better-auth-ui/react\"\nimport { useCheckSlug } from \"@better-auth-ui/react/plugins/organization\"\nimport { useDebouncer } from \"@tanstack/react-pacer\"\nimport { Check, X } from \"lucide-react\"\nimport { useEffect, useState } from \"react\"\n\nimport { Field, FieldError, FieldLabel } from \"@/components/ui/field\"\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupInput\n} from \"@/components/ui/input-group\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { organizationPlugin } from \"@/lib/auth/organization-plugin\"\n\n/** Props for the `SlugField` component. */\nexport type SlugFieldProps = {\n  value: string\n  onChange: (value: string) => void\n  currentSlug?: string\n  disabled?: boolean\n  id?: string\n}\n\n/**\n * Sanitize a slug value so it only contains lowercase alphanumeric characters\n * and dashes. Runs of disallowed characters are collapsed to a single dash, but\n * leading/trailing dashes are preserved while the user is still typing.\n */\nexport function sanitizeSlug(value: string) {\n  return value.toLowerCase().replace(/[^a-z0-9]+/g, \"-\")\n}\n\n/**\n * Organization slug field with debounced availability checking.\n */\nexport function SlugField({\n  value,\n  onChange,\n  currentSlug,\n  disabled,\n  id = \"slug\"\n}: SlugFieldProps) {\n  const { authClient, localization: authLocalization } =\n    useAuth<OrganizationAuthClient>()\n  const {\n    localization,\n    checkSlug: checkSlugEnabled,\n    slugPrefix\n  } = useAuthPlugin(organizationPlugin)\n\n  const [slugError, setSlugError] = useState<string>()\n\n  const {\n    mutate: checkSlug,\n    data: checkSlugData,\n    error: checkSlugError,\n    reset: resetCheckSlug\n  } = useCheckSlug(authClient)\n\n  const debouncer = useDebouncer(\n    (next: string) => {\n      if (!checkSlugEnabled || !next.trim() || next.trim() === currentSlug)\n        return\n\n      checkSlug({ slug: next.trim() })\n    },\n    { wait: 500 }\n  )\n\n  useEffect(() => {\n    // Clear stale validation errors when the controlled value changes\n    // externally (e.g. the parent resets the form), not just via this\n    // input's onChange.\n    setSlugError(undefined)\n\n    if (!checkSlugEnabled) return\n\n    resetCheckSlug()\n    debouncer.maybeExecute(value)\n  }, [checkSlugEnabled, value, debouncer.maybeExecute, resetCheckSlug])\n\n  return (\n    <Field data-invalid={!!slugError}>\n      <FieldLabel htmlFor={id}>{localization.slug}</FieldLabel>\n\n      <InputGroup>\n        {slugPrefix && (\n          <InputGroupAddon align=\"inline-start\">{slugPrefix}</InputGroupAddon>\n        )}\n\n        <InputGroupInput\n          id={id}\n          name=\"slug\"\n          value={value}\n          onChange={(e) => {\n            onChange(sanitizeSlug(e.target.value))\n            setSlugError(undefined)\n          }}\n          onInvalid={(e) => {\n            e.preventDefault()\n            setSlugError(authLocalization.auth.fieldRequired)\n          }}\n          aria-invalid={!!slugError}\n          placeholder={localization.slugPlaceholder}\n          required\n          disabled={disabled}\n        />\n\n        {checkSlugEnabled && !!value.trim() && value.trim() !== currentSlug && (\n          <InputGroupAddon align=\"inline-end\">\n            {checkSlugData?.status ? (\n              <Check className=\"size-4 text-foreground\" />\n            ) : checkSlugError ? (\n              <X className=\"size-4 text-destructive\" />\n            ) : (\n              <Spinner />\n            )}\n          </InputGroupAddon>\n        )}\n      </InputGroup>\n\n      <FieldError>{slugError}</FieldError>\n    </Field>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/organization/slug-field.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/organization/user-invitation-row-skeleton.tsx",
      "content": "\"use client\"\n\nimport { Item, ItemContent, ItemMedia } from \"@/components/ui/item\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\n\n/**\n * Placeholder row matching `UserInvitationRow` while invitations load.\n */\nexport function UserInvitationRowSkeleton() {\n  return (\n    <Item>\n      <ItemMedia>\n        <Skeleton className=\"size-10 shrink-0 rounded-md\" />\n      </ItemMedia>\n      <ItemContent>\n        <Skeleton className=\"h-4 w-40 rounded-md\" />\n        <Skeleton className=\"h-3 w-28 rounded-md\" />\n      </ItemContent>\n    </Item>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/organization/user-invitation-row-skeleton.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/organization/user-invitation-row.tsx",
      "content": "\"use client\"\n\nimport type { OrganizationAuthClient } from \"@better-auth-ui/core/plugins/organization\"\nimport { useAuth, useAuthPlugin } from \"@better-auth-ui/react\"\nimport {\n  useAcceptInvitation,\n  useRejectInvitation\n} from \"@better-auth-ui/react/plugins/organization\"\nimport type { Invitation } from \"better-auth/client\"\nimport { Check, Clock, X } from \"lucide-react\"\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\"\nimport { organizationPlugin } from \"@/lib/auth/organization-plugin\"\n\nexport type UserInvitationRowProps = {\n  invitation: Invitation & { organizationName?: string }\n}\n\n/**\n * Single invitation row with accept/reject actions for the current user.\n */\nexport function UserInvitationRow({ invitation }: UserInvitationRowProps) {\n  const { authClient } = useAuth<OrganizationAuthClient>()\n  const { localization: organizationLocalization, roles } =\n    useAuthPlugin(organizationPlugin)\n\n  const { mutate: acceptInvitation, isPending: isAccepting } =\n    useAcceptInvitation(authClient)\n\n  const { mutate: rejectInvitation, isPending: isRejecting } =\n    useRejectInvitation(authClient)\n\n  return (\n    <Item>\n      <ItemMedia variant=\"icon\">\n        <Clock />\n      </ItemMedia>\n      <ItemContent>\n        <ItemTitle>\n          {invitation.organizationName}\n          <Badge variant=\"secondary\">\n            {roles?.[invitation.role] ?? invitation.role}\n          </Badge>\n        </ItemTitle>\n        <ItemDescription>\n          {new Date(invitation.createdAt).toLocaleString(undefined, {\n            dateStyle: \"medium\",\n            timeStyle: \"short\"\n          })}\n        </ItemDescription>\n      </ItemContent>\n      <ItemActions>\n        <Button\n          variant=\"outline\"\n          size=\"sm\"\n          disabled={isAccepting || isRejecting}\n          onClick={() => acceptInvitation({ invitationId: invitation.id })}\n        >\n          {isAccepting ? <Spinner /> : <Check />}\n\n          {organizationLocalization.accept}\n        </Button>\n\n        <Button\n          variant=\"outline\"\n          size=\"icon\"\n          className=\"size-8 text-destructive\"\n          disabled={isAccepting || isRejecting}\n          onClick={() => rejectInvitation({ invitationId: invitation.id })}\n          aria-label={organizationLocalization.rejectInvitation}\n        >\n          {isRejecting ? <Spinner /> : <X />}\n        </Button>\n      </ItemActions>\n    </Item>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/organization/user-invitation-row.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/organization/user-invitations-empty.tsx",
      "content": "\"use client\"\n\nimport { useAuthPlugin } from \"@better-auth-ui/react\"\nimport { MailWarning, Send } from \"lucide-react\"\n\nimport {\n  Empty,\n  EmptyDescription,\n  EmptyHeader,\n  EmptyMedia,\n  EmptyTitle\n} from \"@/components/ui/empty\"\nimport { organizationPlugin } from \"@/lib/auth/organization-plugin\"\n\n/**\n * Empty state for `UserInvitations`.\n */\nexport function UserInvitationsEmpty({\n  verificationRequired = false\n}: {\n  verificationRequired?: boolean\n}) {\n  const { localization: organizationLocalization } =\n    useAuthPlugin(organizationPlugin)\n\n  return (\n    <Empty>\n      <EmptyHeader>\n        <EmptyMedia variant=\"icon\">\n          {verificationRequired ? <MailWarning /> : <Send />}\n        </EmptyMedia>\n        <EmptyTitle>\n          {verificationRequired\n            ? organizationLocalization.verifyEmailToViewInvitations\n            : organizationLocalization.noInvitations}\n        </EmptyTitle>\n        <EmptyDescription>\n          {verificationRequired\n            ? organizationLocalization.verifyEmailToViewInvitationsDescription\n            : organizationLocalization.userInvitationsEmptyDescription}\n        </EmptyDescription>\n      </EmptyHeader>\n    </Empty>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/organization/user-invitations-empty.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/organization/user-invitations.tsx",
      "content": "\"use client\"\n\nimport type { OrganizationAuthClient } from \"@better-auth-ui/core/plugins/organization\"\nimport { useAuth, useAuthPlugin, useSession } from \"@better-auth-ui/react\"\nimport { useListUserInvitations } from \"@better-auth-ui/react/plugins/organization\"\nimport { Fragment } from \"react\"\n\nimport { Card, CardContent } from \"@/components/ui/card\"\nimport { ItemGroup, ItemSeparator } from \"@/components/ui/item\"\nimport { organizationPlugin } from \"@/lib/auth/organization-plugin\"\nimport { UserInvitationRow } from \"./user-invitation-row\"\nimport { UserInvitationRowSkeleton } from \"./user-invitation-row-skeleton\"\nimport { UserInvitationsEmpty } from \"./user-invitations-empty\"\n\nexport type UserInvitationsProps = {\n  className?: string\n}\n\n/**\n * Organization invitations for the signed-in user. Always renders the section\n * card; uses `UserInvitationsEmpty` when there are no pending invitations.\n */\nexport function UserInvitations({ className }: UserInvitationsProps) {\n  const { authClient } = useAuth<OrganizationAuthClient>()\n  const { localization: organizationLocalization } =\n    useAuthPlugin(organizationPlugin)\n  const session = useSession(authClient)\n  const emailVerified = session.data?.user.emailVerified === true\n\n  const { data: invitations, isPending } = useListUserInvitations(authClient, {\n    enabled: emailVerified\n  })\n\n  return (\n    <div className={className}>\n      <div className=\"flex flex-col gap-3\">\n        <h2 className=\"truncate text-sm font-semibold\">\n          {organizationLocalization.invitations}\n        </h2>\n\n        <Card className=\"p-0\">\n          <CardContent className=\"p-0\">\n            {session.isPending || (emailVerified && isPending) ? (\n              <ItemGroup>\n                <UserInvitationRowSkeleton />\n              </ItemGroup>\n            ) : !invitations?.length ? (\n              <UserInvitationsEmpty verificationRequired={!emailVerified} />\n            ) : (\n              <ItemGroup className=\"gap-0\">\n                {invitations.map((invitation, index) => (\n                  <Fragment key={invitation.id}>\n                    {index > 0 && <ItemSeparator />}\n                    <UserInvitationRow invitation={invitation} />\n                  </Fragment>\n                ))}\n              </ItemGroup>\n            )}\n          </CardContent>\n        </Card>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/organization/user-invitations.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/server-table-state.ts",
      "content": "\"use client\"\n\nimport { useCreateAtom, useSelector } from \"@tanstack/react-store\"\nimport {\n  type ColumnFiltersState,\n  functionalUpdate,\n  type PaginationState,\n  type SortingState,\n  type Updater\n} from \"@tanstack/react-table\"\nimport { useCallback, useEffect, useMemo } from \"react\"\n\nexport function useServerTableState({\n  initialSorting = [],\n  pageSize\n}: {\n  initialSorting?: SortingState\n  pageSize: number\n}) {\n  const columnFiltersAtom = useCreateAtom<ColumnFiltersState>([])\n  const globalFilterAtom = useCreateAtom(\"\")\n  const paginationAtom = useCreateAtom<PaginationState>({\n    pageIndex: 0,\n    pageSize\n  })\n  const sortingAtom = useCreateAtom<SortingState>(initialSorting)\n  const columnFilters = useSelector(columnFiltersAtom)\n  const globalFilter = useSelector(globalFilterAtom)\n  const pagination = useSelector(paginationAtom)\n  const sorting = useSelector(sortingAtom)\n  const atoms = useMemo(\n    () => ({\n      columnFilters: columnFiltersAtom,\n      globalFilter: globalFilterAtom,\n      pagination: paginationAtom,\n      sorting: sortingAtom\n    }),\n    [columnFiltersAtom, globalFilterAtom, paginationAtom, sortingAtom]\n  )\n\n  useEffect(() => {\n    const resetPage = () => {\n      const current = paginationAtom.get()\n      if (current.pageIndex !== 0) {\n        paginationAtom.set({ ...current, pageIndex: 0 })\n      }\n    }\n    const subscriptions = [\n      columnFiltersAtom.subscribe(resetPage),\n      globalFilterAtom.subscribe(resetPage),\n      sortingAtom.subscribe(resetPage)\n    ]\n\n    return () => {\n      for (const subscription of subscriptions) subscription.unsubscribe()\n    }\n  }, [columnFiltersAtom, globalFilterAtom, paginationAtom, sortingAtom])\n\n  const setPagination = useCallback(\n    (updater: Updater<PaginationState>) => {\n      paginationAtom.set((current) => functionalUpdate(updater, current))\n    },\n    [paginationAtom]\n  )\n\n  return {\n    atoms,\n    columnFilters,\n    globalFilter,\n    pagination,\n    setPagination,\n    sorting\n  }\n}\n",
      "type": "registry:component",
      "target": "@components/auth/server-table-state.ts"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/use-is-hydrated.ts",
      "content": "\"use client\"\n\nimport { useSyncExternalStore } from \"react\"\n\n/**\n * Returns `true` once the component is mounted on the client (hydrated) and\n * `false` while rendering on the server, so client-only reads (e.g.\n * `sessionStorage`) stay safe during SSR.\n *\n * @returns Whether the component has hydrated on the client.\n */\nexport function useIsHydrated() {\n  const subscribe = () => () => {}\n  return useSyncExternalStore(\n    subscribe,\n    () => true,\n    () => false\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/use-is-hydrated.ts"
    },
    {
      "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/api-key-plugin.ts",
      "content": "import { createAuthPlugin } from \"@better-auth-ui/core\"\nimport {\n  type ApiKeyPluginOptions,\n  apiKeyPlugin as coreApiKeyPlugin\n} from \"@better-auth-ui/core/plugins/api-key\"\n\nimport { ApiKeys } from \"@/components/auth/api-key/api-keys\"\nimport { OrganizationApiKeys } from \"@/components/auth/api-key/organization-api-keys\"\n\nexport const apiKeyPlugin = createAuthPlugin(\n  coreApiKeyPlugin.id,\n  (options: ApiKeyPluginOptions = {}) => {\n    const core = coreApiKeyPlugin(options)\n\n    return {\n      ...core,\n      securityCards: [ApiKeys],\n      ...(core.organization ? { organizationCards: [OrganizationApiKeys] } : {})\n    }\n  }\n)\n",
      "type": "registry:lib",
      "target": "@lib/auth/api-key-plugin.ts"
    },
    {
      "path": "examples/start-shadcn-example/src/lib/auth/auth-plugin.ts",
      "content": "import type {\n  AuthPluginComponents,\n  AuthPlugin as AuthPluginPrimitive\n} from \"@better-auth-ui/react\"\n\ndeclare module \"@better-auth-ui/core\" {\n  /** Widens `useAuth().plugins` to the shadcn-typed `AuthPlugin`. */\n  interface AuthPluginRegister {\n    shadcn: AuthPlugin\n  }\n}\n\n/** Props the shadcn `<Auth>` router spreads onto plugin-contributed auth views. */\nexport type AuthViewProps = {\n  className?: string\n  socialLayout?: \"auto\" | \"horizontal\" | \"vertical\" | \"grid\"\n  socialPosition?: \"top\" | \"bottom\"\n}\n\n/** Props the shadcn `<Settings>` router spreads onto plugin-contributed settings views. */\nexport type SettingsViewProps = {\n  className?: string\n}\n\n/** Shadcn plugin type. Plugin authors import this from `@/lib/auth/auth-plugin`. */\nexport type AuthPlugin = AuthPluginPrimitive<\n  AuthPluginComponents,\n  AuthViewProps,\n  SettingsViewProps\n>\n",
      "type": "registry:lib",
      "target": "@lib/auth/auth-plugin.ts"
    },
    {
      "path": "examples/start-shadcn-example/src/lib/auth/organization-plugin.tsx",
      "content": "import { createAuthPlugin } from \"@better-auth-ui/core\"\nimport {\n  organizationPlugin as coreOrganizationPlugin,\n  type OrganizationLocalization,\n  type OrganizationPluginOptions\n} from \"@better-auth-ui/core/plugins/organization\"\nimport { Briefcase } from \"lucide-react\"\n\nimport { AcceptInvitation } from \"@/components/auth/organization/accept-invitation\"\nimport { OrganizationsSettings } from \"@/components/auth/organization/organizations-settings\"\n\nexport const organizationPlugin = createAuthPlugin(\n  coreOrganizationPlugin.id,\n  (options: OrganizationPluginOptions = {}) => {\n    const core = coreOrganizationPlugin(options)\n\n    return {\n      ...core,\n      localization: core.localization as OrganizationLocalization,\n      views: {\n        auth: { acceptInvitation: AcceptInvitation }\n      },\n      settingsTabs: [\n        {\n          view: \"organizations\",\n          label: (\n            <>\n              <Briefcase className=\"text-muted-foreground\" />\n              {core.localization.organizations}\n            </>\n          ),\n          component: OrganizationsSettings\n        }\n      ]\n    }\n  }\n)\n",
      "type": "registry:lib",
      "target": "@lib/auth/organization-plugin.tsx"
    }
  ],
  "type": "registry:component"
}