Set up the basic structure and core functionality for the application
Initialize project with UI components, core libraries, and basic app structure. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 556aa286-edd2-4cea-8583-f4fc3cfd119b
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { useLocation } from "wouter";
|
||||
import { useEffect } 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 { 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
|
||||
} from "lucide-react";
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { user, isLoading } = useAuth();
|
||||
const [, setLocation] = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && !user) {
|
||||
setLocation("/login");
|
||||
}
|
||||
}, [user, isLoading, setLocation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (user && !user.onboardingComplete) {
|
||||
setLocation("/onboarding");
|
||||
}
|
||||
}, [user, setLocation]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-primary to-secondary rounded-lg animate-pulse mx-auto mb-4" />
|
||||
<p className="text-muted-foreground">Loading dashboard...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const getWelcomeMessage = () => {
|
||||
const hour = new Date().getHours();
|
||||
const name = user.firstName || user.username;
|
||||
|
||||
if (hour < 12) return `Good morning, ${name}!`;
|
||||
if (hour < 18) return `Good afternoon, ${name}!`;
|
||||
return `Good evening, ${name}!`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<Header />
|
||||
|
||||
<div className="dashboard-grid">
|
||||
{/* Sidebar */}
|
||||
<Sidebar className="hidden md:block" />
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 p-6 overflow-auto">
|
||||
<div className="max-w-7xl mx-auto space-y-6">
|
||||
|
||||
{/* Welcome Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-foreground">
|
||||
{getWelcomeMessage()}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Here's what's happening with your tasks and finances today.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{user.role === "admin" ? "Admin" : user.role === "pro" ? "Pro" : "Standard"} User
|
||||
</Badge>
|
||||
<Badge className="bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400 text-xs">
|
||||
Voice Active
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Daily AI Joke */}
|
||||
<DailyJoke />
|
||||
|
||||
{/* Quick Actions */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<Zap className="w-5 h-5" />
|
||||
<span>Quick Actions</span>
|
||||
<Badge variant="outline" className="ml-2 text-xs">
|
||||
Voice Ready
|
||||
</Badge>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
|
||||
{/* Voice Task Creation */}
|
||||
<Card className="bg-gradient-to-br from-primary-50 to-secondary-50 dark:from-primary-900/20 dark:to-secondary-900/20 border-primary-200 dark:border-primary-800">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="w-10 h-10 bg-primary-600 rounded-xl flex items-center justify-center">
|
||||
<Calendar className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<Mic className="w-5 h-5 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<h3 className="font-semibold text-slate-900 dark:text-white mb-1">Voice Task</h3>
|
||||
<p className="text-sm text-slate-600 dark:text-slate-400 mb-3">Say "Create task" to get started</p>
|
||||
<div className="text-xs text-slate-500 dark:text-slate-500">
|
||||
Try: "Create task to review budget reports"
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Voice Expense Entry */}
|
||||
<Card className="bg-gradient-to-br from-emerald-50 to-teal-50 dark:from-emerald-900/20 dark:to-teal-900/20 border-emerald-200 dark:border-emerald-800">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="w-10 h-10 bg-emerald-600 rounded-xl flex items-center justify-center">
|
||||
<DollarSign className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<Mic className="w-5 h-5 text-emerald-600 dark:text-emerald-400" />
|
||||
</div>
|
||||
<h3 className="font-semibold text-slate-900 dark:text-white mb-1">Voice Expense</h3>
|
||||
<p className="text-sm text-slate-600 dark:text-slate-400 mb-3">Say "Add expense" for quick entry</p>
|
||||
<div className="text-xs text-slate-500 dark:text-slate-500">
|
||||
Try: "Add $50 expense for groceries"
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Voice Assistant */}
|
||||
<Card className="bg-gradient-to-br from-purple-50 to-violet-50 dark:from-purple-900/20 dark:to-violet-900/20 border-purple-200 dark:border-purple-800">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="w-10 h-10 bg-purple-600 rounded-xl flex items-center justify-center">
|
||||
<Brain className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<Mic className="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<h3 className="font-semibold text-slate-900 dark:text-white mb-1">AI Assistant</h3>
|
||||
<p className="text-sm text-slate-600 dark:text-slate-400 mb-3">Ask questions about your data</p>
|
||||
<div className="text-xs text-slate-500 dark:text-slate-500">
|
||||
Try: "What's my financial summary?"
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Main Dashboard Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
|
||||
{/* Tasks Section */}
|
||||
<div className="space-y-6">
|
||||
<TaskList limit={5} status="pending" />
|
||||
|
||||
{/* Voice Recorder */}
|
||||
<VoiceRecorder variant="default" className="lg:hidden" />
|
||||
</div>
|
||||
|
||||
{/* Financial Section */}
|
||||
<div className="space-y-6">
|
||||
<FinancialOverview period="month" limit={5} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Voice Recorder for Desktop */}
|
||||
<div className="hidden lg:block">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<Mic className="w-5 h-5" />
|
||||
<span>Voice Commands</span>
|
||||
<Badge className="bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400 text-xs">
|
||||
Local Processing
|
||||
</Badge>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<VoiceRecorder variant="default" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* System Status (for Pro/Admin users) */}
|
||||
{(user.role === "pro" || user.role === "admin") && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<Activity className="w-5 h-5" />
|
||||
<span>System Status</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||
<div>
|
||||
<p className="font-medium text-sm">STT Model</p>
|
||||
<p className="text-xs text-muted-foreground">Online</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||
<div>
|
||||
<p className="font-medium text-sm">TTS Model</p>
|
||||
<p className="text-xs text-muted-foreground">Online</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||
<div>
|
||||
<p className="font-medium text-sm">AI Model</p>
|
||||
<p className="text-xs text-muted-foreground">Ready</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||
<div>
|
||||
<p className="font-medium text-sm">Database</p>
|
||||
<p className="text-xs text-muted-foreground">Connected</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{/* Global Voice Indicator */}
|
||||
<VoiceIndicator showControls />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
import { useLocation } from "wouter";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { useVoice } from "@/hooks/useVoice";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { aiService } from "@/services/aiService";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
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,
|
||||
Shield,
|
||||
Brain,
|
||||
CheckCircle,
|
||||
DollarSign,
|
||||
Calendar,
|
||||
Play,
|
||||
ArrowRight,
|
||||
Zap
|
||||
} from "lucide-react";
|
||||
|
||||
export default function LandingPage() {
|
||||
const [, setLocation] = useLocation();
|
||||
const { user } = useAuth();
|
||||
const { isListening, startListening, stopListening, isSupported } = useVoice();
|
||||
const { theme } = useTheme();
|
||||
|
||||
// Redirect to dashboard if already authenticated
|
||||
if (user) {
|
||||
setLocation("/dashboard");
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleGetStarted = () => {
|
||||
setLocation("/login");
|
||||
};
|
||||
|
||||
const handleVoiceDemo = () => {
|
||||
if (isListening) {
|
||||
stopListening();
|
||||
} else {
|
||||
startListening();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-violet-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
|
||||
<Header />
|
||||
|
||||
{/* Hero Section */}
|
||||
<main className="relative py-20 lg:py-32 overflow-hidden">
|
||||
{/* Background Pattern */}
|
||||
<div className="absolute inset-0 bg-[url('data:image/svg+xml,%3Csvg width=\"60\" height=\"60\" viewBox=\"0 0 60 60\" xmlns=\"http://www.w3.org/2000/svg\"%3E%3Cg fill=\"none\" fill-rule=\"evenodd\"%3E%3Cg fill=\"%236366f1\" fill-opacity=\"0.05\"%3E%3Ccircle cx=\"30\" cy=\"30\" r=\"2\"/%3E%3C/g%3E%3C/g%3E%3C/svg%3E')] opacity-50"></div>
|
||||
|
||||
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||
|
||||
{/* Left Column: Content */}
|
||||
<div className="space-y-8">
|
||||
{/* Daily Joke Display */}
|
||||
<DailyJoke />
|
||||
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-4xl lg:text-6xl font-bold text-slate-900 dark:text-white leading-tight">
|
||||
Voice-Powered
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-primary-600 to-secondary-600">
|
||||
{" "}TaskFin
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-slate-600 dark:text-slate-300 leading-relaxed">
|
||||
Complete offline task and financial management with local AI voice processing.
|
||||
No internet required, complete privacy guaranteed.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Key Features */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Card className="border-slate-200 dark:border-slate-700">
|
||||
<CardContent className="p-4">
|
||||
<div className="w-8 h-8 bg-primary-100 dark:bg-primary-900/50 rounded-lg flex items-center justify-center mb-3">
|
||||
<Calendar className="w-5 h-5 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<h4 className="font-semibold text-slate-900 dark:text-white text-sm">Task Management</h4>
|
||||
<p className="text-xs text-slate-600 dark:text-slate-400 mt-1">Voice-powered task creation</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-200 dark:border-slate-700">
|
||||
<CardContent className="p-4">
|
||||
<div className="w-8 h-8 bg-emerald-100 dark:bg-emerald-900/50 rounded-lg flex items-center justify-center mb-3">
|
||||
<DollarSign className="w-5 h-5 text-emerald-600 dark:text-emerald-400" />
|
||||
</div>
|
||||
<h4 className="font-semibold text-slate-900 dark:text-white text-sm">Financial Tracking</h4>
|
||||
<p className="text-xs text-slate-600 dark:text-slate-400 mt-1">Automated expense logging</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Button
|
||||
onClick={handleGetStarted}
|
||||
className="flex-1 bg-primary-600 hover:bg-primary-700 text-white px-8 py-4 text-lg font-semibold shadow-lg hover:shadow-xl transition-all"
|
||||
>
|
||||
Start Free Trial
|
||||
<ArrowRight className="w-5 h-5 ml-2" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setLocation("/demo")}
|
||||
className="px-8 py-4 text-lg font-semibold border-slate-300 dark:border-slate-600 hover:border-primary-300 dark:hover:border-primary-500"
|
||||
>
|
||||
<Play className="w-5 h-5 mr-2" />
|
||||
View Demo
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column: Voice Demo */}
|
||||
<div className="lg:pl-8">
|
||||
<Card className="shadow-2xl border-slate-200 dark:border-slate-700">
|
||||
<CardContent className="p-8">
|
||||
<div className="text-center space-y-6">
|
||||
<h3 className="text-xl font-semibold text-slate-900 dark:text-white">Try Voice Commands</h3>
|
||||
|
||||
{/* Voice Visualizer */}
|
||||
<div className="flex justify-center">
|
||||
<div className="relative">
|
||||
<Button
|
||||
onClick={handleVoiceDemo}
|
||||
disabled={!isSupported}
|
||||
className={`w-24 h-24 rounded-full bg-gradient-to-r from-primary-500 to-secondary-500 hover:from-primary-600 hover:to-secondary-600 text-white border-0 ${
|
||||
isListening ? 'animate-pulse scale-110' : ''
|
||||
}`}
|
||||
>
|
||||
<Mic className="w-12 h-12" />
|
||||
</Button>
|
||||
{isListening && (
|
||||
<div className="absolute -top-1 -right-1 w-4 h-4 bg-red-500 rounded-full animate-pulse" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<Button
|
||||
onClick={handleVoiceDemo}
|
||||
disabled={!isSupported}
|
||||
className="w-full bg-secondary-600 hover:bg-secondary-700 text-white font-medium"
|
||||
>
|
||||
<Mic className="w-4 h-4 mr-2" />
|
||||
{isListening ? 'Stop Listening' : 'Start Voice Demo'}
|
||||
</Button>
|
||||
|
||||
<div className="text-sm text-slate-600 dark:text-slate-400 space-y-1">
|
||||
<p className="font-medium">Try saying:</p>
|
||||
<Card className="bg-slate-50 dark:bg-slate-900 border-slate-200 dark:border-slate-700">
|
||||
<CardContent className="p-3 text-left space-y-1">
|
||||
<p>"Create a task to review budget"</p>
|
||||
<p>"Add $50 expense for groceries"</p>
|
||||
<p>"Show me today's schedule"</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features Section */}
|
||||
<section className="py-20 bg-white dark:bg-slate-800 mt-20">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl lg:text-4xl font-bold text-slate-900 dark:text-white mb-4">
|
||||
Complete Offline Voice AI
|
||||
</h2>
|
||||
<p className="text-xl text-slate-600 dark:text-slate-300 max-w-3xl mx-auto">
|
||||
Enterprise-grade task and financial management with local AI processing
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{/* Local AI Processing */}
|
||||
<Card className="bg-slate-50 dark:bg-slate-700 border-slate-200 dark:border-slate-600">
|
||||
<CardContent className="p-8">
|
||||
<div className="w-12 h-12 bg-primary-100 dark:bg-primary-900 rounded-xl flex items-center justify-center mb-4">
|
||||
<Brain className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-slate-900 dark:text-white mb-3">Local AI Models</h3>
|
||||
<p className="text-slate-600 dark:text-slate-300 mb-4">
|
||||
Speech-to-text and text-to-speech processing entirely offline. No data leaves your premises.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span className="text-sm text-slate-500 dark:text-slate-400">Base models included</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span className="text-sm text-slate-500 dark:text-slate-400">Swappable model architecture</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Voice Task Management */}
|
||||
<Card className="bg-slate-50 dark:bg-slate-700 border-slate-200 dark:border-slate-600">
|
||||
<CardContent className="p-8">
|
||||
<div className="w-12 h-12 bg-secondary-100 dark:bg-secondary-900 rounded-xl flex items-center justify-center mb-4">
|
||||
<Calendar className="w-6 h-6 text-secondary-600 dark:text-secondary-400" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-slate-900 dark:text-white mb-3">Voice Task Creation</h3>
|
||||
<p className="text-slate-600 dark:text-slate-300 mb-4">
|
||||
Create, update, and manage tasks using natural voice commands with intelligent parsing.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span className="text-sm text-slate-500 dark:text-slate-400">Natural language processing</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span className="text-sm text-slate-500 dark:text-slate-400">Smart date/time extraction</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Financial Voice Entry */}
|
||||
<Card className="bg-slate-50 dark:bg-slate-700 border-slate-200 dark:border-slate-600">
|
||||
<CardContent className="p-8">
|
||||
<div className="w-12 h-12 bg-emerald-100 dark:bg-emerald-900 rounded-xl flex items-center justify-center mb-4">
|
||||
<DollarSign className="w-6 h-6 text-emerald-600 dark:text-emerald-400" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-slate-900 dark:text-white mb-3">Voice Financial Tracking</h3>
|
||||
<p className="text-slate-600 dark:text-slate-300 mb-4">
|
||||
Record expenses and income through voice with automatic categorization and receipt parsing.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span className="text-sm text-slate-500 dark:text-slate-400">Auto-categorization</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span className="text-sm text-slate-500 dark:text-slate-400">Receipt OCR integration</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Privacy & Security */}
|
||||
<Card className="bg-slate-50 dark:bg-slate-700 border-slate-200 dark:border-slate-600">
|
||||
<CardContent className="p-8">
|
||||
<div className="w-12 h-12 bg-red-100 dark:bg-red-900 rounded-xl flex items-center justify-center mb-4">
|
||||
<Shield className="w-6 h-6 text-red-600 dark:text-red-400" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-slate-900 dark:text-white mb-3">Complete Privacy</h3>
|
||||
<p className="text-slate-600 dark:text-slate-300 mb-4">
|
||||
All voice processing happens locally. Zero cloud dependencies, complete data sovereignty.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span className="text-sm text-slate-500 dark:text-slate-400">On-premise deployment</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span className="text-sm text-slate-500 dark:text-slate-400">Encrypted local storage</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Real-time Processing */}
|
||||
<Card className="bg-slate-50 dark:bg-slate-700 border-slate-200 dark:border-slate-600">
|
||||
<CardContent className="p-8">
|
||||
<div className="w-12 h-12 bg-amber-100 dark:bg-amber-900 rounded-xl flex items-center justify-center mb-4">
|
||||
<Zap className="w-6 h-6 text-amber-600 dark:text-amber-400" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-slate-900 dark:text-white mb-3">Real-time Processing</h3>
|
||||
<p className="text-slate-600 dark:text-slate-300 mb-4">
|
||||
Instant voice-to-text conversion and immediate AI responses for seamless interaction.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span className="text-sm text-slate-500 dark:text-slate-400">Sub-second response time</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span className="text-sm text-slate-500 dark:text-slate-400">Continuous listening mode</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Multi-user Support */}
|
||||
<Card className="bg-slate-50 dark:bg-slate-700 border-slate-200 dark:border-slate-600">
|
||||
<CardContent className="p-8">
|
||||
<div className="w-12 h-12 bg-purple-100 dark:bg-purple-900 rounded-xl flex items-center justify-center mb-4">
|
||||
<Volume2 className="w-6 h-6 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-slate-900 dark:text-white mb-3">Multi-Tier Access</h3>
|
||||
<p className="text-slate-600 dark:text-slate-300 mb-4">
|
||||
Standard, Pro, and Admin user roles with progressive feature unlocking and onboarding.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span className="text-sm text-slate-500 dark:text-slate-400">Progressive onboarding</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span className="text-sm text-slate-500 dark:text-slate-400">Feature-based permissions</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
{/* Voice Status Indicator */}
|
||||
<VoiceIndicator />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
import { useState } from "react";
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { useTheme } from "@/context/ThemeContext";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { Mic, Moon, Sun, Loader2, AlertCircle, CheckCircle2 } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export default function LoginPage() {
|
||||
const [, setLocation] = useLocation();
|
||||
const { login, user } = useAuth();
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const { toast } = useToast();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
email: "",
|
||||
password: "",
|
||||
rememberMe: false,
|
||||
});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
// Redirect if already authenticated
|
||||
if (user) {
|
||||
setLocation("/dashboard");
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
await login(formData.email, formData.password);
|
||||
|
||||
toast({
|
||||
title: "Welcome back!",
|
||||
description: "You have successfully signed in.",
|
||||
});
|
||||
|
||||
setLocation("/dashboard");
|
||||
} catch (error: any) {
|
||||
console.error("Login error:", error);
|
||||
setError(error.message || "Failed to sign in. Please check your credentials.");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (field: string, value: string | boolean) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
if (error) setError(""); // Clear error on input change
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-violet-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900 flex items-center justify-center p-4">
|
||||
|
||||
{/* Header with Logo and Theme Toggle */}
|
||||
<div className="absolute top-6 left-6 right-6 flex items-center justify-between">
|
||||
<Link href="/" className="flex items-center space-x-3">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-primary to-secondary rounded-lg flex items-center justify-center">
|
||||
<Mic className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
<span className="text-xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
|
||||
TaskFin
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={toggleTheme}
|
||||
className="w-9 px-0"
|
||||
>
|
||||
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Main Login Card */}
|
||||
<Card className="w-full max-w-md shadow-2xl border-slate-200 dark:border-slate-700">
|
||||
<CardHeader className="text-center space-y-2">
|
||||
<CardTitle className="text-2xl font-bold">Welcome Back</CardTitle>
|
||||
<CardDescription>
|
||||
Sign in to your TaskFin account to access your voice-powered productivity tools
|
||||
</CardDescription>
|
||||
|
||||
{/* Features Badge */}
|
||||
<div className="flex items-center justify-center space-x-2 pt-2">
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
<CheckCircle2 className="w-3 h-3 mr-1" />
|
||||
Local Voice AI
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
<CheckCircle2 className="w-3 h-3 mr-1" />
|
||||
Complete Privacy
|
||||
</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Error Alert */}
|
||||
{error && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Email Field */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email Address</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleInputChange("email", e.target.value)}
|
||||
disabled={isSubmitting}
|
||||
required
|
||||
className="focus-ring"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Password Field */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="Enter your password"
|
||||
value={formData.password}
|
||||
onChange={(e) => handleInputChange("password", e.target.value)}
|
||||
disabled={isSubmitting}
|
||||
required
|
||||
className="focus-ring"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Remember Me & Forgot Password */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="rememberMe"
|
||||
checked={formData.rememberMe}
|
||||
onCheckedChange={(checked) => handleInputChange("rememberMe", !!checked)}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<Label
|
||||
htmlFor="rememberMe"
|
||||
className="text-sm font-normal cursor-pointer"
|
||||
>
|
||||
Remember me
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="link"
|
||||
className="px-0 h-auto text-sm"
|
||||
type="button"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
Forgot password?
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
<CardFooter className="flex flex-col space-y-4">
|
||||
{/* Submit Button */}
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
disabled={isSubmitting || !formData.email || !formData.password}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Signing In...
|
||||
</>
|
||||
) : (
|
||||
"Sign In"
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t border-slate-300 dark:border-slate-600" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-background px-2 text-muted-foreground">
|
||||
Or
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Register Link */}
|
||||
<div className="text-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Don't have an account?{" "}
|
||||
</span>
|
||||
<Button
|
||||
variant="link"
|
||||
className="px-0 h-auto text-sm font-medium"
|
||||
type="button"
|
||||
onClick={() => setLocation("/register")}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
Create Account
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Demo Credentials (for development) */}
|
||||
<div className="text-center">
|
||||
<div className="text-xs text-muted-foreground space-y-1">
|
||||
<p className="font-medium">Demo Accounts:</p>
|
||||
<p>Standard: user@taskfin.local / password</p>
|
||||
<p>Pro: pro@taskfin.local / password</p>
|
||||
<p>Admin: admin@taskfin.local / password</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</form>
|
||||
</Card>
|
||||
|
||||
{/* Features Overview */}
|
||||
<div className="absolute bottom-6 left-6 right-6 text-center">
|
||||
<div className="flex items-center justify-center space-x-6 text-xs text-muted-foreground">
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
||||
<span>Local STT/TTS Processing</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
||||
<span>Zero Cloud Dependencies</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
||||
<span>Complete Data Privacy</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="min-h-screen w-full flex items-center justify-center bg-gray-50">
|
||||
<Card className="w-full max-w-md mx-4">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex mb-4 gap-2">
|
||||
<AlertCircle className="h-8 w-8 text-red-500" />
|
||||
<h1 className="text-2xl font-bold text-gray-900">404 Page Not Found</h1>
|
||||
</div>
|
||||
|
||||
<p className="mt-4 text-sm text-gray-600">
|
||||
Did you forget to add the page to the router?
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user