feat(platform): harden access scoping and delivery baseline

This commit is contained in:
2026-03-30 00:27:31 +02:00
parent 00b936fa1f
commit 819345acfa
109 changed files with 26142 additions and 8081 deletions
@@ -1,6 +1,7 @@
"use client";
import { clsx } from "clsx";
import { useSession } from "next-auth/react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useAllocationHistory } from "~/hooks/useAllocationHistory.js";
import { useProjectDragContext } from "~/hooks/useProjectDragContext.js";
@@ -38,7 +39,13 @@ import { useMultiSelectIntersection } from "~/hooks/useMultiSelectIntersection.j
// then wraps children with TimelineProvider. The inner content consumes context.
export function TimelineView() {
const { data: session, status: sessionStatus } = useSession();
const mousePosRef = useRef({ x: 0, y: 0 });
const role = sessionStatus === "authenticated"
? ((session.user as { role?: string } | undefined)?.role ?? "USER")
: null;
const isSelfServiceTimeline = role === "USER" || role === "VIEWER";
const canManageTimeline = !isSelfServiceTimeline;
const { push: pushHistory, pushBatch: pushBatchHistory, undo, redo, canUndo, canRedo } = useAllocationHistory();
const pushHistoryRef = useRef(pushHistory);
@@ -147,8 +154,8 @@ export function TimelineView() {
const [openPanelProjectId, setOpenPanelProjectId] = useState<string | null>(null);
const dragProjectId = dragState.isDragging ? dragState.projectId : null;
const contextProjectId = dragProjectId ?? openPanelProjectId;
const { contextResourceIds, contextAllocations } = useProjectDragContext(contextProjectId);
const contextProjectId = canManageTimeline ? (dragProjectId ?? openPanelProjectId) : null;
const { contextResourceIds, contextAllocations } = useProjectDragContext(contextProjectId, canManageTimeline);
return (
<TimelineProvider
@@ -189,6 +196,7 @@ export function TimelineView() {
setOpenPanelProjectId={setOpenPanelProjectId}
canUndo={canUndo}
canRedo={canRedo}
isSelfServiceTimeline={isSelfServiceTimeline}
undo={undo}
redo={redo}
/>
@@ -232,6 +240,7 @@ function TimelineViewContent({
setOpenPanelProjectId,
canUndo,
canRedo,
isSelfServiceTimeline,
undo,
redo,
}: {
@@ -278,6 +287,7 @@ function TimelineViewContent({
setOpenPanelProjectId: React.Dispatch<React.SetStateAction<string | null>>;
canUndo: boolean;
canRedo: boolean;
isSelfServiceTimeline: boolean;
undo: () => Promise<void>;
redo: () => Promise<void>;
}) {
@@ -642,7 +652,7 @@ function TimelineViewContent({
onMouseUp={(e) => void onCanvasMouseUp(e)}
onMouseLeave={onCanvasMouseLeave}
onMouseDown={(e) => {
if (e.button === 2) {
if (!isSelfServiceTimeline && e.button === 2) {
onCanvasRightMouseDown(e);
}
}}
@@ -666,11 +676,11 @@ function TimelineViewContent({
rangeState={effectiveRangeState}
shiftPreview={shiftPreview}
contextResourceIds={contextResourceIds}
onAllocMouseDown={onAllocMouseDown}
onAllocTouchStart={onAllocTouchStart}
onRowMouseDown={onRowMouseDown}
onRowTouchStart={onRowTouchStart}
onAllocationContextMenu={openAllocationPopoverAt}
onAllocMouseDown={isSelfServiceTimeline ? () => undefined : onAllocMouseDown}
onAllocTouchStart={isSelfServiceTimeline ? () => undefined : onAllocTouchStart}
onRowMouseDown={isSelfServiceTimeline ? () => undefined : onRowMouseDown}
onRowTouchStart={isSelfServiceTimeline ? () => undefined : onRowTouchStart}
onAllocationContextMenu={isSelfServiceTimeline ? () => undefined : openAllocationPopoverAt}
multiSelectState={multiSelectState}
CELL_WIDTH={CELL_WIDTH}
dates={dates}
@@ -689,15 +699,15 @@ function TimelineViewContent({
allocDragState={allocDragState}
rangeState={effectiveRangeState}
multiSelectState={multiSelectState}
onProjectBarMouseDown={onProjectBarMouseDown}
onProjectBarTouchStart={onProjectBarTouchStart}
onAllocMouseDown={onAllocMouseDown}
onAllocTouchStart={onAllocTouchStart}
onRowMouseDown={onRowMouseDown}
onRowTouchStart={onRowTouchStart}
onOpenPanel={setOpenPanelProjectId}
onOpenDemandClick={setOpenDemandToAssign}
onAllocationContextMenu={openAllocationPopoverAt}
onProjectBarMouseDown={isSelfServiceTimeline ? () => undefined : onProjectBarMouseDown}
onProjectBarTouchStart={isSelfServiceTimeline ? () => undefined : onProjectBarTouchStart}
onAllocMouseDown={isSelfServiceTimeline ? () => undefined : onAllocMouseDown}
onAllocTouchStart={isSelfServiceTimeline ? () => undefined : onAllocTouchStart}
onRowMouseDown={isSelfServiceTimeline ? () => undefined : onRowMouseDown}
onRowTouchStart={isSelfServiceTimeline ? () => undefined : onRowTouchStart}
onOpenPanel={isSelfServiceTimeline ? () => undefined : setOpenPanelProjectId}
onOpenDemandClick={isSelfServiceTimeline ? () => undefined : setOpenDemandToAssign}
onAllocationContextMenu={isSelfServiceTimeline ? () => undefined : openAllocationPopoverAt}
CELL_WIDTH={CELL_WIDTH}
dates={dates}
totalCanvasWidth={totalCanvasWidth}
@@ -815,7 +825,7 @@ function TimelineViewContent({
)}
{/* Allocation / Demand popover (click path) */}
{popover && (() => {
{!isSelfServiceTimeline && popover && (() => {
// Check if clicked allocation is actually a demand
const clickedDemand = openDemandsByProject.get(popover.projectId)?.find((d) => d.id === popover.allocationId);
if (clickedDemand) {
@@ -863,7 +873,7 @@ function TimelineViewContent({
})()}
{/* Demand popover */}
{demandPopover && (
{!isSelfServiceTimeline && demandPopover && (
<DemandPopover
demand={demandPopover.demand}
onClose={() => setDemandPopover(null)}
@@ -892,7 +902,7 @@ function TimelineViewContent({
)}
{/* New allocation popover */}
{newAllocPopover && (
{!isSelfServiceTimeline && newAllocPopover && (
<NewAllocationPopover
resourceId={newAllocPopover.resourceId}
startDate={newAllocPopover.startDate}
@@ -906,12 +916,12 @@ function TimelineViewContent({
)}
{/* Project side panel */}
{openPanelProjectId && (
{!isSelfServiceTimeline && openPanelProjectId && (
<ProjectPanel projectId={openPanelProjectId} onClose={() => setOpenPanelProjectId(null)} />
)}
{/* Open-demand assignment modal */}
{openDemandToAssign && (
{!isSelfServiceTimeline && openDemandToAssign && (
<FillOpenDemandModal
allocation={openDemandToAssign}
onClose={() => setOpenDemandToAssign(null)}