Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 128 additions & 48 deletions src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useSearchParams } from "next/navigation";
import { Loader2 } from "lucide-react";
import { useRouter } from "next/navigation";
import { usePostHog } from 'posthog-js/react';
import type { AgentState } from "@/lib/workspace-state/types";
import type { AgentState, PdfData, WorkspaceWithState } from "@/lib/workspace-state/types";
import { initialState } from "@/lib/workspace-state/state";
import { useScrollHeader } from "@/hooks/ui/use-scroll-header";
import { useKeyboardShortcuts } from "@/hooks/ui/use-keyboard-shortcuts";
Expand All @@ -22,6 +22,7 @@ import { useUIStore } from "@/lib/stores/ui-store";
import { useWorkspaceStore } from "@/lib/stores/workspace-store";
import { useSession } from "@/lib/auth-client";
import { WorkspaceSection } from "@/components/workspace-canvas/WorkspaceSection";
import WorkspaceHeader from "@/components/workspace-canvas/WorkspaceHeader";
import { ModalManager } from "@/components/modals/ModalManager";
import { AnonymousSignInPrompt } from "@/components/modals/AnonymousSignInPrompt";
import { DashboardLayout } from "@/components/layout/DashboardLayout";
Expand All @@ -32,23 +33,26 @@ import { AnonymousSessionHandler, SidebarCoordinator } from "@/components/layout
// import { OnboardingVideoDialog } from "@/components/onboarding/OnboardingVideoDialog";
// import { useOnboardingStatus } from "@/hooks/user/use-onboarding-status";
import { PdfEngineWrapper } from "@/components/pdf/PdfEngineWrapper";
import WorkspaceSettingsModal from "@/components/workspace/WorkspaceSettingsModal";
import ShareWorkspaceDialog from "@/components/workspace/ShareWorkspaceDialog";
// Main dashboard content component
interface DashboardContentProps {
currentWorkspace: WorkspaceWithState | null;
loadingWorkspaces: boolean;
}

