diff --git a/backend/main.py b/backend/main.py index 2ab2ebe..ec5ae56 100644 --- a/backend/main.py +++ b/backend/main.py @@ -339,8 +339,14 @@ def api_update_member_role( data: MemberRoleUpdate, current_user: int = Depends(get_current_user), ): - current_role = get_member_role(room_id, current_user) - target_role = get_member_role(room_id, user_id) + role_levels = { + "member": 1, + "admin": 2, + "owner": 3, + } + + current_role = role_levels.get(get_role(room_id, current_user), 0) + target_role = role_levels.get(get_role(room_id, user_id), 0) if current_role is None: raise HTTPException( diff --git a/frontend(experimental)/newchat/components/event-detail-dialog.tsx b/frontend(experimental)/newchat/components/event-detail-dialog.tsx index 3166334..fa3bcd4 100644 --- a/frontend(experimental)/newchat/components/event-detail-dialog.tsx +++ b/frontend(experimental)/newchat/components/event-detail-dialog.tsx @@ -46,6 +46,7 @@ export function EventDetailDialog({ }: EventDetailDialogProps) { const [busy, setBusy] = useState(null) const [error, setError] = useState(null) + const [deleteConfirming, setDeleteConfirming] = useState(false) const { data: responses, mutate: mutateResponses } = useSWR( ["responses", event.id], @@ -171,32 +172,71 @@ export function EventDetailDialog({ {/* Management actions */} {canManage && ( -
- {event.status !== "confirmed" && event.status !== "cancelled" && ( - - )} - {event.status !== "cancelled" && ( - - )} - +
+
+ {event.status !== "confirmed" && event.status !== "cancelled" && ( + + )} + {event.status !== "cancelled" && ( + + )} +
+ + {/* Delete lives in its own fixed spot, separate from Confirm/Cancel, + so its position never shifts depending on which of those show. */} +
+ {!deleteConfirming ? ( + + ) : ( +
+

+ Weet je zeker dat je dit evenement wilt verwijderen? Dit kan niet ongedaan + worden gemaakt. +

+
+ {/* Destructive action sits on top, off the "usual" spot. */} + + {/* Safe action sits below, in the spot people click fast out of habit. */} + +
+
+ )} +
)}
diff --git a/frontend(experimental)/newchat/components/room-settings-dialog.tsx b/frontend(experimental)/newchat/components/room-settings-dialog.tsx index 778d9d6..b7fe84e 100644 --- a/frontend(experimental)/newchat/components/room-settings-dialog.tsx +++ b/frontend(experimental)/newchat/components/room-settings-dialog.tsx @@ -43,6 +43,7 @@ export function RoomSettingsDialog({ const [copied, setCopied] = useState(false) const [busy, setBusy] = useState(null) const [error, setError] = useState(null) + const [deleteConfirming, setDeleteConfirming] = useState(false) const isOwner = room.role === "owner" const isAdmin = room.role === "admin" || isOwner @@ -241,28 +242,58 @@ export function RoomSettingsDialog({ )} {/* Danger zone */} -
+
+ {isOwner && ( - ) : ( - +
+

+ Weet je zeker dat je deze room wilt verwijderen? Dit kan niet ongedaan worden + gemaakt. +

+
+ {/* Destructive action sits on top, off the "usual" spot. */} + + {/* Safe action sits below, in the spot people click fast out of habit. */} + +
+
)} - Delete room - +
)}
diff --git a/frontend(experimental)/newchat/lib/room-theme.ts b/frontend(experimental)/newchat/lib/room-theme.ts index b742114..df2240b 100644 --- a/frontend(experimental)/newchat/lib/room-theme.ts +++ b/frontend(experimental)/newchat/lib/room-theme.ts @@ -42,9 +42,10 @@ export function getRoomColor(roomId: number | null | undefined) { // ---------- Event coloring by type / status / RSVP ---------- // // Confirmed and busy events each pick a color from a small themed pool, -// deterministically by room so the same room always looks the same. -// Private, proposed, declined and cancelled get one fixed look each so -// they're recognizable at a glance regardless of room. +// deterministically by event id — so color varies event-to-event rather +// than clustering by room, but a given event still looks the same on +// every render/refresh. Private, proposed, declined and cancelled get +// one fixed look each so they're recognizable at a glance. export interface EventStyle { bg: string @@ -73,9 +74,9 @@ const BUSY_PALETTE = [ function pickFromPalette( palette: { bg: string; ring: string }[], - roomId: number | null | undefined, + seed: number | null | undefined, ) { - const id = typeof roomId === "number" && Number.isFinite(roomId) ? roomId : 0 + const id = typeof seed === "number" && Number.isFinite(seed) ? seed : 0 const idx = ((id % palette.length) + palette.length) % palette.length return palette[idx] } @@ -98,17 +99,17 @@ export const STRIPE_STYLE: CSSProperties = { } export function getEventStyle( - event: Pick, + event: Pick, myResponse: SignupStatus | undefined, ): EventStyle { if (event.type === "private") return PRIVATE_STYLE - if (event.type === "busy") return pickFromPalette(BUSY_PALETTE, event.room_id) + if (event.type === "busy") return pickFromPalette(BUSY_PALETTE, event.id) // Proposal-type events: colored by where they stand. if (event.status === "cancelled") return CANCELLED_STYLE if (myResponse === "declined") return DECLINED_STYLE if (event.status === "proposed" || event.status === "draft") return PROPOSED_STYLE - // Confirmed proposal: pick from the confirmed palette, by room. - return pickFromPalette(CONFIRMED_PALETTE, event.room_id) + // Confirmed proposal: pick from the confirmed palette, per event. + return pickFromPalette(CONFIRMED_PALETTE, event.id) } diff --git a/frontend(experimental)/newchat_updated.tar.gz b/frontend(experimental)/newchat_updated.tar.gz index 6154eb1..5be6dcc 100644 Binary files a/frontend(experimental)/newchat_updated.tar.gz and b/frontend(experimental)/newchat_updated.tar.gz differ