Admin page changes to allow creation of a user and hidden behind the admin permission
This commit is contained in:
@@ -9,16 +9,24 @@ const EMPTY_HOUSEHOLD_FORM = {
|
||||
description: '',
|
||||
}
|
||||
|
||||
function formatMemberName(member) {
|
||||
const fullName = [member.firstName, member.lastName]
|
||||
const EMPTY_USER_FORM = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
}
|
||||
|
||||
function formatPersonName(person) {
|
||||
const fullName = [person.firstName, person.lastName]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
|
||||
return fullName || member.email || 'Unnamed member'
|
||||
return fullName || person.email || 'Unnamed person'
|
||||
}
|
||||
|
||||
function AdminPage() {
|
||||
const { isAuthenticated, isSiteAdmin } = useAuth()
|
||||
const { isAuthenticated, isSiteAdmin, register } = useAuth()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [errorMessage, setErrorMessage] = useState('')
|
||||
const [statusMessage, setStatusMessage] = useState('')
|
||||
@@ -27,6 +35,8 @@ function AdminPage() {
|
||||
const [editingHouseholdId, setEditingHouseholdId] = useState('')
|
||||
const [householdForm, setHouseholdForm] = useState(EMPTY_HOUSEHOLD_FORM)
|
||||
const [inviteEmail, setInviteEmail] = useState('')
|
||||
const [userForm, setUserForm] = useState(EMPTY_USER_FORM)
|
||||
const [createdUser, setCreatedUser] = useState(null)
|
||||
|
||||
async function loadHouseholds(preferredSelectionId = '') {
|
||||
const response = await householdsApi.getHouseholds()
|
||||
@@ -59,6 +69,8 @@ function AdminPage() {
|
||||
setEditingHouseholdId('')
|
||||
setHouseholdForm(EMPTY_HOUSEHOLD_FORM)
|
||||
setInviteEmail('')
|
||||
setUserForm(EMPTY_USER_FORM)
|
||||
setCreatedUser(null)
|
||||
setErrorMessage('')
|
||||
setStatusMessage('')
|
||||
return
|
||||
@@ -99,12 +111,17 @@ function AdminPage() {
|
||||
const canSubmitHouseholdForm = editingHouseholdId
|
||||
? Boolean(editingHousehold && (isSiteAdmin || editingHousehold.isCurrentUserHouseholdAdmin))
|
||||
: isSiteAdmin
|
||||
const createdUserRoles = Array.isArray(createdUser?.roles) ? createdUser.roles : []
|
||||
|
||||
function resetHouseholdEditor() {
|
||||
setEditingHouseholdId('')
|
||||
setHouseholdForm(EMPTY_HOUSEHOLD_FORM)
|
||||
}
|
||||
|
||||
function resetUserForm() {
|
||||
setUserForm(EMPTY_USER_FORM)
|
||||
}
|
||||
|
||||
async function handleHouseholdSubmit(event) {
|
||||
event.preventDefault()
|
||||
|
||||
@@ -182,6 +199,54 @@ function AdminPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUserSubmit(event) {
|
||||
event.preventDefault()
|
||||
|
||||
const email = userForm.email.trim()
|
||||
|
||||
if (!isSiteAdmin) {
|
||||
setErrorMessage('Only site admins can create users from the admin page.')
|
||||
return
|
||||
}
|
||||
|
||||
if (!email) {
|
||||
setErrorMessage('User email is required.')
|
||||
return
|
||||
}
|
||||
|
||||
if (!userForm.password) {
|
||||
setErrorMessage('A password is required to create a user.')
|
||||
return
|
||||
}
|
||||
|
||||
if (userForm.password !== userForm.confirmPassword) {
|
||||
setErrorMessage('Passwords do not match.')
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
setErrorMessage('')
|
||||
setStatusMessage('')
|
||||
|
||||
try {
|
||||
const result = await register({
|
||||
email,
|
||||
password: userForm.password,
|
||||
confirmPassword: userForm.confirmPassword,
|
||||
firstName: userForm.firstName.trim() || null,
|
||||
lastName: userForm.lastName.trim() || null,
|
||||
})
|
||||
|
||||
setCreatedUser(result?.user ?? null)
|
||||
resetUserForm()
|
||||
setStatusMessage(result?.message || 'User created.')
|
||||
} catch (error) {
|
||||
setErrorMessage(error.message)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLeaveHousehold(householdId) {
|
||||
const confirmed = window.confirm('Leave this household?')
|
||||
|
||||
@@ -221,7 +286,7 @@ function AdminPage() {
|
||||
<>
|
||||
{errorMessage && <div className="status-banner status-banner--error">{errorMessage}</div>}
|
||||
{statusMessage && <div className="status-banner status-banner--success">{statusMessage}</div>}
|
||||
{loading && <div className="status-banner status-banner--info">Syncing household data...</div>}
|
||||
{loading && <div className="status-banner status-banner--info">Processing admin request...</div>}
|
||||
|
||||
<section className="panel admin-summary-panel">
|
||||
<h3>Household Summary</h3>
|
||||
@@ -335,6 +400,117 @@ function AdminPage() {
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="panel">
|
||||
<div className="section-heading">
|
||||
<h3>Create User</h3>
|
||||
<span className="subtle-text">{createdUser?.email || 'Register endpoint'}</span>
|
||||
</div>
|
||||
|
||||
<p className="form-note">
|
||||
{isSiteAdmin
|
||||
? 'This creates a new account with the register endpoint. Role assignment still happens separately in the backend.'
|
||||
: 'Only site admins can create users from this page.'}
|
||||
</p>
|
||||
|
||||
<form className="editor-form" onSubmit={handleUserSubmit}>
|
||||
<div className="form-grid">
|
||||
<div className="field-group">
|
||||
<label htmlFor="register-first-name">First Name</label>
|
||||
<input
|
||||
id="register-first-name"
|
||||
type="text"
|
||||
value={userForm.firstName}
|
||||
onChange={event => setUserForm(current => ({ ...current, firstName: event.target.value }))}
|
||||
placeholder="Jordan"
|
||||
disabled={!isSiteAdmin}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field-group">
|
||||
<label htmlFor="register-last-name">Last Name</label>
|
||||
<input
|
||||
id="register-last-name"
|
||||
type="text"
|
||||
value={userForm.lastName}
|
||||
onChange={event => setUserForm(current => ({ ...current, lastName: event.target.value }))}
|
||||
placeholder="Lee"
|
||||
disabled={!isSiteAdmin}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="field-group">
|
||||
<label htmlFor="register-email">Email</label>
|
||||
<input
|
||||
id="register-email"
|
||||
type="email"
|
||||
value={userForm.email}
|
||||
onChange={event => setUserForm(current => ({ ...current, email: event.target.value }))}
|
||||
placeholder="new-user@example.com"
|
||||
disabled={!isSiteAdmin}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-grid">
|
||||
<div className="field-group">
|
||||
<label htmlFor="register-password">Password</label>
|
||||
<input
|
||||
id="register-password"
|
||||
type="password"
|
||||
value={userForm.password}
|
||||
onChange={event => setUserForm(current => ({ ...current, password: event.target.value }))}
|
||||
placeholder="Create a password"
|
||||
disabled={!isSiteAdmin}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field-group">
|
||||
<label htmlFor="register-confirm-password">Confirm Password</label>
|
||||
<input
|
||||
id="register-confirm-password"
|
||||
type="password"
|
||||
value={userForm.confirmPassword}
|
||||
onChange={event => setUserForm(current => ({ ...current, confirmPassword: event.target.value }))}
|
||||
placeholder="Repeat the password"
|
||||
disabled={!isSiteAdmin}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="button-row">
|
||||
<button type="submit" className="btn btn-primary" disabled={!isSiteAdmin}>
|
||||
Create user
|
||||
</button>
|
||||
<button type="button" className="btn btn-secondary" onClick={resetUserForm} disabled={!isSiteAdmin}>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{createdUser && (
|
||||
<div className="admin-created-user-card">
|
||||
<div className="admin-created-user-copy">
|
||||
<strong>{formatPersonName(createdUser)}</strong>
|
||||
<div className="entity-meta">{createdUser.email || 'No email provided.'}</div>
|
||||
<div className="entity-meta">ID: {createdUser.id || 'Not available'}</div>
|
||||
</div>
|
||||
|
||||
<div className="admin-badge-row">
|
||||
{createdUserRoles.length === 0 ? (
|
||||
<span className="member-badge">No roles assigned</span>
|
||||
) : (
|
||||
createdUserRoles.map(role => (
|
||||
<span className="member-badge" key={role}>{role}</span>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section className="panel admin-list-panel">
|
||||
@@ -371,7 +547,7 @@ function AdminPage() {
|
||||
{(household.members ?? []).map(member => (
|
||||
<div className="member-row" key={member.userId}>
|
||||
<div className="member-copy">
|
||||
<strong>{formatMemberName(member)}</strong>
|
||||
<strong>{formatPersonName(member)}</strong>
|
||||
<span className="entity-meta">{member.email}</span>
|
||||
</div>
|
||||
{member.isHouseholdAdmin && <span className="member-badge">Admin</span>}
|
||||
|
||||
Reference in New Issue
Block a user