Admin shortcut in the account menu, switched on per study user from /admin; v2.25.0
Publish Container / build (push) Has been cancelled
Publish Container / build (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
853e758bab
commit
d0741d628e
7 files changed
+40
-6
No files matched your search
+21
-1
@@ -120,6 +120,7 @@ interface StudyUserRecord {
|
||||
reminderFrequency: 'immediate' | 'weekly'
|
||||
remindersPausedUntil: string | null
|
||||
reengagementSentAt: Record<string, string>
|
||||
adminShortcut?: boolean
|
||||
}
|
||||
|
||||
function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
|
||||
@@ -145,7 +146,7 @@ function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
|
||||
.catch(() => setLoading(false))
|
||||
}, [])
|
||||
|
||||
async function patchUser(id: string, body: Record<string, string>) {
|
||||
async function patchUser(id: string, body: Record<string, string | boolean>) {
|
||||
const res = await fetch(`/api/admin/study-users/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -165,6 +166,14 @@ function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
|
||||
} catch (e) { flashMsg(user.id, e instanceof Error ? e.message : 'Error') }
|
||||
}
|
||||
|
||||
async function handleToggleAdminShortcut(user: StudyUserRecord, on: boolean) {
|
||||
try {
|
||||
await patchUser(user.id, { adminShortcut: on })
|
||||
setUsers(prev => prev.map(u => (u.id === user.id ? { ...u, adminShortcut: on } : u)))
|
||||
flashMsg(user.id, on ? 'Admin shortcut turned on.' : 'Admin shortcut turned off.')
|
||||
} catch (e) { flashMsg(user.id, e instanceof Error ? e.message : 'Error') }
|
||||
}
|
||||
|
||||
async function handleResetPassword(user: StudyUserRecord) {
|
||||
const pw = newPassword[user.id] ?? ''
|
||||
if (pw.length < 8) { flashMsg(user.id, 'Password must be at least 8 characters.'); return }
|
||||
@@ -265,6 +274,7 @@ function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
|
||||
{user.displayName && <span className="admin-study-user-email">{user.username}</span>}
|
||||
</div>
|
||||
<div className="admin-study-user-header-right">
|
||||
{user.adminShortcut && <span className="admin-study-user-pill">Admin shortcut</span>}
|
||||
<span className="admin-study-user-pill">{user.enrolledStudies.length} enrolled</span>
|
||||
<span className="admin-study-user-pill">{user.noteCount} notes</span>
|
||||
{(user.currentStreak ?? 0) > 0 && <span className="admin-study-user-pill">{user.currentStreak} 🔥</span>}
|
||||
@@ -304,6 +314,16 @@ function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Admin shortcut */}
|
||||
<div className="admin-study-user-section">
|
||||
<h4>Admin Shortcut</h4>
|
||||
<label style={{ display: 'flex', gap: 8, alignItems: 'center', cursor: 'pointer' }}>
|
||||
<input type="checkbox" checked={user.adminShortcut === true} onChange={e => handleToggleAdminShortcut(user, e.target.checked)} />
|
||||
Show an "Admin" link in this account's menu on the site
|
||||
</label>
|
||||
<p className="admin-stats-note" style={{ marginTop: '0.25rem' }}>Only accounts with this turned on see the link. It only takes them to the admin sign-in page; signing in still needs the admin password and code.</p>
|
||||
</div>
|
||||
|
||||
{/* Password Reset */}
|
||||
<div className="admin-study-user-section">
|
||||
<h4>Reset Password</h4>
|
||||
|
||||
@@ -6,6 +6,7 @@ interface AccountStatus {
|
||||
username: string
|
||||
displayName?: string
|
||||
avatarUrl?: string
|
||||
adminShortcut?: boolean
|
||||
}
|
||||
|
||||
// Shared across header instances so moving between pages doesn't flash the signed-out icon
|
||||
@@ -87,6 +88,9 @@ export function HeaderAccount() {
|
||||
<div className="header-account-menu" role="menu">
|
||||
<p className="header-account-name">Signed in as <strong>{name}</strong></p>
|
||||
<Link to="/study/account" role="menuitem" className="header-account-item" onClick={() => setOpen(false)}>My Account</Link>
|
||||
{status.adminShortcut && (
|
||||
<Link to="/admin" role="menuitem" className="header-account-item" onClick={() => setOpen(false)}>Admin</Link>
|
||||
)}
|
||||
<button type="button" role="menuitem" className="header-account-item" onClick={signOut}>Sign out</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user