Skip to main content
The Admin Dashboard provides comprehensive user management, platform metrics, and administrative controls for AdRecon. Access is restricted to users with app_metadata.user_type = 'admin'.

Accessing the Admin Dashboard

Navigate to /admin or /app/admin while signed in as an admin user. Non-admin users will see an access-required message. Route: /admin or /app/admin Component: AdminDashboard.tsx

User Statistics Overview

The dashboard displays real-time platform metrics across seven stat cards.

Stat Cards

StatDescriptionIcon
Total UsersCount of all registered usersUsers
AdminsCount of users with admin privilegesShieldCheck
Active (7d)Users who signed in within the last 7 daysClock3
Email ConfirmedUsers with confirmed email addressesMailCheck
Saved AdsTotal saved ads across all usersBookMarked
ProjectsTotal projects created by all usersDatabase
Indexed AdsTotal ads in the platform feedDatabase
Data source: The stats are fetched from /api/admin/users (AdminDashboard.tsx:301-315).

Coverage Metrics

Three coverage percentage cards show admin distribution, email verification rate, and recent activity:
  • Admin Coverage: (adminUsers / totalUsers) * 100
  • Email Verification: (confirmedUsers / totalUsers) * 100
  • 7-Day Activity: (recentlyActiveUsers7d / totalUsers) * 100
Each metric displays a progress bar with color-coded themes.

User Directory

The user directory is the primary interface for listing, searching, filtering, and managing user accounts.

Search and Filters

1

Search by email or name

Use the search input to filter users by email address or display name. Search is debounced by 260ms to reduce query load.
2

Filter by role

Select All Roles, Admins, or Members to narrow the user list.
3

Filter by confirmation status

Select All Status, Confirmed, or Unconfirmed to show only verified or unverified email accounts.
Filter reset: Click Clear filters to reset all search and filter fields (AdminDashboard.tsx:475-479).

Pagination

The user directory displays 25 users per page. Use Previous and Next buttons to navigate pages. Pagination state (AdminDashboard.tsx:259-264):
const [pagination, setPagination] = useState<AdminPagination>({
  page: 1,
  perPage: PAGE_SIZE,
  total: 0,
  totalPages: 1,
});

User Risk Profiles

Each user is assigned a risk profile based on activity patterns:
Risk LevelConditionBadge Color
ElevatedAdmin without confirmed emailRed
DormantNo sign-in historyOrange
StaleNo activity for 120+ daysOrange
HealthyRecently active accountGreen
Risk calculation (AdminDashboard.tsx:188-220):
function getUserRiskProfile(user: AdminUserRecord): { label: string; detail: string; tone: string } {
  const daysSinceSignIn = getDaysSince(user.lastSignInAt);

  if (user.userType === 'admin' && !user.emailConfirmedAt) {
    return {
      label: 'Elevated',
      detail: 'Admin without confirmed email.',
      tone: 'border-[#EF4444]/30 bg-[#EF4444]/10 text-[#FCA5A5]',
    };
  }

  if (daysSinceSignIn === null) {
    return {
      label: 'Dormant',
      detail: 'No sign-in history.',
      tone: 'border-[#F59E0B]/30 bg-[#F59E0B]/10 text-[#FCD34D]',
    };
  }

  if (daysSinceSignIn >= 120) {
    return {
      label: 'Stale',
      detail: `No activity for ${daysSinceSignIn} days.`,
      tone: 'border-[#F59E0B]/30 bg-[#F59E0B]/10 text-[#FCD34D]',
    };
  }

  return {
    label: 'Healthy',
    detail: 'Recently active account.',
    tone: 'border-[#34D399]/30 bg-[#34D399]/10 text-[#6EE7B7]',
  };
}

User Management Actions

Changing User Roles

Admins can promote members to admin or demote admins to member status.
1

Locate the user

Search or browse the user directory to find the target user.
2

Toggle the role dropdown

Each user row displays a role selector with Admin and Member options.
3

Select the new role

Click the desired role. The change is applied immediately via PATCH /api/admin/users.
4

Confirm the change

A success toast confirms the update, and the user directory refreshes to reflect the new role.
You cannot remove your own admin access. The system prevents self-demotion with the error: “You cannot remove your own admin access from this session.”
Role change handler (AdminDashboard.tsx:481-506):
const handleUserTypeChange = async (user: AdminUserRecord, nextUserType: AdminUserType) => {
  if (user.userType === nextUserType) {
    return;
  }

  if (user.id === currentUserId && nextUserType !== 'admin') {
    toast.error('You cannot remove your own admin access from this session.');
    return;
  }

  const previousUserType = user.userType;
  setUsers((prev) => prev.map((item) => (item.id === user.id ? { ...item, userType: nextUserType } : item)));
  markPending(user.id, 'role');

  try {
    const updatedUser = await updateAdminUserType(user.id, nextUserType);
    setUsers((prev) => prev.map((item) => (item.id === user.id ? updatedUser : item)));
    setRefreshTick((prev) => prev + 1);
    toast.success(`Updated ${user.email || 'user'} to ${nextUserType}.`);
  } catch (err) {
    setUsers((prev) => prev.map((item) => (item.id === user.id ? { ...item, userType: previousUserType } : item)));
    toast.error(toUserFacingError(err, 'Failed to update user type.'));
  } finally {
    clearPending(user.id);
  }
};

