a
This commit is contained in:
@@ -6,9 +6,7 @@ import { usePathname } from "next/navigation"
|
||||
import {
|
||||
Command,
|
||||
Frame,
|
||||
HelpCircle,
|
||||
Settings,
|
||||
Settings2,
|
||||
Terminal,
|
||||
Target,
|
||||
Plus,
|
||||
@@ -66,11 +64,16 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
const [isSubmittingProject, setIsSubmittingProject] = React.useState(false);
|
||||
const createProject = useMutation(api.projects.createProject);
|
||||
const updateProject = useMutation(api.projects.updateProject);
|
||||
const deleteProject = useMutation(api.projects.deleteProject);
|
||||
const [isEditingProject, setIsEditingProject] = React.useState(false);
|
||||
const [editingProjectId, setEditingProjectId] = React.useState<string | null>(null);
|
||||
const [editingProjectName, setEditingProjectName] = React.useState("");
|
||||
const [editingProjectDefault, setEditingProjectDefault] = React.useState(false);
|
||||
const [editingProjectError, setEditingProjectError] = React.useState<string | null>(null);
|
||||
const [deleteConfirmName, setDeleteConfirmName] = React.useState("");
|
||||
const [deleteProjectError, setDeleteProjectError] = React.useState<string | null>(null);
|
||||
const [isDeletingProject, setIsDeletingProject] = React.useState(false);
|
||||
const [isDeleteConfirmOpen, setIsDeleteConfirmOpen] = React.useState(false);
|
||||
const [isSubmittingEdit, setIsSubmittingEdit] = React.useState(false);
|
||||
const [sourceUrl, setSourceUrl] = React.useState("");
|
||||
const [sourceName, setSourceName] = React.useState("");
|
||||
@@ -107,6 +110,8 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
const toggleConfig = useMutation(api.projects.toggleDataSourceConfig);
|
||||
|
||||
const selectedProject = projects?.find(p => p._id === selectedProjectId);
|
||||
const editingProject = projects?.find((project) => project._id === editingProjectId);
|
||||
const canDeleteProject = (projects?.length ?? 0) > 1;
|
||||
const selectedSourceIds = selectedProject?.dorkingConfig?.selectedSourceIds || [];
|
||||
const selectedProjectName = selectedProject?.name || "Select Project";
|
||||
|
||||
@@ -291,9 +296,6 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton size="lg">
|
||||
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
|
||||
<Command className="size-4" />
|
||||
</div>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">{selectedProjectName}</span>
|
||||
<span className="truncate text-xs text-muted-foreground">Projects</span>
|
||||
@@ -332,6 +334,9 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
setEditingProjectName(project.name);
|
||||
setEditingProjectDefault(project.isDefault);
|
||||
setEditingProjectError(null);
|
||||
setDeleteConfirmName("");
|
||||
setDeleteProjectError(null);
|
||||
setIsDeleteConfirmOpen(false);
|
||||
setIsEditingProject(true);
|
||||
}}
|
||||
aria-label={`Project settings for ${project.name}`}
|
||||
@@ -353,54 +358,41 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
<SidebarContent>
|
||||
{/* Platform Nav */}
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
||||
<SidebarGroupLabel>Main</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
tooltip="Dashboard"
|
||||
tooltip="Overview"
|
||||
isActive={pathname === "/dashboard"}
|
||||
>
|
||||
<Link href="/dashboard">
|
||||
<Terminal />
|
||||
<span>Dashboard</span>
|
||||
<span>Overview</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
tooltip="Opportunities"
|
||||
tooltip="Search"
|
||||
isActive={pathname === "/opportunities"}
|
||||
>
|
||||
<Link href="/opportunities">
|
||||
<Target />
|
||||
<span>Opportunities</span>
|
||||
<span>Search</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
tooltip="Settings"
|
||||
isActive={pathname === "/settings"}
|
||||
tooltip="Inbox"
|
||||
isActive={pathname === "/leads"}
|
||||
>
|
||||
<Link href="/settings">
|
||||
<Settings2 />
|
||||
<span>Settings</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
tooltip="Help"
|
||||
isActive={pathname === "/help"}
|
||||
>
|
||||
<Link href="/help">
|
||||
<HelpCircle />
|
||||
<span>Help</span>
|
||||
<Link href="/leads" className="pl-8 text-sm text-muted-foreground hover:text-foreground">
|
||||
<span>Inbox</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
@@ -412,7 +404,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
{selectedProjectId && (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>
|
||||
Active Data Sources
|
||||
Selected Sources
|
||||
<span className="ml-2 text-xs font-normal text-muted-foreground">({selectedProject?.name})</span>
|
||||
</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
@@ -624,6 +616,33 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
{editingProjectError && (
|
||||
<div className="text-sm text-destructive">{editingProjectError}</div>
|
||||
)}
|
||||
<div className="border-t border-border pt-4 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-destructive">Delete project</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
This removes the project and all related data sources, analyses, and opportunities.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
disabled={!canDeleteProject}
|
||||
onClick={() => {
|
||||
setDeleteConfirmName("");
|
||||
setDeleteProjectError(null);
|
||||
setIsDeleteConfirmOpen(true);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
{!canDeleteProject && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
You must keep at least one project.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -662,6 +681,77 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Dialog open={isDeleteConfirmOpen} onOpenChange={setIsDeleteConfirmOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete project</DialogTitle>
|
||||
<DialogDescription>
|
||||
This action is permanent. You are deleting{" "}
|
||||
<span className="font-semibold text-foreground">
|
||||
{editingProject?.name || "this project"}
|
||||
</span>
|
||||
. Type the project name to confirm deletion.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="deleteProjectConfirm">Project name</Label>
|
||||
<Input
|
||||
id="deleteProjectConfirm"
|
||||
value={deleteConfirmName}
|
||||
onChange={(event) => setDeleteConfirmName(event.target.value)}
|
||||
disabled={isDeletingProject || !canDeleteProject}
|
||||
/>
|
||||
</div>
|
||||
{deleteProjectError && (
|
||||
<div className="text-sm text-destructive">{deleteProjectError}</div>
|
||||
)}
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setIsDeleteConfirmOpen(false)}
|
||||
disabled={isDeletingProject}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
disabled={
|
||||
isDeletingProject ||
|
||||
!canDeleteProject ||
|
||||
!editingProject ||
|
||||
deleteConfirmName.trim() !== editingProject.name
|
||||
}
|
||||
onClick={async () => {
|
||||
if (!editingProjectId || !editingProject) return;
|
||||
if (deleteConfirmName.trim() !== editingProject.name) {
|
||||
setDeleteProjectError("Project name does not match.");
|
||||
return;
|
||||
}
|
||||
setDeleteProjectError(null);
|
||||
setIsDeletingProject(true);
|
||||
try {
|
||||
const result = await deleteProject({
|
||||
projectId: editingProjectId as any,
|
||||
});
|
||||
if (selectedProjectId === editingProjectId && result?.newDefaultProjectId) {
|
||||
setSelectedProjectId(result.newDefaultProjectId);
|
||||
}
|
||||
setIsDeleteConfirmOpen(false);
|
||||
setIsEditingProject(false);
|
||||
} catch (err: any) {
|
||||
setDeleteProjectError(err?.message || "Failed to delete project.");
|
||||
} finally {
|
||||
setIsDeletingProject(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isDeletingProject ? "Deleting..." : "Delete Project"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Dialog open={isCreatingProject} onOpenChange={setIsCreatingProject}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
|
||||
Reference in New Issue
Block a user