import { create } from 'zustand' import type { AccountMigrationImpact, AccountMigrationRecord, AdminUserRepository, AdminUserSummary, UserRole, } from '../data/AdminUserRepository' import { SupabaseAdminUserRepository } from '../data/SupabaseAdminUserRepository' const repository: AdminUserRepository = new SupabaseAdminUserRepository() interface AdminUserStoreState { users: AdminUserSummary[] isLoaded: boolean /** The account-migration audit log — loaded on demand (opening the * migration UI), not alongside users on every app load. */ migrations: AccountMigrationRecord[] loadUsers: () => Promise updateRole: (userId: string, role: UserRole) => Promise updateCanPostAnnouncements: (userId: string, canPostAnnouncements: boolean) => Promise banUser: (userId: string) => Promise unbanUser: (userId: string) => Promise deleteUser: (userId: string) => Promise previewMigration: (fromUserId: string, toUserId: string) => Promise /** Throws on failure (see AdminUserRepository.migrateAccount) — the * caller is expected to catch and surface the reason. */ migrateAccount: (fromUserId: string, toUserId: string, verificationNotes: string) => Promise loadMigrations: () => Promise } export const useAdminUserStore = create((set) => ({ users: [], isLoaded: false, migrations: [], loadUsers: async () => { const users = await repository.listUsers() set({ users, isLoaded: true }) }, updateRole: async (userId, role) => { await repository.updateRole(userId, role) set((state) => ({ users: state.users.map((u) => (u.id === userId ? { ...u, role } : u)) })) }, updateCanPostAnnouncements: async (userId, canPostAnnouncements) => { await repository.updateCanPostAnnouncements(userId, canPostAnnouncements) set((state) => ({ users: state.users.map((u) => (u.id === userId ? { ...u, canPostAnnouncements } : u)), })) }, banUser: async (userId) => { await repository.banUser(userId) // Re-fetch rather than guessing bannedUntil client-side — the Edge // Function is the one place that actually knows the real value. const users = await repository.listUsers() set({ users }) }, unbanUser: async (userId) => { await repository.unbanUser(userId) const users = await repository.listUsers() set({ users }) }, deleteUser: async (userId) => { await repository.deleteUser(userId) set((state) => ({ users: state.users.filter((u) => u.id !== userId) })) }, previewMigration: (fromUserId, toUserId) => repository.previewAccountMigration(fromUserId, toUserId), migrateAccount: async (fromUserId, toUserId, verificationNotes) => { const impact = await repository.migrateAccount(fromUserId, toUserId, verificationNotes) // Re-fetch rather than patch client-side — migratedToUserId/Username // both need the fresh server-computed join, and the migration log needs // its new row. const [users, migrations] = await Promise.all([repository.listUsers(), repository.listAccountMigrations()]) set({ users, migrations }) return impact }, loadMigrations: async () => { const migrations = await repository.listAccountMigrations() set({ migrations }) }, }))