✨(sharing) use UI Kit native sharing modal
This commit is contained in:
@@ -15,6 +15,7 @@ export const CalendarItemMenu = ({
|
|||||||
onOpenChange,
|
onOpenChange,
|
||||||
onEdit,
|
onEdit,
|
||||||
onDelete,
|
onDelete,
|
||||||
|
onShare,
|
||||||
onImport,
|
onImport,
|
||||||
onSubscription,
|
onSubscription,
|
||||||
}: CalendarItemMenuProps) => {
|
}: CalendarItemMenuProps) => {
|
||||||
@@ -29,6 +30,14 @@ export const CalendarItemMenu = ({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (onShare) {
|
||||||
|
items.push({
|
||||||
|
label: t("calendar.list.share"),
|
||||||
|
icon: <span className="material-icons">person_add</span>,
|
||||||
|
callback: onShare,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (onImport) {
|
if (onImport) {
|
||||||
items.push({
|
items.push({
|
||||||
label: t("calendar.list.import"),
|
label: t("calendar.list.import"),
|
||||||
@@ -52,7 +61,7 @@ export const CalendarItemMenu = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}, [t, onEdit, onDelete, onImport, onSubscription]);
|
}, [t, onEdit, onDelete, onShare, onImport, onSubscription]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu options={options} isOpen={isOpen} onOpenChange={onOpenChange}>
|
<DropdownMenu options={options} isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
|
|||||||
@@ -279,36 +279,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__share-section {
|
}
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__share-divider {
|
// ============================================================================
|
||||||
height: 1px;
|
// Share Modal Styles (close button position fix)
|
||||||
background-color: var(--c--theme--colors--greyscale-200);
|
// ============================================================================
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__share-input-row {
|
.c__modal__scroller:has(.c__share-modal) .c__modal__close .c__button {
|
||||||
display: flex;
|
top: 0.75rem !important;
|
||||||
gap: 0.5rem;
|
right: 0.75rem !important;
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
> div:first-child {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
> button {
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__share-hint {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: var(--c--theme--colors--greyscale-500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { useCalendarContext } from "../../contexts";
|
import { useCalendarContext } from "../../contexts";
|
||||||
|
|
||||||
import { CalendarModal } from "./CalendarModal";
|
import { CalendarModal } from "./CalendarModal";
|
||||||
|
import { CalendarShareModal } from "./CalendarShareModal";
|
||||||
import { DeleteConfirmModal } from "./DeleteConfirmModal";
|
import { DeleteConfirmModal } from "./DeleteConfirmModal";
|
||||||
import { ImportEventsModal } from "./ImportEventsModal";
|
import { ImportEventsModal } from "./ImportEventsModal";
|
||||||
import { SubscriptionUrlModal } from "./SubscriptionUrlModal";
|
import { SubscriptionUrlModal } from "./SubscriptionUrlModal";
|
||||||
@@ -25,20 +26,21 @@ export const CalendarList = () => {
|
|||||||
createCalendar,
|
createCalendar,
|
||||||
updateCalendar,
|
updateCalendar,
|
||||||
deleteCalendar,
|
deleteCalendar,
|
||||||
shareCalendar,
|
|
||||||
calendarRef,
|
calendarRef,
|
||||||
} = useCalendarContext();
|
} = useCalendarContext();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
modalState,
|
modalState,
|
||||||
deleteState,
|
deleteState,
|
||||||
|
shareModalState,
|
||||||
isMyCalendarsExpanded,
|
isMyCalendarsExpanded,
|
||||||
openMenuUrl,
|
openMenuUrl,
|
||||||
handleOpenCreateModal,
|
handleOpenCreateModal,
|
||||||
handleOpenEditModal,
|
handleOpenEditModal,
|
||||||
handleCloseModal,
|
handleCloseModal,
|
||||||
handleSaveCalendar,
|
handleSaveCalendar,
|
||||||
handleShareCalendar,
|
handleOpenShareModal,
|
||||||
|
handleCloseShareModal,
|
||||||
handleOpenDeleteModal,
|
handleOpenDeleteModal,
|
||||||
handleCloseDeleteModal,
|
handleCloseDeleteModal,
|
||||||
handleConfirmDelete,
|
handleConfirmDelete,
|
||||||
@@ -49,7 +51,6 @@ export const CalendarList = () => {
|
|||||||
createCalendar,
|
createCalendar,
|
||||||
updateCalendar,
|
updateCalendar,
|
||||||
deleteCalendar,
|
deleteCalendar,
|
||||||
shareCalendar,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Subscription modal state
|
// Subscription modal state
|
||||||
@@ -147,6 +148,7 @@ export const CalendarList = () => {
|
|||||||
onMenuToggle={handleMenuToggle}
|
onMenuToggle={handleMenuToggle}
|
||||||
onEdit={handleOpenEditModal}
|
onEdit={handleOpenEditModal}
|
||||||
onDelete={handleOpenDeleteModal}
|
onDelete={handleOpenDeleteModal}
|
||||||
|
onShare={handleOpenShareModal}
|
||||||
onImport={handleOpenImportModal}
|
onImport={handleOpenImportModal}
|
||||||
onSubscription={handleOpenSubscriptionModal}
|
onSubscription={handleOpenSubscriptionModal}
|
||||||
onCloseMenu={handleCloseMenu}
|
onCloseMenu={handleCloseMenu}
|
||||||
@@ -163,7 +165,12 @@ export const CalendarList = () => {
|
|||||||
calendar={modalState.calendar}
|
calendar={modalState.calendar}
|
||||||
onClose={handleCloseModal}
|
onClose={handleCloseModal}
|
||||||
onSave={handleSaveCalendar}
|
onSave={handleSaveCalendar}
|
||||||
onShare={handleShareCalendar}
|
/>
|
||||||
|
|
||||||
|
<CalendarShareModal
|
||||||
|
isOpen={shareModalState.isOpen}
|
||||||
|
calendar={shareModalState.calendar}
|
||||||
|
onClose={handleCloseShareModal}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteConfirmModal
|
<DeleteConfirmModal
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const CalendarListItem = ({
|
|||||||
onMenuToggle,
|
onMenuToggle,
|
||||||
onEdit,
|
onEdit,
|
||||||
onDelete,
|
onDelete,
|
||||||
|
onShare,
|
||||||
onImport,
|
onImport,
|
||||||
onSubscription,
|
onSubscription,
|
||||||
onCloseMenu,
|
onCloseMenu,
|
||||||
@@ -53,6 +54,9 @@ export const CalendarListItem = ({
|
|||||||
}
|
}
|
||||||
onEdit={() => onEdit(calendar)}
|
onEdit={() => onEdit(calendar)}
|
||||||
onDelete={() => onDelete(calendar)}
|
onDelete={() => onDelete(calendar)}
|
||||||
|
onShare={
|
||||||
|
onShare ? () => onShare(calendar) : undefined
|
||||||
|
}
|
||||||
onImport={
|
onImport={
|
||||||
onImport ? () => onImport(calendar) : undefined
|
onImport ? () => onImport(calendar) : undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* CalendarModal component.
|
* CalendarModal component.
|
||||||
* Handles creation and editing of calendars, including sharing.
|
* Handles creation and editing of calendars.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
@@ -22,7 +22,6 @@ export const CalendarModal = ({
|
|||||||
calendar,
|
calendar,
|
||||||
onClose,
|
onClose,
|
||||||
onSave,
|
onSave,
|
||||||
onShare,
|
|
||||||
}: CalendarModalProps) => {
|
}: CalendarModalProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
@@ -31,12 +30,6 @@ export const CalendarModal = ({
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
// Share state
|
|
||||||
const [shareEmail, setShareEmail] = useState("");
|
|
||||||
const [isSharing, setIsSharing] = useState(false);
|
|
||||||
const [shareSuccess, setShareSuccess] = useState<string | null>(null);
|
|
||||||
const [shareError, setShareError] = useState<string | null>(null);
|
|
||||||
|
|
||||||
// Reset form when modal opens or calendar changes
|
// Reset form when modal opens or calendar changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
@@ -50,9 +43,6 @@ export const CalendarModal = ({
|
|||||||
setDescription("");
|
setDescription("");
|
||||||
}
|
}
|
||||||
setError(null);
|
setError(null);
|
||||||
setShareEmail("");
|
|
||||||
setShareSuccess(null);
|
|
||||||
setShareError(null);
|
|
||||||
}
|
}
|
||||||
}, [isOpen, mode, calendar]);
|
}, [isOpen, mode, calendar]);
|
||||||
|
|
||||||
@@ -74,47 +64,11 @@ export const CalendarModal = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShare = async () => {
|
|
||||||
if (!shareEmail.trim() || !onShare) return;
|
|
||||||
|
|
||||||
// Basic email validation
|
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
||||||
if (!emailRegex.test(shareEmail.trim())) {
|
|
||||||
setShareError(t('calendar.shareCalendar.invalidEmail'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSharing(true);
|
|
||||||
setShareError(null);
|
|
||||||
setShareSuccess(null);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await onShare(shareEmail.trim());
|
|
||||||
if (result.success) {
|
|
||||||
setShareSuccess(
|
|
||||||
t('calendar.shareCalendar.success', { email: shareEmail.trim() })
|
|
||||||
);
|
|
||||||
setShareEmail("");
|
|
||||||
} else {
|
|
||||||
setShareError(result.error || t('calendar.shareCalendar.error'));
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
setShareError(
|
|
||||||
err instanceof Error ? err.message : t('calendar.shareCalendar.error')
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
setIsSharing(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setName("");
|
setName("");
|
||||||
setColor(DEFAULT_COLORS[0]);
|
setColor(DEFAULT_COLORS[0]);
|
||||||
setDescription("");
|
setDescription("");
|
||||||
setError(null);
|
setError(null);
|
||||||
setShareEmail("");
|
|
||||||
setShareSuccess(null);
|
|
||||||
setShareError(null);
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -186,50 +140,6 @@ export const CalendarModal = ({
|
|||||||
rows={2}
|
rows={2}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Share section - only visible in edit mode */}
|
|
||||||
{mode === "edit" && onShare && (
|
|
||||||
<div className="calendar-modal__share-section">
|
|
||||||
<div className="calendar-modal__share-divider" />
|
|
||||||
<label className="calendar-modal__label">
|
|
||||||
<span className="material-icons">person_add</span>
|
|
||||||
{t('calendar.shareCalendar.title')}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{shareSuccess && (
|
|
||||||
<div className="calendar-modal__success">{shareSuccess}</div>
|
|
||||||
)}
|
|
||||||
{shareError && (
|
|
||||||
<div className="calendar-modal__error">{shareError}</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="calendar-modal__share-input-row">
|
|
||||||
<Input
|
|
||||||
label=""
|
|
||||||
value={shareEmail}
|
|
||||||
onChange={(e) => setShareEmail(e.target.value)}
|
|
||||||
placeholder={t('calendar.shareCalendar.emailPlaceholder')}
|
|
||||||
fullWidth
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
e.preventDefault();
|
|
||||||
handleShare();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
color="brand"
|
|
||||||
onClick={handleShare}
|
|
||||||
disabled={isSharing || !shareEmail.trim()}
|
|
||||||
>
|
|
||||||
{isSharing ? "..." : t('calendar.shareCalendar.share')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<p className="calendar-modal__share-hint">
|
|
||||||
{t('calendar.shareCalendar.hint')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
/**
|
||||||
|
* CalendarShareModal component.
|
||||||
|
* Wraps the UI Kit ShareModal for managing calendar sharing via CalDAV.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState, useEffect, useCallback } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ShareModal } from "@gouvfr-lasuite/ui-kit";
|
||||||
|
|
||||||
|
import { useCalendarContext } from "../../contexts";
|
||||||
|
import { useAuth } from "../../../auth/Auth";
|
||||||
|
import {
|
||||||
|
addToast,
|
||||||
|
ToasterItem,
|
||||||
|
} from "../../../ui/components/toaster/Toaster";
|
||||||
|
import type { CalDavCalendar } from "../../services/dav/types/caldav-service";
|
||||||
|
|
||||||
|
interface CalendarShareModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
calendar: CalDavCalendar | null;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShareUser = {
|
||||||
|
id: string;
|
||||||
|
full_name: string;
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ShareAccess = {
|
||||||
|
id: string;
|
||||||
|
role: string;
|
||||||
|
user: ShareUser;
|
||||||
|
can_delete?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
|
||||||
|
export const CalendarShareModal = ({
|
||||||
|
isOpen,
|
||||||
|
calendar,
|
||||||
|
onClose,
|
||||||
|
}: CalendarShareModalProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { caldavService, shareCalendar } = useCalendarContext();
|
||||||
|
const { user } = useAuth();
|
||||||
|
const [accesses, setAccesses] = useState<ShareAccess[]>([]);
|
||||||
|
const [searchResults, setSearchResults] = useState<ShareUser[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const buildAccesses = useCallback(
|
||||||
|
(sharees: ShareAccess[]) => {
|
||||||
|
const ownerAccess: ShareAccess | null = user
|
||||||
|
? {
|
||||||
|
id: "owner",
|
||||||
|
role: "owner",
|
||||||
|
can_delete: false,
|
||||||
|
user: {
|
||||||
|
id: user.id,
|
||||||
|
full_name: user.email,
|
||||||
|
email: user.email,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
return ownerAccess ? [ownerAccess, ...sharees] : sharees;
|
||||||
|
},
|
||||||
|
[user],
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchSharees = useCallback(async () => {
|
||||||
|
if (!calendar) return;
|
||||||
|
|
||||||
|
const result = await caldavService.getCalendarSharees(calendar.url);
|
||||||
|
if (result.success && result.data) {
|
||||||
|
const shareeAccesses = result.data.map((sharee) => {
|
||||||
|
const email = sharee.href.replace(/^mailto:/, "");
|
||||||
|
return {
|
||||||
|
id: sharee.href,
|
||||||
|
role: "read-write",
|
||||||
|
user: {
|
||||||
|
id: sharee.href,
|
||||||
|
full_name: sharee.displayName || email,
|
||||||
|
email,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setAccesses(buildAccesses(shareeAccesses));
|
||||||
|
} else {
|
||||||
|
setAccesses(buildAccesses([]));
|
||||||
|
}
|
||||||
|
}, [calendar, caldavService, buildAccesses]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen && calendar) {
|
||||||
|
fetchSharees();
|
||||||
|
}
|
||||||
|
if (!isOpen) {
|
||||||
|
setAccesses([]);
|
||||||
|
setSearchResults([]);
|
||||||
|
}
|
||||||
|
}, [isOpen, calendar, fetchSharees]);
|
||||||
|
|
||||||
|
const handleSearchUsers = useCallback((query: string) => {
|
||||||
|
if (EMAIL_REGEX.test(query.trim())) {
|
||||||
|
const email = query.trim();
|
||||||
|
setSearchResults([
|
||||||
|
{ id: email, email, full_name: email },
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
setSearchResults([]);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleInviteUser = useCallback(
|
||||||
|
async (users: ShareUser[]) => {
|
||||||
|
if (!calendar || users.length === 0) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const user = users[0];
|
||||||
|
const result = await shareCalendar(calendar.url, user.email);
|
||||||
|
if (result.success) {
|
||||||
|
addToast(
|
||||||
|
<ToasterItem>
|
||||||
|
{t("calendar.shareCalendar.success", {
|
||||||
|
email: user.email,
|
||||||
|
})}
|
||||||
|
</ToasterItem>,
|
||||||
|
);
|
||||||
|
await fetchSharees();
|
||||||
|
} else {
|
||||||
|
addToast(
|
||||||
|
<ToasterItem type="error">
|
||||||
|
{result.error || t("calendar.shareCalendar.error")}
|
||||||
|
</ToasterItem>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
addToast(
|
||||||
|
<ToasterItem type="error">
|
||||||
|
{t("calendar.shareCalendar.error")}
|
||||||
|
</ToasterItem>,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
setSearchResults([]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[calendar, shareCalendar, fetchSharees, t],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDeleteAccess = useCallback(
|
||||||
|
async (access: ShareAccess) => {
|
||||||
|
if (!calendar) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const shareeHref = access.id.startsWith("mailto:")
|
||||||
|
? access.id
|
||||||
|
: `mailto:${access.user.email}`;
|
||||||
|
const result = await caldavService.unshareCalendar(
|
||||||
|
calendar.url,
|
||||||
|
shareeHref,
|
||||||
|
);
|
||||||
|
if (result.success) {
|
||||||
|
await fetchSharees();
|
||||||
|
} else {
|
||||||
|
addToast(
|
||||||
|
<ToasterItem type="error">
|
||||||
|
{result.error || t("calendar.shareCalendar.error")}
|
||||||
|
</ToasterItem>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
addToast(
|
||||||
|
<ToasterItem type="error">
|
||||||
|
{t("calendar.shareCalendar.error")}
|
||||||
|
</ToasterItem>,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[calendar, caldavService, fetchSharees, t],
|
||||||
|
);
|
||||||
|
|
||||||
|
const invitationRoles = [
|
||||||
|
{ label: t("roles.editor"), value: "read-write" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const getAccessRoles = useCallback(
|
||||||
|
(access: ShareAccess) => {
|
||||||
|
if (access.role === "owner") {
|
||||||
|
return [{ label: t("roles.owner"), value: "owner" }];
|
||||||
|
}
|
||||||
|
return [{ label: t("roles.editor"), value: "read-write" }];
|
||||||
|
},
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ShareModal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
modalTitle={t("calendar.shareCalendar.title")}
|
||||||
|
accesses={accesses}
|
||||||
|
getAccessRoles={getAccessRoles}
|
||||||
|
onDeleteAccess={handleDeleteAccess}
|
||||||
|
searchUsersResult={searchResults}
|
||||||
|
onSearchUsers={handleSearchUsers}
|
||||||
|
onInviteUser={handleInviteUser}
|
||||||
|
searchPlaceholder={t("calendar.shareCalendar.emailPlaceholder")}
|
||||||
|
invitationRoles={invitationRoles}
|
||||||
|
hideInvitations
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -10,7 +10,7 @@ import type {
|
|||||||
CalDavCalendarCreate,
|
CalDavCalendarCreate,
|
||||||
CalDavCalendarUpdate,
|
CalDavCalendarUpdate,
|
||||||
} from "../../../services/dav/types/caldav-service";
|
} from "../../../services/dav/types/caldav-service";
|
||||||
import type { CalendarModalState, DeleteState } from "../types";
|
import type { CalendarModalState, DeleteState, ShareModalState } from "../types";
|
||||||
|
|
||||||
interface UseCalendarListStateProps {
|
interface UseCalendarListStateProps {
|
||||||
createCalendar: (
|
createCalendar: (
|
||||||
@@ -21,17 +21,12 @@ interface UseCalendarListStateProps {
|
|||||||
options: CalDavCalendarUpdate
|
options: CalDavCalendarUpdate
|
||||||
) => Promise<{ success: boolean; error?: string }>;
|
) => Promise<{ success: boolean; error?: string }>;
|
||||||
deleteCalendar: (url: string) => Promise<{ success: boolean; error?: string }>;
|
deleteCalendar: (url: string) => Promise<{ success: boolean; error?: string }>;
|
||||||
shareCalendar: (
|
|
||||||
url: string,
|
|
||||||
email: string
|
|
||||||
) => Promise<{ success: boolean; error?: string }>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCalendarListState = ({
|
export const useCalendarListState = ({
|
||||||
createCalendar,
|
createCalendar,
|
||||||
updateCalendar,
|
updateCalendar,
|
||||||
deleteCalendar,
|
deleteCalendar,
|
||||||
shareCalendar,
|
|
||||||
}: UseCalendarListStateProps) => {
|
}: UseCalendarListStateProps) => {
|
||||||
// Modal states
|
// Modal states
|
||||||
const [modalState, setModalState] = useState<CalendarModalState>({
|
const [modalState, setModalState] = useState<CalendarModalState>({
|
||||||
@@ -46,6 +41,11 @@ export const useCalendarListState = ({
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [shareModalState, setShareModalState] = useState<ShareModalState>({
|
||||||
|
isOpen: false,
|
||||||
|
calendar: null,
|
||||||
|
});
|
||||||
|
|
||||||
const [isMyCalendarsExpanded, setIsMyCalendarsExpanded] = useState(true);
|
const [isMyCalendarsExpanded, setIsMyCalendarsExpanded] = useState(true);
|
||||||
const [openMenuUrl, setOpenMenuUrl] = useState<string | null>(null);
|
const [openMenuUrl, setOpenMenuUrl] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -100,15 +100,14 @@ export const useCalendarListState = ({
|
|||||||
[modalState, createCalendar, updateCalendar]
|
[modalState, createCalendar, updateCalendar]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleShareCalendar = useCallback(
|
// Share modal handlers
|
||||||
async (email: string): Promise<{ success: boolean; error?: string }> => {
|
const handleOpenShareModal = useCallback((calendar: CalDavCalendar) => {
|
||||||
if (!modalState.calendar) {
|
setShareModalState({ isOpen: true, calendar });
|
||||||
return { success: false, error: 'No calendar selected' };
|
}, []);
|
||||||
}
|
|
||||||
return shareCalendar(modalState.calendar.url, email);
|
const handleCloseShareModal = useCallback(() => {
|
||||||
},
|
setShareModalState({ isOpen: false, calendar: null });
|
||||||
[modalState.calendar, shareCalendar]
|
}, []);
|
||||||
);
|
|
||||||
|
|
||||||
// Delete handlers
|
// Delete handlers
|
||||||
const handleOpenDeleteModal = useCallback((calendar: CalDavCalendar) => {
|
const handleOpenDeleteModal = useCallback((calendar: CalDavCalendar) => {
|
||||||
@@ -164,6 +163,7 @@ export const useCalendarListState = ({
|
|||||||
// Modal state
|
// Modal state
|
||||||
modalState,
|
modalState,
|
||||||
deleteState,
|
deleteState,
|
||||||
|
shareModalState,
|
||||||
|
|
||||||
// Expansion state
|
// Expansion state
|
||||||
isMyCalendarsExpanded,
|
isMyCalendarsExpanded,
|
||||||
@@ -174,7 +174,10 @@ export const useCalendarListState = ({
|
|||||||
handleOpenEditModal,
|
handleOpenEditModal,
|
||||||
handleCloseModal,
|
handleCloseModal,
|
||||||
handleSaveCalendar,
|
handleSaveCalendar,
|
||||||
handleShareCalendar,
|
|
||||||
|
// Share modal handlers
|
||||||
|
handleOpenShareModal,
|
||||||
|
handleCloseShareModal,
|
||||||
|
|
||||||
// Delete handlers
|
// Delete handlers
|
||||||
handleOpenDeleteModal,
|
handleOpenDeleteModal,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export interface CalendarModalProps {
|
|||||||
calendar?: CalDavCalendar | null;
|
calendar?: CalDavCalendar | null;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSave: (name: string, color: string, description?: string) => Promise<void>;
|
onSave: (name: string, color: string, description?: string) => Promise<void>;
|
||||||
onShare?: (email: string) => Promise<{ success: boolean; error?: string }>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,6 +23,7 @@ export interface CalendarItemMenuProps {
|
|||||||
onOpenChange: (isOpen: boolean) => void;
|
onOpenChange: (isOpen: boolean) => void;
|
||||||
onEdit: () => void;
|
onEdit: () => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
|
onShare?: () => void;
|
||||||
onImport?: () => void;
|
onImport?: () => void;
|
||||||
onSubscription?: () => void;
|
onSubscription?: () => void;
|
||||||
}
|
}
|
||||||
@@ -50,6 +50,7 @@ export interface CalendarListItemProps {
|
|||||||
onMenuToggle: (url: string) => void;
|
onMenuToggle: (url: string) => void;
|
||||||
onEdit: (calendar: CalDavCalendar) => void;
|
onEdit: (calendar: CalDavCalendar) => void;
|
||||||
onDelete: (calendar: CalDavCalendar) => void;
|
onDelete: (calendar: CalDavCalendar) => void;
|
||||||
|
onShare?: (calendar: CalDavCalendar) => void;
|
||||||
onImport?: (calendar: CalDavCalendar) => void;
|
onImport?: (calendar: CalDavCalendar) => void;
|
||||||
onSubscription?: (calendar: CalDavCalendar) => void;
|
onSubscription?: (calendar: CalDavCalendar) => void;
|
||||||
onCloseMenu: () => void;
|
onCloseMenu: () => void;
|
||||||
@@ -64,6 +65,14 @@ export interface CalendarModalState {
|
|||||||
calendar: CalDavCalendar | null;
|
calendar: CalDavCalendar | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State for the share modal.
|
||||||
|
*/
|
||||||
|
export interface ShareModalState {
|
||||||
|
isOpen: boolean;
|
||||||
|
calendar: CalDavCalendar | null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State for the delete confirmation.
|
* State for the delete confirmation.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -183,6 +183,7 @@
|
|||||||
"showCalendar": "Show calendar",
|
"showCalendar": "Show calendar",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
|
"share": "Share",
|
||||||
"import": "Import events",
|
"import": "Import events",
|
||||||
"subscription": "Subscription URL",
|
"subscription": "Subscription URL",
|
||||||
"options": "Options"
|
"options": "Options"
|
||||||
@@ -809,6 +810,7 @@
|
|||||||
"showCalendar": "Afficher le calendrier",
|
"showCalendar": "Afficher le calendrier",
|
||||||
"edit": "Modifier",
|
"edit": "Modifier",
|
||||||
"delete": "Supprimer",
|
"delete": "Supprimer",
|
||||||
|
"share": "Partager",
|
||||||
"import": "Importer des événements",
|
"import": "Importer des événements",
|
||||||
"subscription": "URL d'abonnement",
|
"subscription": "URL d'abonnement",
|
||||||
"options": "Options"
|
"options": "Options"
|
||||||
@@ -1182,6 +1184,7 @@
|
|||||||
"showCalendar": "Agenda tonen",
|
"showCalendar": "Agenda tonen",
|
||||||
"edit": "Bewerken",
|
"edit": "Bewerken",
|
||||||
"delete": "Verwijderen",
|
"delete": "Verwijderen",
|
||||||
|
"share": "Delen",
|
||||||
"import": "Evenementen importeren",
|
"import": "Evenementen importeren",
|
||||||
"subscription": "Abonnements-URL",
|
"subscription": "Abonnements-URL",
|
||||||
"options": "Opties"
|
"options": "Opties"
|
||||||
|
|||||||
Reference in New Issue
Block a user