Sending Password Reset Emails

Admins can trigger password reset emails for any user.
1

Locate the user

Find the user in the directory.
2

Click the reset button

Each user row includes a password reset action button (key icon).
3

Email is sent

The system calls POST /api/admin/users with action: 'send_password_reset'. The user receives a reset email at their registered address.
Reset handler (AdminDashboard.tsx:508-519):
const handleSendReset = async (user: AdminUserRecord) => {
  markPending(user.id, 'reset');

  try {
    await sendAdminPasswordReset(user.id);
    toast.success(`Password reset email sent to ${user.email || 'user'}.`);
  } catch (err) {
    toast.error(toUserFacingError(err, 'Failed to send password reset email.'));
  } finally {
    clearPending(user.id);
  }
};

Deleting Users

Admins can permanently delete user accounts. This action is irreversible.
1

Locate the user

Find the user in the directory.
2

Click the delete button

Each user row includes a delete action button (trash icon).
3

Confirm deletion

A browser confirmation dialog appears: “Delete user ? This cannot be undone.”
4

User is deleted

Upon confirmation, the system calls DELETE /api/admin/users?userId={id} and removes the user from the directory.
You cannot delete your own account from the admin panel. The system blocks self-deletion with the error: “You cannot delete your own account from this panel.”
Delete handler (AdminDashboard.tsx:521-543):
const handleDelete = async (user: AdminUserRecord) => {
  if (user.id === currentUserId) {
    toast.error('You cannot delete your own account from this panel.');
    return;
  }

  const confirmed = window.confirm(`Delete user ${user.email || user.id}? This cannot be undone.`);
  if (!confirmed) {
    return;
  }

  markPending(user.id, 'delete');

  try {
    await deleteAdminUser(user.id);
    toast.success(`Deleted ${user.email || 'user'} successfully.`);
    setRefreshTick((prev) => prev + 1);
  } catch (err) {
    toast.error(toUserFacingError(err, 'Failed to delete user.'));
  } finally {
    clearPending(user.id);
  }
};

Additional Admin Controls

Accent Theme Toggle

Admins can switch the global accent theme between Gold and Green. This setting applies to all users. Theme toggle (AdminDashboard.tsx:545-561):
const handleThemeToggle = async () => {
  const next: AccentTheme = accentTheme === 'gold' ? 'green' : 'gold';
  setAccentTheme(next);
  applyTheme(next);
  setThemeSaving(true);
  try {
    await setTheme(next);
    toast.success(`Accent theme switched to ${next}.`);
  } catch (err) {
    // revert on failure
    setAccentTheme(accentTheme);
    applyTheme(accentTheme);
    toast.error(toUserFacingError(err, 'Failed to save theme. Make sure the app_settings table exists.'));
  } finally {
    setThemeSaving(false);
  }
};

Phantom Growth Settings

The Phantom Growth panel controls inflated ad count displays on the unfiltered dashboard. Admins configure:
  • Min Daily: Minimum daily phantom ad growth
  • Max Daily: Maximum daily phantom ad growth
  • Base Multiplier: Multiplier applied to the real indexed ad count
  • Anchor Date: Start date for phantom growth calculations
Live preview shows the projected phantom total based on current settings.

Meta Pixel Configuration

Admins can set or remove a Facebook/Meta tracking pixel ID that fires on every page load (including the login screen).
1

Enter Pixel ID

Input a 10–20 digit Pixel ID from Meta Events Manager.
2

Save

Click Save to inject the pixel globally.
3

Remove (optional)

Click Remove to clear the pixel configuration.

Ads Feed Mode Toggle

Admins can control which ads are shown in the “All Ads” tab:
  • All Ads: Show the current full feed
  • SpyHero Imported: Only ads imported from SpyHero
  • Legacy Only: Hide SpyHero imported ads

Ad Scraper Panel

The Ad Scraper panel lets admins trigger Facebook Ad Library scraping runs via Apify. This populates the database with fresh ad creatives.
1

Select target count

Choose how many ads to scrape: 1,000, 2,500, or 5,000 ads
2

Start scrape

Click Start Scrape to launch an Apify actor run with curated search queries across niches (Health, Wealth, BizOp, Relationships, Survival)
3

Monitor progress

The terminal-style interface shows real-time progress:
  • Scraping phase: Apify actor status updates
  • Ingesting phase: Processing and inserting ads into the database
  • Stats: Displays deduplication metrics (skipped duplicates, non-English ads, malformed entries, final insert count)
Ingestion filtering (api/admin/scrape.js:41-52): The scraper automatically filters out:
  • Duplicate ad content
  • Non-English ads (language detection)
  • Ads without advertiser information
  • Ads without media assets
  • Malformed ad data
Scrape runs are admin-only operations that directly modify the apify_ads_raw table. The Apify token and actor ID are configured via environment variables (APIFY_TOKEN, ACTOR_ID).

Refresh and Sync

Click the Refresh button in the header to reload user data, stats, and pagination state. The sync indicator shows Synced when data is current and Refreshing during updates.

Error Handling

If the admin dashboard fails to load, an error screen displays with:
  • Admin Access Required: Your account lacks admin privileges
  • Session Expired: Your session token is invalid or expired
  • Admin API Error: A generic failure occurred
Each error includes a Retry option or prompts you to sign in again.

Build docs developers (and LLMs) love