Add new pages to access key features and fix page navigation

Adds Tasks, Finances, Voice, AI, Analytics, and Settings pages with routing in App.tsx.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 10e60398-05df-4c0e-8698-578a1494e818
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/81470e0d-8ae8-4335-9301-cd9a69e670fa/29a30ac5-78e2-49a4-b73b-7a3f9967ce79.jpg
This commit is contained in:
ghaddaditw
2025-05-30 17:59:27 +00:00
parent 07fc4fe199
commit 7dcd2117d3
7 changed files with 1661 additions and 0 deletions
+12
View File
@@ -12,6 +12,12 @@ 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 SettingsPage from "@/pages/SettingsPage";
import NotFound from "@/pages/not-found";
function Router() {
@@ -22,6 +28,12 @@ function Router() {
<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="/settings" component={SettingsPage} />
<Route path="/professionals" component={ProfessionalDirectory} />
<Route component={NotFound} />
</Switch>
+272
View File
@@ -0,0 +1,272 @@
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { useToast } from "@/hooks/use-toast";
import Sidebar from "@/components/layout/Sidebar";
import { Brain, MessageSquare, Lightbulb, Laugh, Send } from "lucide-react";
import { apiRequest } from "@/lib/queryClient";
export default function AIPage() {
const { toast } = useToast();
const [message, setMessage] = useState("");
const [chatHistory, setChatHistory] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(false);
const { data: dailyJoke } = useQuery({
queryKey: ["/api/ai/daily-joke"],
});
const { data: status } = useQuery({
queryKey: ["/api/ai/status"],
});
const { data: interactions = [] } = useQuery({
queryKey: ["/api/ai/interactions"],
});
const handleSendMessage = async (e: React.FormEvent) => {
e.preventDefault();
if (!message.trim()) return;
const userMessage = { role: "user", content: message, timestamp: new Date() };
setChatHistory(prev => [...prev, userMessage]);
setMessage("");
setIsLoading(true);
try {
const response = await apiRequest("/api/ai/chat", {
method: "POST",
body: JSON.stringify({ message }),
});
const aiMessage = {
role: "assistant",
content: response.content || "I'm here to help! How can I assist you today?",
timestamp: new Date()
};
setChatHistory(prev => [...prev, aiMessage]);
} catch (error) {
toast({
title: "Error",
description: "Failed to send message. Please try again.",
variant: "destructive",
});
} finally {
setIsLoading(false);
}
};
const generatePersonalizedJoke = async () => {
try {
const response = await apiRequest("/api/ai/joke", {
method: "POST",
});
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!",
});
} catch (error) {
toast({
title: "Error",
description: "Failed to generate joke. Please try again.",
variant: "destructive",
});
}
};
const generateInsight = async (type: string) => {
try {
const response = await apiRequest("/api/ai/insight", {
method: "POST",
body: JSON.stringify({ type }),
});
toast({
title: `${type.charAt(0).toUpperCase() + type.slice(1)} Insight`,
description: response.content || "Here's an insight based on your data!",
});
} catch (error) {
toast({
title: "Error",
description: "Failed to generate insight. Please try again.",
variant: "destructive",
});
}
};
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">
<div className="p-6">
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">AI Assistant</h1>
<p className="text-gray-600 dark:text-gray-300">
Chat with your AI assistant and get personalized insights
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Chat Interface */}
<div className="lg:col-span-2">
<Card className="h-[600px] flex flex-col">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<MessageSquare className="w-5 h-5" />
AI Chat
</CardTitle>
<CardDescription>
Have a conversation with your AI assistant
</CardDescription>
</CardHeader>
<CardContent className="flex-1 flex flex-col">
<div className="flex-1 overflow-auto space-y-4 mb-4">
{chatHistory.length === 0 ? (
<div className="text-center text-gray-500 mt-8">
<Brain className="w-12 h-12 mx-auto mb-4 text-gray-400" />
<p>Start a conversation with your AI assistant!</p>
<p className="text-sm mt-2">Try asking about your tasks, finances, or request insights.</p>
</div>
) : (
chatHistory.map((msg, index) => (
<div key={index} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[80%] p-3 rounded-lg ${
msg.role === 'user'
? 'bg-blue-500 text-white'
: 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white'
}`}>
<p>{msg.content}</p>
<p className="text-xs opacity-70 mt-1">
{new Date(msg.timestamp).toLocaleTimeString()}
</p>
</div>
</div>
))
)}
{isLoading && (
<div className="flex justify-start">
<div className="bg-gray-100 dark:bg-gray-800 p-3 rounded-lg">
<div className="flex items-center gap-2">
<div className="animate-spin w-4 h-4 border-2 border-gray-300 border-t-gray-600 rounded-full"></div>
<span className="text-gray-600 dark:text-gray-300">AI is thinking...</span>
</div>
</div>
</div>
)}
</div>
<form onSubmit={handleSendMessage} className="flex gap-2">
<Input
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Type your message..."
disabled={isLoading}
className="flex-1"
/>
<Button type="submit" disabled={isLoading || !message.trim()}>
<Send className="w-4 h-4" />
</Button>
</form>
</CardContent>
</Card>
</div>
{/* AI Features Sidebar */}
<div className="space-y-6">
{/* AI Status */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Brain className="w-5 h-5" />
AI Status
</CardTitle>
</CardHeader>
<CardContent>
<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>
</div>
<div className="flex items-center justify-between">
<span className="text-sm">Interactions Today</span>
<Badge variant="outline">
{interactions.length}
</Badge>
</div>
</div>
</CardContent>
</Card>
{/* Quick Actions */}
<Card>
<CardHeader>
<CardTitle>Quick Actions</CardTitle>
<CardDescription>
Get instant AI-powered assistance
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<Button onClick={generatePersonalizedJoke} className="w-full" variant="outline">
<Laugh className="w-4 h-4 mr-2" />
Get a Joke
</Button>
<Button onClick={() => generateInsight('productivity')} className="w-full" variant="outline">
<Lightbulb className="w-4 h-4 mr-2" />
Productivity Insight
</Button>
<Button onClick={() => generateInsight('financial')} className="w-full" variant="outline">
<Lightbulb className="w-4 h-4 mr-2" />
Financial Insight
</Button>
<Button onClick={() => generateInsight('general')} className="w-full" variant="outline">
<Lightbulb className="w-4 h-4 mr-2" />
General Insight
</Button>
</CardContent>
</Card>
{/* Daily Joke */}
{dailyJoke && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Laugh className="w-5 h-5" />
Daily Joke
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm italic">"{dailyJoke.joke}"</p>
</CardContent>
</Card>
)}
{/* Recent 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>
<div className="text-gray-600 dark:text-gray-300">
{new Date(interaction.createdAt).toLocaleDateString()}
</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
</div>
</div>
</div>
</div>
</div>
);
}
+320
View File
@@ -0,0 +1,320 @@
import { useQuery } from "@tanstack/react-query";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import Sidebar from "@/components/layout/Sidebar";
import { BarChart3, TrendingUp, TrendingDown, Calendar, Target, DollarSign } from "lucide-react";
export default function AnalyticsPage() {
const { data: tasks = [], isLoading: tasksLoading } = useQuery({
queryKey: ["/api/tasks"],
});
const { data: financialSummary = { income: 0, expenses: 0, net: 0 }, isLoading: financialLoading } = useQuery({
queryKey: ["/api/financial/summary"],
});
const { data: records = [], isLoading: recordsLoading } = useQuery({
queryKey: ["/api/financial/records"],
});
if (tasksLoading || financialLoading || recordsLoading) {
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-2 gap-4">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="h-32 bg-gray-200 dark:bg-gray-700 rounded"></div>
))}
</div>
</div>
</div>
</div>
);
}
// Calculate task analytics
const completedTasks = tasks.filter((task: any) => task.status === 'completed').length;
const pendingTasks = tasks.filter((task: any) => task.status === 'pending').length;
const inProgressTasks = tasks.filter((task: any) => task.status === 'in_progress').length;
const totalTasks = tasks.length;
const completionRate = totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0;
// Calculate financial analytics
const thisMonth = new Date().getMonth();
const thisYear = new Date().getFullYear();
const monthlyRecords = records.filter((record: any) => {
const recordDate = new Date(record.createdAt);
return recordDate.getMonth() === thisMonth && recordDate.getFullYear() === thisYear;
});
const monthlyIncome = monthlyRecords
.filter((record: any) => record.type === 'income')
.reduce((sum: number, record: any) => sum + record.amount, 0);
const monthlyExpenses = monthlyRecords
.filter((record: any) => record.type === 'expense')
.reduce((sum: number, record: any) => sum + record.amount, 0);
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">
<div className="p-6">
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Analytics</h1>
<p className="text-gray-600 dark:text-gray-300">
Insights and performance metrics for your tasks and finances
</p>
</div>
{/* Task Analytics */}
<div className="mb-6">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">Task Performance</h2>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Tasks</CardTitle>
<Target className="h-4 w-4 text-blue-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{totalTasks}</div>
<p className="text-xs text-muted-foreground">All time</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Completed</CardTitle>
<TrendingUp className="h-4 w-4 text-green-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-600">{completedTasks}</div>
<p className="text-xs text-muted-foreground">
{completionRate.toFixed(1)}% completion rate
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">In Progress</CardTitle>
<Calendar className="h-4 w-4 text-orange-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-orange-600">{inProgressTasks}</div>
<p className="text-xs text-muted-foreground">Active tasks</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Pending</CardTitle>
<TrendingDown className="h-4 w-4 text-red-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-red-600">{pendingTasks}</div>
<p className="text-xs text-muted-foreground">Awaiting action</p>
</CardContent>
</Card>
</div>
</div>
{/* Financial Analytics */}
<div className="mb-6">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">Financial Overview</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Income</CardTitle>
<TrendingUp className="h-4 w-4 text-green-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-600">
${financialSummary.income.toFixed(2)}
</div>
<p className="text-xs text-muted-foreground">
This month: ${monthlyIncome.toFixed(2)}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Expenses</CardTitle>
<TrendingDown className="h-4 w-4 text-red-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-red-600">
${financialSummary.expenses.toFixed(2)}
</div>
<p className="text-xs text-muted-foreground">
This month: ${monthlyExpenses.toFixed(2)}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Net Balance</CardTitle>
<DollarSign className="h-4 w-4 text-blue-600" />
</CardHeader>
<CardContent>
<div className={`text-2xl font-bold ${financialSummary.net >= 0 ? 'text-green-600' : 'text-red-600'}`}>
${financialSummary.net.toFixed(2)}
</div>
<p className="text-xs text-muted-foreground">
Monthly: ${(monthlyIncome - monthlyExpenses).toFixed(2)}
</p>
</CardContent>
</Card>
</div>
</div>
{/* Performance Insights */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<BarChart3 className="w-5 h-5" />
Task Distribution
</CardTitle>
<CardDescription>
Breakdown of your task statuses
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-green-500 rounded"></div>
<span className="text-sm">Completed</span>
</div>
<div className="flex items-center gap-2">
<span className="text-sm font-medium">{completedTasks}</span>
<Badge variant="secondary">{totalTasks > 0 ? ((completedTasks / totalTasks) * 100).toFixed(0) : 0}%</Badge>
</div>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-orange-500 rounded"></div>
<span className="text-sm">In Progress</span>
</div>
<div className="flex items-center gap-2">
<span className="text-sm font-medium">{inProgressTasks}</span>
<Badge variant="secondary">{totalTasks > 0 ? ((inProgressTasks / totalTasks) * 100).toFixed(0) : 0}%</Badge>
</div>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-red-500 rounded"></div>
<span className="text-sm">Pending</span>
</div>
<div className="flex items-center gap-2">
<span className="text-sm font-medium">{pendingTasks}</span>
<Badge variant="secondary">{totalTasks > 0 ? ((pendingTasks / totalTasks) * 100).toFixed(0) : 0}%</Badge>
</div>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Financial Health</CardTitle>
<CardDescription>
Your financial performance indicators
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-sm">Savings Rate</span>
<Badge variant={financialSummary.income > 0 && (financialSummary.net / financialSummary.income) > 0.2 ? "default" : "secondary"}>
{financialSummary.income > 0 ? ((financialSummary.net / financialSummary.income) * 100).toFixed(1) : 0}%
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-sm">Monthly Trends</span>
<Badge variant={(monthlyIncome - monthlyExpenses) >= 0 ? "default" : "destructive"}>
{(monthlyIncome - monthlyExpenses) >= 0 ? "Positive" : "Negative"}
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-sm">Transaction Count</span>
<Badge variant="outline">
{records.length} total
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-sm">This Month</span>
<Badge variant="outline">
{monthlyRecords.length} transactions
</Badge>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Recommendations */}
<Card className="mt-6">
<CardHeader>
<CardTitle>Recommendations</CardTitle>
<CardDescription>
Suggestions to improve your productivity and financial health
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
{completionRate < 70 && (
<div className="p-3 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
<p className="text-sm text-yellow-800 dark:text-yellow-200">
<strong>Task Management:</strong> Your completion rate is {completionRate.toFixed(1)}%.
Consider breaking down large tasks into smaller, manageable pieces.
</p>
</div>
)}
{pendingTasks > inProgressTasks && pendingTasks > 0 && (
<div className="p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
<p className="text-sm text-blue-800 dark:text-blue-200">
<strong>Productivity:</strong> You have {pendingTasks} pending tasks.
Start working on them to improve your productivity flow.
</p>
</div>
)}
{financialSummary.net < 0 && (
<div className="p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
<p className="text-sm text-red-800 dark:text-red-200">
<strong>Financial Health:</strong> Your expenses exceed your income.
Review your spending patterns and consider creating a budget.
</p>
</div>
)}
{financialSummary.income > 0 && (financialSummary.net / financialSummary.income) > 0.3 && (
<div className="p-3 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg">
<p className="text-sm text-green-800 dark:text-green-200">
<strong>Great Job!</strong> You're saving over 30% of your income.
Consider investing your savings for long-term growth.
</p>
</div>
)}
</div>
</CardContent>
</Card>
</div>
</div>
</div>
);
}
+277
View File
@@ -0,0 +1,277 @@
import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { useToast } from "@/hooks/use-toast";
import Sidebar from "@/components/layout/Sidebar";
import { Plus, DollarSign, TrendingUp, TrendingDown } from "lucide-react";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { apiRequest } from "@/lib/queryClient";
export default function FinancesPage() {
const { toast } = useToast();
const queryClient = useQueryClient();
const [open, setOpen] = useState(false);
const [newRecord, setNewRecord] = useState({
type: "expense",
amount: "",
description: "",
category: "",
});
const { data: records = [], isLoading: recordsLoading } = useQuery({
queryKey: ["/api/financial/records"],
});
const { data: summary = { income: 0, expenses: 0, net: 0 }, isLoading: summaryLoading } = useQuery({
queryKey: ["/api/financial/summary"],
});
const createRecordMutation = useMutation({
mutationFn: (record: typeof newRecord) =>
apiRequest("/api/financial/records", {
method: "POST",
body: JSON.stringify({
...record,
amount: parseFloat(record.amount),
}),
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["/api/financial/records"] });
queryClient.invalidateQueries({ queryKey: ["/api/financial/summary"] });
setOpen(false);
setNewRecord({ type: "expense", amount: "", description: "", category: "" });
toast({
title: "Record created",
description: "Your financial record has been successfully created.",
});
},
onError: () => {
toast({
title: "Error",
description: "Failed to create record. Please try again.",
variant: "destructive",
});
},
});
const handleCreateRecord = (e: React.FormEvent) => {
e.preventDefault();
if (!newRecord.amount || isNaN(parseFloat(newRecord.amount))) {
toast({
title: "Invalid amount",
description: "Please enter a valid amount.",
variant: "destructive",
});
return;
}
createRecordMutation.mutate(newRecord);
};
const categories = {
income: ["Salary", "Freelance", "Investment", "Business", "Other"],
expense: ["Food", "Transportation", "Housing", "Entertainment", "Healthcare", "Shopping", "Other"],
};
if (recordsLoading || summaryLoading) {
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">
{[1, 2, 3].map((i) => (
<div key={i} className="h-24 bg-gray-200 dark:bg-gray-700 rounded"></div>
))}
</div>
</div>
</div>
</div>
);
}
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">
<div className="p-6">
<div className="flex justify-between items-center mb-6">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Finances</h1>
<p className="text-gray-600 dark:text-gray-300">
Track your income, expenses, and financial goals
</p>
</div>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>
<Plus className="w-4 h-4 mr-2" />
Add Record
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Add Financial Record</DialogTitle>
<DialogDescription>
Add a new income or expense record.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleCreateRecord} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="type">Type</Label>
<Select value={newRecord.type} onValueChange={(value) => setNewRecord({ ...newRecord, type: value, category: "" })}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="income">Income</SelectItem>
<SelectItem value="expense">Expense</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="amount">Amount</Label>
<Input
id="amount"
type="number"
step="0.01"
placeholder="0.00"
value={newRecord.amount}
onChange={(e) => setNewRecord({ ...newRecord, amount: e.target.value })}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="category">Category</Label>
<Select value={newRecord.category} onValueChange={(value) => setNewRecord({ ...newRecord, category: value })}>
<SelectTrigger>
<SelectValue placeholder="Select category" />
</SelectTrigger>
<SelectContent>
{categories[newRecord.type as keyof typeof categories].map((category) => (
<SelectItem key={category} value={category}>
{category}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="description">Description</Label>
<Input
id="description"
placeholder="Brief description"
value={newRecord.description}
onChange={(e) => setNewRecord({ ...newRecord, description: e.target.value })}
/>
</div>
<Button type="submit" disabled={createRecordMutation.isPending}>
{createRecordMutation.isPending ? "Adding..." : "Add Record"}
</Button>
</form>
</DialogContent>
</Dialog>
</div>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Income</CardTitle>
<TrendingUp className="h-4 w-4 text-green-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-600">
${summary.income.toFixed(2)}
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Expenses</CardTitle>
<TrendingDown className="h-4 w-4 text-red-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-red-600">
${summary.expenses.toFixed(2)}
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Net Balance</CardTitle>
<DollarSign className="h-4 w-4 text-blue-600" />
</CardHeader>
<CardContent>
<div className={`text-2xl font-bold ${summary.net >= 0 ? 'text-green-600' : 'text-red-600'}`}>
${summary.net.toFixed(2)}
</div>
</CardContent>
</Card>
</div>
{/* Records List */}
<Card>
<CardHeader>
<CardTitle>Recent Transactions</CardTitle>
<CardDescription>
Your latest financial transactions
</CardDescription>
</CardHeader>
<CardContent>
{records.length === 0 ? (
<div className="text-center py-6">
<DollarSign className="w-12 h-12 mx-auto text-gray-400 mb-4" />
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
No records yet
</h3>
<p className="text-gray-600 dark:text-gray-300 mb-4">
Start tracking your finances by adding your first record.
</p>
<Button onClick={() => setOpen(true)}>
<Plus className="w-4 h-4 mr-2" />
Add Record
</Button>
</div>
) : (
<div className="space-y-4">
{records.map((record: any) => (
<div key={record.id} className="flex items-center justify-between p-4 border rounded-lg">
<div className="flex items-center gap-4">
<div className={`p-2 rounded-full ${record.type === 'income' ? 'bg-green-100 text-green-600' : 'bg-red-100 text-red-600'}`}>
{record.type === 'income' ? <TrendingUp className="w-4 h-4" /> : <TrendingDown className="w-4 h-4" />}
</div>
<div>
<div className="font-medium">{record.description || record.category}</div>
<div className="text-sm text-gray-600 dark:text-gray-300">
{record.category} {new Date(record.createdAt).toLocaleDateString()}
</div>
</div>
</div>
<div className="text-right">
<div className={`font-semibold ${record.type === 'income' ? 'text-green-600' : 'text-red-600'}`}>
{record.type === 'income' ? '+' : '-'}${record.amount.toFixed(2)}
</div>
<Badge variant={record.type === 'income' ? 'default' : 'secondary'}>
{record.type}
</Badge>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
</div>
</div>
);
}
+251
View File
@@ -0,0 +1,251 @@
import { useState } from "react";
import { useAuth } from "@/context/AuthContext";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Separator } from "@/components/ui/separator";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useToast } from "@/hooks/use-toast";
import Sidebar from "@/components/layout/Sidebar";
import { User, Bell, Shield, Palette } from "lucide-react";
export default function SettingsPage() {
const { user, updateProfile } = useAuth();
const { toast } = useToast();
const [loading, setLoading] = useState(false);
const [profile, setProfile] = useState({
username: user?.username || "",
email: user?.email || "",
fullName: user?.fullName || "",
});
const [notifications, setNotifications] = useState({
emailNotifications: true,
pushNotifications: true,
taskReminders: true,
financialAlerts: true,
});
const handleProfileUpdate = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
await updateProfile(profile);
toast({
title: "Profile updated",
description: "Your profile has been successfully updated.",
});
} catch (error) {
toast({
title: "Error",
description: "Failed to update profile. Please try again.",
variant: "destructive",
});
} finally {
setLoading(false);
}
};
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">
<div className="p-6">
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Settings</h1>
<p className="text-gray-600 dark:text-gray-300">
Manage your account settings and preferences
</p>
</div>
<Tabs defaultValue="profile" className="space-y-6">
<TabsList>
<TabsTrigger value="profile">
<User className="w-4 h-4 mr-2" />
Profile
</TabsTrigger>
<TabsTrigger value="notifications">
<Bell className="w-4 h-4 mr-2" />
Notifications
</TabsTrigger>
<TabsTrigger value="security">
<Shield className="w-4 h-4 mr-2" />
Security
</TabsTrigger>
<TabsTrigger value="appearance">
<Palette className="w-4 h-4 mr-2" />
Appearance
</TabsTrigger>
</TabsList>
<TabsContent value="profile">
<Card>
<CardHeader>
<CardTitle>Profile Information</CardTitle>
<CardDescription>
Update your personal information and account details.
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleProfileUpdate} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="username">Username</Label>
<Input
id="username"
value={profile.username}
onChange={(e) => setProfile({ ...profile, username: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
value={profile.email}
onChange={(e) => setProfile({ ...profile, email: e.target.value })}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="fullName">Full Name</Label>
<Input
id="fullName"
value={profile.fullName}
onChange={(e) => setProfile({ ...profile, fullName: e.target.value })}
/>
</div>
<Button type="submit" disabled={loading}>
{loading ? "Updating..." : "Update Profile"}
</Button>
</form>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="notifications">
<Card>
<CardHeader>
<CardTitle>Notification Preferences</CardTitle>
<CardDescription>
Choose what notifications you want to receive.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>Email Notifications</Label>
<p className="text-sm text-gray-600 dark:text-gray-400">
Receive notifications via email
</p>
</div>
<Switch
checked={notifications.emailNotifications}
onCheckedChange={(checked) =>
setNotifications({ ...notifications, emailNotifications: checked })
}
/>
</div>
<Separator />
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>Push Notifications</Label>
<p className="text-sm text-gray-600 dark:text-gray-400">
Receive push notifications in your browser
</p>
</div>
<Switch
checked={notifications.pushNotifications}
onCheckedChange={(checked) =>
setNotifications({ ...notifications, pushNotifications: checked })
}
/>
</div>
<Separator />
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>Task Reminders</Label>
<p className="text-sm text-gray-600 dark:text-gray-400">
Get reminded about upcoming tasks and deadlines
</p>
</div>
<Switch
checked={notifications.taskReminders}
onCheckedChange={(checked) =>
setNotifications({ ...notifications, taskReminders: checked })
}
/>
</div>
<Separator />
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>Financial Alerts</Label>
<p className="text-sm text-gray-600 dark:text-gray-400">
Receive alerts about financial goals and budgets
</p>
</div>
<Switch
checked={notifications.financialAlerts}
onCheckedChange={(checked) =>
setNotifications({ ...notifications, financialAlerts: checked })
}
/>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="security">
<Card>
<CardHeader>
<CardTitle>Security Settings</CardTitle>
<CardDescription>
Manage your account security and privacy settings.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="currentPassword">Current Password</Label>
<Input id="currentPassword" type="password" />
</div>
<div className="space-y-2">
<Label htmlFor="newPassword">New Password</Label>
<Input id="newPassword" type="password" />
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword">Confirm New Password</Label>
<Input id="confirmPassword" type="password" />
</div>
<Button>Update Password</Button>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="appearance">
<Card>
<CardHeader>
<CardTitle>Appearance Settings</CardTitle>
<CardDescription>
Customize the look and feel of your application.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>Dark Mode</Label>
<p className="text-sm text-gray-600 dark:text-gray-400">
Switch between light and dark themes
</p>
</div>
<Switch />
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
</div>
</div>
);
}
+273
View File
@@ -0,0 +1,273 @@
import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { useToast } from "@/hooks/use-toast";
import Sidebar from "@/components/layout/Sidebar";
import { Plus, Calendar, Clock, AlertCircle } from "lucide-react";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { apiRequest } from "@/lib/queryClient";
export default function TasksPage() {
const { toast } = useToast();
const queryClient = useQueryClient();
const [open, setOpen] = useState(false);
const [newTask, setNewTask] = useState({
title: "",
description: "",
priority: "medium",
dueDate: "",
});
const { data: tasks = [], isLoading } = useQuery({
queryKey: ["/api/tasks"],
});
const createTaskMutation = useMutation({
mutationFn: (task: typeof newTask) =>
apiRequest("/api/tasks", {
method: "POST",
body: JSON.stringify(task),
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["/api/tasks"] });
setOpen(false);
setNewTask({ title: "", description: "", priority: "medium", dueDate: "" });
toast({
title: "Task created",
description: "Your task has been successfully created.",
});
},
onError: () => {
toast({
title: "Error",
description: "Failed to create task. Please try again.",
variant: "destructive",
});
},
});
const updateTaskMutation = useMutation({
mutationFn: ({ id, status }: { id: number; status: string }) =>
apiRequest(`/api/tasks/${id}`, {
method: "PATCH",
body: JSON.stringify({ status }),
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["/api/tasks"] });
toast({
title: "Task updated",
description: "Task status has been updated.",
});
},
});
const handleCreateTask = (e: React.FormEvent) => {
e.preventDefault();
createTaskMutation.mutate(newTask);
};
const getPriorityColor = (priority: string) => {
switch (priority) {
case "high": return "destructive";
case "medium": return "default";
case "low": return "secondary";
default: return "default";
}
};
const getStatusColor = (status: string) => {
switch (status) {
case "completed": return "secondary";
case "in_progress": return "default";
case "pending": return "outline";
default: return "outline";
}
};
if (isLoading) {
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="space-y-3">
{[1, 2, 3].map((i) => (
<div key={i} className="h-24 bg-gray-200 dark:bg-gray-700 rounded"></div>
))}
</div>
</div>
</div>
</div>
);
}
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">
<div className="p-6">
<div className="flex justify-between items-center mb-6">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Tasks</h1>
<p className="text-gray-600 dark:text-gray-300">
Manage your tasks and track your progress
</p>
</div>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>
<Plus className="w-4 h-4 mr-2" />
Add Task
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create New Task</DialogTitle>
<DialogDescription>
Add a new task to your todo list.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleCreateTask} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="title">Title</Label>
<Input
id="title"
value={newTask.title}
onChange={(e) => setNewTask({ ...newTask, title: e.target.value })}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="description">Description</Label>
<Textarea
id="description"
value={newTask.description}
onChange={(e) => setNewTask({ ...newTask, description: e.target.value })}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="priority">Priority</Label>
<Select value={newTask.priority} onValueChange={(value) => setNewTask({ ...newTask, priority: value })}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="low">Low</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="high">High</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="dueDate">Due Date</Label>
<Input
id="dueDate"
type="date"
value={newTask.dueDate}
onChange={(e) => setNewTask({ ...newTask, dueDate: e.target.value })}
/>
</div>
</div>
<Button type="submit" disabled={createTaskMutation.isPending}>
{createTaskMutation.isPending ? "Creating..." : "Create Task"}
</Button>
</form>
</DialogContent>
</Dialog>
</div>
<div className="space-y-4">
{tasks.length === 0 ? (
<Card>
<CardContent className="p-6 text-center">
<Calendar className="w-12 h-12 mx-auto text-gray-400 mb-4" />
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
No tasks yet
</h3>
<p className="text-gray-600 dark:text-gray-300 mb-4">
Get started by creating your first task.
</p>
<Button onClick={() => setOpen(true)}>
<Plus className="w-4 h-4 mr-2" />
Add Task
</Button>
</CardContent>
</Card>
) : (
tasks.map((task: any) => (
<Card key={task.id}>
<CardHeader>
<div className="flex justify-between items-start">
<div>
<CardTitle className="text-lg">{task.title}</CardTitle>
{task.description && (
<CardDescription className="mt-1">
{task.description}
</CardDescription>
)}
</div>
<div className="flex gap-2">
<Badge variant={getPriorityColor(task.priority)}>
{task.priority}
</Badge>
<Badge variant={getStatusColor(task.status)}>
{task.status?.replace('_', ' ')}
</Badge>
</div>
</div>
</CardHeader>
<CardContent>
<div className="flex justify-between items-center">
<div className="flex items-center gap-4 text-sm text-gray-600 dark:text-gray-300">
{task.dueDate && (
<div className="flex items-center gap-1">
<Clock className="w-4 h-4" />
Due: {new Date(task.dueDate).toLocaleDateString()}
</div>
)}
<div className="flex items-center gap-1">
<Calendar className="w-4 h-4" />
Created: {new Date(task.createdAt).toLocaleDateString()}
</div>
</div>
<div className="flex gap-2">
{task.status !== "completed" && (
<Button
size="sm"
onClick={() => updateTaskMutation.mutate({ id: task.id, status: "completed" })}
disabled={updateTaskMutation.isPending}
>
Mark Complete
</Button>
)}
{task.status === "pending" && (
<Button
size="sm"
variant="outline"
onClick={() => updateTaskMutation.mutate({ id: task.id, status: "in_progress" })}
disabled={updateTaskMutation.isPending}
>
Start Task
</Button>
)}
</div>
</div>
</CardContent>
</Card>
))
)}
</div>
</div>
</div>
</div>
);
}
+256
View File
@@ -0,0 +1,256 @@
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { useToast } from "@/hooks/use-toast";
import Sidebar from "@/components/layout/Sidebar";
import { Mic, MicOff, Volume2, Settings, PlayCircle, StopCircle } 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 { data: commands = [], isLoading } = useQuery({
queryKey: ["/api/voice/commands"],
});
const { data: status } = useQuery({
queryKey: ["/api/voice/status"],
});
const handleToggleListening = () => {
if (isListening) {
stopListening();
setIsListening(false);
toast({
title: "Voice recognition stopped",
description: "Voice commands are no longer being listened for.",
});
} else {
startListening();
setIsListening(true);
toast({
title: "Voice recognition started",
description: "Say a command to interact with the application.",
});
}
};
const testTTS = () => {
if ('speechSynthesis' in window) {
const utterance = new SpeechSynthesisUtterance("Voice assistant is working correctly!");
speechSynthesis.speak(utterance);
} else {
toast({
title: "Text-to-speech not supported",
description: "Your browser does not support text-to-speech.",
variant: "destructive",
});
}
};
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">
<div className="p-6">
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Voice Commands</h1>
<p className="text-gray-600 dark:text-gray-300">
Control your application using voice commands
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
{/* Voice Control */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Mic className="w-5 h-5" />
Voice Control
</CardTitle>
<CardDescription>
Start or stop voice recognition
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Voice Recognition</span>
<Badge variant={isSupported ? "default" : "destructive"}>
{isSupported ? "Supported" : "Not Supported"}
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Status</span>
<Badge variant={isListening ? "default" : "secondary"}>
{isListening ? "Listening" : "Stopped"}
</Badge>
</div>
<Button
onClick={handleToggleListening}
disabled={!isSupported}
className="w-full"
variant={isListening ? "destructive" : "default"}
>
{isListening ? (
<>
<MicOff className="w-4 h-4 mr-2" />
Stop Listening
</>
) : (
<>
<Mic className="w-4 h-4 mr-2" />
Start Listening
</>
)}
</Button>
</CardContent>
</Card>
{/* Text-to-Speech */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Volume2 className="w-5 h-5" />
Text-to-Speech
</CardTitle>
<CardDescription>
Test and configure speech output
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">TTS Support</span>
<Badge variant={'speechSynthesis' in window ? "default" : "destructive"}>
{'speechSynthesis' in window ? "Available" : "Not Available"}
</Badge>
</div>
<Button onClick={testTTS} className="w-full" variant="outline">
<PlayCircle className="w-4 h-4 mr-2" />
Test Speech
</Button>
</CardContent>
</Card>
</div>
{/* Available Commands */}
<Card>
<CardHeader>
<CardTitle>Available Voice Commands</CardTitle>
<CardDescription>
These are the voice commands you can use
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<h4 className="font-medium">Task Management</h4>
<ul className="space-y-1 text-sm text-gray-600 dark:text-gray-300">
<li>"Create task [task name]"</li>
<li>"Mark task [task name] complete"</li>
<li>"Show my tasks"</li>
<li>"What are my pending tasks?"</li>
</ul>
</div>
<div className="space-y-2">
<h4 className="font-medium">Financial Commands</h4>
<ul className="space-y-1 text-sm text-gray-600 dark:text-gray-300">
<li>"Add expense [amount] for [description]"</li>
<li>"Add income [amount] from [source]"</li>
<li>"Show my balance"</li>
<li>"What did I spend this month?"</li>
</ul>
</div>
<div className="space-y-2">
<h4 className="font-medium">Navigation</h4>
<ul className="space-y-1 text-sm text-gray-600 dark:text-gray-300">
<li>"Go to dashboard"</li>
<li>"Open tasks"</li>
<li>"Show finances"</li>
<li>"Open settings"</li>
</ul>
</div>
<div className="space-y-2">
<h4 className="font-medium">AI Assistant</h4>
<ul className="space-y-1 text-sm text-gray-600 dark:text-gray-300">
<li>"Tell me a joke"</li>
<li>"Give me productivity tips"</li>
<li>"Analyze my spending"</li>
<li>"What should I focus on today?"</li>
</ul>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Recent Voice Commands */}
{!isLoading && commands.length > 0 && (
<Card className="mt-6">
<CardHeader>
<CardTitle>Recent Commands</CardTitle>
<CardDescription>
Your recent voice command history
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
{commands.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>
<div className="text-sm text-gray-600 dark:text-gray-300">
{new Date(command.createdAt).toLocaleString()}
</div>
</div>
<Badge variant={command.processed ? "default" : "secondary"}>
{command.processed ? "Processed" : "Pending"}
</Badge>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* Setup Instructions */}
<Card className="mt-6">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Settings className="w-5 h-5" />
Setup Instructions
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div>
<h4 className="font-medium mb-2">Getting Started</h4>
<ol className="list-decimal list-inside space-y-1 text-sm text-gray-600 dark:text-gray-300">
<li>Make sure your browser supports voice recognition (Chrome, Edge recommended)</li>
<li>Allow microphone access when prompted</li>
<li>Click "Start Listening" to begin voice recognition</li>
<li>Speak clearly and wait for the system to process your command</li>
<li>Commands are processed automatically and actions are executed</li>
</ol>
</div>
<div>
<h4 className="font-medium mb-2">Tips for Better Recognition</h4>
<ul className="list-disc list-inside space-y-1 text-sm text-gray-600 dark:text-gray-300">
<li>Speak clearly and at a normal pace</li>
<li>Use the exact command phrases listed above</li>
<li>Ensure you're in a quiet environment</li>
<li>Wait for the previous command to finish processing</li>
</ul>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
);
}