From 52bb170d7a9840052b0bcf4aa36c71258be4ec76 Mon Sep 17 00:00:00 2001 From: Philip Date: Thu, 12 Mar 2026 11:33:02 -0700 Subject: [PATCH] Fix sign out redirect, add admin user password reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Sign out: skip NextAuth redirect (which resolves against NEXTAUTH_URL) and use window.location.href='/' instead — works from any hostname - Admin users page: add 'Reset PW' button per user that opens a modal to set a new password (no current password required) - Allow COMMISSIONER role to reset user passwords via PATCH /api/users/[id] Co-Authored-By: Claude Sonnet 4.6 --- src/app/admin/users/page.tsx | 76 +++++++++++++++++++++++++++++- src/app/api/users/[id]/route.ts | 8 ++-- src/components/grid/GridHeader.tsx | 2 +- 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx index 9a94997..691081e 100644 --- a/src/app/admin/users/page.tsx +++ b/src/app/admin/users/page.tsx @@ -24,6 +24,14 @@ export default function UsersPage() { const [newRole, setNewRole] = useState('PLAYER'); const [error, setError] = useState(''); + // Reset password modal + const [resetModal, setResetModal] = useState(false); + const [resetUserId, setResetUserId] = useState(''); + const [resetUserName, setResetUserName] = useState(''); + const [resetPassword, setResetPassword] = useState(''); + const [resetError, setResetError] = useState(''); + const [resetLoading, setResetLoading] = useState(false); + const fetchUsers = () => { fetch('/api/users') .then((res) => res.json()) @@ -74,6 +82,40 @@ export default function UsersPage() { fetchUsers(); }; + const openResetModal = (user: User) => { + setResetUserId(user.id); + setResetUserName(user.name); + setResetPassword(''); + setResetError(''); + setResetModal(true); + }; + + const handleResetPassword = async () => { + setResetError(''); + if (resetPassword.length < 8) { + setResetError('Password must be at least 8 characters'); + return; + } + setResetLoading(true); + try { + const res = await fetch(`/api/users/${resetUserId}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ password: resetPassword }), + }); + const data = await res.json(); + if (!res.ok) { + setResetError(data.error || 'Failed to reset password'); + } else { + setResetModal(false); + } + } catch { + setResetError('Failed to reset password'); + } finally { + setResetLoading(false); + } + }; + const handleDelete = async (userId: string) => { if (!confirm('Are you sure you want to delete this user?')) return; await fetch(`/api/users/${userId}`, { method: 'DELETE' }); @@ -121,7 +163,13 @@ export default function UsersPage() { {new Date(user.createdAt).toLocaleDateString()} - + + + + + + + setCreateModal(false)} title="Create User">
{error && ( diff --git a/src/app/api/users/[id]/route.ts b/src/app/api/users/[id]/route.ts index ebecf19..89ee86f 100644 --- a/src/app/api/users/[id]/route.ts +++ b/src/app/api/users/[id]/route.ts @@ -15,10 +15,12 @@ export async function PATCH( return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } - const isAdmin = (session.user as any).role === 'ADMIN'; + const role = (session.user as any).role; + const isAdmin = role === 'ADMIN'; + const isCommissioner = role === 'COMMISSIONER'; const isSelf = (session.user as any).id === params.id; - if (!isAdmin && !isSelf) { + if (!isAdmin && !isCommissioner && !isSelf) { return NextResponse.json({ error: 'Unauthorized' }, { status: 403 }); } @@ -38,7 +40,7 @@ export async function PATCH( } // Only admins can change roles - if (body.role && isAdmin) { + if (body.role && (isAdmin || isCommissioner)) { updateData.role = body.role; } diff --git a/src/components/grid/GridHeader.tsx b/src/components/grid/GridHeader.tsx index 0357235..570a141 100644 --- a/src/components/grid/GridHeader.tsx +++ b/src/components/grid/GridHeader.tsx @@ -74,7 +74,7 @@ export function GridHeader({ settings }: GridHeaderProps) { Admin )} -