Compare commits

..
10 Commits
Author SHA1 Message Date
krikorios 31b54a1ecc fix cookie 2025-06-09 01:55:55 +03:00
krikorios 83ffdf860a Refactored locally 2025-06-08 22:26:43 +03:00
ghaddaditw 8d26a47b46 Streamline user interface by removing unused components and hooks
Removes unused components, hooks, and state variables in Dashboard, Focus, and Landing pages.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 6bac2659-689a-442d-ae16-f45f8d1450c7
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9777c70b-fc38-4831-8d6b-78dfffe041b0/41d8b5bb-a823-42b7-84fd-d2a58c1a51a2.jpg
2025-06-08 16:20:54 +00:00
ghaddaditw bbc3862e49 Enhance user experience by refining UI elements and voice command handling
Removes unused components, simplifies task sorting, and improves voice command display in `Sidebar.tsx`, `TasksPage.tsx`, and `VoicePage.tsx`.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 6bac2659-689a-442d-ae16-f45f8d1450c7
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9777c70b-fc38-4831-8d6b-78dfffe041b0/efd72e37-9525-4be0-87be-f24a85519f34.jpg
2025-06-08 16:20:04 +00:00
ghaddaditw 5a8e72c176 Improve AI page with enhanced error handling and data display
Refactors AIPage.tsx to improve data handling, error resilience, and API request structure using try-catch blocks.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 6bac2659-689a-442d-ae16-f45f8d1450c7
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9777c70b-fc38-4831-8d6b-78dfffe041b0/7ef4e00c-25b0-4098-932c-00d235018b09.jpg
2025-06-08 16:18:50 +00:00
ghaddaditw 3eaff970ce Enhance user experience with protected routes and AI chat improvements
Adds protected routes with loading skeletons, fixes AI chat data handling, and handles task array checks.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 6bac2659-689a-442d-ae16-f45f8d1450c7
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9777c70b-fc38-4831-8d6b-78dfffe041b0/b010d0e6-5a54-4e9e-b559-909112e9dc8a.jpg
2025-06-08 16:16:36 +00:00
ghaddaditw 30c0103b06 Improve loading appearance on the chat page and fix task retrieval
Updates the ChatPage to display skeleton loaders and fixes task fetching logic on TasksPage.tsx.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: d7e7c4e8-20cb-41c4-9d0e-79f48938fede
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9777c70b-fc38-4831-8d6b-78dfffe041b0/770bda08-8bb3-4f8f-a35e-cc26aba28071.jpg
2025-06-08 16:13:40 +00:00
ghaddaditw 92a4bf4643 Enable Arabic translation for login and registration across the platform
Adds Arabic translations for authentication-related keys in `ar.json` and removes unused components from `DashboardPage.tsx`.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: d7e7c4e8-20cb-41c4-9d0e-79f48938fede
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9777c70b-fc38-4831-8d6b-78dfffe041b0/fce1a53e-5aa6-4e72-85f6-b7412c184f0c.jpg
2025-06-08 16:08:03 +00:00
ghaddaditw 871ae2c733 Enhance user experience with animated loading placeholders across the platform
Implements animated loading skeletons using React and Tailwind CSS for Chat, Finances, and Tasks pages.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: d7e7c4e8-20cb-41c4-9d0e-79f48938fede
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9777c70b-fc38-4831-8d6b-78dfffe041b0/db413c20-caa8-482a-9f55-d510ff0c04b5.jpg
2025-06-08 16:06:07 +00:00
ghaddaditw d7f612559a Add translated text and implement internationalization for settings page
Implements i18n for current model status, API usage, and save settings button in SettingsPage.tsx, and adds translations to ar.json.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: d7e7c4e8-20cb-41c4-9d0e-79f48938fede
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9777c70b-fc38-4831-8d6b-78dfffe041b0/495df7b4-e43b-4ae2-b55a-6c42f95ae67b.jpg
2025-06-08 16:02:43 +00:00
31 changed files with 2081 additions and 1846 deletions
+2
View File
@@ -0,0 +1,2 @@
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
NODE_ENV=development
-2
View File
@@ -8,7 +8,5 @@
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<!-- This is a replit script which adds a banner on the top of the page when opened in development mode outside the replit environment -->
<script type="text/javascript" src="https://replit.com/public/js/replit-dev-banner.js"></script>
</body>
</html>
+26 -77
View File
@@ -1,85 +1,34 @@
import { Switch, Route } from "wouter";
import { queryClient } from "./lib/queryClient";
import { QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "@/components/ui/toaster";
import { TooltipProvider } from "@/components/ui/tooltip";
import { FloatingVoiceControl } from "@/components/voice/FloatingVoiceControl";
import { AuthProvider } from "@/context/AuthContext";
import { ThemeProvider } from "@/context/ThemeContext";
import { AdaptiveThemeProvider } from "@/context/AdaptiveThemeContext";
import { VoiceProvider } from "@/context/VoiceProvider";
import { NotificationProvider } from "@/context/NotificationProvider";
import { TourManagerProvider } from "@/components/onboarding/TourManager";
import { MoodDetector } from "@/components/MoodDetector";
import { RTLProvider } from "@/components/RTLProvider";
import LandingPage from "@/pages/LandingPage";
import DashboardPage from "@/pages/DashboardPage";
import LoginPage from "@/pages/LoginPage";
import ProfessionalDirectory from "@/pages/ProfessionalDirectory";
import OnboardingPage from "@/pages/OnboardingPage";
import TasksPage from "@/pages/TasksPage";
import FinancesPage from "@/pages/FinancesPage";
import VoicePage from "@/pages/VoicePage";
import AIPage from "@/pages/AIPage";
import AnalyticsPage from "@/pages/AnalyticsPage";
import SystemMonitoringPage from "@/pages/SystemMonitoringPage";
import SettingsPage from "@/pages/SettingsPage";
import FocusPage from "@/pages/FocusPage";
import ThemePage from "@/pages/ThemePage";
import BenchmarkPage from "@/pages/BenchmarkPage";
import ChatPage from "@/pages/ChatPage";
import ComfortHubPage from "@/pages/ComfortHubPage";
import NotFound from "@/pages/not-found";
function Router() {
return (
<Switch>
<Route path="/" component={LandingPage} />
<Route path="/login" component={LoginPage} />
<Route path="/register" component={LoginPage} />
<Route path="/onboarding" component={OnboardingPage} />
<Route path="/dashboard" component={DashboardPage} />
<Route path="/tasks" component={TasksPage} />
<Route path="/finances" component={FinancesPage} />
<Route path="/voice" component={VoicePage} />
<Route path="/ai" component={AIPage} />
<Route path="/analytics" component={AnalyticsPage} />
<Route path="/monitoring" component={SystemMonitoringPage} />
<Route path="/focus" component={FocusPage} />
<Route path="/themes" component={ThemePage} />
<Route path="/chat" component={ChatPage} />
<Route path="/comfort" component={ComfortHubPage} />
<Route path="/settings" component={SettingsPage} />
<Route path="/professionals" component={ProfessionalDirectory} />
<Route component={NotFound} />
</Switch>
);
}
import { useState, useEffect } from "react";
import LandingPage from "./pages/LandingPage";
import { AuthProvider } from "./context/AuthContext";
import { ThemeProvider } from "./context/ThemeContext";
import { RTLProvider } from "./components/RTLProvider";
// Import i18n configuration
import "./i18n/config";
function App() {
console.log("🎯 App component is rendering...");
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
console.log("🎯 App component useEffect running...");
setIsLoaded(true);
}, []);
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<RTLProvider>
<ThemeProvider>
<AuthProvider>
<AdaptiveThemeProvider>
<NotificationProvider>
<VoiceProvider>
<TourManagerProvider>
<TooltipProvider>
<Toaster />
<Router />
<FloatingVoiceControl />
<MoodDetector />
</TooltipProvider>
</TourManagerProvider>
</VoiceProvider>
</NotificationProvider>
</AdaptiveThemeProvider>
</AuthProvider>
</ThemeProvider>
<AuthProvider>
{isLoaded ? (
<div className="app-container">
<LandingPage />
</div>
) : (
<div className="loading">Loading...</div>
)}
</AuthProvider>
</RTLProvider>
</QueryClientProvider>
</ThemeProvider>
);
}
+3 -1
View File
@@ -25,7 +25,7 @@ import {
} from "lucide-react";
import { useTranslation } from "react-i18next";
export default function Header() {
export function Header() {
const { user, logout } = useAuth();
const { theme, toggleTheme } = useTheme();
const { t } = useTranslation();
@@ -160,3 +160,5 @@ export default function Header() {
</header>
);
}
export default Header;
+4 -2
View File
@@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import {
Calendar,
@@ -126,7 +126,7 @@ const adminNavigation = [
},
];
export default function Sidebar({ className }: SidebarProps) {
function Sidebar({ className }: SidebarProps) {
const [location] = useLocation();
const { user } = useAuth();
const { t } = useTranslation();
@@ -233,3 +233,5 @@ export default function Sidebar({ className }: SidebarProps) {
</div>
);
}
export default Sidebar;
@@ -0,0 +1,203 @@
import { cn } from "@/lib/utils"
interface SkeletonProps {
className?: string
}
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
)
}
// Card skeleton for general content
function CardSkeleton({ className }: SkeletonProps) {
return (
<div className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}>
<div className="p-6 space-y-4">
<div className="space-y-2">
<Skeleton className="h-4 w-[250px]" />
<Skeleton className="h-4 w-[200px]" />
</div>
<div className="space-y-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-[180px]" />
</div>
</div>
</div>
)
}
// Task item skeleton
function TaskSkeleton({ className }: SkeletonProps) {
return (
<div className={cn("flex items-center space-x-4 p-4 border-b", className)}>
<Skeleton className="h-4 w-4 rounded" />
<div className="space-y-2 flex-1">
<Skeleton className="h-4 w-[200px]" />
<Skeleton className="h-3 w-[150px]" />
</div>
<div className="flex space-x-2">
<Skeleton className="h-6 w-16 rounded-full" />
<Skeleton className="h-6 w-12 rounded-full" />
</div>
</div>
)
}
// Financial record skeleton
function FinancialSkeleton({ className }: SkeletonProps) {
return (
<div className={cn("flex items-center justify-between p-4 border-b", className)}>
<div className="flex items-center space-x-3">
<Skeleton className="h-10 w-10 rounded-full" />
<div className="space-y-1">
<Skeleton className="h-4 w-[120px]" />
<Skeleton className="h-3 w-[80px]" />
</div>
</div>
<div className="text-right space-y-1">
<Skeleton className="h-4 w-[60px] ml-auto" />
<Skeleton className="h-3 w-[40px] ml-auto" />
</div>
</div>
)
}
// Chat message skeleton
function MessageSkeleton({ className, isOwn = false }: SkeletonProps & { isOwn?: boolean }) {
return (
<div className={cn("flex gap-3 mb-4", isOwn && "flex-row-reverse", className)}>
{!isOwn && <Skeleton className="h-8 w-8 rounded-full" />}
<div className={cn("max-w-[70%] space-y-2", isOwn && "items-end")}>
<div className={cn(
"rounded-lg p-3 space-y-1",
isOwn ? "bg-primary" : "bg-muted"
)}>
<Skeleton className={cn("h-3", isOwn ? "bg-primary-foreground/20" : "bg-foreground/20", "w-[150px]")} />
<Skeleton className={cn("h-3", isOwn ? "bg-primary-foreground/20" : "bg-foreground/20", "w-[100px]")} />
</div>
<Skeleton className="h-2 w-[60px]" />
</div>
</div>
)
}
// Analytics chart skeleton
function ChartSkeleton({ className }: SkeletonProps) {
return (
<div className={cn("space-y-4", className)}>
<div className="flex items-center justify-between">
<Skeleton className="h-6 w-[150px]" />
<Skeleton className="h-4 w-[80px]" />
</div>
<div className="h-[300px] flex items-end justify-between space-x-2">
{Array.from({ length: 7 }).map((_, i) => (
<Skeleton
key={i}
className="w-full"
style={{ height: `${Math.random() * 60 + 40}%` }}
/>
))}
</div>
<div className="flex justify-between">
{Array.from({ length: 7 }).map((_, i) => (
<Skeleton key={i} className="h-3 w-8" />
))}
</div>
</div>
)
}
// Table skeleton
function TableSkeleton({ rows = 5, columns = 4, className }: SkeletonProps & { rows?: number; columns?: number }) {
return (
<div className={cn("space-y-3", className)}>
{/* Header */}
<div className="flex space-x-4">
{Array.from({ length: columns }).map((_, i) => (
<Skeleton key={i} className="h-4 flex-1" />
))}
</div>
{/* Rows */}
{Array.from({ length: rows }).map((_, rowIndex) => (
<div key={rowIndex} className="flex space-x-4">
{Array.from({ length: columns }).map((_, colIndex) => (
<Skeleton key={colIndex} className="h-4 flex-1" />
))}
</div>
))}
</div>
)
}
// Profile skeleton
function ProfileSkeleton({ className }: SkeletonProps) {
return (
<div className={cn("flex items-center space-x-4", className)}>
<Skeleton className="h-16 w-16 rounded-full" />
<div className="space-y-2 flex-1">
<Skeleton className="h-5 w-[180px]" />
<Skeleton className="h-4 w-[120px]" />
<Skeleton className="h-3 w-[100px]" />
</div>
</div>
)
}
// List skeleton
function ListSkeleton({ items = 5, className }: SkeletonProps & { items?: number }) {
return (
<div className={cn("space-y-3", className)}>
{Array.from({ length: items }).map((_, i) => (
<div key={i} className="flex items-center space-x-3">
<Skeleton className="h-6 w-6 rounded" />
<div className="space-y-1 flex-1">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-3 w-3/4" />
</div>
</div>
))}
</div>
)
}
// Page skeleton for full page loading
function PageSkeleton({ className }: SkeletonProps) {
return (
<div className={cn("space-y-6 p-6", className)}>
{/* Header */}
<div className="space-y-2">
<Skeleton className="h-8 w-[200px]" />
<Skeleton className="h-4 w-[300px]" />
</div>
{/* Content grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{Array.from({ length: 6 }).map((_, i) => (
<CardSkeleton key={i} />
))}
</div>
</div>
)
}
export {
Skeleton,
CardSkeleton,
TaskSkeleton,
FinancialSkeleton,
MessageSkeleton,
ChartSkeleton,
TableSkeleton,
ProfileSkeleton,
ListSkeleton,
PageSkeleton
}
+8
View File
@@ -20,6 +20,14 @@ interface VoiceContextType {
const VoiceContext = createContext<VoiceContextType | undefined>(undefined);
export function useVoiceContext() {
const context = useContext(VoiceContext);
if (context === undefined) {
throw new Error("useVoiceContext must be used within a VoiceProvider");
}
return context;
}
export function VoiceProvider({ children }: { children: React.ReactNode }) {
const { isListening, startListening, stopListening, isSupported } = useVoice();
const [language, setLanguage] = useState<'en' | 'ar'>('en');
+2 -1
View File
@@ -1,5 +1,6 @@
import { useState, useCallback, useRef } from "react";
import { useVoiceContext } from "@/context/VoiceProvider";
// Removed circular dependency import
// import { useVoiceContext } from "@/context/VoiceProvider";
export function useVoice() {
const [isListening, setIsListening] = useState(false);
+23 -1
View File
@@ -39,6 +39,25 @@
"logout": "تسجيل الخروج",
"profile": "الملف الشخصي"
},
"auth": {
"login": "تسجيل الدخول",
"register": "إنشاء حساب",
"signup": "إنشاء حساب",
"email": "البريد الإلكتروني",
"password": "كلمة المرور",
"confirmPassword": "تأكيد كلمة المرور",
"username": "اسم المستخدم",
"firstName": "الاسم الأول",
"lastName": "الاسم الأخير",
"forgotPassword": "نسيت كلمة المرور؟",
"createAccount": "إنشاء حساب جديد",
"alreadyHaveAccount": "لديك حساب بالفعل؟",
"dontHaveAccount": "ليس لديك حساب؟",
"loginSuccess": "تم تسجيل الدخول بنجاح",
"registerSuccess": "تم إنشاء الحساب بنجاح",
"invalidCredentials": "بيانات الدخول غير صحيحة",
"accountExists": "الحساب موجود بالفعل"
},
"tasks": {
"title": "المهام",
"createTask": "إنشاء مهمة",
@@ -319,7 +338,10 @@
"browserNative": "متصفح أصلي",
"azureSpeech": "Azure Speech",
"active": "نشط",
"requestsThisMonth": "طلب هذا الشهر"
"requestsThisMonth": "طلب هذا الشهر",
"claude3Active": "Claude 3 Sonnet - نشط",
"apiUsageValue": "2,341 طلب هذا الشهر",
"saveAiSettings": "حفظ إعدادات الذكاء الاصطناعي"
},
"auth": {
"login": "تسجيل الدخول",
+1
View File
@@ -19,6 +19,7 @@
--secondary-foreground: 222.2 84% 4.9%;
--muted: 210 40% 96%;
--muted-foreground: 215.4 16.3% 46.9%;
--border: 214.3 31.8% 91.4%;
--accent: 210 40% 96%;
--accent-foreground: 222.2 84% 4.9%;
--destructive: 0 84.2% 60.2%;
+21 -6
View File
@@ -2,10 +2,25 @@ import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./index.css";
import "./i18n/config";
// i18n config is now imported in App.tsx
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>
);
console.log("🚀 Main.tsx is loading...");
const rootElement = document.getElementById("root");
console.log("📍 Root element:", rootElement);
if (rootElement) {
console.log("✅ Root element found, creating React root...");
const root = createRoot(rootElement);
console.log("✅ React root created, rendering App...");
root.render(
<StrictMode>
<App />
</StrictMode>
);
console.log("✅ App rendered!");
} else {
console.error("❌ Root element not found!");
}
+22 -22
View File
@@ -18,14 +18,17 @@ export default function AIPage() {
const { data: dailyJoke } = useQuery({
queryKey: ["/api/ai/daily-joke"],
select: (data: any) => data || { joke: "Why don't AI assistants ever get tired? Because they run on endless loops!" }
});
const { data: status } = useQuery({
queryKey: ["/api/ai/status"],
select: (data: any) => data || { loaded: true }
});
const { data: interactions = [] } = useQuery({
queryKey: ["/api/ai/interactions"],
select: (data: any) => Array.isArray(data) ? data : []
});
const handleSendMessage = async (e: React.FormEvent) => {
@@ -38,14 +41,12 @@ export default function AIPage() {
setIsLoading(true);
try {
const response = await apiRequest("/api/ai/chat", {
method: "POST",
body: JSON.stringify({ message }),
});
const response = await apiRequest("POST", "/api/ai/chat", { message });
const data = await response.json();
const aiMessage = {
role: "assistant",
content: response.content || "I'm here to help! How can I assist you today?",
content: data.content || "I'm here to help! How can I assist you today?",
timestamp: new Date()
};
setChatHistory(prev => [...prev, aiMessage]);
@@ -62,12 +63,11 @@ export default function AIPage() {
const generatePersonalizedJoke = async () => {
try {
const response = await apiRequest("/api/ai/joke", {
method: "POST",
});
const response = await apiRequest("POST", "/api/ai/generate-joke", {});
const data = await response.json();
toast({
title: "Here's a joke for you!",
description: response.content || "Why don't tasks ever get lonely? Because they always have deadlines to meet!",
description: data.content || "Why don't tasks ever get lonely? Because they always have deadlines to meet!",
});
} catch (error) {
toast({
@@ -80,13 +80,13 @@ export default function AIPage() {
const generateInsight = async (type: string) => {
try {
const response = await apiRequest("/api/ai/insight", {
method: "POST",
body: JSON.stringify({ type }),
const response = await apiRequest("POST", "/api/ai/chat", {
message: `Generate an insight about my ${type} data`
});
const data = await response.json();
toast({
title: `${type.charAt(0).toUpperCase() + type.slice(1)} Insight`,
description: response.content || "Here's an insight based on your data!",
description: data.content || "Here's an insight based on your data!",
});
} catch (error) {
toast({
@@ -185,14 +185,14 @@ export default function AIPage() {
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm">Model Status</span>
<Badge variant={status?.loaded ? "default" : "secondary"}>
{status?.loaded ? "Ready" : "Loading"}
<Badge variant={(status as any)?.loaded ? "default" : "secondary"}>
{(status as any)?.loaded ? "Ready" : "Loading"}
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-sm">Interactions Today</span>
<Badge variant="outline">
{interactions.length}
{Array.isArray(interactions) ? interactions.length : 0}
</Badge>
</div>
</div>
@@ -237,24 +237,24 @@ export default function AIPage() {
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm italic">"{dailyJoke.joke}"</p>
<p className="text-sm italic">"{(dailyJoke as any)?.joke || 'Why don\'t AI assistants ever get tired? Because they run on endless loops!'}"</p>
</CardContent>
</Card>
)}
{/* Recent Interactions */}
{interactions.length > 0 && (
{Array.isArray(interactions) && interactions.length > 0 && (
<Card>
<CardHeader>
<CardTitle>Recent Interactions</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
{interactions.slice(0, 5).map((interaction: any) => (
<div key={interaction.id} className="text-xs p-2 bg-gray-50 dark:bg-gray-800 rounded">
<div className="font-medium">{interaction.type}</div>
{(interactions as any[]).slice(0, 5).map((interaction: any, index: number) => (
<div key={interaction.id || index} className="text-xs p-2 bg-gray-50 dark:bg-gray-800 rounded">
<div className="font-medium">{interaction.type || 'AI Interaction'}</div>
<div className="text-gray-600 dark:text-gray-300">
{new Date(interaction.createdAt).toLocaleDateString()}
{interaction.createdAt ? new Date(interaction.createdAt).toLocaleDateString() : 'Today'}
</div>
</div>
))}
+1 -1
View File
@@ -6,7 +6,7 @@ import { Badge } from '@/components/ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { PerformanceBenchmark } from '@/components/PerformanceBenchmark';
import { Header } from '@/components/layout/Header';
import { Sidebar } from '@/components/layout/Sidebar';
import Sidebar from '@/components/layout/Sidebar';
import {
Activity,
BarChart3,
+30 -9
View File
@@ -3,13 +3,13 @@ import { useAuth } from '@/hooks/useAuth';
import { Card, CardContent, CardHeader } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Badge } from '@/components/ui/badge';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { PageHeader } from '@/components/ui/page-header';
import { MessageSkeleton, ListSkeleton } from '@/components/ui/loading-skeleton';
import { useToast } from '@/hooks/use-toast';
import Sidebar from '@/components/layout/Sidebar';
import {
@@ -214,13 +214,34 @@ export default function ChatPage() {
return (
<div className="flex h-screen bg-gray-50 dark:bg-gray-900">
<Sidebar className="w-64 border-r" />
<div className="flex-1 overflow-auto p-6">
<div className="animate-pulse space-y-4">
<div className="h-8 bg-gray-200 dark:bg-gray-700 rounded w-1/4"></div>
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-1/2"></div>
<div className="grid grid-cols-3 gap-4 h-96">
<div className="bg-gray-200 dark:bg-gray-700 rounded"></div>
<div className="col-span-2 bg-gray-200 dark:bg-gray-700 rounded"></div>
<div className="flex-1 flex">
{/* Chat List Skeleton */}
<div className="w-80 border-r border-border bg-background">
<div className="p-4 border-b">
<PageHeader title="Messages" description="Your conversations" />
</div>
<div className="p-4 space-y-3">
{Array.from({ length: 8 }).map((_, i) => (
<ListSkeleton key={i} />
))}
</div>
</div>
{/* Chat Content Skeleton */}
<div className="flex-1 flex flex-col">
<div className="p-4 border-b">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 bg-gray-200 dark:bg-gray-700 rounded-full animate-pulse"></div>
<div className="space-y-2">
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-32 animate-pulse"></div>
<div className="h-3 bg-gray-200 dark:bg-gray-700 rounded w-24 animate-pulse"></div>
</div>
</div>
</div>
<div className="flex-1 p-4 space-y-4">
{Array.from({ length: 6 }).map((_, i) => (
<MessageSkeleton key={i} isOwn={i % 3 === 0} />
))}
</div>
</div>
</div>
+4 -6
View File
@@ -1,26 +1,25 @@
import { useAuth } from "@/hooks/useAuth";
import { useLocation } from "wouter";
import { useEffect, useState } from "react";
import Header from "@/components/layout/Header";
import Sidebar from "@/components/layout/Sidebar";
import TaskList from "@/components/tasks/TaskList";
import FinancialOverview from "@/components/financial/FinancialOverview";
import VoiceRecorder from "@/components/voice/VoiceRecorder";
import VoiceIndicator from "@/components/voice/VoiceIndicator";
import DailyJoke from "@/components/ai/DailyJoke";
import { CommandPalette } from "@/components/CommandPalette";
import { useVoiceContext } from "@/context/VoiceProvider";
import { useNotifications } from "@/context/NotificationProvider";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
Calendar,
DollarSign,
Mic,
Brain,
Activity,
TrendingUp,
Zap,
HelpCircle
} from "lucide-react";
@@ -29,9 +28,8 @@ import { useTourManager, getMainTourSteps } from "@/components/onboarding/TourMa
export default function DashboardPage() {
const { user, isLoading } = useAuth();
const [, setLocation] = useLocation();
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
const { isListening, speak } = useVoiceContext();
const { unreadCount } = useNotifications();
useVoiceContext();
useNotifications();
const { startTour } = useTourManager();
const handleStartTour = () => {
+36 -7
View File
@@ -9,8 +9,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Badge } from '@/components/ui/badge';
import { PageHeader } from '@/components/ui/page-header';
import { FinancialSkeleton, CardSkeleton, ChartSkeleton } from '@/components/ui/loading-skeleton';
import { useToast } from '@/hooks/use-toast';
import { apiRequest } from '@/lib/queryClient';
import Sidebar from '@/components/layout/Sidebar';
import { Plus, TrendingUp, TrendingDown, DollarSign } from 'lucide-react';
@@ -99,14 +99,43 @@ export default function FinancesPage() {
<div className="flex h-screen bg-gray-50 dark:bg-gray-900">
<Sidebar className="w-64 border-r" />
<div className="flex-1 overflow-auto p-6">
<div className="animate-pulse space-y-4">
<div className="h-8 bg-gray-200 dark:bg-gray-700 rounded w-1/4"></div>
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-1/2"></div>
<div className="grid grid-cols-3 gap-4">
{[...Array(3)].map((_, i) => (
<div key={i} className="h-24 bg-gray-200 dark:bg-gray-700 rounded"></div>
<div className="space-y-6">
<PageHeader
title="Financial Overview"
description="Track your income and expenses"
/>
{/* Summary cards skeleton */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{Array.from({ length: 3 }).map((_, i) => (
<CardSkeleton key={i} />
))}
</div>
{/* Chart skeleton */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle>Spending Overview</CardTitle>
</CardHeader>
<CardContent>
<ChartSkeleton />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Recent Transactions</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
{Array.from({ length: 6 }).map((_, i) => (
<FinancialSkeleton key={i} />
))}
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</div>
+1 -1
View File
@@ -9,7 +9,7 @@ import Sidebar from "@/components/layout/Sidebar";
import FocusMode from "@/components/focus/FocusMode";
export default function FocusPage() {
const { t } = useTranslation();
useTranslation();
const [isFocusModeOpen, setIsFocusModeOpen] = useState(false);
return (
+28 -38
View File
@@ -1,15 +1,13 @@
import { useLocation } from "wouter";
import { useAuth } from "@/hooks/useAuth";
import { useVoice } from "@/hooks/useVoice";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import DailyJoke from "@/components/ai/DailyJoke";
import VoiceIndicator from "@/components/voice/VoiceIndicator";
import Header from "@/components/layout/Header";
import { useTheme } from "@/context/ThemeContext";
import {
Mic,
Volume2,
Brain,
DollarSign,
Calendar,
@@ -17,26 +15,35 @@ import {
} from "lucide-react";
export default function LandingPage() {
console.log("🚀 LandingPage is rendering...");
const [, setLocation] = useLocation();
const { user } = useAuth();
const { isListening, startListening, stopListening, isSupported } = useVoice();
// Temporarily removed voice hooks that might be causing issues
// const { isListening, startListening, stopListening, isSupported } = useVoice();
// Log auth state for debugging
console.log("👤 User authentication state:", user);
// Redirect to dashboard if already authenticated
if (user) {
console.log("👤 User authenticated, redirecting to dashboard...");
setLocation("/dashboard");
return null;
}
const handleGetStarted = () => {
console.log("🔘 Get Started button clicked");
setLocation("/login");
};
const handleVoiceDemo = () => {
if (isListening) {
stopListening();
} else {
startListening();
}
console.log("🔘 Voice Demo button clicked");
// Voice functionality temporarily disabled for debugging
// if (isListening) {
// stopListening();
// } else {
// startListening();
// }
};
return (
@@ -78,26 +85,15 @@ export default function LandingPage() {
<ArrowRight className="ml-2 w-5 h-5" />
</Button>
{isSupported && (
<Button
size="lg"
variant="outline"
onClick={handleVoiceDemo}
className="border-2 border-blue-300 dark:border-blue-700 text-blue-700 dark:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/20 px-8 py-3 text-lg font-semibold rounded-xl transition-all"
>
{isListening ? (
<>
<Volume2 className="w-5 h-5 mr-2 animate-pulse" />
Listening...
</>
) : (
<>
<Mic className="w-5 h-5 mr-2" />
Try Voice Demo
</>
)}
</Button>
)}
<Button
size="lg"
variant="outline"
onClick={handleVoiceDemo}
className="border-2 border-blue-300 dark:border-blue-700 text-blue-700 dark:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/20 px-8 py-3 text-lg font-semibold rounded-xl transition-all"
>
<Mic className="w-5 h-5 mr-2" />
Try Voice Demo
</Button>
</div>
{/* Features Grid */}
@@ -143,15 +139,9 @@ export default function LandingPage() {
<p className="text-slate-600 dark:text-slate-300 max-w-2xl mx-auto">
Control your productivity suite with natural voice commands. Create tasks, check finances, and navigate the app hands-free.
</p>
{isSupported ? (
<p className="text-sm text-green-600 dark:text-green-400 font-medium">
Voice control supported on your device
</p>
) : (
<p className="text-sm text-orange-600 dark:text-orange-400">
Voice control requires a modern browser with microphone access
</p>
)}
<p className="text-sm text-green-600 dark:text-green-400 font-medium">
Voice control supported on your device
</p>
</div>
</div>
</div>
+5 -5
View File
@@ -344,18 +344,18 @@ export default function SettingsPage() {
<div className="pt-4 border-t">
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<p className="font-medium">Current Model Status</p>
<p className="text-green-600 dark:text-green-400">Claude 3 Sonnet - Active</p>
<p className="font-medium">{t('settings.currentModelStatus')}</p>
<p className="text-green-600 dark:text-green-400">{t('settings.claude3Active')}</p>
</div>
<div>
<p className="font-medium">API Usage</p>
<p className="text-gray-600 dark:text-gray-400">2,341 requests this month</p>
<p className="font-medium">{t('settings.apiUsage')}</p>
<p className="text-gray-600 dark:text-gray-400">{t('settings.apiUsageValue')}</p>
</div>
</div>
</div>
<Button className="w-full">
Save AI Model Settings
{t('settings.saveAiSettings')}
</Button>
</CardContent>
</Card>
+25 -14
View File
@@ -1,6 +1,6 @@
import { useState, useMemo } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Card, CardContent } from '@/components/ui/card';
import { Card } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
@@ -9,6 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Badge } from '@/components/ui/badge';
import { PageHeader } from '@/components/ui/page-header';
import { TaskSkeleton, CardSkeleton } from '@/components/ui/loading-skeleton';
import { useToast } from '@/hooks/use-toast';
import { VoiceShortcuts } from '@/components/voice/VoiceShortcuts';
import Sidebar from '@/components/layout/Sidebar';
@@ -21,8 +22,8 @@ export default function TasksPage() {
const [searchQuery, setSearchQuery] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
const [priorityFilter, setPriorityFilter] = useState('all');
const [sortBy, setSortBy] = useState('createdAt');
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
const [sortBy] = useState('createdAt');
const [sortOrder] = useState<'asc' | 'desc'>('desc');
const [newTask, setNewTask] = useState({
title: '',
@@ -33,18 +34,21 @@ export default function TasksPage() {
});
// Fetch tasks
const { data: tasks, isLoading } = useQuery({
const { data: tasksData, isLoading } = useQuery({
queryKey: ['/api/tasks'],
queryFn: async () => {
const response = await fetch('/api/tasks');
if (!response.ok) throw new Error('Failed to fetch tasks');
return response.json();
const data = await response.json();
return data;
}
});
const tasks = tasksData?.tasks || [];
// Filter and sort tasks
const filteredAndSortedTasks = useMemo(() => {
if (!tasks) return [];
if (!Array.isArray(tasks)) return [];
const filtered = tasks.filter((task: any) => {
const matchesSearch = task.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
@@ -64,9 +68,9 @@ export default function TasksPage() {
bValue = b.title.toLowerCase();
break;
case "priority":
const priorityOrder = { high: 3, medium: 2, low: 1 };
aValue = priorityOrder[a.priority] || 0;
bValue = priorityOrder[b.priority] || 0;
const priorityOrder: Record<string, number> = { high: 3, medium: 2, low: 1 };
aValue = priorityOrder[a.priority as string] || 0;
bValue = priorityOrder[b.priority as string] || 0;
break;
case "dueDate":
aValue = a.dueDate ? new Date(a.dueDate) : new Date('9999-12-31');
@@ -170,12 +174,19 @@ export default function TasksPage() {
<div className="flex h-screen bg-gray-50 dark:bg-gray-900">
<Sidebar className="w-64 border-r" />
<div className="flex-1 overflow-auto p-6">
<div className="animate-pulse space-y-4">
<div className="h-8 bg-gray-200 dark:bg-gray-700 rounded w-1/4"></div>
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-1/2"></div>
<div className="space-y-6">
<PageHeader
title="Tasks"
description="Manage your tasks and projects efficiently"
/>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{Array.from({ length: 6 }).map((_, i) => (
<CardSkeleton key={i} />
))}
</div>
<div className="space-y-3">
{[...Array(5)].map((_, i) => (
<div key={i} className="h-20 bg-gray-200 dark:bg-gray-700 rounded"></div>
{Array.from({ length: 8 }).map((_, i) => (
<TaskSkeleton key={i} />
))}
</div>
</div>
+7 -5
View File
@@ -6,20 +6,22 @@ import { Badge } from "@/components/ui/badge";
import { PageHeader } from "@/components/ui/page-header";
import { useToast } from "@/hooks/use-toast";
import Sidebar from "@/components/layout/Sidebar";
import { Mic, MicOff, Volume2, Settings, PlayCircle, StopCircle } from "lucide-react";
import { Mic, MicOff, Volume2, Settings, PlayCircle } from "lucide-react";
import { useVoice } from "@/hooks/useVoice";
export default function VoicePage() {
const { toast } = useToast();
const [isListening, setIsListening] = useState(false);
const { isEnabled, isSupported, startListening, stopListening } = useVoice();
const { isSupported, startListening, stopListening } = useVoice();
const { data: commands = [], isLoading } = useQuery({
queryKey: ["/api/voice/commands"],
select: (data: any) => Array.isArray(data) ? data : []
});
const { data: status } = useQuery({
useQuery({
queryKey: ["/api/voice/status"],
select: (data: any) => data || { enabled: true }
});
const handleToggleListening = () => {
@@ -188,7 +190,7 @@ export default function VoicePage() {
</Card>
{/* Recent Voice Commands */}
{!isLoading && commands.length > 0 && (
{!isLoading && Array.isArray(commands) && commands.length > 0 && (
<Card className="mt-6">
<CardHeader>
<CardTitle>Recent Commands</CardTitle>
@@ -198,7 +200,7 @@ export default function VoicePage() {
</CardHeader>
<CardContent>
<div className="space-y-3">
{commands.slice(0, 10).map((command: any) => (
{(commands as any[]).slice(0, 10).map((command: any) => (
<div key={command.id} className="flex items-center justify-between p-3 border rounded-lg">
<div>
<div className="font-medium">{command.transcription}</div>
+12
View File
@@ -0,0 +1,12 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
});
-1
View File
@@ -2,4 +2,3 @@
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_localhost FALSE / FALSE 1749461849 connect.sid s%3A7zNq68aZliAxFVLCx2Je90dRY7pfLT09.S88988v3rhMbUHGcYdlwP%2BnTeYkWW4UYUggKYWfcgco
+1562 -1625
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -51,6 +51,7 @@
"compression": "^1.8.0",
"connect-pg-simple": "^10.0.0",
"date-fns": "^3.6.0",
"dotenv": "^16.5.0",
"drizzle-orm": "^0.39.1",
"drizzle-zod": "^0.7.0",
"embla-carousel-react": "^8.6.0",
+19
View File
@@ -1,7 +1,10 @@
import 'dotenv/config';
import express, { type Request, Response, NextFunction } from "express";
import { registerRoutes } from "./routes";
import { taskScheduler } from "./services/taskScheduler";
import { setupVite, serveStatic, log } from "./vite";
import session from 'express-session';
import passport from 'passport';
const app = express();
app.set('trust proxy', true);
@@ -23,6 +26,22 @@ app.use((req, res, next) => {
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// Session middleware
app.use(session({
secret: process.env.SESSION_SECRET || 'default_secret',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: app.get('env') === 'production',
maxAge: 1000 * 60 * 60 * 24, // 1 day
},
}));
// Passport middleware
app.use(passport.initialize());
app.use(passport.session());
app.use((req, res, next) => {
const start = Date.now();
const path = req.path;
+1 -1
View File
@@ -57,7 +57,7 @@ export const securityMiddleware = helmet({
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://replit.com"],
scriptSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "ws:", "wss:"]
}
+1 -1
View File
@@ -84,7 +84,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
cookie: {
secure: false, // Set to true in production with HTTPS
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // 24 hours
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days for development
sameSite: 'lax' // Allow cross-origin requests for development
}
}));
+15 -3
View File
@@ -1,11 +1,14 @@
import express, { type Express } from "express";
import fs from "fs";
import path from "path";
import { fileURLToPath } from 'node:url';
import { createServer as createViteServer, createLogger } from "vite";
import { type Server } from "http";
import viteConfig from "../vite.config";
import { nanoid } from "nanoid";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const viteLogger = createLogger();
export function log(message: string, source = "express") {
@@ -20,10 +23,12 @@ export function log(message: string, source = "express") {
}
export async function setupVite(app: Express, server: Server) {
log("🔧 Setting up Vite development server...");
const serverOptions = {
middlewareMode: true,
hmr: { server },
allowedHosts: true,
allowedHosts: true as true,
};
const vite = await createViteServer({
@@ -40,27 +45,34 @@ export async function setupVite(app: Express, server: Server) {
appType: "custom",
});
log("✅ Vite server created, adding middleware...");
app.use(vite.middlewares);
log("✅ Vite middleware added");
app.use("*", async (req, res, next) => {
const url = req.originalUrl;
log(`🌐 Handling request for: ${url}`);
try {
const clientTemplate = path.resolve(
import.meta.dirname,
__dirname,
"..",
"client",
"index.html",
);
log(`📄 Reading template from: ${clientTemplate}`);
// always reload the index.html file from disk incase it changes
let template = await fs.promises.readFile(clientTemplate, "utf-8");
template = template.replace(
`src="/src/main.tsx"`,
`src="/src/main.tsx?v=${nanoid()}"`,
);
log(`🔄 Transforming HTML with Vite...`);
const page = await vite.transformIndexHtml(url, template);
log(`✅ Sending transformed HTML`);
res.status(200).set({ "Content-Type": "text/html" }).end(page);
} catch (e) {
log(`❌ Error processing request: ${e}`);
vite.ssrFixStacktrace(e as Error);
next(e);
}
@@ -68,7 +80,7 @@ export async function setupVite(app: Express, server: Server) {
}
export function serveStatic(app: Express) {
const distPath = path.resolve(import.meta.dirname, "public");
const distPath = path.resolve(__dirname, "public");
if (!fs.existsSync(distPath)) {
throw new Error(
+10 -2
View File
@@ -2,7 +2,12 @@ import type { Config } from "tailwindcss";
export default {
darkMode: ["class"],
content: ["./client/index.html", "./client/src/**/*.{js,jsx,ts,tsx}"],
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
"./client/index.html",
"./client/src/**/*.{js,ts,jsx,tsx}"
],
theme: {
extend: {
borderRadius: {
@@ -10,9 +15,13 @@ export default {
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
borderColor: {
DEFAULT: "hsl(var(--border))",
},
colors: {
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
border: "hsl(var(--border))",
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
@@ -41,7 +50,6 @@ export default {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
chart: {
+8 -15
View File
@@ -1,31 +1,24 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
import runtimeErrorOverlay from "@replit/vite-plugin-runtime-error-modal";
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default defineConfig({
plugins: [
react(),
runtimeErrorOverlay(),
...(process.env.NODE_ENV !== "production" &&
process.env.REPL_ID !== undefined
? [
await import("@replit/vite-plugin-cartographer").then((m) =>
m.cartographer(),
),
]
: []),
],
resolve: {
alias: {
"@": path.resolve(import.meta.dirname, "client", "src"),
"@shared": path.resolve(import.meta.dirname, "shared"),
"@assets": path.resolve(import.meta.dirname, "attached_assets"),
"@": path.resolve(__dirname, "client", "src"),
"@shared": path.resolve(__dirname, "shared"),
"@assets": path.resolve(__dirname, "attached_assets"),
},
},
root: path.resolve(import.meta.dirname, "client"),
root: path.resolve(__dirname, "client"),
build: {
outDir: path.resolve(import.meta.dirname, "dist/public"),
outDir: path.resolve(__dirname, "dist/public"),
emptyOutDir: true,
},
});