useAdmin

Administrative operations for user management, tenant configuration, and system monitoring.

Admin Features: User lifecycle, role management, tenant settings, and analytics

Import

import { useAdmin } from '@heliorim/sdk-react';

Type Signature

interface AdminState {
  isAdmin: boolean;
  permissions: Permission[];
  users: User[];
  tenantInfo: TenantInfo | null;
  analytics: Analytics | null;
  isLoading: boolean;
  error: Error | null;
}

interface AdminActions {
  // User Management
  listUsers: (filters?: UserFilters) => Promise<User[]>;
  getUser: (userId: string) => Promise<User>;
  createUser: (userData: CreateUserData) => Promise<User>;
  updateUser: (userId: string, updates: Partial<User>) => Promise<User>;
  deleteUser: (userId: string) => Promise<void>;
  suspendUser: (userId: string, reason: string) => Promise<void>;
  reactivateUser: (userId: string) => Promise<void>;

  // Role Management
  assignRole: (userId: string, role: Role) => Promise<void>;
  removeRole: (userId: string, role: Role) => Promise<void>;
  createCustomRole: (role: CustomRole) => Promise<Role>;

  // Tenant Management
  updateTenantSettings: (settings: TenantSettings) => Promise<void>;
  getTenantUsage: () => Promise<TenantUsage>;
  manageBilling: (action: BillingAction) => Promise<void>;

  // Analytics
  getAnalytics: (period: Period) => Promise<Analytics>;
  exportReport: (type: ReportType) => Promise<Blob>;
}

interface User {
  id: string;
  email: string;
  name: string;
  roles: Role[];
  status: 'active' | 'suspended' | 'pending' | 'deleted';
  createdAt: number;
  lastLoginAt?: number;
  passkeys: PasskeyInfo[];
  metadata?: Record<string, any>;
}

interface TenantInfo {
  id: string;
  name: string;
  plan: 'starter' | 'pro' | 'enterprise';
  userLimit: number;
  currentUsers: number;
  settings: TenantSettings;
  createdAt: number;
}

interface Analytics {
  period: Period;
  totalUsers: number;
  activeUsers: number;
  newUsers: number;
  authentications: number;
  failedAuthentications: number;
  averageSessionDuration: number;
  topEvents: Event[];
}

type UseAdminReturn = AdminState & AdminActions;

Basic Usage

