Spacerr
  • Features
  • Pricing
  • FAQ
  • Docs
Get Access
Spacerr
  • Introduction
  • Features
  • Tech Stack
  • Setup
  • Configuration
  • Agents
  • Database
  • Jobs
  • Admin
  • Settings
  • Billing
  • Storage
  • Email
  • Support
  • Localization
  • SEO
  • Analytics
  • UI And Navigation
  • Deploying To Production
  • Testing And QA
  • Troubleshooting

Search documentation

Search documentation pages.

Documentation/Admin

Admin

Spacerr includes a protected admin dashboard for managing users, roles, account access, and support workflows. The admin area is not part of the public product surface. It is guarded by role checks and private route metadata.

What It Includes

The admin surface includes:

  • User list with avatar, name, email, role, chat count, purchase state, created date, last active date, and deactivation state.
  • Total user, active user, subscribed user, and deactivated account stats.
  • User email and role editing.
  • User deletion with self deletion blocked.
  • User impersonation through the Better Auth admin client.
  • Magic link sending from the admin panel.
  • Admin, moderator, and user roles.

Route And Access

The admin page lives at src/app/(app)/dashboard/admin/page.tsx and is available at:

txt
/dashboard/admin

The page reads the current session and only renders for admin users.

typescript
const user = await getSessionUser()

if (!user || !isAdmin(user)) {
  redirect(WebRoutes.dashboard.path)
}

return <AdminUsersTable currentUserId={user.id} />

Admin API routes perform the same role check before returning data or mutating users.

typescript
const user = await getSessionUser()

if (!user || !isAdmin(user)) {
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

User Roles

Use admin for full admin dashboard access. Use moderator for protected content workflows such as blog publishing. Use user for normal product access.

To create the first admin, create a normal account first, then update the user's role to admin in the database. The Database section covers the recommended database editing options.

API Layer

Admin API paths are centralized in ApiRoutes.

typescript
export const ApiRoutes = {
  admin: {
    users: {
      list: "/api/admin/users",
      sendMagicLinks: "/api/admin/users/magic-links",
      update: (userId: string) => `/api/admin/users/${userId}`,
      delete: (userId: string) => `/api/admin/users/${userId}`,
    },
  },
}

Browser requests go through src/features/admin/api/admin-users.api.ts, and data loading goes through TanStack Query hooks under src/features/admin/hooks.

User Management

The user table data comes from src/features/admin/repositories/admin-users.repository.ts. Keep admin data shaping in the repository, then return a UI friendly response to the table.

txt
async function getAdminUserSubscriptionStatus(user: {
  id: string
  oneTimePurchasedAt: Date | null
  stripeCustomerId: string | null
}) {
  const activeSubscriptionProduct = user.stripeCustomerId
    ? await getActiveSubscriptionProduct(user.stripeCustomerId, user.id)
    : null

  if (user.oneTimePurchasedAt || activeSubscriptionProduct) {
    return "active"
  }

  return "inactive"
}

export async function listAdminUsers(): Promise<AdminUsersListResponse> {
  const { totalUsers, users } = await loadAdminUsers()
  const adminUsers = await Promise.all(
    users.map(async (user) => ({
      id: user.id,
      name: user.name,
      image: user.image,
      email: user.email,
      role: user.role,
      chatsCount: user._count.chats,
      createdAt: user.createdAt.toISOString(),
      lastActiveAt: user.lastActiveAt?.toISOString() ?? null,
      deactivatedAt: user.deactivatedAt?.toISOString() ?? null,
      subscriptionStatus: await getAdminUserSubscriptionStatus(user),
    }))
  )

  return {
    totalUsers,
    users: adminUsers,
  }
}

Keep admin data shaping in the repository. The starter stores one time access on the user record with oneTimePurchasedAt, keeps the Stripe customer ID as stripeCustomerId, and resolves active subscription access from Stripe. The table can then show one clear access status while still letting you change the billing model behind it. Keep route handlers thin and limited to auth checks, validation, and response handling.

Magic Links And Impersonation

Admins can send magic links for account access support and impersonate a user when they need to reproduce account specific issues.

typescript
export function useMutateImpersonateAdminUser() {
  return useMutation({
    mutationFn: async (userId: string) => authClient.admin.impersonateUser({ userId }),
  })
}

Use impersonation carefully. It is intended for support and debugging, not normal product usage.

SEO And Privacy

Admin routes should stay private. The admin page uses noindex metadata, and admin API responses include noindex headers.

typescript
const noIndexHeaders = {
  "X-Robots-Tag": "noindex, nofollow, noarchive, nosnippet",
}

Do not add admin pages to public navigation, sitemap priority, marketing copy, or structured data.

Where To Customize

Use these files first:

  • src/features/admin/components/admin-users-table.tsx for the dashboard table.
  • src/features/admin/components/admin-users-columns.tsx for visible columns and row actions.
  • src/features/admin/components/admin-user-edit-dialog.tsx for editing email and role.
  • src/features/admin/components/admin-send-magic-links-card.tsx for magic link sending.
  • src/features/admin/repositories/admin-users.repository.ts for admin data shaping.
  • src/features/admin/schemas for admin request validation.
  • src/app/api/admin/users for admin route handlers.
Passkeys And 2FA

Learn how passkeys, two factor authentication, backup codes, and account security settings work.

Settings

Learn how account settings, profile details, notifications, appearance, language, and legal links are structured.

On this page
What It IncludesRoute And AccessUser RolesAPI LayerUser ManagementMagic Links And ImpersonationSEO And PrivacyWhere To Customize