Compare commits
10
Commits
3bb6f85db9
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31b54a1ecc | ||
|
|
83ffdf860a | ||
|
|
8d26a47b46 | ||
|
|
bbc3862e49 | ||
|
|
5a8e72c176 | ||
|
|
3eaff970ce | ||
|
|
30c0103b06 | ||
|
|
92a4bf4643 | ||
|
|
871ae2c733 | ||
|
|
d7f612559a |
@@ -0,0 +1,2 @@
|
||||
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
|
||||
NODE_ENV=development
|
||||
@@ -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>
|
||||
+24
-75
@@ -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}>
|
||||
<RTLProvider>
|
||||
<ThemeProvider>
|
||||
<RTLProvider>
|
||||
<AuthProvider>
|
||||
<AdaptiveThemeProvider>
|
||||
<NotificationProvider>
|
||||
<VoiceProvider>
|
||||
<TourManagerProvider>
|
||||
<TooltipProvider>
|
||||
<Toaster />
|
||||
<Router />
|
||||
<FloatingVoiceControl />
|
||||
<MoodDetector />
|
||||
</TooltipProvider>
|
||||
</TourManagerProvider>
|
||||
</VoiceProvider>
|
||||
</NotificationProvider>
|
||||
</AdaptiveThemeProvider>
|
||||
{isLoaded ? (
|
||||
<div className="app-container">
|
||||
<LandingPage />
|
||||
</div>
|
||||
) : (
|
||||
<div className="loading">Loading...</div>
|
||||
)}
|
||||
</AuthProvider>
|
||||
</ThemeProvider>
|
||||
</RTLProvider>
|
||||
</QueryClientProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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": "تسجيل الدخول",
|
||||
|
||||
@@ -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%;
|
||||
|
||||
+18
-3
@@ -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(
|
||||
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
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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
|
||||
|
||||
Generated
+1562
-1625
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user