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:
Jose Selesan
2026-04-22 15:30:04 -03:00
parent 5f2a7fd494
commit 6ad4fc9ee9
37 changed files with 1935 additions and 78 deletions

View File

@@ -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) {

View 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,
});