diff --git a/components/upload-zone.tsx b/components/upload-zone.tsx index 39f2561a..2f0f1bb0 100644 --- a/components/upload-zone.tsx +++ b/components/upload-zone.tsx @@ -25,6 +25,7 @@ import { } from "@/lib/folders/create-folder"; import { usePlan } from "@/lib/swr/use-billing"; import useLimits from "@/lib/swr/use-limits"; +import { useTeamSettings } from "@/lib/swr/use-team-settings"; import { CustomUser } from "@/lib/types"; import { cn } from "@/lib/utils"; import { getSupportedContentType } from "@/lib/utils/get-content-type"; @@ -103,6 +104,11 @@ export default function UploadZone({ ? limits?.documents - limits?.usage?.documents : 0; + // Fetch team settings with proper revalidation - ensures settings stay fresh across tabs + const { settings: teamSettings } = useTeamSettings(teamInfo?.currentTeam?.id); + const replicateDataroomFolders = + teamSettings?.replicateDataroomFolders ?? true; + // Track if we've created the dataroom folder in "All Documents" for non-replication mode // Using promise-lock pattern to prevent race conditions during concurrent folder creation const dataroomFolderPathRef = useRef(null); @@ -113,7 +119,7 @@ export default function UploadZone({ useEffect(() => { dataroomFolderPathRef.current = null; dataroomFolderCreationPromiseRef.current = null; - }, [teamInfo?.currentTeam?.replicateDataroomFolders]); + }, [replicateDataroomFolders, dataroomId]); const fileSizeLimits = useMemo( () => @@ -618,10 +624,6 @@ export default function UploadZone({ isFirstLevelFolder, }); - // Get team's replication setting, default to true if not set - const replicateDataroomFolders = - teamInfo.currentTeam?.replicateDataroomFolders ?? true; - // If replication is disabled, ensure the dataroom folder exists in "All Documents" // Uses promise-lock pattern to prevent race conditions if (!replicateDataroomFolders && dataroomName) { @@ -730,9 +732,6 @@ export default function UploadZone({ : entry.fullPath; // Determine where to upload in "All Documents" - const replicateDataroomFolders = - teamInfo.currentTeam?.replicateDataroomFolders ?? true; - if (!replicateDataroomFolders && dataroomId && dataroomName) { // If replication is disabled, ensure the dataroom folder exists and use it // This await is safe because getOrCreateDataroomFolder uses a promise-lock diff --git a/context/team-context.tsx b/context/team-context.tsx index e8d02bce..7b4ea4a5 100644 --- a/context/team-context.tsx +++ b/context/team-context.tsx @@ -36,6 +36,7 @@ export const TeamProvider = ({ children }: TeamContextProps): JSX.Element => { const { teams, loading } = useTeams(); const [currentTeam, setCurrentTeamState] = useState(null); + // Effect to set initial currentTeam on mount useEffect(() => { if (!teams || teams.length === 0 || currentTeam) return; diff --git a/lib/swr/use-team-settings.ts b/lib/swr/use-team-settings.ts new file mode 100644 index 00000000..4124d04c --- /dev/null +++ b/lib/swr/use-team-settings.ts @@ -0,0 +1,31 @@ +import useSWR from "swr"; + +import { fetcher } from "@/lib/utils"; + +interface TeamSettings { + replicateDataroomFolders: boolean; + enableExcelAdvancedMode: boolean; +} + +/** + * Hook to fetch fresh team settings with proper revalidation. + * Useful when you need to ensure settings are up-to-date across tabs. + */ +export function useTeamSettings(teamId: string | undefined | null) { + const { data, error, isValidating } = useSWR( + teamId ? `/api/teams/${teamId}/settings` : null, + fetcher, + { + revalidateOnFocus: true, + revalidateOnReconnect: true, + dedupingInterval: 5000, // Short deduping for settings + }, + ); + + return { + settings: data, + isLoading: !data && !error, + isError: error, + isValidating, + }; +} diff --git a/pages/api/teams/[teamId]/settings.ts b/pages/api/teams/[teamId]/settings.ts new file mode 100644 index 00000000..3f534b7f --- /dev/null +++ b/pages/api/teams/[teamId]/settings.ts @@ -0,0 +1,64 @@ +import { NextApiRequest, NextApiResponse } from "next"; + +import { getServerSession } from "next-auth/next"; + +import { errorhandler } from "@/lib/errorHandler"; +import prisma from "@/lib/prisma"; +import { CustomUser } from "@/lib/types"; + +import { authOptions } from "../../auth/[...nextauth]"; + +export default async function handle( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method === "GET") { + // GET /api/teams/:teamId/settings + const session = await getServerSession(req, res, authOptions); + if (!session) { + return res.status(401).end("Unauthorized"); + } + + const { teamId } = req.query as { teamId: string }; + const userId = (session.user as CustomUser).id; + + try { + // Verify user has access to the team + const teamAccess = await prisma.userTeam.findUnique({ + where: { + userId_teamId: { + userId: userId, + teamId: teamId, + }, + }, + select: { teamId: true }, + }); + + if (!teamAccess) { + return res.status(401).end("Unauthorized"); + } + + // Fetch only the settings fields + const teamSettings = await prisma.team.findUnique({ + where: { + id: teamId, + }, + select: { + replicateDataroomFolders: true, + enableExcelAdvancedMode: true, + }, + }); + + if (!teamSettings) { + return res.status(404).json({ error: "Team not found" }); + } + + return res.status(200).json(teamSettings); + } catch (error) { + errorhandler(error, res); + } + } else { + res.setHeader("Allow", ["GET"]); + return res.status(405).end(`Method ${req.method} Not Allowed`); + } +} diff --git a/pages/settings/general.tsx b/pages/settings/general.tsx index 8c38260d..14f6b923 100644 --- a/pages/settings/general.tsx +++ b/pages/settings/general.tsx @@ -7,6 +7,7 @@ import { mutate } from "swr"; import { useAnalytics } from "@/lib/analytics"; import { usePlan } from "@/lib/swr/use-billing"; +import { useTeamSettings } from "@/lib/swr/use-team-settings"; import { validateContent } from "@/lib/utils/sanitize-html"; import { UpgradePlanModal } from "@/components/billing/upgrade-plan-modal"; @@ -26,6 +27,9 @@ export default function General() { const [planModalTrigger, setPlanModalTrigger] = useState(""); const [planModalOpen, setPlanModalOpen] = useState(false); + // Fetch fresh team settings with proper revalidation + const { settings: teamSettings } = useTeamSettings(teamId); + const showUpgradeModal = (plan: PlanEnum, trigger: string) => { setSelectedPlan(plan); setPlanModalTrigger(trigger); @@ -62,7 +66,11 @@ export default function General() { const { error } = await res.json(); throw new Error(error.message); } - await Promise.all([mutate(`/api/teams/${teamId}`), mutate(`/api/teams`)]); + await Promise.all([ + mutate(`/api/teams/${teamId}`), + mutate(`/api/teams`), + mutate(`/api/teams/${teamId}/settings`), + ]); return res.json(); }); @@ -97,7 +105,11 @@ export default function General() { const { error } = await res.json(); throw new Error(error.message); } - await Promise.all([mutate(`/api/teams/${teamId}`), mutate(`/api/teams`)]); + await Promise.all([ + mutate(`/api/teams/${teamId}`), + mutate(`/api/teams`), + mutate(`/api/teams/${teamId}/settings`), + ]); return res.json(); }); @@ -188,7 +200,7 @@ export default function General() { placeholder: "Enable advanced mode for Excel files", }} defaultValue={String( - teamInfo?.currentTeam?.enableExcelAdvancedMode ?? false, + teamSettings?.enableExcelAdvancedMode ?? false, )} helpText="When enabled, newly uploaded Excel files will be viewed using the Microsoft Office viewer for better formatting and compatibility." handleSubmit={handleExcelAdvancedModeChange} @@ -204,7 +216,7 @@ export default function General() { placeholder: "Replicate folder structure in All Documents", }} defaultValue={String( - teamInfo?.currentTeam?.replicateDataroomFolders ?? true, + teamSettings?.replicateDataroomFolders ?? true, )} helpText="When enabled, folders uploaded to datarooms will be created in 'All Documents' with the same structure. When disabled, all documents will be placed in a single folder named after the dataroom in 'All Documents'." handleSubmit={handleReplicateFoldersChange}