Skip to main content
useMerchant and useRoles are admin hooks — they expose operations that require elevated permissions on the backend. Build them into admin-only routes protected by AuthGuard with requiredRoles={['ADMIN']}.

useMerchant

Fetches GET /merchants/me on mount. Exposes methods for updating merchant settings, managing the theme, and generating API keys. This is the hook behind the MerchantSettings component.

Return Shape

return {
  merchant,     // MerchantResponse | null
  isLoading,    // boolean
  error,        // string | null
  refresh,      // () => Promise<void> — re-fetches GET /merchants/me
  update,       // (data: UpdateMerchantRequest) => Promise<MerchantResponse>
  addApiKey,    // (label: string) => Promise<GenerateApiKeyResponse>
  removeApiKey, // (keyId: string) => Promise<void>
  clearError,   // () => void
}
merchant
MerchantResponse | null
The loaded merchant record. null while loading on mount. Contains tenant settings including primaryColor, secondaryColor, loginMode, signupMode, and totpAllowed.
isLoading
boolean
true during the initial fetch and while an update call is in progress.
error
string | null
Last error message from any operation. Cleared automatically at the start of the next operation or manually via clearError().
refresh
() => Promise<void>
Re-fetches GET /merchants/me and updates the local merchant state.
update
(data: UpdateMerchantRequest) => Promise<MerchantResponse>
Updates the merchant record via PATCH /merchants/:id. Returns the updated merchant.
Calling update() automatically applies the updated primaryColor and secondaryColor to the current session via applyTheme(). You do not need to call any theme method manually — the UI updates immediately.
addApiKey
(label: string) => Promise<GenerateApiKeyResponse>
Generates a new API key for this merchant. Throws 'No merchant loaded' if called before mount completes — guard with if (!merchant) return before calling.
removeApiKey
(keyId: string) => Promise<void>
Revokes an API key by its ID. Throws 'No merchant loaded' if the merchant has not loaded yet.
clearError
() => void
Manually clear the error state.

MerchantResponse Type

export interface MerchantResponse {
  id: string
  name: string
  mid: string
  app: string
  subdomain: string
  publicApiKey: string
  logoUrl: string | null
  primaryColor: string | null
  secondaryColor: string | null
  adminUserId: string
  createdAt: string
  signupMode?: SignupMode       // 'OPEN' | 'INVITATION_ONLY'
  loginMode?: LoginMode         // 'PASSWORD' | 'OTP_EMAIL' | 'PASSWORD_OR_OTP_EMAIL'
  totpAllowed?: boolean
  allowUserDisableTotp?: boolean
}

UpdateMerchantRequest Type

export interface UpdateMerchantRequest {
  name?: string
  logoUrl?: string | null
  primaryColor?: string | null
  secondaryColor?: string | null
  signupMode?: SignupMode
  loginMode?: LoginMode
  totpAllowed?: boolean | null
  allowUserDisableTotp?: boolean | null
}

Updating Theme Colors

