diff --git a/apps/calendar/apps/web/src/lib/api/shares.ts b/apps/calendar/apps/web/src/lib/api/shares.ts index 24c6472c7..93f43e77d 100644 --- a/apps/calendar/apps/web/src/lib/api/shares.ts +++ b/apps/calendar/apps/web/src/lib/api/shares.ts @@ -35,8 +35,16 @@ export async function updateShare(shareId: string, data: UpdateShareInput) { }); } -export async function deleteShare(shareId: string) { - return fetchApi(`/shares/${shareId}`, { +export async function deleteShare(calendarId: string, shareId: string) { + return fetchApi(`/calendars/${calendarId}/shares/${shareId}`, { method: 'DELETE', }); } + +export async function getInvitations() { + return fetchApi('/shares/invitations'); +} + +export async function getSharedWithMe() { + return fetchApi('/shares/shared-with-me'); +} diff --git a/apps/calendar/apps/web/src/lib/components/ServiceStatusBanner.svelte b/apps/calendar/apps/web/src/lib/components/ServiceStatusBanner.svelte new file mode 100644 index 000000000..6bd6310bd --- /dev/null +++ b/apps/calendar/apps/web/src/lib/components/ServiceStatusBanner.svelte @@ -0,0 +1,76 @@ + + +{#if !available} + +{/if} + + diff --git a/apps/calendar/apps/web/src/lib/stores/shares.svelte.ts b/apps/calendar/apps/web/src/lib/stores/shares.svelte.ts new file mode 100644 index 000000000..672b66f05 --- /dev/null +++ b/apps/calendar/apps/web/src/lib/stores/shares.svelte.ts @@ -0,0 +1,143 @@ +/** + * Calendar Shares Store - Manages calendar sharing and invitations + */ + +import type { CalendarShare, CalendarShareWithDetails } from '@calendar/shared'; +import * as api from '$lib/api/shares'; +import { toastStore } from '@manacore/shared-ui'; + +// State +let shares = $state>(new Map()); +let invitations = $state([]); +let sharedWithMe = $state([]); +let loading = $state(false); + +export const sharesStore = { + get loading() { + return loading; + }, + get invitations() { + return invitations; + }, + get sharedWithMe() { + return sharedWithMe; + }, + + getSharesForCalendar(calendarId: string): CalendarShare[] { + return shares.get(calendarId) || []; + }, + + async fetchSharesForCalendar(calendarId: string) { + const result = await api.getShares(calendarId); + if (result.data) { + const arr = Array.isArray(result.data) ? result.data : []; + shares = new Map(shares).set(calendarId, arr); + } + return result; + }, + + async fetchInvitations() { + const result = await api.getInvitations(); + if (result.data) { + invitations = Array.isArray(result.data) ? result.data : []; + } + return result; + }, + + async fetchSharedWithMe() { + const result = await api.getSharedWithMe(); + if (result.data) { + sharedWithMe = Array.isArray(result.data) ? result.data : []; + } + return result; + }, + + async shareCalendar(calendarId: string, email: string, permission: 'read' | 'write' | 'admin') { + const result = await api.createShare(calendarId, { calendarId, email, permission }); + + if (result.error) { + toastStore.error(`Freigabe fehlgeschlagen: ${result.error.message}`); + } else { + toastStore.success(`Kalender mit ${email} geteilt`); + await this.fetchSharesForCalendar(calendarId); + } + + return result; + }, + + async createShareLink(calendarId: string, permission: 'read' | 'write') { + const result = await api.createShare(calendarId, { + calendarId, + permission, + createLink: true, + }); + + if (result.error) { + toastStore.error(`Link-Erstellung fehlgeschlagen: ${result.error.message}`); + } else { + toastStore.success('Freigabe-Link erstellt'); + await this.fetchSharesForCalendar(calendarId); + } + + return result; + }, + + async acceptInvitation(shareId: string) { + const result = await api.acceptShare(shareId); + + if (result.error) { + toastStore.error(`Annahme fehlgeschlagen: ${result.error.message}`); + } else { + toastStore.success('Einladung angenommen'); + invitations = invitations.filter((i) => i.id !== shareId); + await this.fetchSharedWithMe(); + } + + return result; + }, + + async declineInvitation(shareId: string) { + const result = await api.declineShare(shareId); + + if (result.error) { + toastStore.error(`Ablehnung fehlgeschlagen: ${result.error.message}`); + } else { + invitations = invitations.filter((i) => i.id !== shareId); + } + + return result; + }, + + async removeShare(calendarId: string, shareId: string) { + const result = await api.deleteShare(calendarId, shareId); + + if (result.error) { + toastStore.error(`Entfernen fehlgeschlagen: ${result.error.message}`); + } else { + toastStore.success('Freigabe entfernt'); + const current = shares.get(calendarId) || []; + shares = new Map(shares).set( + calendarId, + current.filter((s) => s.id !== shareId) + ); + } + + return result; + }, + + async updatePermission(shareId: string, permission: 'read' | 'write' | 'admin') { + const result = await api.updateShare(shareId, { permission }); + + if (result.error) { + toastStore.error(`Aktualisierung fehlgeschlagen: ${result.error.message}`); + } + + return result; + }, + + clear() { + shares = new Map(); + invitations = []; + sharedWithMe = []; + }, +}; diff --git a/apps/calendar/apps/web/src/routes/(app)/+page.svelte b/apps/calendar/apps/web/src/routes/(app)/+page.svelte index efd0099d4..735cad494 100644 --- a/apps/calendar/apps/web/src/routes/(app)/+page.svelte +++ b/apps/calendar/apps/web/src/routes/(app)/+page.svelte @@ -5,9 +5,12 @@ import { eventsStore } from '$lib/stores/events.svelte'; import { calendarsStore } from '$lib/stores/calendars.svelte'; import { settingsStore } from '$lib/stores/settings.svelte'; + import { todosStore } from '$lib/stores/todos.svelte'; + import { birthdaysStore } from '$lib/stores/birthdays.svelte'; import ViewCarousel from '$lib/components/calendar/ViewCarousel.svelte'; import TodoSidebarSection from '$lib/components/calendar/TodoSidebarSection.svelte'; import QuickEventOverlay from '$lib/components/event/QuickEventOverlay.svelte'; + import ServiceStatusBanner from '$lib/components/ServiceStatusBanner.svelte'; import { CalendarViewSkeleton } from '$lib/components/skeletons'; import type { CalendarEvent } from '@calendar/shared'; import { addMinutes } from 'date-fns'; @@ -163,6 +166,23 @@ {$_('app.name')} +
+ todosStore.fetchTodos()} + /> + {#if settingsStore.showBirthdays} + birthdaysStore.fetchBirthdays(true)} + /> + {/if} +
+