Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Updated login and signup components to utilize AuthCard for better user experience during redirection. - Introduced AuthentikEmbedDialog for seamless integration of Authentik's identity portal within the application. - Enhanced password recovery and signup flows with dynamic theme handling and improved loading states. - Refactored existing components to streamline authentication processes and improve maintainability.
219 lines
6.4 KiB
TypeScript
219 lines
6.4 KiB
TypeScript
"use client"
|
|
|
|
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
|
import { apiClient } from "@/lib/api/client"
|
|
import { formatICSDateOnly, formatICSDateTimeUTC } from "@/lib/agenda/agenda-date"
|
|
import type { AgendaApiEvent, AgendaEventDraft } from "@/lib/agenda/agenda-types"
|
|
import { useIsDemoAgenda } from "@/lib/demo/demo-agenda-context"
|
|
import { DEMO_AGENDA_QUERY_ROOT } from "@/lib/demo/demo-agenda-bootstrap"
|
|
import { useDemoAgendaStore } from "@/lib/demo/demo-agenda-store"
|
|
|
|
function uidFromEventPath(path?: string): string | undefined {
|
|
if (!path) return undefined
|
|
const match = /\/([^/]+)\.ics$/i.exec(path.trim())
|
|
return match?.[1]
|
|
}
|
|
|
|
function eventApiPath(davPath: string): string {
|
|
return `/calendar/events/${davPath.replace(/^\/+/, "")}`
|
|
}
|
|
|
|
/** Convertit un brouillon UI en payload API (dates ICS). */
|
|
export function draftToApiEvent(
|
|
draft: AgendaEventDraft,
|
|
existing?: AgendaApiEvent,
|
|
opts?: { uid?: string; meetUrl?: string | null },
|
|
): Partial<AgendaApiEvent> {
|
|
const start = draft.allDay
|
|
? formatICSDateOnly(draft.start)
|
|
: formatICSDateTimeUTC(draft.start)
|
|
const end = draft.allDay
|
|
? formatICSDateOnly(draft.end)
|
|
: formatICSDateTimeUTC(draft.end)
|
|
let meet_url = existing?.meet_url ?? ""
|
|
if (opts?.meetUrl !== undefined) {
|
|
meet_url = opts.meetUrl ?? ""
|
|
}
|
|
return {
|
|
uid: opts?.uid ?? existing?.uid ?? uidFromEventPath(existing?.path),
|
|
summary: draft.title.trim() || "(Sans titre)",
|
|
description: draft.description ?? existing?.description ?? "",
|
|
location: draft.location ?? existing?.location ?? "",
|
|
start,
|
|
end,
|
|
all_day: draft.allDay,
|
|
attendees: draft.attendees ?? existing?.attendees ?? [],
|
|
organizer: existing?.organizer,
|
|
meet_url,
|
|
color: draft.color ?? existing?.color,
|
|
rrule: draft.rrule ?? existing?.rrule ?? "",
|
|
exdates: existing?.exdates,
|
|
}
|
|
}
|
|
|
|
function useInvalidateAgenda() {
|
|
const queryClient = useQueryClient()
|
|
const isDemoAgenda = useIsDemoAgenda()
|
|
return () => {
|
|
if (isDemoAgenda) {
|
|
useDemoAgendaStore.getState().bump()
|
|
void queryClient.invalidateQueries({ queryKey: DEMO_AGENDA_QUERY_ROOT })
|
|
return
|
|
}
|
|
void queryClient.invalidateQueries({ queryKey: ["agenda", "events"] })
|
|
}
|
|
}
|
|
|
|
export function useCreateAgendaEvent() {
|
|
const invalidate = useInvalidateAgenda()
|
|
const isDemoAgenda = useIsDemoAgenda()
|
|
return useMutation({
|
|
mutationFn: async ({
|
|
calendarId,
|
|
event,
|
|
}: {
|
|
calendarId: string
|
|
event: Partial<AgendaApiEvent>
|
|
}) => {
|
|
if (isDemoAgenda) {
|
|
const uid = event.uid ?? `demo-event-${crypto.randomUUID().slice(0, 8)}`
|
|
useDemoAgendaStore.getState().upsertEvent(calendarId, {
|
|
...event,
|
|
uid,
|
|
} as AgendaApiEvent)
|
|
return
|
|
}
|
|
return apiClient.post(`/calendar/${encodeURIComponent(calendarId)}/events`, event)
|
|
},
|
|
onSuccess: invalidate,
|
|
})
|
|
}
|
|
|
|
export function useUpdateAgendaEvent() {
|
|
const invalidate = useInvalidateAgenda()
|
|
const isDemoAgenda = useIsDemoAgenda()
|
|
return useMutation({
|
|
mutationFn: async ({
|
|
path,
|
|
etag,
|
|
event,
|
|
}: {
|
|
path: string
|
|
etag: string
|
|
event: Partial<AgendaApiEvent>
|
|
}) => {
|
|
if (isDemoAgenda) {
|
|
const calendarId = path.match(/\/calendars\/([^/]+)\//)?.[1] ?? "personal"
|
|
useDemoAgendaStore.getState().upsertEvent(calendarId, {
|
|
...event,
|
|
path,
|
|
etag: etag || `"demo-${event.uid ?? path}"`,
|
|
} as AgendaApiEvent)
|
|
return { etag: `"demo-v2"` }
|
|
}
|
|
return apiClient.put<{ etag: string }>(eventApiPath(path), event, {
|
|
"If-Match": etag || "*",
|
|
})
|
|
},
|
|
onSuccess: invalidate,
|
|
})
|
|
}
|
|
|
|
export function useDeleteAgendaEvent() {
|
|
const invalidate = useInvalidateAgenda()
|
|
const isDemoAgenda = useIsDemoAgenda()
|
|
return useMutation({
|
|
mutationFn: async ({ path }: { path: string }) => {
|
|
if (isDemoAgenda) {
|
|
useDemoAgendaStore.getState().deleteEvent(path)
|
|
return
|
|
}
|
|
return apiClient.delete(eventApiPath(path))
|
|
},
|
|
onSuccess: invalidate,
|
|
})
|
|
}
|
|
|
|
export function useRespondAgendaInvitation() {
|
|
const invalidate = useInvalidateAgenda()
|
|
return useMutation({
|
|
mutationFn: ({
|
|
path,
|
|
response,
|
|
etag,
|
|
}: {
|
|
path: string
|
|
response: "accepted" | "declined" | "tentative"
|
|
etag?: string
|
|
}) =>
|
|
apiClient.post<{ etag: string }>(
|
|
`/calendar/events/response/${path.replace(/^\/+/, "")}`,
|
|
{ response, if_match: etag ?? "" },
|
|
),
|
|
onSuccess: invalidate,
|
|
})
|
|
}
|
|
|
|
export function useCreateAgendaMeetLink() {
|
|
const invalidate = useInvalidateAgenda()
|
|
const isDemoAgenda = useIsDemoAgenda()
|
|
return useMutation({
|
|
mutationFn: async ({ path, etag }: { path: string; etag?: string }) => {
|
|
if (isDemoAgenda) {
|
|
const meetUrl = `https://meet.demo.ulti/${path.split("/").pop()?.replace(".ics", "") ?? "room"}`
|
|
const calendarId = path.match(/\/calendars\/([^/]+)\//)?.[1] ?? "personal"
|
|
useDemoAgendaStore.getState().upsertEvent(calendarId, {
|
|
path,
|
|
etag: etag ?? `"demo-meet"`,
|
|
meet_url: meetUrl,
|
|
} as AgendaApiEvent)
|
|
return { meet_url: meetUrl, etag: `"demo-meet"` }
|
|
}
|
|
return apiClient.post<{ meet_url: string; etag: string }>(
|
|
`/calendar/events/meet-link/${path.replace(/^\/+/, "")}`,
|
|
{ if_match: etag ?? "" },
|
|
)
|
|
},
|
|
onSuccess: invalidate,
|
|
})
|
|
}
|
|
|
|
export function useCreateAgendaCalendar() {
|
|
const queryClient = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (input: { display_name: string; color?: string }) =>
|
|
apiClient.post<{ id: string }>("/calendar/", input),
|
|
onSuccess: () => {
|
|
void queryClient.invalidateQueries({ queryKey: ["agenda", "calendars"] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useUpdateAgendaCalendar() {
|
|
const queryClient = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: ({
|
|
id,
|
|
...input
|
|
}: {
|
|
id: string
|
|
display_name?: string
|
|
color?: string
|
|
}) => apiClient.patch(`/calendar/${encodeURIComponent(id)}`, input),
|
|
onSuccess: () => {
|
|
void queryClient.invalidateQueries({ queryKey: ["agenda"] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useDeleteAgendaCalendar() {
|
|
const queryClient = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: ({ id }: { id: string }) =>
|
|
apiClient.delete(`/calendar/${encodeURIComponent(id)}`),
|
|
onSuccess: () => {
|
|
void queryClient.invalidateQueries({ queryKey: ["agenda"] })
|
|
},
|
|
})
|
|
}
|