"use client"; import { useEffect, useMemo, useState, type ComponentType, type DragEvent } from "react"; import { useTranslations } from "next-intl"; import { NavigationRail } from "@/components/layout/navigation-rail"; import { KeyboardShortcutsModal } from "@/components/keyboard-shortcuts-modal"; import { SidebarAppsModal } from "@/components/layout/sidebar-apps-modal"; import { InlineAppView } from "@/components/layout/inline-app-view"; import { useSidebarApps } from "@/hooks/use-sidebar-apps"; import { useAuthStore, redirectToLogin } from "@/stores/auth-store"; import { useEmailStore } from "@/stores/email-store"; import { useDeviceDetection } from "@/hooks/use-media-query"; import { EmbeddedContext } from "@/hooks/use-is-embedded"; import { ProTabBar, PRO_TAB_DRAG_MIME } from "@/components/pro/pro-tab-bar"; import { useProTabStore, type ProTab, type ProTabKind, type ProPaneId } from "@/stores/pro-tab-store"; import { cn } from "@/lib/utils"; import MailPage from "@/app/[locale]/page"; import CalendarPage from "@/app/[locale]/calendar/page"; import ContactsPage from "@/app/[locale]/contacts/page"; import FilesPage from "@/app/[locale]/files/page"; import SettingsPage from "@/app/[locale]/settings/page"; import { ProComposeTabBody } from "@/components/pro/pro-compose-tab-body"; import { ProEmailTabBody } from "@/components/pro/pro-email-tab-body"; const APP_TAB_COMPONENTS: Partial> = { mail: MailPage, calendar: CalendarPage, contacts: ContactsPage, files: FilesPage, settings: SettingsPage, }; type DropTarget = 'left' | 'right' | 'top' | 'bottom' | null; function renderTabBody(tab: ProTab): React.ReactNode { if (tab.kind === 'compose' && tab.composeData) { return ; } if (tab.kind === 'email' && tab.emailData) { return ; } const Component = APP_TAB_COMPONENTS[tab.kind]; return Component ? : null; } interface PaneProps { paneId: ProPaneId; tabs: ProTab[]; activeTabId: string | null; loadedTabIds: string[]; onPaneFocus: (paneId: ProPaneId) => void; isFocused: boolean; } function Pane({ paneId, tabs, activeTabId, loadedTabIds, onPaneFocus, isFocused }: PaneProps) { return (
{ if (!isFocused) onPaneFocus(paneId); }} > {tabs .filter((tab) => loadedTabIds.includes(tab.id)) .map((tab) => { const isActive = tab.id === activeTabId; return (
{renderTabBody(tab)}
); })}
); } export default function ProHome() { const t = useTranslations(); const { isMobile, isTablet, isDesktop } = useDeviceDetection(); const [initialCheckDone, setInitialCheckDone] = useState( () => useAuthStore.getState().isAuthenticated && !!useAuthStore.getState().client ); const [showShortcutsModal, setShowShortcutsModal] = useState(false); const { showAppsModal, inlineApp, loadedApps, handleManageApps, handleInlineApp, closeInlineApp, closeAppsModal, } = useSidebarApps(); const isAuthenticated = useAuthStore((s) => s.isAuthenticated); const client = useAuthStore((s) => s.client); const logout = useAuthStore((s) => s.logout); const checkAuth = useAuthStore((s) => s.checkAuth); const authLoading = useAuthStore((s) => s.isLoading); const quota = useEmailStore((s) => s.quota); const isPushConnected = useEmailStore((s) => s.isPushConnected); const tabs = useProTabStore((s) => s.tabs); const activeMainTabId = useProTabStore((s) => s.activeTabId); const activeSplitTabId = useProTabStore((s) => s.activeSplitTabId); const splitOrientation = useProTabStore((s) => s.splitOrientation); const focusedPaneId = useProTabStore((s) => s.focusedPaneId); const loadedTabIds = useProTabStore((s) => s.loadedTabIds); const openTab = useProTabStore((s) => s.openTab); const closeTab = useProTabStore((s) => s.closeTab); const setActiveTab = useProTabStore((s) => s.setActiveTab); const setFocusedPane = useProTabStore((s) => s.setFocusedPane); const moveTabToPane = useProTabStore((s) => s.moveTabToPane); const [isTabDragging, setIsTabDragging] = useState(false); const [splitDropTarget, setSplitDropTarget] = useState(null); /** Whether the split pane visually renders before (true) or after (false) main. */ const [splitLeading, setSplitLeading] = useState(false); // Auth bootstrap (mirrors standard page) useEffect(() => { const state = useAuthStore.getState(); if (state.isAuthenticated && state.client) { setInitialCheckDone(true); return; } checkAuth().finally(() => { setInitialCheckDone(true); }); }, [checkAuth]); useEffect(() => { if (initialCheckDone && !isAuthenticated && !authLoading) { redirectToLogin(); } }, [initialCheckDone, isAuthenticated, authLoading]); useEffect(() => { if (initialCheckDone && (isMobile || isTablet) && typeof window !== "undefined") { window.location.replace("/"); } }, [initialCheckDone, isMobile, isTablet]); const mainTabs = useMemo(() => tabs.filter((t) => t.paneId === 'main'), [tabs]); const splitTabs = useMemo(() => tabs.filter((t) => t.paneId === 'split'), [tabs]); const focusedActiveTab = useMemo(() => { const id = focusedPaneId === 'main' ? activeMainTabId : activeSplitTabId; return tabs.find((t) => t.id === id) ?? null; }, [tabs, focusedPaneId, activeMainTabId, activeSplitTabId]); const handleRailNavigate = (itemId: 'mail' | 'calendar' | 'contacts' | 'files' | 'settings') => { openTab(itemId); return true; }; const railActiveItemId: 'mail' | 'calendar' | 'contacts' | 'files' | 'settings' | null = focusedActiveTab && ( focusedActiveTab.kind === 'mail' || focusedActiveTab.kind === 'calendar' || focusedActiveTab.kind === 'contacts' || focusedActiveTab.kind === 'files' || focusedActiveTab.kind === 'settings' ) ? focusedActiveTab.kind : null; const isSplit = splitOrientation !== null && splitTabs.length > 0; // ---- Body-level drop targets ---- const isProTabDrag = (e: DragEvent) => e.dataTransfer.types.includes(PRO_TAB_DRAG_MIME); const computeDropTarget = (e: DragEvent): DropTarget => { const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const xFrac = x / rect.width; const yFrac = y / rect.height; if (isSplit) { // When split: each pane gets half the body as its drop zone. if (splitOrientation === 'vertical') { return xFrac < 0.5 ? 'left' : 'right'; } return yFrac < 0.5 ? 'top' : 'bottom'; } // No split: only the outer 22% of each edge creates a split. const fromLeft = xFrac; const fromRight = 1 - xFrac; const fromTop = yFrac; const fromBottom = 1 - yFrac; const min = Math.min(fromLeft, fromRight, fromTop, fromBottom); if (min > 0.22) return null; if (min === fromRight) return 'right'; if (min === fromLeft) return 'left'; if (min === fromBottom) return 'bottom'; return 'top'; }; const targetPaneFromDrop = (target: DropTarget): ProPaneId | null => { if (!target || !isSplit) return null; if (splitOrientation === 'vertical') { const leftIsSplit = splitLeading; if (target === 'left') return leftIsSplit ? 'split' : 'main'; if (target === 'right') return leftIsSplit ? 'main' : 'split'; } if (splitOrientation === 'horizontal') { const topIsSplit = splitLeading; if (target === 'top') return topIsSplit ? 'split' : 'main'; if (target === 'bottom') return topIsSplit ? 'main' : 'split'; } return null; }; const handleBodyDragOver = (e: DragEvent) => { if (!isProTabDrag(e)) return; e.preventDefault(); e.dataTransfer.dropEffect = "move"; const next = computeDropTarget(e); if (next !== splitDropTarget) setSplitDropTarget(next); }; const handleBodyDragLeave = (e: DragEvent) => { const next = e.relatedTarget as Node | null; if (next && e.currentTarget.contains(next)) return; setSplitDropTarget(null); }; const handleBodyDrop = (e: DragEvent) => { if (!isProTabDrag(e)) return; const target = computeDropTarget(e); setSplitDropTarget(null); setIsTabDragging(false); if (!target) return; e.preventDefault(); const draggedId = e.dataTransfer.getData(PRO_TAB_DRAG_MIME); if (!draggedId) return; if (isSplit) { // Move tab to whichever pane occupies the dropped side. const destPane = targetPaneFromDrop(target); if (destPane) moveTabToPane(draggedId, destPane); return; } // Create a new split. const orientation = (target === 'left' || target === 'right') ? 'vertical' : 'horizontal'; moveTabToPane(draggedId, 'split', orientation); setSplitLeading(target === 'left' || target === 'top'); }; // Loading state (matches standard page exactly) if (!initialCheckDone || authLoading || !isAuthenticated || !client) { return (

{t("common.loading")}

); } if (!isDesktop) return null; const mainPane = ( ); const splitPane = isSplit ? ( ) : null; const splitDivider = isSplit ? (