diff --git a/attached_assets/Pasted--Complete-TaskFin-AI-Suite-Main-App-Features-Architecture-Highlights-Multi-Context-Provid-1748626434035.txt b/attached_assets/Pasted--Complete-TaskFin-AI-Suite-Main-App-Features-Architecture-Highlights-Multi-Context-Provid-1748626434035.txt new file mode 100644 index 0000000..33fb300 --- /dev/null +++ b/attached_assets/Pasted--Complete-TaskFin-AI-Suite-Main-App-Features-Architecture-Highlights-Multi-Context-Provid-1748626434035.txt @@ -0,0 +1,103 @@ +🎯 Complete TaskFin AI Suite - Main App Features: +🏗️ Architecture Highlights: + +Multi-Context Providers: + +AuthProvider - User authentication and roles +ThemeProvider - Dark/light theme management +VoiceProvider - Voice AI capabilities +NotificationProvider - Real-time notifications + + +Comprehensive Routing: + +Public routes - Landing, professional directory, login/register +Protected routes - Main app with role-based access +Professional routes - Appointment booking, client management +Admin routes - System management and analytics +AI routes - Voice chat, AI insights, assistant + + +Adaptive Navigation: + +Standard users - Basic task/finance management +Pro users - Advanced AI insights + professional features +Professionals - Full appointment booking system +Admins - Complete system management + + + +🎤 Voice-First Experience: + +Global voice commands - Navigate anywhere by voice +Voice task creation - "أنشئ مهمة مراجعة التقارير غداً" +Voice appointment booking - "احجز موعد مع دكتور سميث" +Multilingual support - Arabic and English seamlessly +Voice feedback - Audio confirmations in Omani Arabic + +👥 Social & Professional Features: + +Professional profiles - Complete service showcases +Appointment system - Real-time booking with availability +Client management - Full CRM for professionals +Review system - Ratings and feedback +Connection network - Professional networking + +🤖 AI-Powered Intelligence: + +Smart task prioritization - AI suggests optimal workflows +Financial insights - AI analyzes spending patterns +Voice conversations - Natural language AI interactions +Predictive scheduling - AI optimizes appointment booking +Context-aware suggestions - Personalized recommendations + +📱 Modern UI/UX: + +Responsive design - Perfect on desktop, tablet, mobile +Dark theme - Professional appearance with system detection +Command palette - Quick actions via Cmd/Ctrl+K +Global search - Find anything across the entire system +Real-time status - Live system health and connectivity + +🔒 Enterprise Security: + +Role-based access - Granular permission controls +Session management - Secure authentication +Audit logging - Complete activity tracking +Data privacy - 100% on-premise deployment + +🚀 Key User Flows: +For Healthcare Professionals: + +Set up professional profile with specializations +Configure availability schedule (9 AM - 5 PM weekdays) +Patients discover and book appointments online +Voice-enabled appointment management +Financial tracking for practice income/expenses +AI insights for patient scheduling optimization + +For Business Consultants: + +Create service portfolio with expertise areas +Enable online booking for consultations +Manage client relationships and project tasks +Voice-powered task creation and updates +Financial reporting and business insights +AI-driven schedule optimization + +For Personal Users: + +Voice-enabled task management in Arabic/English +Smart financial tracking with AI categorization +Find and book professional services +AI assistant for productivity optimization +Integrated calendar and scheduling + +🌟 Unique Selling Points: + +🇴🇲 Omani Arabic Integration - Native TTS and voice recognition +🏥 Healthcare-Focused - Built for medical/professional services +🤖 AI-First Design - Intelligence woven throughout every feature +🔒 Complete Privacy - 100% on-premise, no cloud dependencies +👥 Social Professional Network - Built-in discovery and booking +🎤 Voice-Everything - Complete voice interaction capability \ No newline at end of file diff --git a/client/src/App.tsx b/client/src/App.tsx index c767143..56e4d16 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -5,9 +5,12 @@ import { Toaster } from "@/components/ui/toaster"; import { TooltipProvider } from "@/components/ui/tooltip"; import { AuthProvider } from "@/context/AuthContext"; import { ThemeProvider } from "@/context/ThemeContext"; +import { VoiceProvider } from "@/context/VoiceProvider"; +import { NotificationProvider } from "@/context/NotificationProvider"; 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 NotFound from "@/pages/not-found"; @@ -18,6 +21,7 @@ function Router() { + ); @@ -28,10 +32,14 @@ function App() { - - - - + + + + + + + + diff --git a/client/src/components/CommandPalette.tsx b/client/src/components/CommandPalette.tsx new file mode 100644 index 0000000..f2bc32d --- /dev/null +++ b/client/src/components/CommandPalette.tsx @@ -0,0 +1,281 @@ +import { useState, useEffect } from "react"; +import { useLocation } from "wouter"; +import { Dialog, DialogContent } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { useAuth } from "@/hooks/useAuth"; +import { useVoiceContext } from "@/context/VoiceProvider"; +import { + Search, + Home, + CheckSquare, + DollarSign, + Users, + Calendar, + Settings, + Mic, + Moon, + Sun, + LogOut, + Plus +} from "lucide-react"; +import { useTheme } from "@/context/ThemeContext"; + +interface Command { + id: string; + title: string; + subtitle?: string; + icon: React.ReactNode; + action: () => void; + category: string; + keywords: string[]; +} + +interface CommandPaletteProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function CommandPalette({ open, onOpenChange }: CommandPaletteProps) { + const [, setLocation] = useLocation(); + const [query, setQuery] = useState(""); + const { user, logout } = useAuth(); + const { theme, toggleTheme } = useTheme(); + const { isListening, startListening, stopListening, speak } = useVoiceContext(); + + const commands: Command[] = [ + // Navigation + { + id: "nav-home", + title: "Go to Dashboard", + subtitle: "Navigate to main dashboard", + icon: , + action: () => setLocation("/dashboard"), + category: "Navigation", + keywords: ["dashboard", "home", "main"], + }, + { + id: "nav-tasks", + title: "View Tasks", + subtitle: "Open task management", + icon: , + action: () => setLocation("/dashboard?tab=tasks"), + category: "Navigation", + keywords: ["tasks", "todo", "work"], + }, + { + id: "nav-finance", + title: "Financial Records", + subtitle: "Manage income and expenses", + icon: , + action: () => setLocation("/dashboard?tab=finance"), + category: "Navigation", + keywords: ["finance", "money", "budget", "expenses"], + }, + { + id: "nav-professionals", + title: "Professional Directory", + subtitle: "Find and connect with professionals", + icon: , + action: () => setLocation("/professionals"), + category: "Navigation", + keywords: ["professionals", "directory", "services"], + }, + + // Actions + { + id: "action-new-task", + title: "Create New Task", + subtitle: "Add a new task to your list", + icon: , + action: () => { + setLocation("/dashboard?tab=tasks&action=new"); + speak("Creating new task"); + }, + category: "Actions", + keywords: ["create", "new", "task", "add"], + }, + { + id: "action-new-expense", + title: "Add Expense", + subtitle: "Record a new expense", + icon: , + action: () => { + setLocation("/dashboard?tab=finance&action=expense"); + speak("Adding new expense"); + }, + category: "Actions", + keywords: ["expense", "add", "money", "spend"], + }, + { + id: "action-new-income", + title: "Add Income", + subtitle: "Record new income", + icon: , + action: () => { + setLocation("/dashboard?tab=finance&action=income"); + speak("Adding new income"); + }, + category: "Actions", + keywords: ["income", "add", "money", "earn"], + }, + + // Voice + { + id: "voice-toggle", + title: isListening ? "Stop Voice Recognition" : "Start Voice Recognition", + subtitle: "Toggle voice commands", + icon: , + action: () => { + if (isListening) { + stopListening(); + speak("Voice recognition stopped"); + } else { + startListening(); + speak("Voice recognition started"); + } + }, + category: "Voice", + keywords: ["voice", "microphone", "speech", "listen"], + }, + + // Settings + { + id: "settings-theme", + title: `Switch to ${theme === "dark" ? "Light" : "Dark"} Mode`, + subtitle: "Toggle application theme", + icon: theme === "dark" ? : , + action: () => { + toggleTheme(); + speak(`Switched to ${theme === "dark" ? "light" : "dark"} mode`); + }, + category: "Settings", + keywords: ["theme", "dark", "light", "appearance"], + }, + { + id: "settings-logout", + title: "Sign Out", + subtitle: "Log out of your account", + icon: , + action: () => { + logout(); + speak("Signed out successfully"); + }, + category: "Account", + keywords: ["logout", "sign out", "exit"], + }, + ]; + + // Filter commands based on user role + const availableCommands = commands.filter(command => { + if (command.id === "nav-professionals" && user?.role === "standard") { + return false; // Hide professional directory for standard users + } + return true; + }); + + const filteredCommands = availableCommands.filter(command => + command.title.toLowerCase().includes(query.toLowerCase()) || + command.subtitle?.toLowerCase().includes(query.toLowerCase()) || + command.keywords.some(keyword => keyword.toLowerCase().includes(query.toLowerCase())) + ); + + const groupedCommands = filteredCommands.reduce((groups, command) => { + const category = command.category; + if (!groups[category]) { + groups[category] = []; + } + groups[category].push(command); + return groups; + }, {} as Record); + + const handleCommandSelect = (command: Command) => { + command.action(); + onOpenChange(false); + setQuery(""); + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "Enter" && filteredCommands.length > 0) { + handleCommandSelect(filteredCommands[0]); + } + }; + + // Global keyboard shortcut + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if ((event.metaKey || event.ctrlKey) && event.key === "k") { + event.preventDefault(); + onOpenChange(!open); + } + if (event.key === "Escape") { + onOpenChange(false); + } + }; + + document.addEventListener("keydown", handleKeyDown); + return () => document.removeEventListener("keydown", handleKeyDown); + }, [open, onOpenChange]); + + return ( + + +
+
+ + setQuery(e.target.value)} + onKeyDown={handleKeyDown} + className="border-0 focus-visible:ring-0 focus-visible:ring-offset-0" + autoFocus + /> + + {isListening ? "Listening..." : "⌘K"} + +
+
+ +
+ {filteredCommands.length === 0 ? ( +
+ No commands found. Try voice commands in Arabic or English. +
+ ) : ( + Object.entries(groupedCommands).map(([category, commands]) => ( +
+
+ {category} +
+ {commands.map((command) => ( + + ))} +
+ )) + )} +
+ +
+ Press ⌘K to open • + to select • + Voice commands supported in Arabic and English +
+
+
+ ); +} \ No newline at end of file diff --git a/client/src/context/NotificationProvider.tsx b/client/src/context/NotificationProvider.tsx new file mode 100644 index 0000000..153a62b --- /dev/null +++ b/client/src/context/NotificationProvider.tsx @@ -0,0 +1,126 @@ +import React, { createContext, useContext, useState, useEffect } from "react"; +import { useToast } from "@/hooks/use-toast"; + +interface Notification { + id: string; + title: string; + message: string; + type: 'info' | 'success' | 'warning' | 'error'; + timestamp: Date; + read: boolean; + actionUrl?: string; +} + +interface NotificationContextType { + notifications: Notification[]; + unreadCount: number; + addNotification: (notification: Omit) => void; + markAsRead: (id: string) => void; + markAllAsRead: () => void; + removeNotification: (id: string) => void; + clearAll: () => void; +} + +const NotificationContext = createContext(undefined); + +export function NotificationProvider({ children }: { children: React.ReactNode }) { + const [notifications, setNotifications] = useState([]); + const { toast } = useToast(); + + const addNotification = (notification: Omit) => { + const newNotification: Notification = { + ...notification, + id: `notification-${Date.now()}-${Math.random()}`, + timestamp: new Date(), + read: false, + }; + + setNotifications(prev => [newNotification, ...prev]); + + // Show toast for new notifications + toast({ + title: notification.title, + description: notification.message, + variant: notification.type === 'error' ? 'destructive' : 'default', + }); + }; + + const markAsRead = (id: string) => { + setNotifications(prev => + prev.map(notification => + notification.id === id ? { ...notification, read: true } : notification + ) + ); + }; + + const markAllAsRead = () => { + setNotifications(prev => + prev.map(notification => ({ ...notification, read: true })) + ); + }; + + const removeNotification = (id: string) => { + setNotifications(prev => prev.filter(notification => notification.id !== id)); + }; + + const clearAll = () => { + setNotifications([]); + }; + + const unreadCount = notifications.filter(n => !n.read).length; + + // Simulate real-time notifications (in production, this would be WebSocket or SSE) + useEffect(() => { + const interval = setInterval(() => { + // This would be replaced with actual real-time notification system + if (Math.random() < 0.1) { // 10% chance every 30 seconds + const sampleNotifications = [ + { + title: "New Appointment Request", + message: "A client has requested an appointment for tomorrow at 2:00 PM", + type: 'info' as const, + }, + { + title: "Task Due Soon", + message: "Review quarterly reports is due in 2 hours", + type: 'warning' as const, + }, + { + title: "Payment Received", + message: "Payment of $150 received for consultation", + type: 'success' as const, + }, + ]; + + const randomNotification = sampleNotifications[Math.floor(Math.random() * sampleNotifications.length)]; + addNotification(randomNotification); + } + }, 30000); // Every 30 seconds + + return () => clearInterval(interval); + }, []); + + return ( + + {children} + + ); +} + +export function useNotifications() { + const context = useContext(NotificationContext); + if (context === undefined) { + throw new Error("useNotifications must be used within a NotificationProvider"); + } + return context; +} \ No newline at end of file diff --git a/client/src/context/VoiceProvider.tsx b/client/src/context/VoiceProvider.tsx new file mode 100644 index 0000000..d41ca02 --- /dev/null +++ b/client/src/context/VoiceProvider.tsx @@ -0,0 +1,82 @@ +import React, { createContext, useContext, useState, useEffect } from "react"; +import { useVoice } from "@/hooks/useVoice"; + +interface VoiceContextType { + isListening: boolean; + isSupported: boolean; + language: 'en' | 'ar'; + startListening: () => void; + stopListening: () => void; + setLanguage: (lang: 'en' | 'ar') => void; + executeVoiceCommand: (command: string) => Promise; + speak: (text: string, lang?: 'en' | 'ar') => void; +} + +const VoiceContext = createContext(undefined); + +export function VoiceProvider({ children }: { children: React.ReactNode }) { + const { isListening, startListening, stopListening, isSupported } = useVoice(); + const [language, setLanguage] = useState<'en' | 'ar'>('en'); + + const executeVoiceCommand = async (command: string) => { + try { + // Process voice commands for navigation and actions + const lowerCommand = command.toLowerCase(); + + if (lowerCommand.includes('navigate') || lowerCommand.includes('go to')) { + // Handle navigation commands + if (lowerCommand.includes('dashboard')) { + window.location.href = '/dashboard'; + } else if (lowerCommand.includes('tasks')) { + window.location.href = '/dashboard?tab=tasks'; + } else if (lowerCommand.includes('finance')) { + window.location.href = '/dashboard?tab=finance'; + } + } else if (lowerCommand.includes('create task') || lowerCommand.includes('أنشئ مهمة')) { + // Handle task creation + // This would integrate with your task creation API + console.log('Voice task creation:', command); + } else if (lowerCommand.includes('book appointment') || lowerCommand.includes('احجز موعد')) { + // Handle appointment booking + console.log('Voice appointment booking:', command); + } + } catch (error) { + console.error('Voice command execution error:', error); + } + }; + + const speak = (text: string, lang: 'en' | 'ar' = language) => { + if ('speechSynthesis' in window) { + const utterance = new SpeechSynthesisUtterance(text); + utterance.lang = lang === 'ar' ? 'ar-OM' : 'en-US'; // Omani Arabic support + utterance.rate = 0.9; + utterance.pitch = 1; + speechSynthesis.speak(utterance); + } + }; + + return ( + + {children} + + ); +} + +export function useVoiceContext() { + const context = useContext(VoiceContext); + if (context === undefined) { + throw new Error("useVoiceContext must be used within a VoiceProvider"); + } + return context; +} \ No newline at end of file diff --git a/client/src/pages/ProfessionalDirectory.tsx b/client/src/pages/ProfessionalDirectory.tsx new file mode 100644 index 0000000..7f37416 --- /dev/null +++ b/client/src/pages/ProfessionalDirectory.tsx @@ -0,0 +1,225 @@ +import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Star, MapPin, Clock, Phone, Mail, Calendar } from "lucide-react"; +import { Link } from "wouter"; + +interface Professional { + id: number; + firstName: string; + lastName: string; + businessName: string; + professionalType: string; + bio: string; + specializations: string[]; + address: string; + phoneNumber: string; + email: string; + profileImageUrl: string; + averageRating: number; + totalReviews: number; + allowAppointmentBooking: boolean; +} + +export default function ProfessionalDirectory() { + const [searchTerm, setSearchTerm] = useState(""); + const [selectedType, setSelectedType] = useState(""); + + const { data: professionals = [], isLoading } = useQuery({ + queryKey: ["/api/professionals/search", { searchTerm, type: selectedType }], + enabled: true, + }); + + const professionalTypes = [ + "doctor", "dentist", "therapist", "consultant", "lawyer", + "accountant", "coach", "tutor", "other" + ]; + + const filteredProfessionals = professionals.filter((prof: Professional) => + prof.firstName.toLowerCase().includes(searchTerm.toLowerCase()) || + prof.lastName.toLowerCase().includes(searchTerm.toLowerCase()) || + prof.businessName?.toLowerCase().includes(searchTerm.toLowerCase()) || + prof.specializations?.some((spec: string) => + spec.toLowerCase().includes(searchTerm.toLowerCase()) + ) + ); + + return ( +
+
+
+ {/* Header */} +
+

+ Professional Directory +

+

+ Discover and connect with trusted professionals in your area +

+
+ + {/* Search and Filters */} +
+
+ setSearchTerm(e.target.value)} + className="w-full" + /> +
+
+ + {professionalTypes.map((type) => ( + + ))} +
+
+ + {/* Results */} + {isLoading ? ( +
+ {[...Array(6)].map((_, i) => ( + + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ ))} +
+ ) : filteredProfessionals.length === 0 ? ( +
+

+ No professionals found matching your criteria. +

+

+ Try adjusting your search terms or filters. +

+
+ ) : ( +
+ {filteredProfessionals.map((professional: Professional) => ( + + +
+ + + + {professional.firstName[0]}{professional.lastName[0]} + + +
+ + {professional.firstName} {professional.lastName} + + {professional.businessName && ( +

+ {professional.businessName} +

+ )} +
+ + + {professional.averageRating || "New"} + + + ({professional.totalReviews || 0} reviews) + +
+
+
+
+ + + {professional.professionalType} + + + {professional.bio && ( +

+ {professional.bio} +

+ )} + + {professional.specializations && professional.specializations.length > 0 && ( +
+ {professional.specializations.slice(0, 3).map((spec, index) => ( + + {spec} + + ))} + {professional.specializations.length > 3 && ( + + +{professional.specializations.length - 3} more + + )} +
+ )} + +
+ {professional.address && ( +
+ + {professional.address} +
+ )} + {professional.phoneNumber && ( +
+ + {professional.phoneNumber} +
+ )} +
+ +
+ + {professional.allowAppointmentBooking && ( + + )} +
+
+
+ ))} +
+ )} +
+
+
+ ); +} \ No newline at end of file