function DashboardContent({
currentWorkspaceId,
currentWorkspace,
loadingWorkspaces,
currentWorkspaceTitle,
currentWorkspaceIcon,
currentWorkspaceColor,
}: {
currentWorkspaceId: string | null;
loadingWorkspaces: boolean;
currentWorkspaceTitle?: string;
currentWorkspaceIcon?: string | null;
currentWorkspaceColor?: string | null;
}) {
}: DashboardContentProps) {
const posthog = usePostHog();
const { data: session } = useSession();

const currentWorkspaceId = currentWorkspace?.id || null;
const currentWorkspaceTitle = currentWorkspace?.name;
const currentWorkspaceIcon = currentWorkspace?.icon;
const currentWorkspaceColor = currentWorkspace?.color;

// Check onboarding status
// const { shouldShowOnboarding, isLoading: isLoadingOnboarding } = useOnboardingStatus();
// const [showOnboardingDialog, setShowOnboardingDialog] = useState(false);
Expand Down Expand Up @@ -90,6 +94,10 @@ function DashboardContent({
// Track sign-in prompt for anonymous users
const [showSignInPrompt, setShowSignInPrompt] = useState(false);

// Workspace settings/share modals (lifted so header can open them)
const [showWorkspaceSettings, setShowWorkspaceSettings] = useState(false);
const [showWorkspaceShare, setShowWorkspaceShare] = useState(false);

// Show sign-in prompt after 13 events for anonymous users
useEffect(() => {
// Only show for anonymous users
Expand Down Expand Up @@ -128,7 +136,7 @@ function DashboardContent({
const manualSave = useCallback(async () => {
posthog.capture('manual-save-clicked', { workspace_id: currentWorkspaceId });
updateLastSaved(new Date());
}, [updateLastSaved, currentWorkspaceId]);
}, [updateLastSaved, currentWorkspaceId, posthog]);

// Update save status based on mutation status
useEffect(() => {
Expand Down Expand Up @@ -302,6 +310,60 @@ function DashboardContent({
// Text selection handlers - delegate to agent for intelligent processing
const { handleCreateInstantNote, handleCreateCardFromSelections } = useTextSelectionAgent(operations);

const handleWorkspacePdfUpload = useCallback(
async (files: File[]) => {
if (!currentWorkspaceId) {
throw new Error("Workspace not available");
}

const uploadPromises = files.map(async (file) => {
const formData = new FormData();
formData.append("file", file);

const uploadResponse = await fetch("/api/upload-file", {
method: "POST",
body: formData,
});

if (!uploadResponse.ok) {
throw new Error(`Failed to upload PDF: ${uploadResponse.statusText}`);
}

const { url: fileUrl, filename } = await uploadResponse.json();

return {
fileUrl,
filename: filename || file.name,
fileSize: file.size,
name: file.name.replace(/\.pdf$/i, ""),
};
});

const uploadResults = await Promise.all(uploadPromises);
const pdfCardDefinitions = uploadResults.map((result) => {
const pdfData: Partial<PdfData> = {
fileUrl: result.fileUrl,
filename: result.filename,
fileSize: result.fileSize,
};

return {
type: "pdf" as const,
name: result.name,
initialData: pdfData,
};
});

operations.createItems(pdfCardDefinitions);
},
[operations, currentWorkspaceId]
);

const handleShowHistory = useCallback(() => {
posthog.capture("version-history-viewed", { workspace_id: currentWorkspaceId });
setShowVersionHistory(true);
}, [posthog, currentWorkspaceId, setShowVersionHistory]);


return (
<PdfEngineWrapper>
Expand All @@ -320,6 +382,39 @@ function DashboardContent({
onWorkspaceSwitch={switchWorkspace}
showCreateModal={showCreateWorkspaceModal}
setShowCreateModal={setShowCreateWorkspaceModal}
workspaceHeader={
!showJsonView && !isChatMaximized && currentWorkspaceId && !isLoadingWorkspace ? (
<WorkspaceHeader
titleInputRef={titleInputRef}
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
isSaving={isSaving}
lastSavedAt={lastSavedAt}
hasUnsavedChanges={hasUnsavedChanges}
onManualSave={() => {
void manualSave();
}}
currentWorkspaceId={currentWorkspaceId}
onShowHistory={handleShowHistory}
isDesktop={isDesktop}
isChatExpanded={isChatExpanded}
setIsChatExpanded={setIsChatExpanded}
workspaceName={currentWorkspaceTitle || state.globalTitle}
workspaceIcon={currentWorkspaceIcon}
workspaceColor={currentWorkspaceColor}
addItem={operations.createItem}
onPDFUpload={handleWorkspacePdfUpload}
setOpenModalItemId={setOpenModalItemId}
items={state.items || []}
onRenameFolder={(folderId, newName) => {
operations.updateItem(folderId, { name: newName });
}}
onOpenSettings={() => setShowWorkspaceSettings(true)}
onOpenShare={() => setShowWorkspaceShare(true)}
isItemPanelOpen={panels.length > 0}
/>
) : null
}
isDesktop={isDesktop}
isChatExpanded={isChatExpanded}
isChatMaximized={isChatMaximized}
Expand Down Expand Up @@ -356,10 +451,7 @@ function DashboardContent({
setIsChatExpanded={setIsChatExpanded}
isItemPanelOpen={panels.length > 0}
setOpenModalItemId={setOpenModalItemId}
onShowHistory={() => {
posthog.capture('version-history-viewed', { workspace_id: currentWorkspaceId });
setShowVersionHistory(true);
}}
onShowHistory={handleShowHistory}
titleInputRef={titleInputRef as React.RefObject<HTMLInputElement>}
operations={operations}
scrollAreaRef={scrollAreaRef as React.RefObject<HTMLDivElement>}
Expand All @@ -370,6 +462,17 @@ function DashboardContent({
/>
}
/>

<WorkspaceSettingsModal
workspace={currentWorkspace}
open={showWorkspaceSettings}
onOpenChange={setShowWorkspaceSettings}
/>
<ShareWorkspaceDialog
workspace={currentWorkspace}
open={showWorkspaceShare}
onOpenChange={setShowWorkspaceShare}
/>
</PdfEngineWrapper>
);
}
Expand All @@ -386,33 +489,13 @@ export function DashboardPage() {
loadingWorkspaces,
} = useWorkspaceContext();

// Derive workspace ID directly from slug and sync to store
const currentWorkspaceId = useMemo(() => {
if (!currentSlug) return null;
const workspace = workspaces.find(w => w.slug === currentSlug);
return workspace?.id || null;
}, [currentSlug, workspaces]);

// Get current workspace title for JSON download filename
const currentWorkspaceTitle = useMemo(() => {
if (!currentSlug) return undefined;
const workspace = workspaces.find(w => w.slug === currentSlug);
return workspace?.name || undefined;
}, [currentSlug, workspaces]);

// Get current workspace icon for header breadcrumbs
const currentWorkspaceIcon = useMemo(() => {
// Find current workspace from slug
const currentWorkspace = useMemo(() => {
if (!currentSlug) return null;
const workspace = workspaces.find(w => w.slug === currentSlug);
return workspace?.icon || null;
return workspaces.find(w => w.slug === currentSlug) || null;
}, [currentSlug, workspaces]);

// Get current workspace color for header breadcrumbs
const currentWorkspaceColor = useMemo(() => {
if (!currentSlug) return null;
const workspace = workspaces.find(w => w.slug === currentSlug);
return workspace?.color || null;
}, [currentSlug, workspaces]);
const currentWorkspaceId = currentWorkspace?.id || null;

// Sync workspace ID to store
const setCurrentWorkspaceId = useWorkspaceStore((state) => state.setCurrentWorkspaceId);
Expand All @@ -424,18 +507,18 @@ export function DashboardPage() {
const lastTrackedWorkspaceIdRef = useRef<string | null>(null);
useEffect(() => {
if (!currentWorkspaceId) return;

// Only track if this is a different workspace than last time
if (lastTrackedWorkspaceIdRef.current === currentWorkspaceId) return;

// Track the open
fetch(`/api/workspaces/${currentWorkspaceId}/track-open`, {
method: 'POST',
}).catch((error) => {
// Silently fail - tracking is not critical
console.error('Failed to track workspace open:', error);
});

lastTrackedWorkspaceIdRef.current = currentWorkspaceId;
}, [currentWorkspaceId]);

Expand All @@ -459,11 +542,8 @@ export function DashboardPage() {

return (
<DashboardContent
currentWorkspaceId={currentWorkspaceId}
currentWorkspace={currentWorkspace}
loadingWorkspaces={loadingWorkspaces}
currentWorkspaceTitle={currentWorkspaceTitle}
currentWorkspaceIcon={currentWorkspaceIcon}
currentWorkspaceColor={currentWorkspaceColor}
/>
);
}
Expand Down
Loading