{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sso",
  "title": "SSO and Email-First Sign In",
  "description": "Discovers organization SSO from an email address and manages organization identity providers and domain verification.",
  "dependencies": [
    "@better-auth-ui/core@latest",
    "@better-auth-ui/react@latest",
    "@tanstack/react-form",
    "@tanstack/react-pacer",
    "@tanstack/react-query",
    "better-auth",
    "cn",
    "date-fns",
    "lucide-react"
  ],
  "registryDependencies": [
    "alert",
    "alert-dialog",
    "avatar",
    "badge",
    "button",
    "calendar",
    "card",
    "checkbox",
    "combobox",
    "dialog",
    "empty",
    "field",
    "https://better-auth-ui.com/r/radix-nova/sign-in.json",
    "input",
    "input-group",
    "item",
    "popover",
    "select",
    "skeleton",
    "slider",
    "sonner",
    "spinner",
    "switch",
    "tabs",
    "textarea"
  ],
  "files": [
    {
      "path": "examples/start-shadcn-example/src/components/auth/additional-field.tsx",
      "content": "\"use client\"\n\nimport {\n  type AdditionalField as AdditionalFieldConfig,\n  type AdditionalFieldFormValue,\n  getFormFieldErrors,\n  resolveInputType\n} from \"@better-auth-ui/core\"\nimport { useAuth, useCopyToClipboard } from \"@better-auth-ui/react\"\nimport { format } from \"date-fns\"\nimport { CalendarIcon, Check, ChevronDownIcon, Copy } from \"lucide-react\"\nimport { type ComponentType, useRef, useState } from \"react\"\nimport { toast } from \"sonner\"\n\nimport { buttonVariants } from \"@/components/ui/button\"\nimport { Calendar } from \"@/components/ui/calendar\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport {\n  Combobox,\n  ComboboxContent,\n  ComboboxEmpty,\n  ComboboxInput,\n  ComboboxItem,\n  ComboboxList\n} from \"@/components/ui/combobox\"\nimport {\n  Field,\n  FieldContent,\n  FieldError,\n  FieldLabel\n} from \"@/components/ui/field\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupInput\n} from \"@/components/ui/input-group\"\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger\n} from \"@/components/ui/popover\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue\n} from \"@/components/ui/select\"\nimport { Slider } from \"@/components/ui/slider\"\nimport { Switch } from \"@/components/ui/switch\"\nimport { Textarea } from \"@/components/ui/textarea\"\nimport { cn } from \"cn\"\n\nexport type AdditionalFieldProps = {\n  name: string\n  field: AdditionalFieldConfig\n  value: AdditionalFieldFormValue\n  onBlur: () => void\n  onChange: (value: AdditionalFieldFormValue) => void\n  isInvalid?: boolean\n  errors?: unknown[]\n  isPending?: boolean\n  /** Complete suffix appended to labels for fields that are not required. */\n  optionalLabel?: string\n}\n\nfunction valueToString(value: AdditionalFieldFormValue) {\n  if (value == null) return \"\"\n  return value instanceof Date ? value.toISOString() : String(value)\n}\n\n/** Convert a `defaultValue` into a `Date` for the calendar. */\nfunction toDate(value: unknown): Date | undefined {\n  if (value instanceof Date) return value\n  if (typeof value === \"string\") {\n    const parsed = new Date(value)\n    return Number.isNaN(parsed.getTime()) ? undefined : parsed\n  }\n  return undefined\n}\n\n/** Format a Date as `HH:mm:ss` for an `<input type=\"time\">`. */\nfunction formatTime(date: Date) {\n  const pad = (n: number) => n.toString().padStart(2, \"0\")\n  return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`\n}\n\n/**\n * Icon-only copy button used as an `InputGroupAddon`. `getValue` is invoked\n * lazily on click so the button copies the input's *live* value rather than a\n * stale snapshot — important when paired with editable inputs.\n */\nfunction CopyButton({\n  getValue,\n  isDisabled\n}: {\n  getValue: () => string | undefined\n  isDisabled?: boolean\n}) {\n  const { localization } = useAuth()\n  const { copied, copy } = useCopyToClipboard({\n    onError: (error) => {\n      console.error(\"[Better Auth UI] Copy failed\", error)\n      toast.error(localization.errors.copyFailed)\n    }\n  })\n\n  async function handleCopy() {\n    const value = getValue()\n    if (!value) return\n\n    await copy(value)\n  }\n\n  return (\n    <InputGroupButton\n      aria-label={\n        copied\n          ? localization.settings.copiedToClipboard\n          : localization.settings.copyToClipboard\n      }\n      title={\n        copied\n          ? localization.settings.copiedToClipboard\n          : localization.settings.copyToClipboard\n      }\n      onClick={handleCopy}\n      disabled={isDisabled}\n    >\n      {copied ? <Check /> : <Copy />}\n    </InputGroupButton>\n  )\n}\n\n/** Renders a single additional user field via shadcn primitives. */\nexport function AdditionalField({\n  name,\n  field: configuredField,\n  value,\n  onBlur,\n  onChange,\n  isInvalid,\n  errors,\n  isPending,\n  optionalLabel\n}: AdditionalFieldProps) {\n  const field =\n    optionalLabel && !configuredField.required\n      ? {\n          ...configuredField,\n          label: (\n            <>\n              {configuredField.label}\n              {optionalLabel}\n            </>\n          )\n        }\n      : configuredField\n  const inputType = resolveInputType(field)\n  const fieldErrors = getFormFieldErrors(errors ?? [])\n\n  if (field.render) {\n    const FieldRenderer = field.render as ComponentType<AdditionalFieldProps>\n    return (\n      <FieldRenderer\n        name={name}\n        field={field}\n        value={value}\n        onBlur={onBlur}\n        onChange={onChange}\n        isInvalid={isInvalid}\n        errors={errors}\n        isPending={isPending}\n        optionalLabel={optionalLabel}\n      />\n    )\n  }\n\n  if (inputType === \"hidden\") {\n    return (\n      <input type=\"hidden\" name={name} value={valueToString(value)} readOnly />\n    )\n  }\n\n  if (inputType === \"textarea\") {\n    return (\n      <Field data-invalid={isInvalid}>\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n        <Textarea\n          id={name}\n          name={name}\n          value={valueToString(value)}\n          onBlur={onBlur}\n          onChange={(event) => onChange(event.target.value || null)}\n          placeholder={field.placeholder}\n          required={field.required}\n          readOnly={field.readOnly}\n          disabled={isPending}\n          aria-invalid={isInvalid}\n        />\n\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"number\") {\n    const maxFractionDigits = field.formatOptions?.maximumFractionDigits\n\n    return (\n      <Field data-invalid={isInvalid}>\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n        <Input\n          id={name}\n          name={name}\n          type=\"number\"\n          inputMode={maxFractionDigits ? \"decimal\" : \"numeric\"}\n          min={field.min}\n          max={field.max}\n          step={\n            field.step ??\n            (maxFractionDigits ? 1 / 10 ** maxFractionDigits : undefined)\n          }\n          value={typeof value === \"number\" ? value : \"\"}\n          onBlur={onBlur}\n          onChange={(event) =>\n            onChange(\n              event.target.value === \"\" ? null : event.target.valueAsNumber\n            )\n          }\n          placeholder={field.placeholder}\n          required={field.required}\n          readOnly={field.readOnly}\n          disabled={isPending}\n          aria-invalid={isInvalid}\n        />\n\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"slider\") {\n    return (\n      <SliderField\n        name={name}\n        field={field}\n        value={value}\n        onBlur={onBlur}\n        onChange={onChange}\n        isInvalid={isInvalid}\n        errors={errors}\n        isPending={isPending}\n      />\n    )\n  }\n\n  if (inputType === \"switch\") {\n    return (\n      <Field data-invalid={isInvalid} orientation=\"horizontal\">\n        <Switch\n          id={name}\n          name={name}\n          checked={value === true}\n          onBlur={onBlur}\n          onCheckedChange={onChange}\n          disabled={isPending || field.readOnly}\n          aria-invalid={isInvalid}\n        />\n\n        <FieldContent>\n          <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n        </FieldContent>\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"checkbox\") {\n    return (\n      <Field data-invalid={isInvalid} orientation=\"horizontal\">\n        <Checkbox\n          id={name}\n          name={name}\n          checked={value === true}\n          onBlur={onBlur}\n          onCheckedChange={(checked) => onChange(checked === true)}\n          required={field.required}\n          disabled={isPending || field.readOnly}\n          aria-invalid={isInvalid}\n        />\n\n        <FieldContent>\n          <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n        </FieldContent>\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"select\") {\n    return (\n      <Field data-invalid={isInvalid}>\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n        <Select\n          name={name}\n          value={valueToString(value) || undefined}\n          onValueChange={(nextValue) => onChange(nextValue)}\n          required={field.required}\n          disabled={isPending || field.readOnly}\n        >\n          <SelectTrigger\n            id={name}\n            className=\"w-full\"\n            onBlur={onBlur}\n            aria-invalid={isInvalid}\n          >\n            <SelectValue placeholder={field.placeholder} />\n          </SelectTrigger>\n\n          <SelectContent>\n            {field.options?.map((option) => (\n              <SelectItem key={option.value} value={option.value}>\n                {option.label}\n              </SelectItem>\n            ))}\n          </SelectContent>\n        </Select>\n\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"combobox\") {\n    const selectedOption = field.options?.find(\n      (option) => option.value === valueToString(value)\n    )\n\n    return (\n      <Field data-invalid={isInvalid}>\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n        <Combobox\n          items={field.options ?? []}\n          name={name}\n          value={selectedOption ?? null}\n          onValueChange={(option) => onChange(option?.value ?? null)}\n          required={field.required}\n          disabled={isPending || field.readOnly}\n        >\n          <ComboboxInput\n            placeholder={field.placeholder}\n            id={name}\n            onBlur={onBlur}\n            aria-invalid={isInvalid}\n          />\n\n          <ComboboxContent>\n            <ComboboxEmpty>No items found.</ComboboxEmpty>\n\n            <ComboboxList>\n              {(option) => (\n                <ComboboxItem key={option.value} value={option}>\n                  {option.label}\n                </ComboboxItem>\n              )}\n            </ComboboxList>\n          </ComboboxContent>\n        </Combobox>\n\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"date\" || inputType === \"datetime\") {\n    return (\n      <DateInput\n        name={name}\n        field={field}\n        value={value}\n        onBlur={onBlur}\n        onChange={onChange}\n        isInvalid={isInvalid}\n        errors={errors}\n        isPending={isPending}\n      />\n    )\n  }\n\n  return (\n    <InputField\n      name={name}\n      field={field}\n      value={value}\n      onBlur={onBlur}\n      onChange={onChange}\n      isInvalid={isInvalid}\n      errors={errors}\n      isPending={isPending}\n    />\n  )\n}\n\nfunction InputField({\n  name,\n  field,\n  value,\n  onBlur,\n  onChange,\n  isInvalid,\n  errors,\n  isPending\n}: AdditionalFieldProps) {\n  const inputRef = useRef<HTMLInputElement>(null)\n  const fieldErrors = getFormFieldErrors(errors ?? [])\n\n  const hasPrefix = field.prefix != null\n  const hasSuffix = field.suffix != null || field.copyable\n\n  const isNumeric = field.type === \"number\"\n  const maxFractionDigits = field.formatOptions?.maximumFractionDigits\n  const nativeInputType = isNumeric ? \"number\" : undefined\n  const nativeInputMode = isNumeric\n    ? maxFractionDigits\n      ? \"decimal\"\n      : \"numeric\"\n    : undefined\n  const nativeStep = maxFractionDigits ? 1 / 10 ** maxFractionDigits : undefined\n\n  if (hasPrefix || hasSuffix) {\n    return (\n      <Field data-invalid={isInvalid}>\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n        <InputGroup>\n          {hasPrefix && (\n            <InputGroupAddon align=\"inline-start\">\n              {field.prefix}\n            </InputGroupAddon>\n          )}\n\n          <InputGroupInput\n            ref={inputRef}\n            id={name}\n            name={name}\n            type={nativeInputType}\n            inputMode={nativeInputMode}\n            step={nativeStep}\n            value={valueToString(value)}\n            onBlur={onBlur}\n            onChange={(event) => onChange(event.target.value || null)}\n            placeholder={field.placeholder}\n            required={field.required}\n            readOnly={field.readOnly}\n            disabled={isPending}\n            aria-invalid={isInvalid}\n          />\n\n          {field.copyable ? (\n            <InputGroupAddon align=\"inline-end\">\n              <CopyButton\n                getValue={() => inputRef.current?.value}\n                isDisabled={isPending}\n              />\n            </InputGroupAddon>\n          ) : (\n            field.suffix != null && (\n              <InputGroupAddon align=\"inline-end\">\n                {field.suffix}\n              </InputGroupAddon>\n            )\n          )}\n        </InputGroup>\n\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  return (\n    <Field data-invalid={isInvalid}>\n      <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n      <Input\n        id={name}\n        name={name}\n        type={nativeInputType}\n        inputMode={nativeInputMode}\n        step={nativeStep}\n        value={valueToString(value)}\n        onBlur={onBlur}\n        onChange={(event) => onChange(event.target.value || null)}\n        placeholder={field.placeholder}\n        required={field.required}\n        readOnly={field.readOnly}\n        disabled={isPending}\n        aria-invalid={isInvalid}\n      />\n\n      <FieldError errors={fieldErrors} />\n    </Field>\n  )\n}\n\n/**\n * Slider field. Radix Slider doesn't render the current value, so we render\n * it next to the label and control the state to keep the displayed value in\n * sync. The selected value is submitted via the underlying Radix `name` prop.\n */\nfunction SliderField({\n  name,\n  field,\n  value,\n  onBlur,\n  onChange,\n  isInvalid,\n  errors,\n  isPending\n}: AdditionalFieldProps) {\n  const maxFractionDigits = field.formatOptions?.maximumFractionDigits\n  const min = field.min ?? 0\n  const max = field.max ?? 100\n  const step =\n    field.step ?? (maxFractionDigits ? 1 / 10 ** maxFractionDigits : 1)\n  const numericValue = typeof value === \"number\" ? value : min\n  const fieldErrors = getFormFieldErrors(errors ?? [])\n\n  const formatter = new Intl.NumberFormat(undefined, field.formatOptions)\n\n  return (\n    <Field data-invalid={isInvalid}>\n      <div className=\"flex items-center justify-between gap-2\">\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n        <span className=\"text-sm text-muted-foreground tabular-nums\">\n          {formatter.format(numericValue)}\n        </span>\n      </div>\n\n      <Slider\n        id={name}\n        name={name}\n        value={[numericValue]}\n        onBlur={onBlur}\n        onValueChange={(nextValue) =>\n          onChange((Array.isArray(nextValue) ? nextValue[0] : nextValue) ?? min)\n        }\n        min={min}\n        max={max}\n        step={step}\n        disabled={isPending || field.readOnly}\n        aria-invalid={isInvalid}\n      />\n\n      <FieldError errors={fieldErrors} />\n    </Field>\n  )\n}\n\n/**\n * Date / datetime input. Composes `Popover` + `Calendar` for the date and\n * (optionally) `<input type=\"time\">` for the time. Submits the combined ISO\n * value via a hidden `<input>` so it shows up in `FormData`.\n */\nfunction DateInput({\n  name,\n  field,\n  value,\n  onBlur,\n  onChange,\n  isInvalid,\n  errors,\n  isPending\n}: AdditionalFieldProps) {\n  const { localization } = useAuth()\n  const inputType = resolveInputType(field)\n  const isDateTime = inputType === \"datetime\"\n  const fieldErrors = getFormFieldErrors(errors ?? [])\n\n  const date = toDate(value)\n  const [time, setTime] = useState<string>(\n    isDateTime && date ? formatTime(date) : \"\"\n  )\n  const [open, setOpen] = useState(false)\n\n  // Compose the hidden form value: ISO date for \"date\", ISO datetime for\n  // \"datetime\" (date + time).\n  return (\n    <Field data-invalid={isInvalid}>\n      <FieldLabel htmlFor={`${name}-date`}>{field.label}</FieldLabel>\n\n      <div className=\"relative flex gap-2\">\n        <Popover open={open} onOpenChange={setOpen}>\n          <PopoverTrigger\n            type=\"button\"\n            id={`${name}-date`}\n            data-empty={!date}\n            aria-invalid={isInvalid}\n            aria-required={field.required}\n            onBlur={onBlur}\n            disabled={isPending || field.readOnly}\n            className={cn(\n              buttonVariants({ variant: \"outline\" }),\n              \"flex-1 justify-between font-normal\",\n              \"data-[empty=true]:text-muted-foreground\"\n            )}\n          >\n            {date ? format(date, \"PPP\") : <span>{field.placeholder}</span>}\n\n            {isDateTime ? <ChevronDownIcon /> : <CalendarIcon />}\n          </PopoverTrigger>\n\n          <PopoverContent className=\"w-auto overflow-hidden p-0\" align=\"start\">\n            <Calendar\n              mode=\"single\"\n              selected={date}\n              defaultMonth={date}\n              captionLayout=\"dropdown\"\n              onSelect={(value) => {\n                if (!value) {\n                  onChange(null)\n                } else {\n                  const nextValue = new Date(value)\n                  if (isDateTime && time.trim()) {\n                    const [hours = \"0\", minutes = \"0\", seconds = \"0\"] =\n                      time.split(\":\")\n                    nextValue.setHours(\n                      Number(hours),\n                      Number(minutes),\n                      Number(seconds),\n                      0\n                    )\n                  } else {\n                    nextValue.setHours(0, 0, 0, 0)\n                  }\n                  onChange(nextValue)\n                }\n                if (!isDateTime) setOpen(false)\n              }}\n            />\n          </PopoverContent>\n        </Popover>\n\n        {isDateTime && (\n          <Field className=\"w-32\">\n            <FieldLabel htmlFor={`${name}-time`} className=\"sr-only\">\n              {localization.settings.time}\n            </FieldLabel>\n\n            <Input\n              type=\"time\"\n              id={`${name}-time`}\n              step=\"1\"\n              value={time}\n              onChange={(event) => {\n                const nextTime = event.target.value\n                setTime(nextTime)\n                if (!date) return\n                const nextValue = new Date(date)\n                const [hours = \"0\", minutes = \"0\", seconds = \"0\"] =\n                  nextTime.split(\":\")\n                nextValue.setHours(\n                  Number(hours),\n                  Number(minutes),\n                  Number(seconds),\n                  0\n                )\n                onChange(nextValue)\n              }}\n              disabled={isPending || field.readOnly}\n              className=\"appearance-none bg-background [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none\"\n            />\n          </Field>\n        )}\n      </div>\n\n      <FieldError errors={fieldErrors} />\n    </Field>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/additional-field.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/auth-form.tsx",
      "content": "\"use client\"\n\nimport {\n  type AdditionalField as AdditionalFieldConfig,\n  type AdditionalFieldFormValue,\n  DEFAULT_ADDITIONAL_FIELD_VALIDATION_DEBOUNCE_MS,\n  getFormFieldErrors,\n  normalizeAuthFormServerError,\n  validateAdditionalFieldRequired,\n  validateAdditionalFieldValue\n} from \"@better-auth-ui/core\"\nimport {\n  type AnyFormApi,\n  createFormHook,\n  createFormHookContexts\n} from \"@tanstack/react-form\"\nimport {\n  type ComponentProps,\n  type FormEvent,\n  type ReactNode,\n  useRef\n} from \"react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Field,\n  FieldDescription,\n  FieldError,\n  FieldLabel\n} from \"@/components/ui/field\"\nimport { Input } from \"@/components/ui/input\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { AdditionalField, type AdditionalFieldProps } from \"./additional-field\"\n\nconst { fieldContext, formContext, useFieldContext, useFormContext } =\n  createFormHookContexts()\n\nconst DEFAULT_AUTH_FORM_SERVER_ERROR = \"Unable to submit this form. Try again.\"\n\nexport function focusFirstInvalidAuthFormControl(form: HTMLFormElement) {\n  requestAnimationFrame(() => {\n    form\n      .querySelector<HTMLElement>(\n        '[aria-invalid=\"true\"]:not([disabled]), :invalid:not([disabled])'\n      )\n      ?.focus()\n  })\n}\n\nfunction AuthFormFieldError() {\n  const field = useFieldContext<unknown>()\n  const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid\n\n  if (!isInvalid) return null\n\n  const errors = getFormFieldErrors(field.state.meta.errors)\n\n  return errors.length > 0 ? <FieldError errors={errors} /> : null\n}\n\nfunction AuthFormServerError() {\n  const form = useFormContext()\n\n  return (\n    <form.Subscribe selector={(state) => state.errorMap.onServer}>\n      {(error) => {\n        const formError =\n          error && typeof error === \"object\" && \"form\" in error\n            ? error.form\n            : error\n        const errors = getFormFieldErrors(formError ? [formError] : [])\n        return errors.length > 0 ? <FieldError errors={errors} /> : null\n      }}\n    </form.Subscribe>\n  )\n}\n\nexport function setAuthFormServerError(\n  form: AnyFormApi,\n  error: unknown,\n  fallbackMessage: string\n) {\n  const normalized = normalizeAuthFormServerError(error, fallbackMessage)\n  form.setErrorMap({\n    onServer: {\n      fields: normalized.fields ?? {},\n      form: normalized.form\n    }\n  })\n}\n\nexport function clearAuthFormServerError(form: AnyFormApi) {\n  form.setErrorMap({ onServer: { fields: {} } })\n}\n\nexport function clearAuthFormFieldServerError(\n  form: AnyFormApi,\n  fieldName: string\n) {\n  form.setErrorMap({ onServer: undefined })\n  if (!fieldName) return\n\n  const fieldMeta = form.getFieldMeta(fieldName as never)\n  if (!fieldMeta?.errorMap.onServer) return\n\n  form.setFieldMeta(fieldName as never, (current = fieldMeta) => ({\n    ...current,\n    errorMap: { ...current.errorMap, onServer: undefined },\n    errorSourceMap: { ...current.errorSourceMap, onServer: undefined }\n  }))\n}\n\nexport async function runAuthFormAction(\n  form: AnyFormApi,\n  action: () => Promise<unknown>,\n  serverErrorMessage = DEFAULT_AUTH_FORM_SERVER_ERROR\n) {\n  clearAuthFormServerError(form)\n  try {\n    await action()\n    return true\n  } catch (error) {\n    if (!form.state.errorMap.onServer) {\n      setAuthFormServerError(form, error, serverErrorMessage)\n    }\n    return false\n  }\n}\n\nexport async function submitAuthForm(\n  form: AnyFormApi,\n  serverErrorMessage = DEFAULT_AUTH_FORM_SERVER_ERROR\n) {\n  clearAuthFormServerError(form)\n  try {\n    await form.handleSubmit()\n    return form.state.isValid\n  } catch (error) {\n    if (!form.state.errorMap.onServer) {\n      setAuthFormServerError(form, error, serverErrorMessage)\n    }\n    return false\n  }\n}\n\ntype AuthFormRootProps = Omit<ComponentProps<\"form\">, \"onSubmit\"> & {\n  onBeforeSubmit?: () => void\n  serverErrorMessage?: string\n}\n\nfunction AuthFormRoot({\n  children,\n  onBeforeSubmit,\n  onInput,\n  serverErrorMessage = DEFAULT_AUTH_FORM_SERVER_ERROR,\n  ...props\n}: AuthFormRootProps) {\n  const form = useFormContext()\n  const submittingRef = useRef(false)\n\n  async function submit(event: FormEvent<HTMLFormElement>) {\n    event.preventDefault()\n    if (submittingRef.current || form.state.isSubmitting) return\n\n    const formElement = event.currentTarget\n    onBeforeSubmit?.()\n    submittingRef.current = true\n    try {\n      const isValid = await submitAuthForm(form, serverErrorMessage)\n      if (!isValid) focusFirstInvalidAuthFormControl(formElement)\n    } finally {\n      submittingRef.current = false\n    }\n  }\n\n  return (\n    <form\n      {...props}\n      onInvalid={(event) =>\n        focusFirstInvalidAuthFormControl(event.currentTarget)\n      }\n      onInput={(event) => {\n        const target = event.target\n        const fieldName =\n          target instanceof HTMLInputElement ||\n          target instanceof HTMLSelectElement ||\n          target instanceof HTMLTextAreaElement\n            ? target.name\n            : \"\"\n        clearAuthFormFieldServerError(form, fieldName)\n        onInput?.(event)\n      }}\n      onSubmit={submit}\n    >\n      {children}\n    </form>\n  )\n}\n\ntype AuthFormTextFieldProps = Omit<\n  ComponentProps<typeof Input>,\n  \"name\" | \"onBlur\" | \"onChange\" | \"value\"\n> & {\n  description?: ReactNode\n  label: ReactNode\n}\n\nfunction AuthFormTextField({\n  description,\n  id,\n  label,\n  ...props\n}: AuthFormTextFieldProps) {\n  const field = useFieldContext<string>()\n  const form = useFormContext()\n  const isInvalid = isAuthFormFieldInvalid(field.state.meta)\n  const inputId = id ?? field.name\n\n  return (\n    <Field data-invalid={isInvalid}>\n      <FieldLabel htmlFor={inputId}>{label}</FieldLabel>\n      <Input\n        {...props}\n        aria-busy={field.state.meta.isValidating || undefined}\n        aria-invalid={isInvalid}\n        id={inputId}\n        name={field.name}\n        onBlur={field.handleBlur}\n        onChange={(event) => {\n          clearAuthFormFieldServerError(form, field.name)\n          field.handleChange(event.target.value)\n        }}\n        value={field.state.value}\n      />\n      {description ? <FieldDescription>{description}</FieldDescription> : null}\n      <AuthFormFieldError />\n    </Field>\n  )\n}\n\nfunction AuthFormSubmitButton({\n  children,\n  disabled,\n  isPending,\n  ...props\n}: ComponentProps<typeof Button> & { isPending?: boolean }) {\n  const form = useFormContext()\n\n  return (\n    <form.Subscribe\n      selector={(state) => [state.isSubmitting, state.isValidating] as const}\n    >\n      {([isSubmitting, isValidating]) => (\n        <Button\n          {...props}\n          aria-busy={isPending || isSubmitting || undefined}\n          aria-disabled={\n            disabled || isPending || isSubmitting || isValidating || undefined\n          }\n          disabled={disabled || isPending || isSubmitting || isValidating}\n          type=\"submit\"\n        >\n          {isPending || isSubmitting ? (\n            <Spinner data-icon=\"inline-start\" />\n          ) : null}\n          {children}\n        </Button>\n      )}\n    </form.Subscribe>\n  )\n}\n\ntype AuthFormAdditionalFieldProps = Omit<\n  AdditionalFieldProps,\n  \"errors\" | \"isInvalid\" | \"name\" | \"onBlur\" | \"onChange\" | \"value\"\n>\n\nfunction AuthFormAdditionalField(props: AuthFormAdditionalFieldProps) {\n  const field = useFieldContext<AdditionalFieldFormValue>()\n  const form = useFormContext()\n  const isInvalid = isAuthFormFieldInvalid(field.state.meta)\n\n  return (\n    <AdditionalField\n      {...props}\n      errors={\n        isInvalid ? getFormFieldErrors(field.state.meta.errors) : undefined\n      }\n      isInvalid={isInvalid}\n      name={field.name}\n      onBlur={field.handleBlur}\n      onChange={(value) => {\n        clearAuthFormFieldServerError(form, field.name)\n        field.handleChange(value)\n      }}\n      value={field.state.value}\n    />\n  )\n}\n\nexport const {\n  useAppForm: useAuthForm,\n  withFieldGroup: withAuthFieldGroup,\n  withForm: withAuthForm\n} = createFormHook({\n  fieldComponents: {\n    AuthFormAdditionalField,\n    AuthFormFieldError,\n    AuthFormTextField\n  },\n  fieldContext,\n  formComponents: {\n    AuthFormRoot,\n    AuthFormServerError,\n    AuthFormSubmitButton\n  },\n  formContext\n})\n\nexport function isAuthFormFieldInvalid({\n  isTouched,\n  isValid\n}: {\n  isTouched: boolean\n  isValid: boolean\n}) {\n  return isTouched && !isValid\n}\n\nexport function getAuthAdditionalFieldValidators(\n  field: AdditionalFieldConfig,\n  requiredMessage: string\n) {\n  return {\n    onChange: ({ value }: { value: AdditionalFieldFormValue }) =>\n      validateAdditionalFieldRequired(field, value, requiredMessage),\n    onChangeAsync: field.validate\n      ? ({ value }: { value: AdditionalFieldFormValue }) =>\n          validateAdditionalFieldValue(field, value)\n      : undefined,\n    onChangeAsyncDebounceMs: field.validate\n      ? (field.validateDebounceMs ??\n        DEFAULT_ADDITIONAL_FIELD_VALIDATION_DEBOUNCE_MS)\n      : undefined\n  }\n}\n",
      "type": "registry:component",
      "target": "@components/auth/auth-form.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/last-login-method/last-used-badge.tsx",
      "content": "\"use client\"\n\nimport { useLastLoginMethod } from \"@better-auth-ui/react\"\n\nimport { Badge } from \"@/components/ui/badge\"\n\nexport type LastUsedBadgeProps = {\n  /** Login method IDs that should display the indicator. */\n  method: string | string[]\n  /** Use the shorter label in constrained layouts. */\n  compact?: boolean\n  /** Float the compact indicator over the top-right edge of its container. */\n  floating?: boolean\n}\n\n/**\n * Displays an indicator when one of the supplied method IDs matches Better\n * Auth's stored last login method.\n */\nexport function LastUsedBadge({\n  method,\n  compact,\n  floating\n}: LastUsedBadgeProps) {\n  const { method: lastLoginMethod, localization } = useLastLoginMethod()\n  const methods = Array.isArray(method) ? method : [method]\n\n  if (!lastLoginMethod || !methods.includes(lastLoginMethod)) return null\n\n  return (\n    <Badge\n      className={\n        floating\n          ? \"pointer-events-none absolute top-0 right-0 z-10 translate-x-1/4 -translate-y-1/2 shadow-sm\"\n          : undefined\n      }\n      variant=\"secondary\"\n    >\n      {compact || floating ? localization.lastUsedShort : localization.lastUsed}\n    </Badge>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/last-login-method/last-used-badge.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/provider-button.tsx",
      "content": "\"use client\"\n\nimport {\n  type AuthSocialProvider,\n  type AuthView,\n  authMutationKeys,\n  getProviderId,\n  getProviderName,\n  type OAuthPopupAuthClient\n} from \"@better-auth-ui/core\"\nimport {\n  renderProviderIcon,\n  useAuth,\n  useFetchOptions,\n  useSignInOAuthPopup,\n  useSignInSocial\n} from \"@better-auth-ui/react\"\nimport { useIsMutating } from \"@tanstack/react-query\"\nimport type { ComponentProps } from \"react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { cn } from \"cn\"\nimport { LastUsedBadge } from \"./last-login-method/last-used-badge\"\n\nexport type ProviderButtonProps = {\n  provider: AuthSocialProvider\n  display?: \"full\" | \"name\" | \"icon\"\n  view?: AuthView\n} & Omit<ComponentProps<typeof Button>, \"onClick\" | \"children\" | \"disabled\">\n\n/**\n * Social provider sign-in button.\n *\n * @param provider - Provider to sign in with.\n * @param display - `\"full\"` (e.g. \"Continue with Google\"), `\"name\"` (just the provider name), or `\"icon\"` (icon only).\n */\nexport function ProviderButton({\n  provider,\n  display = \"full\",\n  view = \"signIn\",\n  variant = \"outline\",\n  className,\n  ...props\n}: ProviderButtonProps) {\n  const {\n    authClient,\n    baseURL,\n    localization,\n    navigate,\n    redirectTo,\n    socialSignInMode\n  } = useAuth()\n\n  const callbackURL = `${baseURL}${redirectTo}`\n  const { fetchOptions, resetFetchOptions } = useFetchOptions()\n\n  const { mutate: signInSocial, isPending: signInSocialPending } =\n    useSignInSocial(authClient, { onError: resetFetchOptions })\n  const { mutate: signInPopup, isPending: signInPopupPending } =\n    useSignInOAuthPopup(authClient as OAuthPopupAuthClient, {\n      onError: resetFetchOptions\n    })\n\n  const providerId = getProviderId(provider)\n  const providerIcon = renderProviderIcon(provider)\n\n  const signInMutating = useIsMutating({\n    mutationKey: authMutationKeys.signIn.all\n  })\n  const signUpMutating = useIsMutating({\n    mutationKey: authMutationKeys.signUp.all\n  })\n  const isPending = signInMutating + signUpMutating > 0\n\n  const handleSignIn = () => {\n    if (socialSignInMode === \"popup\") {\n      signInPopup(\n        {\n          provider: providerId,\n          callbackURL,\n          requestSignUp: view === \"signUp\"\n        },\n        { onSuccess: () => navigate({ to: redirectTo }) }\n      )\n      return\n    }\n\n    signInSocial({ provider: providerId, callbackURL, fetchOptions })\n  }\n\n  return (\n    <Button\n      type=\"button\"\n      variant={variant}\n      disabled={isPending}\n      onClick={handleSignIn}\n      className={cn(\"relative overflow-visible\", className)}\n      {...props}\n    >\n      {signInSocialPending || signInPopupPending ? <Spinner /> : providerIcon}\n\n      {display === \"full\"\n        ? localization.auth.continueWith.replace(\n            \"{{provider}}\",\n            getProviderName(provider)\n          )\n        : display === \"name\"\n          ? getProviderName(provider)\n          : null}\n\n      {display === \"icon\" && (\n        <span className=\"sr-only\">{getProviderName(provider)}</span>\n      )}\n\n      {view !== \"signUp\" && <LastUsedBadge method={providerId} floating />}\n    </Button>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/provider-button.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/provider-buttons.tsx",
      "content": "\"use client\"\n\nimport { type AuthView, getProviderId } from \"@better-auth-ui/core\"\nimport { useAuth } from \"@better-auth-ui/react\"\nimport { useMemo } from \"react\"\n\nimport { cn } from \"cn\"\nimport { ProviderButton } from \"./provider-button\"\n\nexport type ProviderButtonsProps = {\n  socialLayout?: SocialLayout\n  view?: AuthView\n}\n\nexport type SocialLayout = \"auto\" | \"horizontal\" | \"vertical\" | \"grid\"\n\n/**\n * Render sign-in buttons for configured social providers. Each button owns its own sign-in mutation\n * and reads the shared sign-in pending state from React Query.\n *\n * @param socialLayout - Preferred layout for the provider buttons; `\"auto\"` chooses based on the number of providers.\n */\nexport function ProviderButtons({\n  socialLayout = \"auto\",\n  view = \"signIn\"\n}: ProviderButtonsProps) {\n  const { socialProviders } = useAuth()\n\n  const resolvedSocialLayout = useMemo(() => {\n    if (socialLayout === \"auto\") {\n      if (socialProviders?.length && socialProviders.length >= 4) {\n        return \"horizontal\"\n      }\n\n      return \"vertical\"\n    }\n\n    return socialLayout\n  }, [socialLayout, socialProviders?.length])\n\n  return (\n    <div\n      className={cn(\n        \"gap-3\",\n        resolvedSocialLayout === \"grid\" && \"grid grid-cols-2\",\n        resolvedSocialLayout === \"vertical\" && \"flex flex-col\",\n        resolvedSocialLayout === \"horizontal\" && \"flex flex-row flex-wrap\"\n      )}\n    >\n      {socialProviders?.map((provider) => (\n        <ProviderButton\n          key={getProviderId(provider)}\n          provider={provider}\n          view={view}\n          display={\n            resolvedSocialLayout === \"vertical\"\n              ? \"full\"\n              : resolvedSocialLayout === \"grid\"\n                ? \"name\"\n                : \"icon\"\n          }\n          className={cn(resolvedSocialLayout === \"horizontal\" && \"flex-1\")}\n        />\n      ))}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/provider-buttons.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/reauthentication.tsx",
      "content": "\"use client\"\n\nimport {\n  getReauthenticationSignInURL,\n  isReauthenticationSignInURL\n} from \"@better-auth-ui/core\"\nimport { useAuth, useSignOut } from \"@better-auth-ui/react\"\nimport { useSyncExternalStore } from \"react\"\nimport { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\"\nimport { Button } from \"@/components/ui/button\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { cn } from \"cn\"\n\nconst subscribeToLocation = () => () => undefined\n\nfunction useIsReauthenticationSignIn() {\n  return useSyncExternalStore(\n    subscribeToLocation,\n    () => isReauthenticationSignInURL(new URL(window.location.href)),\n    () => false\n  )\n}\n\nexport type ReauthenticationActionProps = {\n  className?: string\n  showTitle?: boolean\n}\n\nexport function ReauthenticationAction({\n  className,\n  showTitle = true\n}: ReauthenticationActionProps) {\n  const auth = useAuth()\n  const signOut = useSignOut(auth.authClient)\n\n  const handleReauthentication = () => {\n    const signInURL = getReauthenticationSignInURL(\n      new URL(window.location.href),\n      `${auth.basePaths.auth}/${auth.viewPaths.auth.signIn}`\n    )\n    signOut.mutate(undefined, {\n      onSuccess: () => auth.navigate({ to: signInURL })\n    })\n  }\n\n  return (\n    <div className={cn(\"flex flex-col items-start gap-3 p-4\", className)}>\n      <div className=\"flex flex-col gap-1\">\n        {showTitle ? (\n          <h3 className=\"text-sm font-medium\">\n            {auth.localization.settings.reauthenticationTitle}\n          </h3>\n        ) : null}\n        <p className=\"text-sm text-muted-foreground\">\n          {auth.localization.settings.reauthenticationDescription}\n        </p>\n      </div>\n      <Button\n        disabled={signOut.isPending}\n        onClick={handleReauthentication}\n        size=\"sm\"\n      >\n        {signOut.isPending ? <Spinner data-icon=\"inline-start\" /> : null}\n        {auth.localization.settings.reauthenticationAction}\n      </Button>\n    </div>\n  )\n}\n\nexport function ReauthenticationNotice() {\n  const auth = useAuth()\n  const isReauthenticationSignIn = useIsReauthenticationSignIn()\n  if (!isReauthenticationSignIn) return null\n\n  return (\n    <Alert className=\"mx-4 w-auto group-data-[size=sm]/card:mx-3\">\n      <AlertTitle>\n        {auth.localization.settings.reauthenticationTitle}\n      </AlertTitle>\n      <AlertDescription>\n        {auth.localization.settings.reauthenticationDescription}\n      </AlertDescription>\n    </Alert>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/reauthentication.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/sso/email-first-sign-in.tsx",
      "content": "\"use client\"\n\nimport {\n  authMutationKeys,\n  validateEmailAddress,\n  validateStringLength\n} from \"@better-auth-ui/core\"\nimport {\n  isPasskeyAutoFillEnabled,\n  withPasskeyAutoFill\n} from \"@better-auth-ui/core/plugins/passkey\"\nimport {\n  type SsoAuthClient,\n  setSsoFallbackEmail\n} from \"@better-auth-ui/core/plugins/sso\"\nimport {\n  AuthPrompts,\n  getAuthButtonKey,\n  useAuth,\n  useAuthPlugin,\n  useFetchOptions,\n  useSignInEmail\n} from \"@better-auth-ui/react\"\nimport { useSignInSso } from \"@better-auth-ui/react/plugins/sso\"\nimport { useSelector } from \"@tanstack/react-form\"\nimport { useIsMutating } from \"@tanstack/react-query\"\nimport { Eye, EyeOff } from \"lucide-react\"\nimport { useState } from \"react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle\n} from \"@/components/ui/card\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport {\n  Field,\n  FieldDescription,\n  FieldGroup,\n  FieldLabel,\n  FieldSeparator\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 { ssoPlugin } from \"@/lib/auth/sso-plugin\"\nimport { useSignInContinuation } from \"@/lib/auth/use-sign-in-continuation\"\nimport { cn } from \"cn\"\nimport { isAuthFormFieldInvalid, useAuthForm } from \"../auth-form\"\nimport { ProviderButtons } from \"../provider-buttons\"\nimport { ReauthenticationNotice } from \"../reauthentication\"\n\nexport type EmailFirstSignInProps = {\n  className?: string\n  socialLayout?: \"auto\" | \"horizontal\" | \"vertical\" | \"grid\"\n  socialPosition?: \"top\" | \"bottom\"\n}\n\n/** Discover organization SSO by email, then expose configured fallback methods. */\nexport function EmailFirstSignIn({\n  className,\n  socialLayout,\n  socialPosition = \"bottom\"\n}: EmailFirstSignInProps) {\n  const {\n    authClient,\n    basePaths,\n    baseURL,\n    emailAndPassword,\n    localization,\n    navigate,\n    plugins,\n    redirectTo,\n    socialProviders,\n    viewPaths,\n    Link\n  } = useAuth()\n  const { localization: ssoLocalization } = useAuthPlugin(ssoPlugin)\n  const { fetchOptions, resetFetchOptions } = useFetchOptions()\n  const continueSignIn = useSignInContinuation()\n\n  const [step, setStep] = useState<\"email\" | \"fallback\">(\"email\")\n  const [isPasswordVisible, setIsPasswordVisible] = useState(false)\n  const [discoveryError, setDiscoveryError] = useState(\"\")\n\n  const { mutateAsync: signInSso, isPending: isDiscovering } = useSignInSso(\n    authClient as SsoAuthClient,\n    {\n      onError: (error) => {\n        if (error.status === 404) {\n          setSsoFallbackEmail(form.state.values.email)\n          setDiscoveryError(ssoLocalization.noProvider)\n          setStep(\"fallback\")\n          return\n        }\n\n        setDiscoveryError(ssoLocalization.ssoUnavailable)\n      }\n    }\n  )\n\n  const { mutateAsync: signInEmail, isPending: isSigningIn } = useSignInEmail(\n    authClient,\n    {\n      onError: (error) => {\n        form.setFieldValue(\"password\", \"\")\n\n        if (error.error?.code === \"EMAIL_NOT_VERIFIED\") {\n          sessionStorage.setItem(\n            \"better-auth-ui.verify-email\",\n            form.state.values.email\n          )\n          navigate({\n            to: `${basePaths.auth}/${viewPaths.auth.verifyEmail}`\n          })\n        }\n\n        resetFetchOptions()\n      },\n      onSuccess: (data) => continueSignIn(data)\n    }\n  )\n\n  const signInMutating = useIsMutating({\n    mutationKey: authMutationKeys.signIn.all\n  })\n  const isPending = signInMutating > 0\n  const Captcha = plugins.find(\n    (plugin) => plugin.captchaComponent\n  )?.captchaComponent\n\n  const passkeyAutoFill = isPasskeyAutoFillEnabled(plugins)\n  const showSocialSeparator =\n    emailAndPassword.enabled && !!socialProviders?.length\n\n  const form = useAuthForm({\n    defaultValues: { email: \"\", password: \"\", rememberMe: false },\n    onSubmit: async ({ value }) => {\n      if (step === \"email\") {\n        setDiscoveryError(\"\")\n        setSsoFallbackEmail(value.email)\n        try {\n          await signInSso({\n            callbackURL: `${baseURL}${redirectTo}`,\n            email: value.email,\n            loginHint: value.email\n          })\n        } catch (error) {\n          if ((error as { status?: number }).status !== 404) throw error\n        }\n        return\n      }\n      await signInEmail({\n        email: value.email,\n        password: value.password,\n        ...(emailAndPassword.rememberMe\n          ? { rememberMe: value.rememberMe }\n          : {}),\n        fetchOptions\n      })\n    }\n  })\n  const email = useSelector(form.store, (state) => state.values.email)\n\n  const startOver = () => {\n    setStep(\"email\")\n    form.setFieldValue(\"password\", \"\")\n    setDiscoveryError(\"\")\n  }\n\n  return (\n    <Card className={cn(\"w-full max-w-sm\", className)}>\n      <AuthPrompts view=\"signIn\" />\n      <ReauthenticationNotice />\n      <CardHeader>\n        <CardTitle className=\"text-xl font-semibold\">\n          {localization.auth.signIn}\n        </CardTitle>\n        <CardDescription>\n          {step === \"email\" ? ssoLocalization.emailFirstDescription : email}\n        </CardDescription>\n      </CardHeader>\n\n      <CardContent>\n        <form.AppForm>\n          {step === \"email\" ? (\n            <form.AuthFormRoot>\n              <FieldGroup>\n                <form.AppField\n                  name=\"email\"\n                  validators={{\n                    onChange: ({ value }) =>\n                      validateEmailAddress(value, {\n                        invalidMessage: localization.auth.invalidEmail,\n                        requiredMessage: localization.auth.fieldRequired\n                      })\n                  }}\n                >\n                  {(field) => {\n                    const isInvalid = isAuthFormFieldInvalid(field.state.meta)\n                    return (\n                      <Field data-invalid={isInvalid}>\n                        <FieldLabel htmlFor=\"sso-email\">\n                          {localization.auth.email}\n                        </FieldLabel>\n                        <Input\n                          id=\"sso-email\"\n                          name={field.name}\n                          type=\"email\"\n                          autoComplete={withPasskeyAutoFill(\n                            \"email\",\n                            passkeyAutoFill\n                          )}\n                          autoFocus\n                          value={field.state.value}\n                          onBlur={field.handleBlur}\n                          onChange={(event) =>\n                            field.handleChange(event.target.value)\n                          }\n                          placeholder={localization.auth.emailPlaceholder}\n                          required\n                          disabled={isPending}\n                          aria-invalid={isInvalid}\n                        />\n                        <field.AuthFormFieldError />\n                      </Field>\n                    )\n                  }}\n                </form.AppField>\n\n                {discoveryError && (\n                  <FieldDescription role=\"alert\" className=\"text-destructive\">\n                    {discoveryError}\n                  </FieldDescription>\n                )}\n\n                <form.AuthFormServerError />\n\n                <form.AuthFormSubmitButton\n                  isPending={isDiscovering}\n                  disabled={isPending}\n                >\n                  {ssoLocalization.continueWithEmail}\n                </form.AuthFormSubmitButton>\n              </FieldGroup>\n            </form.AuthFormRoot>\n          ) : (\n            <div className=\"flex flex-col gap-4\">\n              {socialPosition === \"top\" && (\n                <>\n                  {!!socialProviders?.length && (\n                    <ProviderButtons\n                      socialLayout={socialLayout}\n                      view=\"signIn\"\n                    />\n                  )}\n                  {showSocialSeparator && (\n                    <FieldSeparator>{localization.auth.or}</FieldSeparator>\n                  )}\n                </>\n              )}\n\n              {discoveryError && (\n                <FieldDescription role=\"status\">\n                  {discoveryError}\n                </FieldDescription>\n              )}\n\n              {emailAndPassword.enabled && (\n                <form.AuthFormRoot>\n                  <FieldGroup>\n                    <form.AppField\n                      name=\"password\"\n                      validators={{\n                        onChange: ({ value }) =>\n                          validateStringLength(value, {\n                            maxLength: emailAndPassword.maxPasswordLength,\n                            maxLengthMessage: localization.auth.tooLong.replace(\n                              \"{{max}}\",\n                              String(emailAndPassword.maxPasswordLength)\n                            ),\n                            minLength: emailAndPassword.minPasswordLength,\n                            minLengthMessage:\n                              localization.auth.tooShort.replace(\n                                \"{{min}}\",\n                                String(emailAndPassword.minPasswordLength)\n                              ),\n                            requiredMessage: localization.auth.fieldRequired\n                          })\n                      }}\n                    >\n                      {(field) => {\n                        const isInvalid = isAuthFormFieldInvalid(\n                          field.state.meta\n                        )\n                        return (\n                          <Field data-invalid={isInvalid}>\n                            <FieldLabel htmlFor=\"sso-password\">\n                              {localization.auth.password}\n                            </FieldLabel>\n                            <InputGroup>\n                              <InputGroupInput\n                                id=\"sso-password\"\n                                name={field.name}\n                                type={isPasswordVisible ? \"text\" : \"password\"}\n                                autoComplete={withPasskeyAutoFill(\n                                  \"current-password\",\n                                  passkeyAutoFill\n                                )}\n                                autoFocus\n                                value={field.state.value}\n                                onBlur={field.handleBlur}\n                                onChange={(event) =>\n                                  field.handleChange(event.target.value)\n                                }\n                                placeholder={\n                                  localization.auth.passwordPlaceholder\n                                }\n                                minLength={emailAndPassword.minPasswordLength}\n                                maxLength={emailAndPassword.maxPasswordLength}\n                                required\n                                disabled={isPending}\n                                aria-invalid={isInvalid}\n                              />\n                              <InputGroupAddon align=\"inline-end\">\n                                <InputGroupButton\n                                  size=\"icon-xs\"\n                                  aria-label={\n                                    isPasswordVisible\n                                      ? localization.auth.hidePassword\n                                      : localization.auth.showPassword\n                                  }\n                                  onClick={() =>\n                                    setIsPasswordVisible((visible) => !visible)\n                                  }\n                                >\n                                  {isPasswordVisible ? <EyeOff /> : <Eye />}\n                                </InputGroupButton>\n                              </InputGroupAddon>\n                            </InputGroup>\n                            <field.AuthFormFieldError />\n                          </Field>\n                        )\n                      }}\n                    </form.AppField>\n\n                    {emailAndPassword.rememberMe && (\n                      <form.AppField name=\"rememberMe\">\n                        {(field) => (\n                          <Field>\n                            <div className=\"flex items-center gap-3\">\n                              <Checkbox\n                                id=\"sso-remember-me\"\n                                name={field.name}\n                                checked={field.state.value}\n                                disabled={isPending}\n                                onCheckedChange={(checked) =>\n                                  field.handleChange(checked === true)\n                                }\n                              />\n                              <FieldLabel\n                                htmlFor=\"sso-remember-me\"\n                                className=\"cursor-pointer text-sm font-normal\"\n                              >\n                                {localization.auth.rememberMe}\n                              </FieldLabel>\n                            </div>\n                          </Field>\n                        )}\n                      </form.AppField>\n                    )}\n\n                    {Captcha && (\n                      <div className=\"flex justify-center\">{Captcha}</div>\n                    )}\n\n                    <form.AuthFormServerError />\n\n                    <form.AuthFormSubmitButton\n                      isPending={isSigningIn}\n                      disabled={isPending}\n                    >\n                      {localization.auth.signIn}\n                    </form.AuthFormSubmitButton>\n                  </FieldGroup>\n                </form.AuthFormRoot>\n              )}\n\n              {plugins.flatMap((plugin) =>\n                (plugin.authButtons ?? []).map((AuthButton) => (\n                  <AuthButton\n                    key={getAuthButtonKey(plugin.id, AuthButton)}\n                    view=\"signIn\"\n                  />\n                ))\n              )}\n\n              {socialPosition === \"bottom\" && (\n                <>\n                  {showSocialSeparator && (\n                    <FieldSeparator>{localization.auth.or}</FieldSeparator>\n                  )}\n                  {!!socialProviders?.length && (\n                    <ProviderButtons\n                      socialLayout={socialLayout}\n                      view=\"signIn\"\n                    />\n                  )}\n                </>\n              )}\n\n              <Button variant=\"ghost\" onClick={startOver}>\n                {ssoLocalization.useDifferentEmail}\n              </Button>\n            </div>\n          )}\n        </form.AppForm>\n      </CardContent>\n\n      {emailAndPassword.enabled && (\n        <CardFooter className=\"flex-col gap-3\">\n          {step === \"fallback\" && emailAndPassword.forgotPassword && (\n            <Link\n              href={`${basePaths.auth}/${viewPaths.auth.forgotPassword}`}\n              className=\"text-sm underline-offset-4 hover:underline\"\n            >\n              {localization.auth.forgotPasswordLink}\n            </Link>\n          )}\n          <FieldDescription className=\"text-center\">\n            {localization.auth.needToCreateAnAccount}{\" \"}\n            <Link\n              href={`${basePaths.auth}/${viewPaths.auth.signUp}`}\n              className=\"underline underline-offset-4\"\n            >\n              {localization.auth.signUp}\n            </Link>\n          </FieldDescription>\n        </CardFooter>\n      )}\n    </Card>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/sso/email-first-sign-in.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/sso/organization-sso-providers.tsx",
      "content": "\"use client\"\n\nimport { validateAbsoluteUrl, validateStringLength } from \"@better-auth-ui/core\"\nimport {\n  hasMemberRole,\n  type OrganizationAuthClient\n} from \"@better-auth-ui/core/plugins/organization\"\nimport type {\n  SsoAuthClient,\n  SsoProvider,\n  UpdateSsoProviderParams\n} from \"@better-auth-ui/core/plugins/sso\"\nimport { useAuth, useAuthPlugin } from \"@better-auth-ui/react\"\nimport { useActiveMemberRole } from \"@better-auth-ui/react/plugins/organization\"\nimport {\n  useDeleteSsoProvider,\n  useSsoProviders,\n  useUpdateSsoProvider\n} from \"@better-auth-ui/react/plugins/sso\"\nimport type { BetterFetchError } from \"better-auth/client\"\nimport { PencilIcon, Trash2Icon } from \"lucide-react\"\nimport { useMemo, useState } from \"react\"\nimport { toast } from \"sonner\"\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle\n} from \"@/components/ui/alert-dialog\"\nimport { Badge } from \"@/components/ui/badge\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Card,\n  CardAction,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from \"@/components/ui/card\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle\n} from \"@/components/ui/dialog\"\nimport {\n  Empty,\n  EmptyDescription,\n  EmptyHeader,\n  EmptyTitle\n} from \"@/components/ui/empty\"\nimport {\n  Field,\n  FieldError,\n  FieldGroup,\n  FieldLabel\n} from \"@/components/ui/field\"\nimport { Input } from \"@/components/ui/input\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { Textarea } from \"@/components/ui/textarea\"\nimport { organizationPlugin } from \"@/lib/auth/organization-plugin\"\nimport { ssoPlugin } from \"@/lib/auth/sso-plugin\"\nimport { cn } from \"cn\"\nimport { isAuthFormFieldInvalid, useAuthForm } from \"../auth-form\"\n\nimport { SsoDomainVerification } from \"./sso-domain-verification\"\nimport { SsoProviderSetup } from \"./sso-provider-setup\"\n\nexport type OrganizationSsoProvidersProps = {\n  className?: string\n  organizationId: string\n  organizationSlug: string\n}\n\nconst providerSkeletonIds = [\"sso-provider-1\", \"sso-provider-2\"]\n\ntype SsoProviderEditorValues = {\n  clientId: string\n  clientSecret: string\n  discoveryEndpoint: string\n  domain: string\n  entryPoint: string\n  identityProviderMetadata: string\n  issuer: string\n}\n\nconst getSsoProviderEditorValues = (\n  provider?: SsoProvider\n): SsoProviderEditorValues => ({\n  clientId: \"\",\n  clientSecret: \"\",\n  discoveryEndpoint: provider?.oidcConfig?.discoveryEndpoint ?? \"\",\n  domain: provider?.domain ?? \"\",\n  entryPoint: provider?.samlConfig?.entryPoint ?? \"\",\n  identityProviderMetadata: \"\",\n  issuer: provider?.issuer ?? \"\"\n})\n\nconst getErrorMessage = (error: Error | null) => {\n  const authError = error as BetterFetchError | null\n  return authError?.error?.message ?? authError?.message\n}\n\n/** Manage the SSO providers that belong to one explicit organization. */\nexport function OrganizationSsoProviders({\n  className,\n  organizationId,\n  organizationSlug: _organizationSlug\n}: OrganizationSsoProvidersProps) {\n  const { authClient } = useAuth()\n  const { localization } = useAuthPlugin(ssoPlugin)\n  const { creatorRole } = useAuthPlugin(organizationPlugin)\n  const memberRole = useActiveMemberRole(authClient as OrganizationAuthClient, {\n    query: { organizationId }\n  })\n  const canManage =\n    hasMemberRole(memberRole.data?.role, creatorRole) ||\n    hasMemberRole(memberRole.data?.role, \"admin\")\n  const providersQuery = useSsoProviders(authClient as SsoAuthClient, {\n    enabled: !memberRole.isPending && canManage\n  })\n  const [creating, setCreating] = useState(false)\n  const [editing, setEditing] = useState<SsoProvider>()\n  const [verifying, setVerifying] = useState<SsoProvider>()\n  const [deleting, setDeleting] = useState<SsoProvider>()\n  const providers = useMemo(\n    () =>\n      providersQuery.data?.providers.filter(\n        (provider) => provider.organizationId === organizationId\n      ) ?? [],\n    [organizationId, providersQuery.data?.providers]\n  )\n\n  return (\n    <Card className={cn(className)}>\n      <CardHeader>\n        <CardTitle>{localization.providerList}</CardTitle>\n        <CardDescription>\n          {localization.providerListDescription}\n        </CardDescription>\n        <CardAction>\n          <Button\n            disabled={memberRole.isPending || !canManage}\n            onClick={() => setCreating((open) => !open)}\n            size=\"sm\"\n          >\n            {localization.addProvider}\n          </Button>\n        </CardAction>\n      </CardHeader>\n      <CardContent className=\"flex flex-col gap-4\">\n        {creating ? (\n          <SsoProviderSetup\n            className=\"max-w-none\"\n            organizationId={organizationId}\n            onRegistered={() => setCreating(false)}\n          />\n        ) : null}\n\n        {memberRole.isPending || (canManage && providersQuery.isPending) ? (\n          <div className=\"flex flex-col gap-2\">\n            {providerSkeletonIds.map((id) => (\n              <Skeleton className=\"h-24 w-full\" key={id} />\n            ))}\n          </div>\n        ) : !canManage ? (\n          <div\n            className=\"rounded-lg border border-amber-500/30 bg-amber-500/5 p-3 text-sm\"\n            role=\"alert\"\n          >\n            {localization.providerAccessDenied}\n          </div>\n        ) : providersQuery.error ? (\n          <div\n            className=\"flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm\"\n            role=\"alert\"\n          >\n            <span>{localization.providerLoadError}</span>\n            <Button\n              onClick={() => providersQuery.refetch()}\n              size=\"sm\"\n              variant=\"outline\"\n            >\n              {localization.retry}\n            </Button>\n          </div>\n        ) : providers.length ? (\n          <div className=\"flex flex-col gap-2\">\n            {providers.map((provider) => (\n              <div\n                className=\"flex flex-col gap-3 rounded-lg border p-3 sm:flex-row sm:items-center sm:justify-between\"\n                key={provider.providerId}\n              >\n                <div className=\"flex min-w-0 flex-col gap-1\">\n                  <div className=\"flex flex-wrap items-center gap-2\">\n                    <span className=\"font-medium\">{provider.providerId}</span>\n                    <Badge variant=\"outline\">\n                      {provider.type.toUpperCase()}\n                    </Badge>\n                    <Badge\n                      variant={\n                        provider.domainVerified ? \"secondary\" : \"outline\"\n                      }\n                    >\n                      {provider.domainVerified\n                        ? localization.domainVerified\n                        : localization.verifyDomain}\n                    </Badge>\n                  </div>\n                  <span className=\"truncate text-sm text-muted-foreground\">\n                    {provider.domain} · {provider.issuer}\n                  </span>\n                </div>\n                <div className=\"flex shrink-0 flex-wrap gap-2\">\n                  {!provider.domainVerified ? (\n                    <Button\n                      onClick={() => setVerifying(provider)}\n                      size=\"sm\"\n                      variant=\"outline\"\n                    >\n                      {localization.verifyDomain}\n                    </Button>\n                  ) : null}\n                  <Button\n                    aria-label={localization.editProvider}\n                    onClick={() => setEditing(provider)}\n                    size=\"icon-sm\"\n                    variant=\"ghost\"\n                  >\n                    <PencilIcon />\n                  </Button>\n                  <Button\n                    aria-label={localization.deleteProvider}\n                    onClick={() => setDeleting(provider)}\n                    size=\"icon-sm\"\n                    variant=\"destructive\"\n                  >\n                    <Trash2Icon />\n                  </Button>\n                </div>\n              </div>\n            ))}\n          </div>\n        ) : (\n          <Empty className=\"min-h-32 border\">\n            <EmptyHeader>\n              <EmptyTitle>{localization.noProviders}</EmptyTitle>\n              <EmptyDescription>\n                {localization.noProvidersDescription}\n              </EmptyDescription>\n            </EmptyHeader>\n          </Empty>\n        )}\n      </CardContent>\n\n      <EditSsoProviderDialog\n        key={editing?.providerId ?? \"closed\"}\n        onOpenChange={setEditing}\n        provider={editing}\n      />\n      <DeleteSsoProviderDialog onOpenChange={setDeleting} provider={deleting} />\n      <Dialog\n        open={Boolean(verifying)}\n        onOpenChange={(open) => !open && setVerifying(undefined)}\n      >\n        <DialogContent className=\"max-h-[90vh] overflow-y-auto sm:max-w-xl\">\n          <DialogHeader>\n            <DialogTitle>{localization.domainVerification}</DialogTitle>\n          </DialogHeader>\n          {verifying ? (\n            <SsoDomainVerification\n              className=\"max-w-none shadow-none ring-0\"\n              defaultProviderId={verifying.providerId}\n            />\n          ) : null}\n        </DialogContent>\n      </Dialog>\n    </Card>\n  )\n}\n\nfunction EditSsoProviderDialog({\n  onOpenChange,\n  provider\n}: {\n  onOpenChange: (provider: SsoProvider | undefined) => void\n  provider?: SsoProvider\n}) {\n  const { authClient, localization: authLocalization } = useAuth()\n  const { localization } = useAuthPlugin(ssoPlugin)\n  const update = useUpdateSsoProvider(authClient as SsoAuthClient)\n\n  const close = () => {\n    update.reset()\n    onOpenChange(undefined)\n  }\n  const form = useAuthForm({\n    defaultValues: getSsoProviderEditorValues(provider),\n    onSubmit: async ({ value }) => {\n      if (!provider) return\n      const clientId = value.clientId.trim()\n      const clientSecret = value.clientSecret.trim()\n      const identityProviderMetadata = value.identityProviderMetadata.trim()\n      const params = {\n        providerId: provider.providerId,\n        issuer: value.issuer.trim(),\n        domain: value.domain.trim(),\n        ...(provider.oidcConfig\n          ? {\n              oidcConfig: {\n                ...(clientId ? { clientId } : {}),\n                ...(clientSecret ? { clientSecret } : {}),\n                discoveryEndpoint: value.discoveryEndpoint.trim() || undefined\n              }\n            }\n          : {}),\n        ...(provider.samlConfig\n          ? {\n              samlConfig: {\n                entryPoint: value.entryPoint.trim(),\n                ...(identityProviderMetadata\n                  ? { idpMetadata: { metadata: identityProviderMetadata } }\n                  : {})\n              }\n            }\n          : {})\n      } as UpdateSsoProviderParams\n\n      try {\n        await update.mutateAsync(params)\n        toast.success(localization.providerUpdated)\n        close()\n      } catch {\n        // The mutation error is rendered below the fields.\n      }\n    }\n  })\n\n  return (\n    <Dialog\n      open={Boolean(provider)}\n      onOpenChange={(open) => {\n        if (!open && !update.isPending) close()\n      }}\n    >\n      <DialogContent className=\"max-h-[90vh] overflow-y-auto sm:max-w-xl\">\n        <form.AppForm>\n          <form.AuthFormRoot className=\"flex flex-col gap-4\">\n            <DialogHeader>\n              <DialogTitle>{localization.editProvider}</DialogTitle>\n              <DialogDescription>{provider?.providerId}</DialogDescription>\n            </DialogHeader>\n            <FieldGroup>\n              <form.AppField\n                name=\"domain\"\n                validators={{\n                  onChange: ({ value }) =>\n                    validateStringLength(value, {\n                      requiredMessage: authLocalization.auth.fieldRequired,\n                      trim: true\n                    })\n                }}\n              >\n                {(field) => (\n                  <Field\n                    data-invalid={isAuthFormFieldInvalid(field.state.meta)}\n                  >\n                    <FieldLabel htmlFor=\"sso-edit-domain\">\n                      {localization.domain}\n                    </FieldLabel>\n                    <Input\n                      id=\"sso-edit-domain\"\n                      name={field.name}\n                      onBlur={field.handleBlur}\n                      onChange={(event) =>\n                        field.handleChange(event.target.value)\n                      }\n                      required\n                      value={field.state.value}\n                      aria-invalid={isAuthFormFieldInvalid(field.state.meta)}\n                    />\n                    <field.AuthFormFieldError />\n                  </Field>\n                )}\n              </form.AppField>\n              <form.AppField\n                name=\"issuer\"\n                validators={{\n                  onChange: ({ value }) =>\n                    validateAbsoluteUrl(value, {\n                      invalidMessage: localization.invalidUrl,\n                      requiredMessage: authLocalization.auth.fieldRequired\n                    })\n                }}\n              >\n                {(field) => (\n                  <Field\n                    data-invalid={isAuthFormFieldInvalid(field.state.meta)}\n                  >\n                    <FieldLabel htmlFor=\"sso-edit-issuer\">\n                      {localization.issuer}\n                    </FieldLabel>\n                    <Input\n                      id=\"sso-edit-issuer\"\n                      name={field.name}\n                      onBlur={field.handleBlur}\n                      onChange={(event) =>\n                        field.handleChange(event.target.value)\n                      }\n                      required\n                      type=\"url\"\n                      value={field.state.value}\n                      aria-invalid={isAuthFormFieldInvalid(field.state.meta)}\n                    />\n                    <field.AuthFormFieldError />\n                  </Field>\n                )}\n              </form.AppField>\n              {provider?.oidcConfig ? (\n                <>\n                  <form.AppField\n                    name=\"discoveryEndpoint\"\n                    validators={{\n                      onChange: ({ value }) =>\n                        validateAbsoluteUrl(value, {\n                          invalidMessage: localization.invalidUrl\n                        })\n                    }}\n                  >\n                    {(field) => (\n                      <Field\n                        data-invalid={isAuthFormFieldInvalid(field.state.meta)}\n                      >\n                        <FieldLabel htmlFor=\"sso-edit-discovery\">\n                          {localization.discoveryEndpoint}\n                        </FieldLabel>\n                        <Input\n                          id=\"sso-edit-discovery\"\n                          name={field.name}\n                          onBlur={field.handleBlur}\n                          onChange={(event) =>\n                            field.handleChange(event.target.value)\n                          }\n                          type=\"url\"\n                          value={field.state.value}\n                          aria-invalid={isAuthFormFieldInvalid(\n                            field.state.meta\n                          )}\n                        />\n                        <field.AuthFormFieldError />\n                      </Field>\n                    )}\n                  </form.AppField>\n                  <div className=\"grid gap-4 sm:grid-cols-2\">\n                    {(\n                      [\n                        [\n                          \"clientId\",\n                          \"sso-edit-client-id\",\n                          localization.clientId\n                        ],\n                        [\n                          \"clientSecret\",\n                          \"sso-edit-client-secret\",\n                          localization.clientSecret\n                        ]\n                      ] as const\n                    ).map(([name, id, label]) => (\n                      <form.Field key={name} name={name}>\n                        {(field) => (\n                          <Field>\n                            <FieldLabel htmlFor={id}>{label}</FieldLabel>\n                            <Input\n                              autoComplete={\n                                name === \"clientSecret\"\n                                  ? \"new-password\"\n                                  : undefined\n                              }\n                              id={id}\n                              name={field.name}\n                              onBlur={field.handleBlur}\n                              onChange={(event) =>\n                                field.handleChange(event.target.value)\n                              }\n                              placeholder={\n                                name === \"clientId\"\n                                  ? `••••${provider.oidcConfig?.clientIdLastFour}`\n                                  : undefined\n                              }\n                              type={\n                                name === \"clientSecret\" ? \"password\" : \"text\"\n                              }\n                              value={field.state.value}\n                            />\n                          </Field>\n                        )}\n                      </form.Field>\n                    ))}\n                  </div>\n                </>\n              ) : null}\n              {provider?.samlConfig ? (\n                <>\n                  <form.AppField\n                    name=\"entryPoint\"\n                    validators={{\n                      onChange: ({ value }) =>\n                        validateAbsoluteUrl(value, {\n                          invalidMessage: localization.invalidUrl,\n                          requiredMessage: authLocalization.auth.fieldRequired\n                        })\n                    }}\n                  >\n                    {(field) => (\n                      <Field\n                        data-invalid={isAuthFormFieldInvalid(field.state.meta)}\n                      >\n                        <FieldLabel htmlFor=\"sso-edit-entry-point\">\n                          {localization.entryPoint}\n                        </FieldLabel>\n                        <Input\n                          id=\"sso-edit-entry-point\"\n                          name={field.name}\n                          onBlur={field.handleBlur}\n                          onChange={(event) =>\n                            field.handleChange(event.target.value)\n                          }\n                          required\n                          type=\"url\"\n                          value={field.state.value}\n                          aria-invalid={isAuthFormFieldInvalid(\n                            field.state.meta\n                          )}\n                        />\n                        <field.AuthFormFieldError />\n                      </Field>\n                    )}\n                  </form.AppField>\n                  <form.Field name=\"identityProviderMetadata\">\n                    {(field) => (\n                      <Field>\n                        <FieldLabel htmlFor=\"sso-edit-metadata\">\n                          {localization.identityProviderMetadata}\n                        </FieldLabel>\n                        <Textarea\n                          className=\"font-mono text-xs\"\n                          id=\"sso-edit-metadata\"\n                          name={field.name}\n                          onBlur={field.handleBlur}\n                          onChange={(event) =>\n                            field.handleChange(event.target.value)\n                          }\n                          rows={6}\n                          value={field.state.value}\n                        />\n                      </Field>\n                    )}\n                  </form.Field>\n                </>\n              ) : null}\n            </FieldGroup>\n            <FieldError>{getErrorMessage(update.error)}</FieldError>\n            <DialogFooter>\n              <Button\n                disabled={update.isPending}\n                onClick={close}\n                type=\"button\"\n                variant=\"outline\"\n              >\n                {localization.cancel}\n              </Button>\n              <form.AuthFormSubmitButton disabled={update.isPending}>\n                {localization.saveProvider}\n              </form.AuthFormSubmitButton>\n            </DialogFooter>\n          </form.AuthFormRoot>\n        </form.AppForm>\n      </DialogContent>\n    </Dialog>\n  )\n}\n\nfunction DeleteSsoProviderDialog({\n  onOpenChange,\n  provider\n}: {\n  onOpenChange: (provider: SsoProvider | undefined) => void\n  provider?: SsoProvider\n}) {\n  const { authClient } = useAuth()\n  const { localization } = useAuthPlugin(ssoPlugin)\n  const remove = useDeleteSsoProvider(authClient as SsoAuthClient)\n  const close = () => {\n    remove.reset()\n    onOpenChange(undefined)\n  }\n\n  return (\n    <AlertDialog\n      open={Boolean(provider)}\n      onOpenChange={(open) => {\n        if (!open && !remove.isPending) close()\n      }}\n    >\n      <AlertDialogContent>\n        <AlertDialogHeader>\n          <AlertDialogTitle>{localization.deleteProvider}</AlertDialogTitle>\n          <AlertDialogDescription>\n            {localization.deleteProviderDescription}\n          </AlertDialogDescription>\n        </AlertDialogHeader>\n        <p className=\"font-mono text-xs\">{provider?.providerId}</p>\n        <FieldError>{getErrorMessage(remove.error)}</FieldError>\n        <AlertDialogFooter>\n          <AlertDialogCancel disabled={remove.isPending}>\n            {localization.cancel}\n          </AlertDialogCancel>\n          <AlertDialogAction\n            disabled={remove.isPending}\n            onClick={(event) => {\n              event.preventDefault()\n              if (!provider) return\n              remove.mutate(\n                { providerId: provider.providerId },\n                {\n                  onSuccess: () => {\n                    toast.success(localization.providerDeleted)\n                    close()\n                  }\n                }\n              )\n            }}\n            variant=\"destructive\"\n          >\n            {localization.deleteProvider}\n          </AlertDialogAction>\n        </AlertDialogFooter>\n      </AlertDialogContent>\n    </AlertDialog>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/sso/organization-sso-providers.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/sso/sso-domain-verification.tsx",
      "content": "\"use client\"\n\nimport type { SsoAuthClient } from \"@better-auth-ui/core/plugins/sso\"\nimport {\n  useAuth,\n  useAuthPlugin,\n  useCopyToClipboard\n} from \"@better-auth-ui/react\"\nimport {\n  useRequestSsoDomainVerification,\n  useVerifySsoDomain\n} from \"@better-auth-ui/react/plugins/sso\"\nimport type { BetterFetchError } from \"better-auth/client\"\nimport { CheckIcon, CopyIcon } from \"lucide-react\"\nimport { useState } from \"react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from \"@/components/ui/card\"\nimport {\n  Field,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\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 { Spinner } from \"@/components/ui/spinner\"\nimport { ssoPlugin } from \"@/lib/auth/sso-plugin\"\nimport { cn } from \"cn\"\nimport { useAuthForm } from \"../auth-form\"\n\nexport type SsoDomainVerificationProps = {\n  className?: string\n  defaultProviderId?: string\n  defaultToken?: string\n  tokenPrefix?: string\n}\n\nconst getErrorMessage = (error: Error | null) => {\n  const authError = error as BetterFetchError | null\n  return authError?.error?.message ?? authError?.message\n}\n\n/** DNS token renewal and verification for an existing SSO provider. */\nexport function SsoDomainVerification({\n  className,\n  defaultProviderId = \"\",\n  defaultToken = \"\",\n  tokenPrefix = \"better-auth-token\"\n}: SsoDomainVerificationProps) {\n  const { authClient, localization: authLocalization } = useAuth()\n  const { localization } = useAuthPlugin(ssoPlugin)\n  const [token, setToken] = useState(defaultToken)\n  const [verified, setVerified] = useState(false)\n  const [copyError, setCopyError] = useState(\"\")\n  const requestToken = useRequestSsoDomainVerification(\n    authClient as SsoAuthClient,\n    {\n      onSuccess: (data) => setToken(data.domainVerificationToken)\n    }\n  )\n  const verify = useVerifySsoDomain(authClient as SsoAuthClient, {\n    onSuccess: () => setVerified(true)\n  })\n  const hostCopy = useCopyToClipboard({\n    onError: (error) => {\n      console.error(\"[Better Auth UI] Copy failed\", error)\n      setCopyError(authLocalization.errors.copyFailed)\n    }\n  })\n  const tokenCopy = useCopyToClipboard({\n    onError: (error) => {\n      console.error(\"[Better Auth UI] Copy failed\", error)\n      setCopyError(authLocalization.errors.copyFailed)\n    }\n  })\n  const error =\n    requestToken.submittedAt > verify.submittedAt\n      ? requestToken.error\n      : verify.error\n\n  const form = useAuthForm({\n    defaultValues: { providerId: defaultProviderId },\n    onSubmit: async ({ value }) => {\n      setCopyError(\"\")\n      setVerified(false)\n      await verify.mutateAsync({ providerId: value.providerId })\n    }\n  })\n\n  return (\n    <Card className={cn(\"w-full max-w-xl\", className)}>\n      <CardHeader>\n        <CardTitle>{localization.domainVerification}</CardTitle>\n        <CardDescription>\n          {localization.domainVerificationDescription}\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <form.AppForm>\n          <form.AuthFormRoot>\n            <form.Subscribe selector={(state) => state.values.providerId}>\n              {(providerId) => {\n                const host = providerId ? `_${tokenPrefix}-${providerId}` : \"\"\n                return (\n                  <FieldGroup>\n                    <form.AppField name=\"providerId\">\n                      {(field) => (\n                        <Field>\n                          <FieldLabel htmlFor=\"sso-verification-provider-id\">\n                            {localization.providerId}\n                          </FieldLabel>\n                          <Input\n                            id=\"sso-verification-provider-id\"\n                            name={field.name}\n                            value={field.state.value}\n                            onBlur={field.handleBlur}\n                            onChange={(event) => {\n                              field.handleChange(event.target.value.trim())\n                              setToken(\"\")\n                              setVerified(false)\n                            }}\n                            required\n                          />\n                        </Field>\n                      )}\n                    </form.AppField>\n\n                    {token ? (\n                      <div className=\"grid gap-3 rounded-lg border p-3\">\n                        <Field>\n                          <FieldLabel htmlFor=\"sso-dns-host\">\n                            {localization.txtRecordHost}\n                          </FieldLabel>\n                          <InputGroup>\n                            <InputGroupInput\n                              className=\"font-mono text-xs\"\n                              id=\"sso-dns-host\"\n                              readOnly\n                              value={host}\n                            />\n                            <InputGroupAddon align=\"inline-end\">\n                              <InputGroupButton\n                                aria-label={localization.copyDnsHost}\n                                size=\"icon-xs\"\n                                onClick={() => {\n                                  setCopyError(\"\")\n                                  void hostCopy.copy(host)\n                                }}\n                              >\n                                {hostCopy.copied ? <CheckIcon /> : <CopyIcon />}\n                              </InputGroupButton>\n                            </InputGroupAddon>\n                          </InputGroup>\n                        </Field>\n                        <Field>\n                          <FieldLabel htmlFor=\"sso-dns-value\">\n                            {localization.txtRecordValue}\n                          </FieldLabel>\n                          <InputGroup>\n                            <InputGroupInput\n                              className=\"font-mono text-xs\"\n                              id=\"sso-dns-value\"\n                              readOnly\n                              value={token}\n                            />\n                            <InputGroupAddon align=\"inline-end\">\n                              <InputGroupButton\n                                aria-label={localization.copyDnsValue}\n                                size=\"icon-xs\"\n                                onClick={() => {\n                                  setCopyError(\"\")\n                                  void tokenCopy.copy(token)\n                                }}\n                              >\n                                {tokenCopy.copied ? (\n                                  <CheckIcon />\n                                ) : (\n                                  <CopyIcon />\n                                )}\n                              </InputGroupButton>\n                            </InputGroupAddon>\n                          </InputGroup>\n                        </Field>\n                      </div>\n                    ) : null}\n\n                    {error ? (\n                      <FieldError>{getErrorMessage(error)}</FieldError>\n                    ) : null}\n                    {copyError ? <FieldError>{copyError}</FieldError> : null}\n                    {verified ? (\n                      <FieldDescription\n                        role=\"status\"\n                        className=\"text-foreground\"\n                      >\n                        {localization.domainVerified}\n                      </FieldDescription>\n                    ) : null}\n\n                    <div className=\"flex flex-wrap justify-end gap-2\">\n                      <Button\n                        type=\"button\"\n                        variant=\"outline\"\n                        disabled={!providerId || verify.isPending}\n                        onClick={() => {\n                          setCopyError(\"\")\n                          setVerified(false)\n                          requestToken.mutate({ providerId })\n                        }}\n                      >\n                        {requestToken.isPending ? (\n                          <Spinner data-icon=\"inline-start\" />\n                        ) : null}\n                        {localization.requestNewToken}\n                      </Button>\n                      <form.AuthFormSubmitButton\n                        isPending={verify.isPending}\n                        disabled={!providerId || requestToken.isPending}\n                      >\n                        {localization.verifyDomain}\n                      </form.AuthFormSubmitButton>\n                    </div>\n\n                    <span className=\"sr-only\" aria-live=\"polite\">\n                      {token && !verified\n                        ? localization.domainVerificationRequested\n                        : \"\"}\n                    </span>\n                  </FieldGroup>\n                )\n              }}\n            </form.Subscribe>\n          </form.AuthFormRoot>\n        </form.AppForm>\n      </CardContent>\n    </Card>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/sso/sso-domain-verification.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/sso/sso-provider-setup.tsx",
      "content": "\"use client\"\n\nimport { validateAbsoluteUrl, validateStringLength } from \"@better-auth-ui/core\"\nimport type {\n  RegisterSsoProviderData,\n  RegisterSsoProviderParams,\n  SsoAuthClient\n} from \"@better-auth-ui/core/plugins/sso\"\nimport { useAuth, useAuthPlugin } from \"@better-auth-ui/react\"\nimport { useRegisterSsoProvider } from \"@better-auth-ui/react/plugins/sso\"\nimport type { BetterFetchError } from \"better-auth/client\"\nimport { FileCode2, KeyRound } from \"lucide-react\"\nimport { useState } from \"react\"\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle\n} from \"@/components/ui/card\"\nimport {\n  Field,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLabel\n} from \"@/components/ui/field\"\nimport { Input } from \"@/components/ui/input\"\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\"\nimport { Textarea } from \"@/components/ui/textarea\"\nimport { ssoPlugin } from \"@/lib/auth/sso-plugin\"\nimport { cn } from \"cn\"\nimport { isAuthFormFieldInvalid, useAuthForm } from \"../auth-form\"\n\ntype SsoProtocol = \"oidc\" | \"saml\"\n\ntype SsoProviderFormValues = {\n  clientId: string\n  clientSecret: string\n  domain: string\n  entryPoint: string\n  identityProviderMetadata: string\n  issuer: string\n  organizationId: string\n  protocol: SsoProtocol\n  providerId: string\n}\n\nexport type SsoProviderSetupProps = {\n  className?: string\n  defaultOrganizationId?: string\n  organizationId?: string\n  onRegistered?: (provider: RegisterSsoProviderData) => void\n}\n\nconst getErrorMessage = (error: Error | null) => {\n  const authError = error as BetterFetchError | null\n  return authError?.error?.message ?? authError?.message\n}\n\n/** Self-service form for registering an OIDC or SAML SSO provider. */\nexport function SsoProviderSetup({\n  className,\n  defaultOrganizationId,\n  organizationId: fixedOrganizationId,\n  onRegistered\n}: SsoProviderSetupProps) {\n  const { authClient, localization: authLocalization } = useAuth()\n  const { localization } = useAuthPlugin(ssoPlugin)\n  const [created, setCreated] = useState(false)\n  const register = useRegisterSsoProvider(authClient as SsoAuthClient)\n  const form = useAuthForm({\n    defaultValues: {\n      clientId: \"\",\n      clientSecret: \"\",\n      domain: \"\",\n      entryPoint: \"\",\n      identityProviderMetadata: \"\",\n      issuer: \"\",\n      organizationId: defaultOrganizationId ?? \"\",\n      protocol: \"oidc\" as SsoProtocol,\n      providerId: \"\"\n    } satisfies SsoProviderFormValues,\n    onSubmit: async ({ value }) => {\n      setCreated(false)\n      const common = {\n        providerId: value.providerId.trim(),\n        issuer: value.issuer.trim(),\n        domain: value.domain.trim(),\n        organizationId:\n          fixedOrganizationId || value.organizationId.trim() || undefined\n      }\n      const params =\n        value.protocol === \"oidc\"\n          ? {\n              ...common,\n              oidcConfig: {\n                clientId: value.clientId.trim(),\n                clientSecret: value.clientSecret.trim()\n              }\n            }\n          : {\n              ...common,\n              samlConfig: {\n                entryPoint: value.entryPoint.trim(),\n                idpMetadata: {\n                  metadata: value.identityProviderMetadata.trim()\n                }\n              }\n            }\n\n      try {\n        const provider = await register.mutateAsync(\n          params as RegisterSsoProviderParams<SsoAuthClient>\n        )\n        setCreated(true)\n        onRegistered?.(provider)\n      } catch {\n        // The mutation exposes its error below the fields.\n      }\n    }\n  })\n\n  return (\n    <form.AppForm>\n      <form.AuthFormRoot className={cn(\"w-full max-w-xl\", className)}>\n        <Card>\n          <CardHeader>\n            <CardTitle>{localization.providerSetup}</CardTitle>\n            <CardDescription>\n              {localization.providerSetupDescription}\n            </CardDescription>\n          </CardHeader>\n          <CardContent>\n            <FieldGroup>\n              <div className=\"grid gap-4 sm:grid-cols-2\">\n                <form.AppField\n                  name=\"providerId\"\n                  validators={{\n                    onChange: ({ value }) =>\n                      validateStringLength(value, {\n                        requiredMessage: authLocalization.auth.fieldRequired,\n                        trim: true\n                      })\n                  }}\n                >\n                  {(field) => (\n                    <Field\n                      data-invalid={isAuthFormFieldInvalid(field.state.meta)}\n                    >\n                      <FieldLabel htmlFor=\"sso-provider-id\">\n                        {localization.providerId}\n                      </FieldLabel>\n                      <Input\n                        id=\"sso-provider-id\"\n                        name={field.name}\n                        onBlur={field.handleBlur}\n                        onChange={(event) =>\n                          field.handleChange(event.target.value)\n                        }\n                        required\n                        value={field.state.value}\n                        aria-invalid={isAuthFormFieldInvalid(field.state.meta)}\n                      />\n                      <field.AuthFormFieldError />\n                    </Field>\n                  )}\n                </form.AppField>\n                <form.AppField\n                  name=\"domain\"\n                  validators={{\n                    onChange: ({ value }) =>\n                      validateStringLength(value, {\n                        requiredMessage: authLocalization.auth.fieldRequired,\n                        trim: true\n                      })\n                  }}\n                >\n                  {(field) => (\n                    <Field\n                      data-invalid={isAuthFormFieldInvalid(field.state.meta)}\n                    >\n                      <FieldLabel htmlFor=\"sso-domain\">\n                        {localization.domain}\n                      </FieldLabel>\n                      <Input\n                        id=\"sso-domain\"\n                        name={field.name}\n                        onBlur={field.handleBlur}\n                        onChange={(event) =>\n                          field.handleChange(event.target.value)\n                        }\n                        placeholder=\"example.com\"\n                        required\n                        value={field.state.value}\n                        aria-invalid={isAuthFormFieldInvalid(field.state.meta)}\n                      />\n                      <field.AuthFormFieldError />\n                    </Field>\n                  )}\n                </form.AppField>\n              </div>\n\n              <form.AppField\n                name=\"issuer\"\n                validators={{\n                  onChange: ({ value }) =>\n                    validateAbsoluteUrl(value, {\n                      invalidMessage: localization.invalidUrl,\n                      requiredMessage: authLocalization.auth.fieldRequired\n                    })\n                }}\n              >\n                {(field) => (\n                  <Field\n                    data-invalid={isAuthFormFieldInvalid(field.state.meta)}\n                  >\n                    <FieldLabel htmlFor=\"sso-issuer\">\n                      {localization.issuer}\n                    </FieldLabel>\n                    <Input\n                      id=\"sso-issuer\"\n                      name={field.name}\n                      onBlur={field.handleBlur}\n                      onChange={(event) =>\n                        field.handleChange(event.target.value)\n                      }\n                      placeholder=\"https://idp.example.com\"\n                      required\n                      type=\"url\"\n                      value={field.state.value}\n                      aria-invalid={isAuthFormFieldInvalid(field.state.meta)}\n                    />\n                    <field.AuthFormFieldError />\n                  </Field>\n                )}\n              </form.AppField>\n\n              {!fixedOrganizationId ? (\n                <form.Field name=\"organizationId\">\n                  {(field) => (\n                    <Field>\n                      <FieldLabel htmlFor=\"sso-organization-id\">\n                        {localization.organizationId}\n                      </FieldLabel>\n                      <Input\n                        id=\"sso-organization-id\"\n                        name={field.name}\n                        onBlur={field.handleBlur}\n                        onChange={(event) =>\n                          field.handleChange(event.target.value)\n                        }\n                        value={field.state.value}\n                      />\n                    </Field>\n                  )}\n                </form.Field>\n              ) : null}\n\n              <form.Field name=\"protocol\">\n                {(protocolField) => (\n                  <Tabs\n                    value={protocolField.state.value}\n                    onValueChange={(value) =>\n                      protocolField.handleChange(value as SsoProtocol)\n                    }\n                  >\n                    <TabsList aria-label={localization.providerSetup}>\n                      <TabsTrigger value=\"oidc\">\n                        <KeyRound\n                          aria-hidden=\"true\"\n                          className=\"text-muted-foreground\"\n                        />\n                        {localization.oidc}\n                      </TabsTrigger>\n                      <TabsTrigger value=\"saml\">\n                        <FileCode2\n                          aria-hidden=\"true\"\n                          className=\"text-muted-foreground\"\n                        />\n                        {localization.saml}\n                      </TabsTrigger>\n                    </TabsList>\n                    <TabsContent\n                      className=\"grid gap-4 sm:grid-cols-2\"\n                      value=\"oidc\"\n                    >\n                      <form.AppField\n                        name=\"clientId\"\n                        validators={{\n                          onChange: ({ value }) =>\n                            validateStringLength(value, {\n                              requiredMessage:\n                                authLocalization.auth.fieldRequired,\n                              trim: true\n                            })\n                        }}\n                      >\n                        {(field) => (\n                          <Field\n                            data-invalid={isAuthFormFieldInvalid(\n                              field.state.meta\n                            )}\n                          >\n                            <FieldLabel htmlFor=\"sso-client-id\">\n                              {localization.clientId}\n                            </FieldLabel>\n                            <Input\n                              autoComplete=\"off\"\n                              id=\"sso-client-id\"\n                              name={field.name}\n                              onBlur={field.handleBlur}\n                              onChange={(event) =>\n                                field.handleChange(event.target.value)\n                              }\n                              required\n                              value={field.state.value}\n                              aria-invalid={isAuthFormFieldInvalid(\n                                field.state.meta\n                              )}\n                            />\n                            <field.AuthFormFieldError />\n                          </Field>\n                        )}\n                      </form.AppField>\n                      <form.AppField\n                        name=\"clientSecret\"\n                        validators={{\n                          onChange: ({ value }) =>\n                            validateStringLength(value, {\n                              requiredMessage:\n                                authLocalization.auth.fieldRequired,\n                              trim: true\n                            })\n                        }}\n                      >\n                        {(field) => (\n                          <Field\n                            data-invalid={isAuthFormFieldInvalid(\n                              field.state.meta\n                            )}\n                          >\n                            <FieldLabel htmlFor=\"sso-client-secret\">\n                              {localization.clientSecret}\n                            </FieldLabel>\n                            <Input\n                              autoComplete=\"new-password\"\n                              id=\"sso-client-secret\"\n                              name={field.name}\n                              onBlur={field.handleBlur}\n                              onChange={(event) =>\n                                field.handleChange(event.target.value)\n                              }\n                              required\n                              type=\"password\"\n                              value={field.state.value}\n                              aria-invalid={isAuthFormFieldInvalid(\n                                field.state.meta\n                              )}\n                            />\n                            <field.AuthFormFieldError />\n                          </Field>\n                        )}\n                      </form.AppField>\n                    </TabsContent>\n                    <TabsContent className=\"flex flex-col gap-4\" value=\"saml\">\n                      <form.AppField\n                        name=\"entryPoint\"\n                        validators={{\n                          onChange: ({ value }) =>\n                            validateAbsoluteUrl(value, {\n                              invalidMessage: localization.invalidUrl,\n                              requiredMessage:\n                                authLocalization.auth.fieldRequired\n                            })\n                        }}\n                      >\n                        {(field) => (\n                          <Field\n                            data-invalid={isAuthFormFieldInvalid(\n                              field.state.meta\n                            )}\n                          >\n                            <FieldLabel htmlFor=\"sso-entry-point\">\n                              {localization.entryPoint}\n                            </FieldLabel>\n                            <Input\n                              id=\"sso-entry-point\"\n                              name={field.name}\n                              onBlur={field.handleBlur}\n                              onChange={(event) =>\n                                field.handleChange(event.target.value)\n                              }\n                              required\n                              type=\"url\"\n                              value={field.state.value}\n                              aria-invalid={isAuthFormFieldInvalid(\n                                field.state.meta\n                              )}\n                            />\n                            <field.AuthFormFieldError />\n                          </Field>\n                        )}\n                      </form.AppField>\n                      <form.AppField\n                        name=\"identityProviderMetadata\"\n                        validators={{\n                          onChange: ({ value }) =>\n                            validateStringLength(value, {\n                              requiredMessage:\n                                authLocalization.auth.fieldRequired,\n                              trim: true\n                            })\n                        }}\n                      >\n                        {(field) => (\n                          <Field\n                            data-invalid={isAuthFormFieldInvalid(\n                              field.state.meta\n                            )}\n                          >\n                            <FieldLabel htmlFor=\"sso-idp-metadata\">\n                              {localization.identityProviderMetadata}\n                            </FieldLabel>\n                            <Textarea\n                              className=\"min-h-40 font-mono text-xs\"\n                              id=\"sso-idp-metadata\"\n                              name={field.name}\n                              onBlur={field.handleBlur}\n                              onChange={(event) =>\n                                field.handleChange(event.target.value)\n                              }\n                              required\n                              value={field.state.value}\n                              aria-invalid={isAuthFormFieldInvalid(\n                                field.state.meta\n                              )}\n                            />\n                            <field.AuthFormFieldError />\n                          </Field>\n                        )}\n                      </form.AppField>\n                    </TabsContent>\n                  </Tabs>\n                )}\n              </form.Field>\n\n              {register.error ? (\n                <FieldError>{getErrorMessage(register.error)}</FieldError>\n              ) : null}\n              {created ? (\n                <FieldDescription role=\"status\" className=\"text-foreground\">\n                  {localization.providerCreated}\n                </FieldDescription>\n              ) : null}\n            </FieldGroup>\n          </CardContent>\n          <CardFooter className=\"justify-end\">\n            <form.AuthFormSubmitButton disabled={register.isPending}>\n              {localization.addProvider}\n            </form.AuthFormSubmitButton>\n          </CardFooter>\n        </Card>\n      </form.AuthFormRoot>\n    </form.AppForm>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/sso/sso-provider-setup.tsx"
    },
    {
      "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/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"
    },
    {
      "path": "examples/start-shadcn-example/src/lib/auth/sso-plugin.ts",
      "content": "import {\n  type AuthPluginBase,\n  type AuthPluginLocalizationContext,\n  createAuthPlugin\n} from \"@better-auth-ui/core\"\nimport {\n  ssoPlugin as coreSsoPlugin,\n  type SsoLocalization,\n  type SsoPluginOptions\n} from \"@better-auth-ui/core/plugins/sso\"\nimport { ShieldCheck } from \"lucide-react\"\nimport { createElement } from \"react\"\n\nimport { EmailFirstSignIn } from \"@/components/auth/sso/email-first-sign-in\"\nimport { OrganizationSsoProviders } from \"@/components/auth/sso/organization-sso-providers\"\n\nexport const ssoPlugin = createAuthPlugin(\n  coreSsoPlugin.id,\n  (options: SsoPluginOptions = {}) => {\n    const plugin = coreSsoPlugin(options)\n    const localizedOrganizationTab = (localization: SsoLocalization) =>\n      plugin.organization\n        ? {\n            organizationTabs: [\n              {\n                id: \"sso\",\n                path: plugin.path,\n                label: createElement(\n                  \"span\",\n                  { className: \"inline-flex items-center gap-1\" },\n                  createElement(ShieldCheck, {\n                    \"aria-hidden\": true,\n                    className: \"text-muted-foreground\"\n                  }),\n                  localization.providerList\n                ),\n                component: OrganizationSsoProviders\n              }\n            ]\n          }\n        : {}\n\n    return {\n      ...plugin,\n      ...localizedOrganizationTab(plugin.localization),\n      ...(plugin.emailFirst && {\n        views: { auth: { signIn: EmailFirstSignIn } }\n      }),\n      _localizationResolver: (\n        resolvedPlugin: AuthPluginBase,\n        context: AuthPluginLocalizationContext\n      ) => ({\n        ...resolvedPlugin,\n        ...localizedOrganizationTab(context.localization as SsoLocalization)\n      })\n    }\n  }\n)\n",
      "type": "registry:lib",
      "target": "@lib/auth/sso-plugin.ts"
    },
    {
      "path": "examples/start-shadcn-example/src/lib/auth/two-factor-methods.ts",
      "content": "/**\n * Second-factor methods a user can complete the challenge with.\n *\n * `totp` and `otp` come from the server's `twoFactorMethods` list. Backup\n * codes are always available when the server has them enabled, so they are\n * never part of that list.\n */\nexport type TwoFactorMethod = \"totp\" | \"otp\"\n\nconst TWO_FACTOR_METHODS: TwoFactorMethod[] = [\"totp\", \"otp\"]\n\n/** Auth plugin id used by Better Auth UI's two-factor integration. */\nexport const TWO_FACTOR_PLUGIN_ID = \"twoFactor\"\n\n/**\n * `sessionStorage` key holding the methods reported by the sign-in response.\n *\n * Only the non-sensitive method names are stored, never a code, token, or the\n * two-factor cookie, which stays HTTP-only.\n */\nexport const TWO_FACTOR_METHODS_STORAGE_KEY =\n  \"better-auth-ui.two-factor-methods\"\n\ntype TwoFactorRedirect = {\n  twoFactorRedirect: true\n  twoFactorMethods?: unknown\n}\n\n/** Detect the redirect payload Better Auth returns before a second factor. */\nexport function isTwoFactorRedirect(data: unknown): data is TwoFactorRedirect {\n  return (\n    typeof data === \"object\" &&\n    data !== null &&\n    (data as { twoFactorRedirect?: unknown }).twoFactorRedirect === true\n  )\n}\n\n/** Narrow arbitrary method names to the challenge views this UI supports. */\nexport function parseTwoFactorMethods(methods?: unknown): TwoFactorMethod[] {\n  if (!Array.isArray(methods)) return []\n\n  return TWO_FACTOR_METHODS.filter((method) => methods.includes(method))\n}\n\n/** Persist the enabled method names without blocking sign-in on storage errors. */\nexport function storeTwoFactorMethods(methods?: unknown) {\n  if (typeof sessionStorage === \"undefined\") return\n\n  try {\n    sessionStorage.setItem(\n      TWO_FACTOR_METHODS_STORAGE_KEY,\n      JSON.stringify(parseTwoFactorMethods(methods))\n    )\n  } catch {\n    // The challenge falls back to every method when storage is unavailable.\n  }\n}\n\n/** Read the stored methods, falling back to every supported challenge. */\nexport function readTwoFactorMethods(): TwoFactorMethod[] {\n  if (typeof sessionStorage === \"undefined\") return TWO_FACTOR_METHODS\n\n  try {\n    const stored = sessionStorage.getItem(TWO_FACTOR_METHODS_STORAGE_KEY)\n    if (!stored) return TWO_FACTOR_METHODS\n\n    const methods = parseTwoFactorMethods(JSON.parse(stored))\n    return methods.length ? methods : TWO_FACTOR_METHODS\n  } catch {\n    return TWO_FACTOR_METHODS\n  }\n}\n\n/** Clear stored method hints after the challenge finishes or is abandoned. */\nexport function clearTwoFactorMethods() {\n  if (typeof sessionStorage === \"undefined\") return\n\n  try {\n    sessionStorage.removeItem(TWO_FACTOR_METHODS_STORAGE_KEY)\n  } catch {\n    // Stale method hints are harmless and must not block navigation.\n  }\n}\n",
      "type": "registry:lib",
      "target": "@lib/auth/two-factor-methods.ts"
    },
    {
      "path": "examples/start-shadcn-example/src/lib/auth/use-sign-in-continuation.ts",
      "content": "\"use client\"\n\nimport { useAuth } from \"@better-auth-ui/react\"\nimport { useCallback } from \"react\"\nimport {\n  isTwoFactorRedirect,\n  storeTwoFactorMethods,\n  TWO_FACTOR_PLUGIN_ID\n} from \"./two-factor-methods\"\n\n/**\n * Resolve what happens after a sign-in request succeeds.\n *\n * Better Auth withholds the session when a second factor is required and\n * answers with `{ twoFactorRedirect: true, twoFactorMethods }` instead, so no\n * sign-in strategy may navigate to `redirectTo` unconditionally. This hook is\n * the single place that decision lives — every password-based form calls it\n * from `onSuccess`.\n *\n * The enabled methods are stashed in session storage (names only, never a\n * code or token) and `redirectTo` rides along in the query string so the\n * challenge view can finish the original navigation.\n *\n * The two-factor plugin is looked up by its stable id, so sign-in forms stay\n * installable without either the two-factor components or a matching release\n * of `@better-auth-ui/core`.\n *\n * @returns A callback taking the resolved data of a sign-in mutation.\n */\nexport function useSignInContinuation() {\n  const { basePaths, navigate, plugins, redirectTo } = useAuth()\n\n  const twoFactorPath = plugins.find(\n    (plugin) => plugin.id === TWO_FACTOR_PLUGIN_ID\n  )?.viewPaths?.auth?.twoFactor\n\n  return useCallback(\n    (data: unknown) => {\n      if (twoFactorPath && isTwoFactorRedirect(data)) {\n        storeTwoFactorMethods(data.twoFactorMethods)\n\n        navigate({\n          to: `${basePaths.auth}/${twoFactorPath}?redirectTo=${encodeURIComponent(redirectTo)}`\n        })\n        return\n      }\n\n      navigate({ to: redirectTo })\n    },\n    [basePaths.auth, navigate, redirectTo, twoFactorPath]\n  )\n}\n",
      "type": "registry:lib",
      "target": "@lib/auth/use-sign-in-continuation.ts"
    }
  ],
  "type": "registry:component"
}