feat: add complex user invitation and management features
- Implemented complex member and invitation schemas using Zod for validation. - Created new API endpoints for inviting, accepting, revoking, and canceling complex user invitations. - Developed handlers for managing complex users, including listing, inviting, revoking, and canceling invitations. - Added frontend components for displaying and managing complex users and invitations. - Introduced session storage management for pending invitations. - Integrated Gravatar for user avatars based on email. - Created database migration for complex invitations table.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { apiClient } from '@/lib/api-client';
|
||||
import { useAuth } from '@/lib/auth';
|
||||
import { acceptStoredPendingInvite } from '@/lib/invitations';
|
||||
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
@@ -15,6 +16,7 @@ function AuthCallbackPage() {
|
||||
}
|
||||
|
||||
try {
|
||||
await acceptStoredPendingInvite(apiClient.complexes.acceptPendingInvitations);
|
||||
const complexes = await apiClient.complexes.listMine();
|
||||
|
||||
if (complexes.length === 1) {
|
||||
|
||||
51
apps/frontend/src/routes/invite.tsx
Normal file
51
apps/frontend/src/routes/invite.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { OnboardLoginPage } from '@/features/onboard/onboard-login-page';
|
||||
import { apiClient } from '@/lib/api-client';
|
||||
import { useAuth } from '@/lib/auth';
|
||||
import { acceptStoredPendingInvite, setPendingInvite } from '@/lib/invitations';
|
||||
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||
import { useEffect } from 'react';
|
||||
import { z } from 'zod';
|
||||
|
||||
const inviteSearchSchema = z.object({
|
||||
email: z.string().email().optional(),
|
||||
inviteToken: z.string().min(1).optional(),
|
||||
});
|
||||
|
||||
function InvitePage() {
|
||||
const navigate = useNavigate();
|
||||
const { isAuthenticated } = useAuth();
|
||||
const search = Route.useSearch();
|
||||
|
||||
useEffect(() => {
|
||||
setPendingInvite({
|
||||
email: search.email ?? null,
|
||||
token: search.inviteToken ?? null,
|
||||
});
|
||||
}, [search.email, search.inviteToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) {
|
||||
return;
|
||||
}
|
||||
|
||||
void (async () => {
|
||||
await acceptStoredPendingInvite(apiClient.complexes.acceptPendingInvitations);
|
||||
await navigate({ to: '/' });
|
||||
})();
|
||||
}, [isAuthenticated, navigate]);
|
||||
|
||||
if (isAuthenticated) {
|
||||
return (
|
||||
<main className="flex min-h-screen w-full items-center justify-center px-6">
|
||||
<p className="text-sm text-muted-foreground">Aceptando invitación...</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return <OnboardLoginPage variant="invite" inviteEmail={search.email ?? null} />;
|
||||
}
|
||||
|
||||
export const Route = createFileRoute('/invite')({
|
||||
validateSearch: inviteSearchSchema,
|
||||
component: InvitePage,
|
||||
});
|
||||
Reference in New Issue
Block a user