function AdminDashboard() {
  const {
    isAdmin,
    permissions,
    users,
    tenantInfo,
    listUsers,
    suspendUser,
    getAnalytics
  } = useAdmin();

  const [selectedUser, setSelectedUser] = useState<User | null>(null);
  const [analytics, setAnalytics] = useState<Analytics | null>(null);

  useEffect(() => {
    if (isAdmin) {
      // Load initial data
      listUsers();
      getAnalytics('week').then(setAnalytics);
    }
  }, [isAdmin]);

  const handleSuspendUser = async (userId: string) => {
    const reason = prompt('Reason for suspension:');
    if (reason) {
      try {
        await suspendUser(userId, reason);
        alert('User suspended successfully');
        await listUsers(); // Refresh list
      } catch (err) {
        console.error('Failed to suspend user:', err);
      }
    }
  };

  if (!isAdmin) {
    return <div>Access denied: Admin privileges required</div>;
  }

  return (
    <div className="admin-dashboard">
      <h1>Admin Dashboard</h1>

      <div className="tenant-info">
        <h2>{tenantInfo?.name}</h2>
        <p>Plan: {tenantInfo?.plan}</p>
        <p>Users: {tenantInfo?.currentUsers} / {tenantInfo?.userLimit}</p>
      </div>

      {analytics && (
        <div className="analytics-summary">
          <h3>Analytics (Last Week)</h3>
          <div className="stats-grid">
            <div>Total Users: {analytics.totalUsers}</div>
            <div>Active Users: {analytics.activeUsers}</div>
            <div>New Users: {analytics.newUsers}</div>
            <div>Auth Success Rate: {
              ((analytics.authentications /
                (analytics.authentications + analytics.failedAuthentications)) * 100
              ).toFixed(1)}%
            </div>
          </div>
        </div>
      )}

      <div className="users-table">
        <h3>User Management</h3>
        <table>
          <thead>
            <tr>
              <th>Email</th>
              <th>Name</th>
              <th>Status</th>
              <th>Roles</th>
              <th>Last Login</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody>
            {users.map(user => (
              <tr key={user.id}>
                <td>{user.email}</td>
                <td>{user.name}</td>
                <td>{user.status}</td>
                <td>{user.roles.join(', ')}</td>
                <td>{user.lastLoginAt ?
                  new Date(user.lastLoginAt).toLocaleDateString() :
                  'Never'
                }</td>
                <td>
                  <button onClick={() => setSelectedUser(user)}>Edit</button>
                  {user.status === 'active' && (
                    <button onClick={() => handleSuspendUser(user.id)}>
                      Suspend
                    </button>
                  )}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

User Management

User Creation & Invitation

function UserInvitation() {
  const { createUser, assignRole } = useAdmin();

  const inviteUser = async (formData: FormData) => {
    try {
      // Create user with pending status
      const user = await createUser({
        email: formData.get('email') as string,
        name: formData.get('name') as string,
        status: 'pending',
        sendInvite: true,
        inviteMessage: 'Welcome to our platform!'
      });

      // Assign initial role
      const role = formData.get('role') as string;
      if (role) {
        await assignRole(user.id, role as Role);
      }

      console.log('User invited:', user);
      return user;
    } catch (err) {
      console.error('Failed to invite user:', err);
      throw err;
    }
  };

  const bulkInvite = async (emails: string[]) => {
    const results = {
      success: [] as User[],
      failed: [] as { email: string; error: string }[]
    };

    for (const email of emails) {
      try {
        const user = await createUser({
          email,
          status: 'pending',
          sendInvite: true
        });
        results.success.push(user);
      } catch (err: any) {
        results.failed.push({ email, error: err.message });
      }
    }

    return results;
  };

  return (
    <div className="user-invitation">
      <h3>Invite Users</h3>

      <form onSubmit={async (e) => {
        e.preventDefault();
        const formData = new FormData(e.target as HTMLFormElement);
        await inviteUser(formData);
        (e.target as HTMLFormElement).reset();
      }}>
        <input name="email" type="email" placeholder="Email" required />
        <input name="name" placeholder="Name" required />
        <select name="role">
          <option value="">Select Role</option>
          <option value="user">User</option>
          <option value="moderator">Moderator</option>
          <option value="admin">Admin</option>
        </select>
        <button type="submit">Send Invitation</button>
      </form>

      <div className="bulk-invite">
        <h4>Bulk Invite</h4>
        <textarea
          placeholder="Enter emails (one per line)"
          onBlur={async (e) => {
            const emails = e.target.value.split('\n').filter(e => e.trim());
            if (emails.length > 0) {
              const results = await bulkInvite(emails);
              console.log('Bulk invite results:', results);
            }
          }}
        />
      </div>
    </div>
  );
}

User Profile Management

function UserProfileManager() {
  const { getUser, updateUser, deleteUser } = useAdmin();
  const [editingUser, setEditingUser] = useState<User | null>(null);

  const loadUserProfile = async (userId: string) => {
    const user = await getUser(userId);
    setEditingUser(user);
  };

  const updateUserProfile = async (updates: Partial<User>) => {
    if (!editingUser) return;

    try {
      const updated = await updateUser(editingUser.id, updates);
      setEditingUser(updated);
      console.log('User updated:', updated);
    } catch (err) {
      console.error('Update failed:', err);
    }
  };

  const handleDeleteUser = async () => {
    if (!editingUser) return;

    if (confirm(`Delete user ${editingUser.email}? This action cannot be undone.`)) {
      try {
        await deleteUser(editingUser.id);
        setEditingUser(null);
        console.log('User deleted');
      } catch (err) {
        console.error('Delete failed:', err);
      }
    }
  };

  const resetUserPasskeys = async () => {
    if (!editingUser) return;

    await updateUser(editingUser.id, {
      passkeys: [],
      metadata: {
        ...editingUser.metadata,
        passkeysResetAt: Date.now(),
        passkeysResetBy: getCurrentAdmin()
      }
    });
  };

  return (
    <div className="user-profile-manager">
      {editingUser && (
        <div>
          <h3>Edit User: {editingUser.email}</h3>

          <form onSubmit={(e) => {
            e.preventDefault();
            const formData = new FormData(e.target as HTMLFormElement);
            updateUserProfile({
              name: formData.get('name') as string,
              metadata: {
                department: formData.get('department'),
                title: formData.get('title')
              }
            });
          }}>
            <input name="name" defaultValue={editingUser.name} />
            <input name="department" placeholder="Department" />
            <input name="title" placeholder="Job Title" />
            <button type="submit">Update Profile</button>
          </form>

          <div className="passkey-management">
            <h4>Passkeys ({editingUser.passkeys.length})</h4>
            {editingUser.passkeys.map(pk => (
              <div key={pk.id}>
                {pk.name} - Created: {new Date(pk.createdAt).toLocaleDateString()}
              </div>
            ))}
            <button onClick={resetUserPasskeys}>Reset All Passkeys</button>
          </div>

          <div className="danger-zone">
            <h4>Danger Zone</h4>
            <button className="danger" onClick={handleDeleteUser}>
              Delete User
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

Role & Permission Management

Role Assignment

function RoleManager() {
  const { users, assignRole, removeRole, createCustomRole } = useAdmin();

  const predefinedRoles = [
    { name: 'admin', permissions: ['all'] },
    { name: 'moderator', permissions: ['read', 'write', 'moderate'] },
    { name: 'user', permissions: ['read', 'write'] },
    { name: 'viewer', permissions: ['read'] }
  ];

  const createAndAssignCustomRole = async () => {
    // Create custom role
    const customRole = await createCustomRole({
      name: 'content_manager',
      permissions: ['read', 'write', 'publish', 'moderate_content'],
      description: 'Can manage and publish content',
      maxUsers: 10
    });

    // Assign to specific users
    const contentTeam = users.filter(u =>
      u.metadata?.department === 'content'
    );

    for (const user of contentTeam) {
      await assignRole(user.id, customRole);
    }
  };

  const bulkRoleUpdate = async (userIds: string[], role: Role, action: 'assign' | 'remove') => {
    const results = [];

    for (const userId of userIds) {
      try {
        if (action === 'assign') {
          await assignRole(userId, role);
        } else {
          await removeRole(userId, role);
        }
        results.push({ userId, success: true });
      } catch (err) {
        results.push({ userId, success: false, error: err });
      }
    }

    return results;
  };

  return (
    <div className="role-manager">
      <h3>Role Management</h3>

      <div className="role-grid">
        {predefinedRoles.map(role => (
          <div key={role.name} className="role-card">
            <h4>{role.name}</h4>
            <p>Permissions: {role.permissions.join(', ')}</p>
            <p>Users: {
              users.filter(u => u.roles.includes(role.name as Role)).length
            }</p>
          </div>
        ))}
      </div>

      <button onClick={createAndAssignCustomRole}>
        Create Content Manager Role
      </button>

      <div className="bulk-operations">
        <h4>Bulk Role Assignment</h4>
        <button onClick={() => {
          const selectedUsers = getSelectedUserIds();
          bulkRoleUpdate(selectedUsers, 'moderator', 'assign');
        }}>
          Assign Moderator to Selected
        </button>
      </div>
    </div>
  );
}

Tenant Management

Tenant Configuration

function TenantSettings() {
  const { tenantInfo, updateTenantSettings, getTenantUsage } = useAdmin();
  const [usage, setUsage] = useState<TenantUsage | null>(null);

  useEffect(() => {
    getTenantUsage().then(setUsage);
  }, []);

  const updateSettings = async (settings: Partial<TenantSettings>) => {
    try {
      await updateTenantSettings({
        ...tenantInfo!.settings,
        ...settings
      });
      console.log('Settings updated');
    } catch (err) {
      console.error('Failed to update settings:', err);
    }
  };

  return (
    <div className="tenant-settings">
      <h3>Tenant Configuration</h3>

      <div className="settings-form">
        <h4>Authentication Settings</h4>
        <label>
          <input
            type="checkbox"
            defaultChecked={tenantInfo?.settings.requireUserVerification}
            onChange={(e) => updateSettings({
              requireUserVerification: e.target.checked
            })}
          />
          Always require user verification
        </label>

        <label>
          <input
            type="checkbox"
            defaultChecked={tenantInfo?.settings.allowCrossPlatform}
            onChange={(e) => updateSettings({
              allowCrossPlatform: e.target.checked
            })}
          />
          Allow cross-platform authenticators
        </label>

        <label>
          Session timeout (minutes):
          <input
            type="number"
            defaultValue={tenantInfo?.settings.sessionTimeout}
            onChange={(e) => updateSettings({
              sessionTimeout: parseInt(e.target.value)
            })}
          />
        </label>
      </div>

      <div className="branding">
        <h4>Branding</h4>
        <input
          placeholder="Company Name"
          defaultValue={tenantInfo?.settings.companyName}
          onBlur={(e) => updateSettings({ companyName: e.target.value })}
        />
        <input
          placeholder="Logo URL"
          defaultValue={tenantInfo?.settings.logoUrl}
          onBlur={(e) => updateSettings({ logoUrl: e.target.value })}
        />
        <input
          type="color"
          defaultValue={tenantInfo?.settings.primaryColor}
          onChange={(e) => updateSettings({ primaryColor: e.target.value })}
        />
      </div>

      {usage && (
        <div className="usage-stats">
          <h4>Current Usage</h4>
          <div>API Calls: {usage.apiCalls} / {usage.apiCallLimit}</div>
          <div>Storage: {usage.storageUsed}MB / {usage.storageLimit}MB</div>
          <div>Active Sessions: {usage.activeSessions}</div>
          <div>Bandwidth: {usage.bandwidthUsed}GB / {usage.bandwidthLimit}GB</div>
        </div>
      )}
    </div>
  );
}

Analytics & Reporting

Dashboard Analytics

function AdminAnalytics() {
  const { getAnalytics, exportReport } = useAdmin();
  const [period, setPeriod] = useState<Period>('week');
  const [analytics, setAnalytics] = useState<Analytics | null>(null);

  useEffect(() => {
    getAnalytics(period).then(setAnalytics);
  }, [period]);

  const downloadReport = async (type: ReportType) => {
    const blob = await exportReport(type);
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `report-${type}-${Date.now()}.csv`;
    a.click();
  };

  return (
    <div className="admin-analytics">
      <h3>Analytics Dashboard</h3>

      <div className="period-selector">
        <button onClick={() => setPeriod('day')}>24 Hours</button>
        <button onClick={() => setPeriod('week')}>7 Days</button>
        <button onClick={() => setPeriod('month')}>30 Days</button>
        <button onClick={() => setPeriod('year')}>12 Months</button>
      </div>

      {analytics && (
        <>
          <div className="metrics-grid">
            <div className="metric">
              <h4>Total Users</h4>
              <p className="value">{analytics.totalUsers}</p>
              <p className="change">+{analytics.newUsers} new</p>
            </div>

            <div className="metric">
              <h4>Active Users</h4>
              <p className="value">{analytics.activeUsers}</p>
              <p className="percent">
                {((analytics.activeUsers / analytics.totalUsers) * 100).toFixed(1)}%
              </p>
            </div>

            <div className="metric">
              <h4>Authentications</h4>
              <p className="value">{analytics.authentications}</p>
              <p className="failed">Failed: {analytics.failedAuthentications}</p>
            </div>

            <div className="metric">
              <h4>Avg Session</h4>
              <p className="value">
                {Math.round(analytics.averageSessionDuration / 60)} min
              </p>
            </div>
          </div>

          <div className="top-events">
            <h4>Top Events</h4>
            <table>
              <thead>
                <tr>
                  <th>Event</th>
                  <th>Count</th>
                  <th>Users</th>
                </tr>
              </thead>
              <tbody>
                {analytics.topEvents.map(event => (
                  <tr key={event.name}>
                    <td>{event.name}</td>
                    <td>{event.count}</td>
                    <td>{event.uniqueUsers}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </>
      )}

      <div className="export-options">
        <h4>Export Reports</h4>
        <button onClick={() => downloadReport('users')}>
          Export Users Report
        </button>
        <button onClick={() => downloadReport('authentication')}>
          Export Auth Report
        </button>
        <button onClick={() => downloadReport('compliance')}>
          Export Compliance Report
        </button>
      </div>
    </div>
  );
}

Security & Compliance

Admin Access Control:

  • Requires admin role authentication
  • All actions are audit logged
  • Sensitive operations require re-authentication
  • IP restrictions can be enforced
  • Session monitoring for admin accounts

Best Practices

✓ Recommended

  • Implement role-based access control
  • Log all administrative actions
  • Require 2FA for admin accounts
  • Regular audit of user permissions
  • Automated alerts for suspicious activity
  • Implement rate limiting on admin APIs

⚠️ Avoid

  • Sharing admin credentials
  • Granting unnecessary admin privileges
  • Skipping audit logs
  • Long-lived admin sessions
  • Bulk operations without confirmation

Related Hooks