feat(auth): add user invitation flow with account setup

- add `createAccountSetup`, `verifyAccountSetupToken`, `deleteAccountSetupToken` to verifications core
- add `completeAccountSetup` function to auth core for password creation on invite
- add `InvitationEmail` template for sending invite links
- add `SetupAccountPage` client page for invited users to set their password
- add `UserCreateModal` admin component to invite new users
- wire invitation action and API endpoint in auth feature
- update admin `UsersPage` to include user creation modal
- update auth and admin README docs
This commit is contained in:
2026-04-25 09:03:15 -04:00
parent 96c8cf1e97
commit abd9d651dc
16 changed files with 681 additions and 21 deletions
+27 -8
View File
@@ -7,8 +7,9 @@ import { PencilEdit01Icon } from '@zen/core/shared/icons';
import { useToast } from '@zen/core/toast';
import AdminHeader from '../components/AdminHeader.js';
import UserEditModal from '../components/UserEditModal.client.js';
import UserCreateModal from '../components/UserCreateModal.client.js';
const UsersPageClient = ({ currentUserId }) => {
const UsersPageClient = ({ currentUserId, refreshKey }) => {
const toast = useToast();
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
@@ -126,7 +127,7 @@ const UsersPageClient = ({ currentUserId }) => {
useEffect(() => {
fetchUsers();
}, [sortBy, sortOrder, pagination.page, pagination.limit]);
}, [sortBy, sortOrder, pagination.page, pagination.limit, refreshKey]);
const handlePageChange = (newPage) => setPagination(prev => ({ ...prev, page: newPage }));
const handleLimitChange = (newLimit) => setPagination(prev => ({ ...prev, limit: newLimit, page: 1 }));
@@ -168,12 +169,30 @@ const UsersPageClient = ({ currentUserId }) => {
);
};
const UsersPage = ({ user }) => (
<div className="flex flex-col gap-4 sm:gap-6 lg:gap-8">
<AdminHeader title="Utilisateurs" description="Gérez les comptes utilisateurs" />
<UsersPageClient currentUserId={user?.id} />
</div>
);
const UsersPage = ({ user }) => {
const [createModalOpen, setCreateModalOpen] = useState(false);
const [refreshKey, setRefreshKey] = useState(0);
return (
<div className="flex flex-col gap-4 sm:gap-6 lg:gap-8">
<AdminHeader
title="Utilisateurs"
description="Gérez les comptes utilisateurs"
action={
<Button variant="primary" onClick={() => setCreateModalOpen(true)}>
Nouvel utilisateur
</Button>
}
/>
<UsersPageClient currentUserId={user?.id} refreshKey={refreshKey} />
<UserCreateModal
isOpen={createModalOpen}
onClose={() => setCreateModalOpen(false)}
onSaved={() => setRefreshKey(k => k + 1)}
/>
</div>
);
};
export default UsersPage;