chore(dev): react-query setup, ui redesign - iteration 1

This commit is contained in:
Soumyadas15 2025-05-05 20:38:59 +05:30
parent d2bbd1bd27
commit 9c1919f579
8 changed files with 186 additions and 156 deletions

View File

@ -4,15 +4,15 @@ import { useEffect, useState } from 'react';
import { SidebarLeft } from '@/components/sidebar/sidebar-left'; import { SidebarLeft } from '@/components/sidebar/sidebar-left';
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
// import { PricingAlert } from "@/components/billing/pricing-alert" // import { PricingAlert } from "@/components/billing/pricing-alert"
import { MaintenanceAlert } from "@/components/maintenance-alert" import { MaintenanceAlert } from '@/components/maintenance-alert';
import { useAccounts } from "@/hooks/use-accounts" import { useAccounts } from '@/hooks/use-accounts';
import { useAuth } from "@/components/AuthProvider" import { useAuth } from '@/components/AuthProvider';
import { useRouter } from "next/navigation" import { useRouter } from 'next/navigation';
import { Loader2 } from "lucide-react" import { Loader2 } from 'lucide-react';
import { checkApiHealth } from "@/lib/api" import { checkApiHealth } from '@/lib/api';
import { MaintenancePage } from "@/components/maintenance/maintenance-page" import { MaintenancePage } from '@/components/maintenance/maintenance-page';
import { DeleteOperationProvider } from "@/contexts/DeleteOperationContext" import { DeleteOperationProvider } from '@/contexts/DeleteOperationContext';
import { StatusOverlay } from "@/components/ui/status-overlay" import { StatusOverlay } from '@/components/ui/status-overlay';
interface DashboardLayoutProps { interface DashboardLayoutProps {
children: React.ReactNode; children: React.ReactNode;
@ -84,9 +84,7 @@ export default function DashboardLayout({ children }: DashboardLayoutProps) {
<SidebarProvider> <SidebarProvider>
<SidebarLeft /> <SidebarLeft />
<SidebarInset> <SidebarInset>
<div className="bg-background"> <div className="bg-background">{children}</div>
{children}
</div>
</SidebarInset> </SidebarInset>
{/* <PricingAlert {/* <PricingAlert
@ -106,5 +104,5 @@ export default function DashboardLayout({ children }: DashboardLayoutProps) {
<StatusOverlay /> <StatusOverlay />
</SidebarProvider> </SidebarProvider>
</DeleteOperationProvider> </DeleteOperationProvider>
) );
} }

View File

@ -666,15 +666,15 @@ export function PricingSection({
} }
}; };
// if (isLocalMode()) { if (isLocalMode()) {
// return ( return (
// <div className="p-4 bg-muted/30 border border-border rounded-lg text-center"> <div className="p-4 bg-muted/30 border border-border rounded-lg text-center">
// <p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
// Running in local development mode - billing features are disabled Running in local development mode - billing features are disabled
// </p> </p>
// </div> </div>
// ); );
// } }
return ( return (
<section <section

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { useEffect, useState, useRef } from "react" import { useEffect, useState, useRef } from 'react';
import { import {
ArrowUpRight, ArrowUpRight,
Link as LinkIcon, Link as LinkIcon,
@ -32,12 +32,12 @@ import {
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
TooltipTrigger TooltipTrigger,
} from "@/components/ui/tooltip" } from '@/components/ui/tooltip';
import { getProjects, getThreads, Project, deleteThread } from "@/lib/api" import { getProjects, getThreads, Project, deleteThread } from '@/lib/api';
import Link from "next/link" import Link from 'next/link';
import { DeleteConfirmationDialog } from "@/components/thread/DeleteConfirmationDialog" import { DeleteConfirmationDialog } from '@/components/thread/DeleteConfirmationDialog';
import { useDeleteOperation } from '@/contexts/DeleteOperationContext' import { useDeleteOperation } from '@/contexts/DeleteOperationContext';
// Thread with associated project info for display in sidebar // Thread with associated project info for display in sidebar
type ThreadWithProject = { type ThreadWithProject = {
@ -49,16 +49,19 @@ type ThreadWithProject = {
}; };
export function NavAgents() { export function NavAgents() {
const { isMobile, state } = useSidebar() const { isMobile, state } = useSidebar();
const [threads, setThreads] = useState<ThreadWithProject[]>([]) const [threads, setThreads] = useState<ThreadWithProject[]>([]);
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true);
const [loadingThreadId, setLoadingThreadId] = useState<string | null>(null) const [loadingThreadId, setLoadingThreadId] = useState<string | null>(null);
const pathname = usePathname() const pathname = usePathname();
const router = useRouter() const router = useRouter();
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false) const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [threadToDelete, setThreadToDelete] = useState<{ id: string; name: string } | null>(null) const [threadToDelete, setThreadToDelete] = useState<{
const [isDeleting, setIsDeleting] = useState(false) id: string;
const isNavigatingRef = useRef(false) name: string;
} | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
const isNavigatingRef = useRef(false);
const { performDelete, isOperationInProgress } = useDeleteOperation(); const { performDelete, isOperationInProgress } = useDeleteOperation();
const isPerformingActionRef = useRef(false); const isPerformingActionRef = useRef(false);
@ -209,32 +212,36 @@ export function NavAgents() {
// Add event handler for completed navigation // Add event handler for completed navigation
useEffect(() => { useEffect(() => {
const handleNavigationComplete = () => { const handleNavigationComplete = () => {
console.log("NAVIGATION - Navigation event completed"); console.log('NAVIGATION - Navigation event completed');
document.body.style.pointerEvents = "auto"; document.body.style.pointerEvents = 'auto';
isNavigatingRef.current = false; isNavigatingRef.current = false;
}; };
window.addEventListener("popstate", handleNavigationComplete); window.addEventListener('popstate', handleNavigationComplete);
return () => { return () => {
window.removeEventListener("popstate", handleNavigationComplete); window.removeEventListener('popstate', handleNavigationComplete);
// Ensure we clean up any leftover styles // Ensure we clean up any leftover styles
document.body.style.pointerEvents = "auto"; document.body.style.pointerEvents = 'auto';
}; };
}, []); }, []);
// Reset isNavigatingRef when pathname changes // Reset isNavigatingRef when pathname changes
useEffect(() => { useEffect(() => {
isNavigatingRef.current = false; isNavigatingRef.current = false;
document.body.style.pointerEvents = "auto"; document.body.style.pointerEvents = 'auto';
}, [pathname]); }, [pathname]);
// Function to handle thread click with loading state // Function to handle thread click with loading state
const handleThreadClick = (e: React.MouseEvent<HTMLAnchorElement>, threadId: string, url: string) => { const handleThreadClick = (
e.preventDefault() e: React.MouseEvent<HTMLAnchorElement>,
setLoadingThreadId(threadId) threadId: string,
router.push(url) url: string,
} ) => {
e.preventDefault();
setLoadingThreadId(threadId);
router.push(url);
};
// Function to handle thread deletion // Function to handle thread deletion
const handleDeleteThread = async (threadId: string, threadName: string) => { const handleDeleteThread = async (threadId: string, threadName: string) => {
@ -258,9 +265,9 @@ export function NavAgents() {
const deletedThread = { ...threadToDelete }; const deletedThread = { ...threadToDelete };
// Log operation start // Log operation start
console.log("DELETION - Starting thread deletion process", { console.log('DELETION - Starting thread deletion process', {
threadId: deletedThread.id, threadId: deletedThread.id,
isCurrentThread: isActive isCurrentThread: isActive,
}); });
// Use the centralized deletion system with completion callback // Use the centralized deletion system with completion callback
@ -272,17 +279,17 @@ export function NavAgents() {
await deleteThread(threadId); await deleteThread(threadId);
// Update the thread list // Update the thread list
setThreads(prev => prev.filter(t => t.threadId !== threadId)); setThreads((prev) => prev.filter((t) => t.threadId !== threadId));
// Show success message // Show success message
toast.success("Conversation deleted successfully"); toast.success('Conversation deleted successfully');
}, },
// Completion callback to reset local state // Completion callback to reset local state
() => { () => {
setThreadToDelete(null); setThreadToDelete(null);
setIsDeleting(false); setIsDeleting(false);
isPerformingActionRef.current = false; isPerformingActionRef.current = false;
} },
); );
}; };
@ -428,7 +435,14 @@ export function NavAgents() {
</a> </a>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem onClick={() => handleDeleteThread(thread.threadId, thread.projectName)}> <DropdownMenuItem
onClick={() =>
handleDeleteThread(
thread.threadId,
thread.projectName,
)
}
>
<Trash2 className="text-muted-foreground" /> <Trash2 className="text-muted-foreground" />
<span>Delete</span> <span>Delete</span>
</DropdownMenuItem> </DropdownMenuItem>

View File

@ -1,7 +1,7 @@
"use client" 'use client';
import React from "react" import React from 'react';
import { Loader2 } from "lucide-react" import { Loader2 } from 'lucide-react';
import { import {
AlertDialog, AlertDialog,
@ -12,14 +12,14 @@ import {
AlertDialogFooter, AlertDialogFooter,
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
} from "@/components/ui/alert-dialog" } from '@/components/ui/alert-dialog';
interface DeleteConfirmationDialogProps { interface DeleteConfirmationDialogProps {
isOpen: boolean isOpen: boolean;
onClose: () => void onClose: () => void;
onConfirm: () => void onConfirm: () => void;
threadName: string threadName: string;
isDeleting: boolean isDeleting: boolean;
} }
/** /**
@ -38,7 +38,7 @@ export function DeleteConfirmationDialog({
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Delete conversation</AlertDialogTitle> <AlertDialogTitle>Delete conversation</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
Are you sure you want to delete the conversation{" "} Are you sure you want to delete the conversation{' '}
<span className="font-semibold">"{threadName}"</span>? <span className="font-semibold">"{threadName}"</span>?
<br /> <br />
This action cannot be undone. This action cannot be undone.
@ -48,8 +48,8 @@ export function DeleteConfirmationDialog({
<AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel> <AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={(e) => { onClick={(e) => {
e.preventDefault() e.preventDefault();
onConfirm() onConfirm();
}} }}
disabled={isDeleting} disabled={isDeleting}
className="bg-destructive text-white hover:bg-destructive/90" className="bg-destructive text-white hover:bg-destructive/90"
@ -60,11 +60,11 @@ export function DeleteConfirmationDialog({
Deleting... Deleting...
</> </>
) : ( ) : (
"Delete" 'Delete'
)} )}
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
) );
} }

View File

@ -46,7 +46,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
case 'base-only': case 'base-only':
return { return {
icon: <Crown className="h-3 w-3 text-blue-500" />, icon: <Crown className="h-3 w-3 text-blue-500" />,
tooltip: 'Requires Base plan or higher', tooltip: 'Requires Pro plan or higher',
}; };
case 'extra-only': case 'extra-only':
return { return {

View File

@ -1,4 +1,10 @@
import React, { createContext, useContext, useReducer, useEffect, useRef } from 'react'; import React, {
createContext,
useContext,
useReducer,
useEffect,
useRef,
} from 'react';
type DeleteState = { type DeleteState = {
isDeleting: boolean; isDeleting: boolean;
@ -17,7 +23,7 @@ const initialState: DeleteState = {
isDeleting: false, isDeleting: false,
targetId: null, targetId: null,
isActive: false, isActive: false,
operation: 'none' operation: 'none',
}; };
function deleteReducer(state: DeleteState, action: DeleteAction): DeleteState { function deleteReducer(state: DeleteState, action: DeleteAction): DeleteState {
@ -28,18 +34,18 @@ function deleteReducer(state: DeleteState, action: DeleteAction): DeleteState {
isDeleting: true, isDeleting: true,
targetId: action.id, targetId: action.id,
isActive: action.isActive, isActive: action.isActive,
operation: 'pending' operation: 'pending',
}; };
case 'DELETE_SUCCESS': case 'DELETE_SUCCESS':
return { return {
...state, ...state,
operation: 'success' operation: 'success',
}; };
case 'DELETE_ERROR': case 'DELETE_ERROR':
return { return {
...state, ...state,
isDeleting: false, isDeleting: false,
operation: 'error' operation: 'error',
}; };
case 'RESET': case 'RESET':
return initialState; return initialState;
@ -55,14 +61,20 @@ type DeleteOperationContextType = {
id: string, id: string,
isActive: boolean, isActive: boolean,
deleteFunction: () => Promise<void>, deleteFunction: () => Promise<void>,
onComplete?: () => void onComplete?: () => void,
) => Promise<void>; ) => Promise<void>;
isOperationInProgress: React.MutableRefObject<boolean>; isOperationInProgress: React.MutableRefObject<boolean>;
}; };
const DeleteOperationContext = createContext<DeleteOperationContextType | undefined>(undefined); const DeleteOperationContext = createContext<
DeleteOperationContextType | undefined
>(undefined);
export function DeleteOperationProvider({ children }: { children: React.ReactNode }) { export function DeleteOperationProvider({
children,
}: {
children: React.ReactNode;
}) {
const [state, dispatch] = useReducer(deleteReducer, initialState); const [state, dispatch] = useReducer(deleteReducer, initialState);
const isOperationInProgress = useRef(false); const isOperationInProgress = useRef(false);
@ -75,7 +87,7 @@ export function DeleteOperationProvider({ children }: { children: React.ReactNod
// Use window.location for reliable navigation // Use window.location for reliable navigation
window.location.pathname = '/dashboard'; window.location.pathname = '/dashboard';
} catch (error) { } catch (error) {
console.error("Navigation error:", error); console.error('Navigation error:', error);
} }
}, 500); }, 500);
return () => clearTimeout(timer); return () => clearTimeout(timer);
@ -88,13 +100,13 @@ export function DeleteOperationProvider({ children }: { children: React.ReactNod
const timer = setTimeout(() => { const timer = setTimeout(() => {
dispatch({ type: 'RESET' }); dispatch({ type: 'RESET' });
// Ensure pointer events are restored // Ensure pointer events are restored
document.body.style.pointerEvents = "auto"; document.body.style.pointerEvents = 'auto';
isOperationInProgress.current = false; isOperationInProgress.current = false;
// Restore sidebar menu interactivity // Restore sidebar menu interactivity
const sidebarMenu = document.querySelector(".sidebar-menu"); const sidebarMenu = document.querySelector('.sidebar-menu');
if (sidebarMenu) { if (sidebarMenu) {
sidebarMenu.classList.remove("pointer-events-none"); sidebarMenu.classList.remove('pointer-events-none');
} }
}, 1000); }, 1000);
return () => clearTimeout(timer); return () => clearTimeout(timer);
@ -102,13 +114,13 @@ export function DeleteOperationProvider({ children }: { children: React.ReactNod
if (state.operation === 'error') { if (state.operation === 'error') {
// Reset on error immediately // Reset on error immediately
document.body.style.pointerEvents = "auto"; document.body.style.pointerEvents = 'auto';
isOperationInProgress.current = false; isOperationInProgress.current = false;
// Restore sidebar menu interactivity // Restore sidebar menu interactivity
const sidebarMenu = document.querySelector(".sidebar-menu"); const sidebarMenu = document.querySelector('.sidebar-menu');
if (sidebarMenu) { if (sidebarMenu) {
sidebarMenu.classList.remove("pointer-events-none"); sidebarMenu.classList.remove('pointer-events-none');
} }
} }
}, [state.operation, state.isActive]); }, [state.operation, state.isActive]);
@ -117,19 +129,19 @@ export function DeleteOperationProvider({ children }: { children: React.ReactNod
id: string, id: string,
isActive: boolean, isActive: boolean,
deleteFunction: () => Promise<void>, deleteFunction: () => Promise<void>,
onComplete?: () => void onComplete?: () => void,
) => { ) => {
// Prevent multiple operations // Prevent multiple operations
if (isOperationInProgress.current) return; if (isOperationInProgress.current) return;
isOperationInProgress.current = true; isOperationInProgress.current = true;
// Disable pointer events during operation // Disable pointer events during operation
document.body.style.pointerEvents = "none"; document.body.style.pointerEvents = 'none';
// Disable sidebar menu interactions // Disable sidebar menu interactions
const sidebarMenu = document.querySelector(".sidebar-menu"); const sidebarMenu = document.querySelector('.sidebar-menu');
if (sidebarMenu) { if (sidebarMenu) {
sidebarMenu.classList.add("pointer-events-none"); sidebarMenu.classList.add('pointer-events-none');
} }
dispatch({ type: 'START_DELETE', id, isActive }); dispatch({ type: 'START_DELETE', id, isActive });
@ -145,10 +157,10 @@ export function DeleteOperationProvider({ children }: { children: React.ReactNod
// For non-active threads, restore interaction with delay // For non-active threads, restore interaction with delay
if (!isActive) { if (!isActive) {
setTimeout(() => { setTimeout(() => {
document.body.style.pointerEvents = "auto"; document.body.style.pointerEvents = 'auto';
if (sidebarMenu) { if (sidebarMenu) {
sidebarMenu.classList.remove("pointer-events-none"); sidebarMenu.classList.remove('pointer-events-none');
} }
// Call the completion callback // Call the completion callback
@ -157,14 +169,14 @@ export function DeleteOperationProvider({ children }: { children: React.ReactNod
} }
}, 50); }, 50);
} catch (error) { } catch (error) {
console.error("Delete operation failed:", error); console.error('Delete operation failed:', error);
// Reset states on error // Reset states on error
document.body.style.pointerEvents = "auto"; document.body.style.pointerEvents = 'auto';
isOperationInProgress.current = false; isOperationInProgress.current = false;
if (sidebarMenu) { if (sidebarMenu) {
sidebarMenu.classList.remove("pointer-events-none"); sidebarMenu.classList.remove('pointer-events-none');
} }
dispatch({ type: 'DELETE_ERROR' }); dispatch({ type: 'DELETE_ERROR' });
@ -175,12 +187,14 @@ export function DeleteOperationProvider({ children }: { children: React.ReactNod
}; };
return ( return (
<DeleteOperationContext.Provider value={{ <DeleteOperationContext.Provider
value={{
state, state,
dispatch, dispatch,
performDelete, performDelete,
isOperationInProgress isOperationInProgress,
}}> }}
>
{children} {children}
</DeleteOperationContext.Provider> </DeleteOperationContext.Provider>
); );
@ -189,7 +203,9 @@ export function DeleteOperationProvider({ children }: { children: React.ReactNod
export function useDeleteOperation() { export function useDeleteOperation() {
const context = useContext(DeleteOperationContext); const context = useContext(DeleteOperationContext);
if (context === undefined) { if (context === undefined) {
throw new Error('useDeleteOperation must be used within a DeleteOperationProvider'); throw new Error(
'useDeleteOperation must be used within a DeleteOperationProvider',
);
} }
return context; return context;
} }

View File

@ -1284,7 +1284,9 @@ export const deleteThread = async (threadId: string): Promise<void> => {
throw new Error(`Error deleting thread: ${threadError.message}`); throw new Error(`Error deleting thread: ${threadError.message}`);
} }
console.log(`Thread ${threadId} successfully deleted with all related items`); console.log(
`Thread ${threadId} successfully deleted with all related items`,
);
} catch (error) { } catch (error) {
console.error('Error deleting thread and related items:', error); console.error('Error deleting thread and related items:', error);
throw error; throw error;