Show success banner after sending a password-reset email
Deploy to route.crispygoat.com / deploy (push) Successful in 4m8s
Deploy to route.crispygoat.com / deploy (push) Successful in 4m8s
The 'Send Reset Email' button in /admin/users previously just cleared the error banner on success, with no actual 'yes it worked' feedback. Added a green success banner that mirrors the error banner's style and auto-dismisses after 6 seconds. The 'Reset Password' button already shows confirmation in the modal (temp password to copy, or 'reset email sent' message), so it doesn't need the banner. Also tightened the type narrowing in the resetAdminPassword unit tests — the discriminated union needed a two-step narrow (`r.success` then `r.method`) before TypeScript would allow access to variant-specific fields like `tempPassword`.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { AdminUserRow, UpdateAdminUserInput } from "@/actions/admin/users";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import CreateUserModal from "./CreateUserModal";
|
||||
@@ -124,6 +124,14 @@ export default function UsersPage({ initialUsers, brands, currentUser, onUserCre
|
||||
const [actionMenuId, setActionMenuId] = useState<string | null>(null);
|
||||
const [passwordModal, setPasswordModal] = useState<PasswordModal | null>(null);
|
||||
const [resetLinkLoading, setResetLinkLoading] = useState<string | null>(null);
|
||||
const [success, setSuccess] = useState<string | null>(null);
|
||||
|
||||
// Auto-dismiss the success banner after 6 seconds.
|
||||
useEffect(() => {
|
||||
if (!success) return;
|
||||
const t = setTimeout(() => setSuccess(null), 6000);
|
||||
return () => clearTimeout(t);
|
||||
}, [success]);
|
||||
|
||||
const canManageUsers =
|
||||
currentUser.role === "platform_admin" ||
|
||||
@@ -300,12 +308,14 @@ export default function UsersPage({ initialUsers, brands, currentUser, onUserCre
|
||||
|
||||
async function handleSendResetLink(email: string) {
|
||||
setResetLinkLoading(email);
|
||||
setError(null);
|
||||
setSuccess(null);
|
||||
try {
|
||||
const { sendPasswordResetEmail } = await import("@/actions/admin/users");
|
||||
const result = await sendPasswordResetEmail(email);
|
||||
setResetLinkLoading(null);
|
||||
if (result.error) throw new Error(result.error);
|
||||
setError(null);
|
||||
setSuccess(`Password-reset email sent to ${email}.`);
|
||||
} catch (e: unknown) {
|
||||
setResetLinkLoading(null);
|
||||
setError(e instanceof Error ? e.message : "Failed to send reset email");
|
||||
@@ -341,6 +351,18 @@ export default function UsersPage({ initialUsers, brands, currentUser, onUserCre
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Success banner */}
|
||||
{success && (
|
||||
<div className="mb-4 flex items-start justify-between rounded-lg bg-emerald-50 p-4 text-sm text-emerald-800 gap-3 border border-emerald-200">
|
||||
<span>✓ {success}</span>
|
||||
<button onClick={() => setSuccess(null)} className="text-emerald-500 hover:text-emerald-700 shrink-0" aria-label="Dismiss">
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error banner */}
|
||||
{error && (
|
||||
<div className="mb-4 flex items-start justify-between rounded-lg bg-red-50 p-4 text-sm text-red-700 gap-3 border border-red-200">
|
||||
|
||||
Reference in New Issue
Block a user