primaryColor and secondaryColor in UpdateMerchantRequest are CSS hex color strings (e.g., #133e8b). When you call update() with new colors, the library calls applyTheme() internally — all ThemeProvider CSS variables update immediately without a page reload.
const { update } = useMerchant()

await update({
  primaryColor: '#1a56db',
  secondaryColor: '#7e3af2',
})
// CSS variables --color-primary and --color-secondary are updated immediately

API Key Management

addApiKey(label) calls POST /merchants/:id/api-keys and returns the full API key in GenerateApiKeyResponse.apiKey. This is the ONLY time the key is available — it is not returned again after creation.
export interface GenerateApiKeyResponse {
  keyId: string
  apiKey: string
  keyPrefix: string
  label: string
}
The apiKey value in GenerateApiKeyResponse is shown only once — store it securely immediately. Subsequent calls to addApiKey() generate new keys; lost keys must be revoked and replaced.
const { addApiKey, removeApiKey, merchant } = useMerchant()

// Generate a new key
const result = await addApiKey('Production web app')
console.log(result.apiKey)  // Store securely — only shown once
console.log(result.keyId)   // Save this for later revocation

// Revoke a key by its ID
await removeApiKey(result.keyId)

Protecting Admin Routes

Always protect any route using useMerchant with AuthGuard requiredRoles={['ADMIN']}. Without this gate, any authenticated user — not just admins — could reach the merchant settings UI.
<AuthGuard
  fallback={<Navigate to="/login" />}
  requiredRoles={['ADMIN']}
>
  <MerchantSettingsPage />
</AuthGuard>

useRoles

useRoles provides full role lifecycle management — list, create, delete, assign, and revoke. It fetches all roles for the app on mount. This is the hook behind the RolesManager component.

Return Shape

return {
  roles,    // RoleResponse[]
  isLoading, // boolean
  error,    // string | null
  refresh,  // () => Promise<void> — re-fetches GET /roles
  add,      // (data: CreateRoleRequest) => Promise<RoleResponse>
  remove,   // (roleId: string) => Promise<void>
  assign,   // (data: AssignRoleRequest) => Promise<void>
  revoke,   // (userId: string, roleId: string) => Promise<void>
}
roles
RoleResponse[]
All roles defined for this app — both SYSTEM (built-in) and MERCHANT (custom) roles.
isLoading
boolean
true during the initial roles fetch.
error
string | null
Last error message from a fetch operation.
refresh
() => Promise<void>
Re-fetches GET /roles and updates the local roles array.
add
(data: CreateRoleRequest) => Promise<RoleResponse>
Creates a new MERCHANT-owned role and appends it to the local roles array. Returns the created role including its generated id.
remove
(roleId: string) => Promise<void>
Deletes a MERCHANT-owned role and removes it from the local array.
Only MERCHANT-owned roles (role.owner === 'MERCHANT') can be deleted. SYSTEM roles are built-in and cannot be removed.
assign
(data: AssignRoleRequest) => Promise<void>
Assigns a role to a user. Accepts an optional expiresAt ISO 8601 string for time-limited role grants. Does NOT update the local roles array — the role list is app-level, not user-level.
revoke
(userId: string, roleId: string) => Promise<void>
Removes a role from a user. Does NOT update the local roles array.

Key Types

export interface RoleResponse {
  id: string
  name: string
  app: string | null
  mid: string | null
  custom: boolean
  priority: number
  owner: RoleOwner    // 'SYSTEM' | 'MERCHANT'
  createdAt: string
}
export interface CreateRoleRequest {
  name: string
  app?: string
  mid?: string
  custom?: boolean
  priority: number
}
export interface AssignRoleRequest {
  userId: string
  roleId: string
  app: string
  mid: string
  expiresAt?: string    // ISO 8601 — optional role expiry
}

Role Hierarchy

Roles have two owner values:
  • SYSTEM — built-in roles defined by the ailita backend (e.g., ADMIN, USER). Cannot be deleted.
  • MERCHANT — custom roles created by your tenant. Created via add(), deleted via remove().
The priority field determines precedence. Lower priority number = higher authority. Assign MERCHANT custom roles priorities of 100 or higher to avoid conflicts with SYSTEM roles (which typically use 1–10).

Creating and Assigning a Custom Role

1

Create the role definition

Call add() with a name and priority. Use a priority of 100 or higher for custom roles.
2

Get the role ID from the result

The returned RoleResponse contains the generated id — save it for the assign call.
3

Assign the role to a user

Call assign() with the user ID, role ID, and tenant context from useMerchant().merchant.
const { merchant } = useMerchant()
const { add, assign } = useRoles()

// Step 1: Create the role
const moderatorRole = await add({
  name: 'MODERATOR',
  priority: 100,
  custom: true,
})

// Step 2: Assign to a user
await assign({
  userId: 'user-id-here',
  roleId: moderatorRole.id,
  app: merchant!.app,
  mid: merchant!.mid,
})
Always read app and mid from useMerchant().merchant — do not hardcode them. These values tie the assignment to the correct tenant.

Time-Limited Role Grants

AssignRoleRequest.expiresAt accepts an ISO 8601 datetime string for temporary role grants:
await assign({
  userId: targetUserId,
  roleId: trialRoleId,
  app: merchant!.app,
  mid: merchant!.mid,
  expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 7 days
})

Using Roles for Access Control

Do not use useRoles for access control checks. useRoles manages the role registry — not the current user’s permissions.For route protection: use AuthGuard with requiredRoles (AND logic — user must have all listed roles). For programmatic checks: read useAuthContext().roles. useRoles is for admin operations only.

Next steps

Hooks Reference

All five hooks in one place — useAuth, useUser, useSessions, useMerchant, and useRoles.

Component Reference

Prop tables for ProfileForm, PasswordForm, EmailChangeForm, SessionsList, and TOTPSetup.