added "members" for an event that help the admins to moderate. members must be invited via email.

This commit is contained in:
Codex Agent
2025-11-09 22:24:40 +01:00
parent 082b78cd43
commit 7ec3db9c59
23 changed files with 836 additions and 101 deletions

View File

@@ -26,7 +26,7 @@ import { ADMIN_EVENTS_PATH } from '../constants';
type InviteForm = {
email: string;
name: string;
role: string;
role: EventMember['role'];
};
const emptyInvite: InviteForm = {
@@ -68,7 +68,6 @@ export default function EventMembersPage() {
() => ({
tenant_admin: t('management.members.roles.tenantAdmin', 'Tenant-Admin'),
member: t('management.members.roles.member', 'Mitglied'),
guest: t('management.members.roles.guest', 'Gast'),
}),
[t]
);
@@ -275,6 +274,12 @@ export default function EventMembersPage() {
<Users className="h-4 w-4 text-emerald-500" />
{t('management.members.sections.invite.title', 'Neues Mitglied einladen')}
</h3>
<p className="text-xs text-slate-500">
{t(
'management.members.sections.invite.helper',
'Mitglieder erhalten Zugriff auf Fotomoderation, Aufgaben und QR-Einladungen. Admins steuern zusätzlich Pakete, Abrechnung und Events.'
)}
</p>
<form className="space-y-3 rounded-2xl border border-emerald-100 bg-white/90 p-4 shadow-sm" onSubmit={handleInvite}>
<div className="space-y-2">
<Label htmlFor="invite-email">{t('management.members.form.emailLabel', 'E-Mail')}</Label>
@@ -308,7 +313,6 @@ export default function EventMembersPage() {
<SelectContent>
<SelectItem value="tenant_admin">{roleLabels.tenant_admin}</SelectItem>
<SelectItem value="member">{roleLabels.member}</SelectItem>
<SelectItem value="guest">{roleLabels.guest}</SelectItem>
</SelectContent>
</Select>
</div>

View File

@@ -10,7 +10,7 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { useAuth } from '../auth/context';
import { ADMIN_DEFAULT_AFTER_LOGIN_PATH } from '../constants';
import { ADMIN_DEFAULT_AFTER_LOGIN_PATH, ADMIN_EVENTS_PATH } from '../constants';
import { encodeReturnTo, resolveReturnTarget } from '../lib/returnTo';
import { useMutation } from '@tanstack/react-query';
@@ -48,7 +48,7 @@ type LoginResponse = {
}
export default function LoginPage(): JSX.Element {
const { status, applyToken } = useAuth();
const { status, applyToken, abilities } = useAuth();
const { t } = useTranslation('auth');
const location = useLocation();
const navigate = useNavigate();
@@ -56,7 +56,15 @@ export default function LoginPage(): JSX.Element {
const searchParams = React.useMemo(() => new URLSearchParams(location.search), [location.search]);
const rawReturnTo = searchParams.get('return_to');
const fallbackTarget = ADMIN_DEFAULT_AFTER_LOGIN_PATH;
const computeDefaultAfterLogin = React.useCallback(
(abilityList?: string[]) => {
const source = abilityList ?? abilities;
return source.includes('tenant-admin') ? ADMIN_DEFAULT_AFTER_LOGIN_PATH : ADMIN_EVENTS_PATH;
},
[abilities],
);
const fallbackTarget = computeDefaultAfterLogin();
const { finalTarget } = React.useMemo(
() => resolveReturnTarget(rawReturnTo, fallbackTarget),
[rawReturnTo, fallbackTarget]
@@ -82,7 +90,9 @@ export default function LoginPage(): JSX.Element {
onSuccess: async (data) => {
setError(null);
await applyToken(data.token, data.abilities ?? []);
navigate(finalTarget, { replace: true });
const postLoginFallback = computeDefaultAfterLogin(data.abilities ?? []);
const { finalTarget: successTarget } = resolveReturnTarget(rawReturnTo, postLoginFallback);
navigate(successTarget, { replace: true });
},
});