Implement SecuPolicy MVP
Implement core features for SecuPolicy MVP, including document upload, automated task extraction, task overview, and export functionality.
This commit is contained in:
parent
48e6d3bdb0
commit
3d2df14d77
76
src/components/ExportOptions.tsx
Normal file
76
src/components/ExportOptions.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Task } from '@/utils/mockData';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { FileDown, Check } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface ExportOptionsProps {
|
||||
tasks: Task[];
|
||||
}
|
||||
|
||||
const ExportOptions = ({ tasks }: ExportOptionsProps) => {
|
||||
const [isExporting, setIsExporting] = useState(false);
|
||||
|
||||
const exportToCSV = () => {
|
||||
try {
|
||||
setIsExporting(true);
|
||||
|
||||
// Create CSV content
|
||||
const headers = ['Description', 'Department', 'Deadline', 'Priority', 'Status', 'Context'];
|
||||
const csvContent = [
|
||||
headers.join(','),
|
||||
...tasks.map(task => [
|
||||
`"${task.description.replace(/"/g, '""')}"`,
|
||||
`"${task.department.replace(/"/g, '""')}"`,
|
||||
task.deadline,
|
||||
task.priority,
|
||||
task.status,
|
||||
`"${task.context.replace(/"/g, '""')}"`
|
||||
].join(','))
|
||||
].join('\n');
|
||||
|
||||
// Create a Blob and download
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', `secupolicy_tasks_${new Date().toISOString().split('T')[0]}.csv`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
toast.success('CSV file exported successfully');
|
||||
} catch (error) {
|
||||
console.error('Export error:', error);
|
||||
toast.error('Failed to export CSV file');
|
||||
} finally {
|
||||
setIsExporting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-xs mx-auto">
|
||||
<Button
|
||||
onClick={exportToCSV}
|
||||
disabled={isExporting || tasks.length === 0}
|
||||
className="w-full rounded-full"
|
||||
variant="outline"
|
||||
>
|
||||
{isExporting ? (
|
||||
<>
|
||||
<Check className="w-4 h-4 mr-2 animate-pulse" />
|
||||
Exporting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FileDown className="w-4 h-4 mr-2" />
|
||||
Export to CSV
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExportOptions;
|
95
src/components/Header.tsx
Normal file
95
src/components/Header.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ShieldCheck, Menu, X } from 'lucide-react';
|
||||
|
||||
const Header = () => {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const location = useLocation();
|
||||
const isHome = location.pathname === '/';
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 10);
|
||||
};
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<header className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${isScrolled ? 'bg-white/80 dark:bg-gray-900/80 backdrop-blur-lg shadow-sm' : 'bg-transparent'}`}>
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-16 sm:h-20">
|
||||
{/* Logo */}
|
||||
<Link to="/" className="flex items-center space-x-2">
|
||||
<ShieldCheck className="w-8 h-8 text-primary" />
|
||||
<span className="text-xl font-semibold tracking-tight">SecuPolicy</span>
|
||||
</Link>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden md:flex items-center space-x-6">
|
||||
<Link to="/" className={`text-sm font-medium transition-colors hover:text-primary ${location.pathname === '/' ? 'text-primary' : 'text-foreground/80'}`}>
|
||||
Home
|
||||
</Link>
|
||||
<Link to="/dashboard" className={`text-sm font-medium transition-colors hover:text-primary ${location.pathname === '/dashboard' ? 'text-primary' : 'text-foreground/80'}`}>
|
||||
Dashboard
|
||||
</Link>
|
||||
<a href="#features" className="text-sm font-medium transition-colors hover:text-primary text-foreground/80">
|
||||
Features
|
||||
</a>
|
||||
<a href="#pricing" className="text-sm font-medium transition-colors hover:text-primary text-foreground/80">
|
||||
Pricing
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
{/* Desktop CTA */}
|
||||
<div className="hidden md:flex items-center space-x-4">
|
||||
{isHome ? (
|
||||
<Button asChild variant="default" size="sm" className="rounded-full px-6 shadow-md hover:shadow-lg transition-all">
|
||||
<Link to="/dashboard">Try for Free</Link>
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="ghost" size="sm" className="rounded-full">
|
||||
Need Help?
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<button onClick={() => setMobileMenuOpen(!mobileMenuOpen)} className="md:hidden text-foreground">
|
||||
{mobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
{mobileMenuOpen && (
|
||||
<div className="md:hidden bg-background border-t animate-fade-in">
|
||||
<div className="container mx-auto px-4 py-4 space-y-3">
|
||||
<Link to="/" className="block py-2 text-base font-medium" onClick={() => setMobileMenuOpen(false)}>
|
||||
Home
|
||||
</Link>
|
||||
<Link to="/dashboard" className="block py-2 text-base font-medium" onClick={() => setMobileMenuOpen(false)}>
|
||||
Dashboard
|
||||
</Link>
|
||||
<a href="#features" className="block py-2 text-base font-medium" onClick={() => setMobileMenuOpen(false)}>
|
||||
Features
|
||||
</a>
|
||||
<a href="#pricing" className="block py-2 text-base font-medium" onClick={() => setMobileMenuOpen(false)}>
|
||||
Pricing
|
||||
</a>
|
||||
{isHome && (
|
||||
<Button asChild className="w-full mt-3 rounded-full" size="sm">
|
||||
<Link to="/dashboard" onClick={() => setMobileMenuOpen(false)}>Try for Free</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
157
src/components/TaskCard.tsx
Normal file
157
src/components/TaskCard.tsx
Normal file
@ -0,0 +1,157 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Task } from '@/utils/mockData';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Calendar, AlertCircle, Users, CornerDownRight } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
interface TaskCardProps {
|
||||
task: Task;
|
||||
onStatusChange: (id: string, status: 'Open' | 'In Progress' | 'Completed') => void;
|
||||
onPriorityChange: (id: string, priority: 'High' | 'Medium' | 'Low') => void;
|
||||
}
|
||||
|
||||
const TaskCard = ({ task, onStatusChange, onPriorityChange }: TaskCardProps) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
const getPriorityColor = (priority: string) => {
|
||||
switch (priority) {
|
||||
case 'High':
|
||||
return 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300 border-red-200 dark:border-red-800/30';
|
||||
case 'Medium':
|
||||
return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300 border-yellow-200 dark:border-yellow-800/30';
|
||||
case 'Low':
|
||||
return 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300 border-green-200 dark:border-green-800/30';
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300 border-gray-200 dark:border-gray-800/30';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'Completed':
|
||||
return 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300 border-green-200 dark:border-green-800/30';
|
||||
case 'In Progress':
|
||||
return 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300 border-blue-200 dark:border-blue-800/30';
|
||||
case 'Open':
|
||||
return 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300 border-gray-200 dark:border-gray-800/30';
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300 border-gray-200 dark:border-gray-800/30';
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return new Intl.DateTimeFormat('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
}).format(date);
|
||||
};
|
||||
|
||||
const calculateDaysLeft = (dateString: string) => {
|
||||
const today = new Date();
|
||||
const deadline = new Date(dateString);
|
||||
const diffTime = deadline.getTime() - today.getTime();
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
return diffDays;
|
||||
};
|
||||
|
||||
const daysLeft = calculateDaysLeft(task.deadline);
|
||||
const isUrgent = daysLeft <= 14 && daysLeft > 0;
|
||||
const isOverdue = daysLeft < 0;
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
"glass-panel p-5 transition-all duration-300 cursor-pointer hover-lift",
|
||||
task.status === 'Completed' && "opacity-70",
|
||||
isExpanded && "ring-1 ring-primary/20"
|
||||
)}
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex justify-between items-start mb-3">
|
||||
<h3 className="font-medium text-base">{task.description}</h3>
|
||||
<Badge variant="outline" className={`ml-2 ${getPriorityColor(task.priority)}`}>
|
||||
{task.priority}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-3 text-sm text-muted-foreground mb-3">
|
||||
<div className="flex items-center">
|
||||
<Users className="w-4 h-4 mr-1" />
|
||||
{task.department}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Calendar className="w-4 h-4 mr-1" />
|
||||
<span className={cn(
|
||||
isUrgent && "text-amber-600 dark:text-amber-400",
|
||||
isOverdue && "text-red-600 dark:text-red-400"
|
||||
)}>
|
||||
{formatDate(task.deadline)}
|
||||
{isUrgent && ` (${daysLeft} days left)`}
|
||||
{isOverdue && ` (Overdue by ${Math.abs(daysLeft)} days)`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<Badge variant="outline" className={getStatusColor(task.status)}>
|
||||
{task.status}
|
||||
</Badge>
|
||||
|
||||
<div className="flex gap-2" onClick={(e) => e.stopPropagation()}>
|
||||
<Select
|
||||
defaultValue={task.priority}
|
||||
onValueChange={(value) => onPriorityChange(task.id, value as 'High' | 'Medium' | 'Low')}
|
||||
>
|
||||
<SelectTrigger className="w-[110px] h-8 text-xs">
|
||||
<SelectValue placeholder="Priority" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="High">High</SelectItem>
|
||||
<SelectItem value="Medium">Medium</SelectItem>
|
||||
<SelectItem value="Low">Low</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
defaultValue={task.status}
|
||||
onValueChange={(value) => onStatusChange(task.id, value as 'Open' | 'In Progress' | 'Completed')}
|
||||
>
|
||||
<SelectTrigger className="w-[110px] h-8 text-xs">
|
||||
<SelectValue placeholder="Status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Open">Open</SelectItem>
|
||||
<SelectItem value="In Progress">In Progress</SelectItem>
|
||||
<SelectItem value="Completed">Completed</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="mt-4 pt-4 border-t border-border animate-fade-up">
|
||||
<div className="flex items-start gap-2 text-sm text-muted-foreground">
|
||||
<CornerDownRight className="w-4 h-4 mt-0.5 shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium mb-1">Context from Policy:</p>
|
||||
<p>{task.context}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskCard;
|
410
src/components/TaskList.tsx
Normal file
410
src/components/TaskList.tsx
Normal file
@ -0,0 +1,410 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Task } from '@/utils/mockData';
|
||||
import TaskCard from './TaskCard';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Search, Filter, CheckCircle2, List, X } from 'lucide-react';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuTrigger
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface TaskListProps {
|
||||
tasks: Task[];
|
||||
onTaskUpdate: (updatedTasks: Task[]) => void;
|
||||
}
|
||||
|
||||
const TaskList = ({ tasks, onTaskUpdate }: TaskListProps) => {
|
||||
const [filteredTasks, setFilteredTasks] = useState<Task[]>(tasks);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [sortBy, setSortBy] = useState('deadline');
|
||||
const [filterPriority, setFilterPriority] = useState<string[]>([]);
|
||||
const [filterStatus, setFilterStatus] = useState<string[]>([]);
|
||||
const [filterDepartment, setFilterDepartment] = useState<string[]>([]);
|
||||
const [isFiltering, setIsFiltering] = useState(false);
|
||||
|
||||
const handleStatusChange = (id: string, status: 'Open' | 'In Progress' | 'Completed') => {
|
||||
const updatedTasks = tasks.map(task =>
|
||||
task.id === id ? { ...task, status } : task
|
||||
);
|
||||
onTaskUpdate(updatedTasks);
|
||||
};
|
||||
|
||||
const handlePriorityChange = (id: string, priority: 'High' | 'Medium' | 'Low') => {
|
||||
const updatedTasks = tasks.map(task =>
|
||||
task.id === id ? { ...task, priority } : task
|
||||
);
|
||||
onTaskUpdate(updatedTasks);
|
||||
};
|
||||
|
||||
const uniqueDepartments = [...new Set(tasks.map(task => task.department))];
|
||||
|
||||
const sortTasks = (tasksToSort: Task[]) => {
|
||||
return [...tasksToSort].sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case 'deadline':
|
||||
return new Date(a.deadline).getTime() - new Date(b.deadline).getTime();
|
||||
case 'priority':
|
||||
const priorityOrder = { High: 0, Medium: 1, Low: 2 };
|
||||
return priorityOrder[a.priority] - priorityOrder[b.priority];
|
||||
case 'department':
|
||||
return a.department.localeCompare(b.department);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const filterTasks = () => {
|
||||
let result = tasks;
|
||||
|
||||
// Search filter
|
||||
if (searchQuery) {
|
||||
result = result.filter(task =>
|
||||
task.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
task.department.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
task.context.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
// Priority filter
|
||||
if (filterPriority.length > 0) {
|
||||
result = result.filter(task => filterPriority.includes(task.priority));
|
||||
}
|
||||
|
||||
// Status filter
|
||||
if (filterStatus.length > 0) {
|
||||
result = result.filter(task => filterStatus.includes(task.status));
|
||||
}
|
||||
|
||||
// Department filter
|
||||
if (filterDepartment.length > 0) {
|
||||
result = result.filter(task => filterDepartment.includes(task.department));
|
||||
}
|
||||
|
||||
// Sort the filtered results
|
||||
return sortTasks(result);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setFilteredTasks(filterTasks());
|
||||
setIsFiltering(
|
||||
searchQuery !== '' ||
|
||||
filterPriority.length > 0 ||
|
||||
filterStatus.length > 0 ||
|
||||
filterDepartment.length > 0
|
||||
);
|
||||
}, [tasks, searchQuery, sortBy, filterPriority, filterStatus, filterDepartment]);
|
||||
|
||||
const clearFilters = () => {
|
||||
setSearchQuery('');
|
||||
setFilterPriority([]);
|
||||
setFilterStatus([]);
|
||||
setFilterDepartment([]);
|
||||
};
|
||||
|
||||
const completedTasksCount = tasks.filter(task => task.status === 'Completed').length;
|
||||
const taskCompletion = Math.round((completedTasksCount / tasks.length) * 100) || 0;
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto">
|
||||
<div className="flex flex-col mb-8">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center justify-between mb-4 gap-4">
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold">Tasks</h2>
|
||||
<p className="text-muted-foreground">
|
||||
{tasks.length} tasks extracted, {completedTasksCount} completed ({taskCompletion}%)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<Button variant="outline" className="rounded-full" onClick={clearFilters}
|
||||
disabled={!isFiltering}>
|
||||
<CheckCircle2 className="w-4 h-4 mr-2" />
|
||||
View All Tasks
|
||||
</Button>
|
||||
|
||||
<Select value={sortBy} onValueChange={setSortBy}>
|
||||
<SelectTrigger className="w-[180px] rounded-full">
|
||||
<List className="w-4 h-4 mr-2" />
|
||||
<SelectValue placeholder="Sort by" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="deadline">Sort by Deadline</SelectItem>
|
||||
<SelectItem value="priority">Sort by Priority</SelectItem>
|
||||
<SelectItem value="department">Sort by Department</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-3 mb-2">
|
||||
<div className="relative flex-grow">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
|
||||
<Input
|
||||
placeholder="Search tasks..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9 rounded-full"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
onClick={() => setSearchQuery('')}
|
||||
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="rounded-full">
|
||||
<Filter className="w-4 h-4 mr-2" />
|
||||
Priority
|
||||
{filterPriority.length > 0 && (
|
||||
<Badge className="ml-2 bg-primary/10 text-primary border-none">
|
||||
{filterPriority.length}
|
||||
</Badge>
|
||||
)}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-40">
|
||||
<DropdownMenuCheckboxItem
|
||||
checked={filterPriority.includes('High')}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setFilterPriority([...filterPriority, 'High']);
|
||||
} else {
|
||||
setFilterPriority(filterPriority.filter(p => p !== 'High'));
|
||||
}
|
||||
}}
|
||||
>
|
||||
High
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
checked={filterPriority.includes('Medium')}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setFilterPriority([...filterPriority, 'Medium']);
|
||||
} else {
|
||||
setFilterPriority(filterPriority.filter(p => p !== 'Medium'));
|
||||
}
|
||||
}}
|
||||
>
|
||||
Medium
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
checked={filterPriority.includes('Low')}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setFilterPriority([...filterPriority, 'Low']);
|
||||
} else {
|
||||
setFilterPriority(filterPriority.filter(p => p !== 'Low'));
|
||||
}
|
||||
}}
|
||||
>
|
||||
Low
|
||||
</DropdownMenuCheckboxItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="rounded-full">
|
||||
<Filter className="w-4 h-4 mr-2" />
|
||||
Status
|
||||
{filterStatus.length > 0 && (
|
||||
<Badge className="ml-2 bg-primary/10 text-primary border-none">
|
||||
{filterStatus.length}
|
||||
</Badge>
|
||||
)}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-40">
|
||||
<DropdownMenuCheckboxItem
|
||||
checked={filterStatus.includes('Open')}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setFilterStatus([...filterStatus, 'Open']);
|
||||
} else {
|
||||
setFilterStatus(filterStatus.filter(s => s !== 'Open'));
|
||||
}
|
||||
}}
|
||||
>
|
||||
Open
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
checked={filterStatus.includes('In Progress')}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setFilterStatus([...filterStatus, 'In Progress']);
|
||||
} else {
|
||||
setFilterStatus(filterStatus.filter(s => s !== 'In Progress'));
|
||||
}
|
||||
}}
|
||||
>
|
||||
In Progress
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
checked={filterStatus.includes('Completed')}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setFilterStatus([...filterStatus, 'Completed']);
|
||||
} else {
|
||||
setFilterStatus(filterStatus.filter(s => s !== 'Completed'));
|
||||
}
|
||||
}}
|
||||
>
|
||||
Completed
|
||||
</DropdownMenuCheckboxItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="rounded-full">
|
||||
<Filter className="w-4 h-4 mr-2" />
|
||||
Department
|
||||
{filterDepartment.length > 0 && (
|
||||
<Badge className="ml-2 bg-primary/10 text-primary border-none">
|
||||
{filterDepartment.length}
|
||||
</Badge>
|
||||
)}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-56">
|
||||
{uniqueDepartments.map(dept => (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={dept}
|
||||
checked={filterDepartment.includes(dept)}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setFilterDepartment([...filterDepartment, dept]);
|
||||
} else {
|
||||
setFilterDepartment(filterDepartment.filter(d => d !== dept));
|
||||
}
|
||||
}}
|
||||
>
|
||||
{dept}
|
||||
</DropdownMenuCheckboxItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isFiltering && (
|
||||
<div className="flex flex-wrap gap-2 my-3">
|
||||
{filterPriority.map(priority => (
|
||||
<Badge
|
||||
key={`priority-${priority}`}
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"rounded-full px-3 py-1 cursor-pointer hover:bg-background",
|
||||
priority === 'High' && "bg-red-100/50 dark:bg-red-900/20",
|
||||
priority === 'Medium' && "bg-yellow-100/50 dark:bg-yellow-900/20",
|
||||
priority === 'Low' && "bg-green-100/50 dark:bg-green-900/20"
|
||||
)}
|
||||
onClick={() => {
|
||||
setFilterPriority(filterPriority.filter(p => p !== priority));
|
||||
}}
|
||||
>
|
||||
{priority} <X className="ml-1 w-3 h-3" />
|
||||
</Badge>
|
||||
))}
|
||||
|
||||
{filterStatus.map(status => (
|
||||
<Badge
|
||||
key={`status-${status}`}
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"rounded-full px-3 py-1 cursor-pointer hover:bg-background",
|
||||
status === 'Completed' && "bg-green-100/50 dark:bg-green-900/20",
|
||||
status === 'In Progress' && "bg-blue-100/50 dark:bg-blue-900/20"
|
||||
)}
|
||||
onClick={() => {
|
||||
setFilterStatus(filterStatus.filter(s => s !== status));
|
||||
}}
|
||||
>
|
||||
{status} <X className="ml-1 w-3 h-3" />
|
||||
</Badge>
|
||||
))}
|
||||
|
||||
{filterDepartment.map(dept => (
|
||||
<Badge
|
||||
key={`dept-${dept}`}
|
||||
variant="outline"
|
||||
className="rounded-full px-3 py-1 cursor-pointer hover:bg-background"
|
||||
onClick={() => {
|
||||
setFilterDepartment(filterDepartment.filter(d => d !== dept));
|
||||
}}
|
||||
>
|
||||
{dept} <X className="ml-1 w-3 h-3" />
|
||||
</Badge>
|
||||
))}
|
||||
|
||||
{searchQuery && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="rounded-full px-3 py-1 cursor-pointer hover:bg-background"
|
||||
onClick={() => setSearchQuery('')}
|
||||
>
|
||||
Search: {searchQuery} <X className="ml-1 w-3 h-3" />
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-xs h-7 px-2 rounded-full"
|
||||
onClick={clearFilters}
|
||||
>
|
||||
Clear all
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{filteredTasks.length === 0 ? (
|
||||
<div className="p-12 text-center glass-panel">
|
||||
<p className="text-lg font-medium">No tasks match your filters</p>
|
||||
<p className="text-muted-foreground mt-2">Try adjusting your search or filters</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="mt-4 rounded-full"
|
||||
onClick={clearFilters}
|
||||
>
|
||||
Clear all filters
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{filteredTasks.map((task) => (
|
||||
<TaskCard
|
||||
key={task.id}
|
||||
task={task}
|
||||
onStatusChange={handleStatusChange}
|
||||
onPriorityChange={handlePriorityChange}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskList;
|
171
src/components/UploadSection.tsx
Normal file
171
src/components/UploadSection.tsx
Normal file
@ -0,0 +1,171 @@
|
||||
|
||||
import { useState, useRef } from 'react';
|
||||
import { Upload, FileType, FileUp, AlertCircle, Check } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface UploadSectionProps {
|
||||
onUploadComplete: (fileName: string) => void;
|
||||
}
|
||||
|
||||
const UploadSection = ({ onUploadComplete }: UploadSectionProps) => {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
};
|
||||
|
||||
const handleDragLeave = () => {
|
||||
setIsDragging(false);
|
||||
};
|
||||
|
||||
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(false);
|
||||
|
||||
const droppedFile = e.dataTransfer.files[0];
|
||||
if (isValidFile(droppedFile)) {
|
||||
setFile(droppedFile);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const selectedFile = e.target.files?.[0];
|
||||
if (selectedFile && isValidFile(selectedFile)) {
|
||||
setFile(selectedFile);
|
||||
}
|
||||
};
|
||||
|
||||
const isValidFile = (file: File) => {
|
||||
const validTypes = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
|
||||
if (!validTypes.includes(file.type)) {
|
||||
toast.error("Please upload a PDF or Word document");
|
||||
return false;
|
||||
}
|
||||
if (file.size > 10 * 1024 * 1024) { // 10MB limit
|
||||
toast.error("File size exceeds 10MB limit");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleUpload = async () => {
|
||||
if (!file) return;
|
||||
|
||||
setIsUploading(true);
|
||||
|
||||
// Simulate file upload with a delay
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
toast.success(`${file.name} uploaded successfully`);
|
||||
onUploadComplete(file.name);
|
||||
} catch (error) {
|
||||
toast.error("Upload failed. Please try again.");
|
||||
console.error("Upload error:", error);
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-3xl mx-auto">
|
||||
<div className="text-center mb-8">
|
||||
<h2 className="text-2xl font-semibold mb-2">Upload Your Policy Document</h2>
|
||||
<p className="text-muted-foreground">Upload a PDF or Word document to extract actionable tasks</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`
|
||||
glass-panel p-8
|
||||
border-2 border-dashed
|
||||
flex flex-col items-center justify-center
|
||||
transition-all duration-300
|
||||
${isDragging ? 'border-primary bg-primary/5' : 'border-border'}
|
||||
${file ? 'border-green-500/30 bg-green-50/30 dark:bg-green-900/10' : ''}
|
||||
`}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
onChange={handleFileSelect}
|
||||
className="hidden"
|
||||
accept=".pdf,.doc,.docx"
|
||||
/>
|
||||
|
||||
{!file ? (
|
||||
<>
|
||||
<div className="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mb-4">
|
||||
<Upload className="w-8 h-8 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium mb-2">Drag and drop your file here</h3>
|
||||
<p className="text-sm text-muted-foreground mb-6 max-w-md text-center">
|
||||
Support for PDF and Word documents up to 10MB
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
variant="outline"
|
||||
className="rounded-full"
|
||||
>
|
||||
<FileUp className="w-4 h-4 mr-2" />
|
||||
Browse Files
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<div className="w-full">
|
||||
<div className="flex items-center justify-center mb-6">
|
||||
<div className="w-12 h-12 bg-green-100 dark:bg-green-900/20 rounded-full flex items-center justify-center">
|
||||
<FileType className="w-6 h-6 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center mb-6">
|
||||
<h3 className="text-lg font-medium mb-1 flex items-center justify-center">
|
||||
{file.name}
|
||||
<Check className="w-4 h-4 text-green-500 ml-2" />
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{(file.size / 1024 / 1024).toFixed(2)} MB
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center space-x-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="rounded-full"
|
||||
onClick={() => {
|
||||
setFile(null);
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
}}
|
||||
>
|
||||
Change
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleUpload}
|
||||
disabled={isUploading}
|
||||
size="sm"
|
||||
className="rounded-full min-w-24"
|
||||
>
|
||||
{isUploading ? 'Processing...' : 'Analyze Document'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 text-center text-sm text-muted-foreground flex items-center justify-center">
|
||||
<AlertCircle className="w-4 h-4 mr-2" />
|
||||
Your documents are processed securely and never stored permanently
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UploadSection;
|
131
src/index.css
131
src/index.css
@ -1,92 +1,71 @@
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--background: 210 40% 98%;
|
||||
--foreground: 222 47% 11%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--card-foreground: 222 47% 11%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--popover-foreground: 222 47% 11%;
|
||||
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary: 221 83% 53%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 210 40% 96%;
|
||||
--secondary-foreground: 222 47% 11%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--muted: 210 40% 96%;
|
||||
--muted-foreground: 215 16% 47%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--accent: 217 91% 60%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive: 0 84% 60%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
--border: 214 32% 91%;
|
||||
--input: 214 32% 91%;
|
||||
--ring: 221 83% 53%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
|
||||
--sidebar-background: 0 0% 98%;
|
||||
|
||||
--sidebar-foreground: 240 5.3% 26.1%;
|
||||
|
||||
--sidebar-primary: 240 5.9% 10%;
|
||||
|
||||
--sidebar-primary-foreground: 0 0% 98%;
|
||||
|
||||
--sidebar-accent: 240 4.8% 95.9%;
|
||||
|
||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||
|
||||
--sidebar-border: 220 13% 91%;
|
||||
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
--radius: 0.75rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--background: 222 47% 11%;
|
||||
--foreground: 210 40% 98%;
|
||||
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card: 222 47% 11%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover: 222 47% 11%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--primary: 217 91% 60%;
|
||||
--primary-foreground: 222 47% 11%;
|
||||
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary: 217 33% 17%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--muted: 217 33% 17%;
|
||||
--muted-foreground: 215 20% 65%;
|
||||
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent: 217 91% 60%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive: 0 63% 31%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
--sidebar-background: 240 5.9% 10%;
|
||||
--sidebar-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-primary: 224.3 76.3% 48%;
|
||||
--sidebar-primary-foreground: 0 0% 100%;
|
||||
--sidebar-accent: 240 3.7% 15.9%;
|
||||
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-border: 240 3.7% 15.9%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
--border: 217 33% 17%;
|
||||
--input: 217 33% 17%;
|
||||
--ring: 224 76% 48%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,6 +75,48 @@
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
@apply bg-background text-foreground font-sans;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.glass-panel {
|
||||
@apply bg-white/70 dark:bg-black/40 backdrop-blur-lg rounded-xl border border-white/20 dark:border-white/10 shadow-lg;
|
||||
}
|
||||
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.hover-lift {
|
||||
@apply transition-all duration-300 hover:-translate-y-1 hover:shadow-lg;
|
||||
}
|
||||
}
|
||||
|
||||
::selection {
|
||||
@apply bg-primary/20;
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-primary/10 rounded-full hover:bg-primary/20 transition-colors;
|
||||
}
|
||||
|
||||
/* Smooth scrolling */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Focus ring styling */
|
||||
*:focus-visible {
|
||||
@apply outline-none ring-2 ring-primary/30 ring-offset-2 ring-offset-background transition-all;
|
||||
}
|
||||
|
169
src/pages/Dashboard.tsx
Normal file
169
src/pages/Dashboard.tsx
Normal file
@ -0,0 +1,169 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import Header from '@/components/Header';
|
||||
import UploadSection from '@/components/UploadSection';
|
||||
import TaskList from '@/components/TaskList';
|
||||
import ExportOptions from '@/components/ExportOptions';
|
||||
import { Task, generateTasks } from '@/utils/mockData';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const Dashboard = () => {
|
||||
const [uploadCompleted, setUploadCompleted] = useState(false);
|
||||
const [documentName, setDocumentName] = useState('');
|
||||
const [tasks, setTasks] = useState<Task[]>([]);
|
||||
|
||||
const handleUploadComplete = (fileName: string) => {
|
||||
// Simulate task extraction with a delay
|
||||
setTimeout(() => {
|
||||
const extractedTasks = generateTasks(fileName);
|
||||
setTasks(extractedTasks);
|
||||
setDocumentName(fileName);
|
||||
setUploadCompleted(true);
|
||||
|
||||
toast.success(`${extractedTasks.length} tasks extracted successfully`, {
|
||||
description: 'Tasks are now ready for review and prioritization'
|
||||
});
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const handleTaskUpdate = (updatedTasks: Task[]) => {
|
||||
setTasks(updatedTasks);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<Header />
|
||||
|
||||
<main className="flex-grow pt-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<Tabs defaultValue="upload" className="w-full">
|
||||
<div className="flex justify-center mb-8">
|
||||
<TabsList className="rounded-full h-12">
|
||||
<TabsTrigger value="upload" className="rounded-full px-6">
|
||||
Upload Document
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="tasks"
|
||||
className="rounded-full px-6"
|
||||
disabled={!uploadCompleted}
|
||||
onClick={() => {
|
||||
if (!uploadCompleted) {
|
||||
toast.error("Please upload a document first");
|
||||
}
|
||||
}}
|
||||
>
|
||||
Review Tasks
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="export"
|
||||
className="rounded-full px-6"
|
||||
disabled={!uploadCompleted}
|
||||
onClick={() => {
|
||||
if (!uploadCompleted) {
|
||||
toast.error("Please upload a document first");
|
||||
}
|
||||
}}
|
||||
>
|
||||
Export
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
<TabsContent value="upload" className="mt-0 focus-visible:outline-none focus-visible:ring-0">
|
||||
<UploadSection onUploadComplete={handleUploadComplete} />
|
||||
|
||||
{uploadCompleted && (
|
||||
<div className="flex justify-center mt-6">
|
||||
<div className="glass-panel px-4 py-2 text-sm flex items-center">
|
||||
<AlertCircle className="w-4 h-4 mr-2 text-green-500" />
|
||||
Document processed successfully! Continue to Review Tasks.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="tasks" className="mt-0 focus-visible:outline-none focus-visible:ring-0">
|
||||
{uploadCompleted ? (
|
||||
<>
|
||||
<div className="mb-8 text-center">
|
||||
<div className="mb-2 text-sm font-medium text-muted-foreground">
|
||||
Extracted tasks from
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold">{documentName}</h3>
|
||||
</div>
|
||||
<TaskList tasks={tasks} onTaskUpdate={handleTaskUpdate} />
|
||||
</>
|
||||
) : (
|
||||
<div className="text-center p-12">
|
||||
<p className="text-muted-foreground">Please upload a document first</p>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="export" className="mt-0 focus-visible:outline-none focus-visible:ring-0">
|
||||
{uploadCompleted ? (
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<div className="mb-8">
|
||||
<h3 className="text-2xl font-semibold mb-2">Export Task List</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Download your tasks to share with your team or import into your project management tool
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="glass-panel p-8 mb-8">
|
||||
<div className="text-left mb-6">
|
||||
<h4 className="font-medium mb-1">Export Summary</h4>
|
||||
<ul className="text-sm text-muted-foreground space-y-1">
|
||||
<li>Document: {documentName}</li>
|
||||
<li>Total Tasks: {tasks.length}</li>
|
||||
<li>Open Tasks: {tasks.filter(t => t.status === 'Open').length}</li>
|
||||
<li>In Progress Tasks: {tasks.filter(t => t.status === 'In Progress').length}</li>
|
||||
<li>Completed Tasks: {tasks.filter(t => t.status === 'Completed').length}</li>
|
||||
<li>High Priority Tasks: {tasks.filter(t => t.priority === 'High').length}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ExportOptions tasks={tasks} />
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground mt-4">
|
||||
Need to make changes? Go back to the <TabsTrigger value="tasks" className="text-primary underline bg-transparent p-0 h-auto">Review Tasks</TabsTrigger> tab.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center p-12">
|
||||
<p className="text-muted-foreground">Please upload a document first</p>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer className="bg-muted/30 py-6">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex flex-col sm:flex-row justify-between items-center gap-4">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
© 2024 SecuPolicy. All rights reserved.
|
||||
</div>
|
||||
<div className="flex space-x-6">
|
||||
<a href="#" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
|
||||
Privacy Policy
|
||||
</a>
|
||||
<a href="#" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
|
||||
Terms of Service
|
||||
</a>
|
||||
<a href="#" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
|
||||
Contact
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
@ -1,12 +1,567 @@
|
||||
// Update this page (the content is just a fallback if you fail to update the page)
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
ShieldCheck,
|
||||
FileText,
|
||||
ClipboardList,
|
||||
Download,
|
||||
CheckCircle2,
|
||||
ArrowRight,
|
||||
ChevronRight,
|
||||
CheckCircle,
|
||||
X
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import Header from '@/components/Header';
|
||||
|
||||
const Index = () => {
|
||||
const [isVisible, setIsVisible] = useState<{ [key: string]: boolean }>({});
|
||||
const refs = {
|
||||
hero: useRef<HTMLDivElement>(null),
|
||||
features: useRef<HTMLDivElement>(null),
|
||||
howItWorks: useRef<HTMLDivElement>(null),
|
||||
pricing: useRef<HTMLDivElement>(null),
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const observerOptions = {
|
||||
root: null,
|
||||
rootMargin: '0px',
|
||||
threshold: 0.1,
|
||||
};
|
||||
|
||||
const observerCallback = (entries: IntersectionObserverEntry[]) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.target.id) {
|
||||
setIsVisible(prev => ({
|
||||
...prev,
|
||||
[entry.target.id]: entry.isIntersecting,
|
||||
}));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(observerCallback, observerOptions);
|
||||
|
||||
Object.values(refs).forEach(ref => {
|
||||
if (ref.current) {
|
||||
observer.observe(ref.current);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: <FileText className="w-6 h-6 text-primary" />,
|
||||
title: 'Document Upload',
|
||||
description: 'Upload your policy documents in PDF or DOCX format for instant processing.'
|
||||
},
|
||||
{
|
||||
icon: <ClipboardList className="w-6 h-6 text-primary" />,
|
||||
title: 'Task Extraction',
|
||||
description: 'Our smart algorithm identifies and extracts actionable tasks from complex documents.'
|
||||
},
|
||||
{
|
||||
icon: <CheckCircle2 className="w-6 h-6 text-primary" />,
|
||||
title: 'Task Prioritization',
|
||||
description: 'Review, edit, and prioritize extracted tasks to create an effective action plan.'
|
||||
},
|
||||
{
|
||||
icon: <Download className="w-6 h-6 text-primary" />,
|
||||
title: 'Export for Implementation',
|
||||
description: 'Export tasks to CSV for easy integration with your team\'s workflow tools.'
|
||||
}
|
||||
];
|
||||
|
||||
const steps = [
|
||||
{
|
||||
number: '01',
|
||||
title: 'Upload',
|
||||
description: 'Upload your IT security or compliance policy document in PDF or Word format.'
|
||||
},
|
||||
{
|
||||
number: '02',
|
||||
title: 'Analyze',
|
||||
description: 'Our system scans the document to extract actionable tasks, deadlines, and assigned roles.'
|
||||
},
|
||||
{
|
||||
number: '03',
|
||||
title: 'Review & Prioritize',
|
||||
description: 'Review the extracted tasks, make edits if needed, and assign priorities.'
|
||||
},
|
||||
{
|
||||
number: '04',
|
||||
title: 'Export',
|
||||
description: 'Export your tasks as CSV to share with your team or import into your task management system.'
|
||||
}
|
||||
];
|
||||
|
||||
const plans = [
|
||||
{
|
||||
name: 'Free',
|
||||
price: '$0',
|
||||
description: 'For individuals and small teams',
|
||||
features: [
|
||||
'3 document uploads per month',
|
||||
'Basic task extraction',
|
||||
'CSV export',
|
||||
'Standard support'
|
||||
],
|
||||
limitations: [
|
||||
'No document storage',
|
||||
'No team collaboration',
|
||||
'No task history'
|
||||
],
|
||||
cta: 'Get Started',
|
||||
highlight: false
|
||||
},
|
||||
{
|
||||
name: 'Pro',
|
||||
price: '$19',
|
||||
period: '/month',
|
||||
description: 'For security professionals',
|
||||
features: [
|
||||
'Unlimited document uploads',
|
||||
'Advanced task extraction',
|
||||
'CSV & PDF exports',
|
||||
'Document storage',
|
||||
'Priority support',
|
||||
'Task history & tracking'
|
||||
],
|
||||
limitations: [],
|
||||
cta: 'Start Free Trial',
|
||||
highlight: true
|
||||
},
|
||||
{
|
||||
name: 'Enterprise',
|
||||
price: 'Custom',
|
||||
description: 'For organizations with advanced needs',
|
||||
features: [
|
||||
'Everything in Pro',
|
||||
'API access',
|
||||
'Custom integrations',
|
||||
'Dedicated support',
|
||||
'SSO authentication',
|
||||
'Compliance reporting'
|
||||
],
|
||||
limitations: [],
|
||||
cta: 'Contact Sales',
|
||||
highlight: false
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-100">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold mb-4">Welcome to Your Blank App</h1>
|
||||
<p className="text-xl text-gray-600">Start building your amazing project here!</p>
|
||||
</div>
|
||||
<div className="min-h-screen">
|
||||
<Header />
|
||||
|
||||
{/* Hero Section */}
|
||||
<section
|
||||
ref={refs.hero}
|
||||
id="hero"
|
||||
className={`pt-24 pb-16 md:pt-32 md:pb-24 transition-opacity duration-1000 ${isVisible.hero ? 'opacity-100' : 'opacity-0'}`}
|
||||
>
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="mb-6 px-4 py-1 rounded-full backdrop-blur-sm bg-background/30 shadow-sm animate-fade-in"
|
||||
>
|
||||
Streamline Your Security Compliance
|
||||
</Badge>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold mb-6 tracking-tight text-balance animate-fade-up">
|
||||
Turn Policy Documents Into <span className="text-primary">Actionable Tasks</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-muted-foreground mb-10 max-w-2xl mx-auto text-balance animate-fade-up" style={{animationDelay: '100ms'}}>
|
||||
SecuPolicy extracts actionable tasks from IT security and compliance policies,
|
||||
helping your team implement requirements quickly and accurately.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row justify-center gap-4 animate-fade-up" style={{animationDelay: '200ms'}}>
|
||||
<Button asChild size="lg" className="rounded-full">
|
||||
<Link to="/dashboard">Try SecuPolicy Free</Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" size="lg" className="rounded-full">
|
||||
<a href="#howItWorks">How It Works <ChevronRight className="ml-1 w-4 h-4" /></a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Illustration */}
|
||||
<section className="py-12 bg-gradient-to-b from-transparent to-muted/30">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="glass-panel max-w-5xl mx-auto shadow-xl overflow-hidden rounded-3xl animate-scale-in">
|
||||
<div className="relative">
|
||||
<div className="px-6 py-4 border-b border-border flex justify-between items-center bg-muted/20">
|
||||
<div className="flex space-x-2">
|
||||
<div className="w-3 h-3 rounded-full bg-red-400"></div>
|
||||
<div className="w-3 h-3 rounded-full bg-yellow-400"></div>
|
||||
<div className="w-3 h-3 rounded-full bg-green-400"></div>
|
||||
</div>
|
||||
<div className="text-sm font-medium">SecuPolicy Dashboard</div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div className="aspect-[16/9] bg-gradient-to-br from-background to-muted overflow-hidden">
|
||||
<div className="p-8 grid grid-cols-1 md:grid-cols-2 gap-6 h-full">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="glass-panel p-4 shadow-sm">
|
||||
<h3 className="text-base font-medium mb-3">Task Overview</h3>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="bg-white/30 dark:bg-black/10 rounded-lg p-3">
|
||||
<div className="text-sm text-muted-foreground">Total Tasks</div>
|
||||
<div className="text-2xl font-semibold">24</div>
|
||||
</div>
|
||||
<div className="bg-white/30 dark:bg-black/10 rounded-lg p-3">
|
||||
<div className="text-sm text-muted-foreground">High Priority</div>
|
||||
<div className="text-2xl font-semibold">8</div>
|
||||
</div>
|
||||
<div className="bg-white/30 dark:bg-black/10 rounded-lg p-3">
|
||||
<div className="text-sm text-muted-foreground">In Progress</div>
|
||||
<div className="text-2xl font-semibold">10</div>
|
||||
</div>
|
||||
<div className="bg-white/30 dark:bg-black/10 rounded-lg p-3">
|
||||
<div className="text-sm text-muted-foreground">Completed</div>
|
||||
<div className="text-2xl font-semibold">6</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="glass-panel p-4 shadow-sm flex-grow">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<h3 className="text-base font-medium">Recent Uploads</h3>
|
||||
<Button variant="ghost" size="sm" className="h-8">
|
||||
View All
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="bg-white/30 dark:bg-black/10 rounded-lg p-3 flex items-center">
|
||||
<FileText className="w-5 h-5 text-primary mr-3" />
|
||||
<div>
|
||||
<div className="text-sm font-medium">GDPR_Compliance.pdf</div>
|
||||
<div className="text-xs text-muted-foreground">7 tasks extracted</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/30 dark:bg-black/10 rounded-lg p-3 flex items-center">
|
||||
<FileText className="w-5 h-5 text-primary mr-3" />
|
||||
<div>
|
||||
<div className="text-sm font-medium">HIPAA_Security.docx</div>
|
||||
<div className="text-xs text-muted-foreground">12 tasks extracted</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/30 dark:bg-black/10 rounded-lg p-3 flex items-center">
|
||||
<FileText className="w-5 h-5 text-primary mr-3" />
|
||||
<div>
|
||||
<div className="text-sm font-medium">ISO27001_Controls.pdf</div>
|
||||
<div className="text-xs text-muted-foreground">5 tasks extracted</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="glass-panel p-4 shadow-sm">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<h3 className="text-base font-medium">High Priority Tasks</h3>
|
||||
<Button variant="ghost" size="sm" className="h-8">
|
||||
Filter
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="bg-white/30 dark:bg-black/10 rounded-lg p-3">
|
||||
<div className="flex justify-between mb-1">
|
||||
<div className="text-sm font-medium">Implement MFA for all employees</div>
|
||||
<Badge variant="outline" className="text-xs bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300">High</Badge>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground flex items-center justify-between">
|
||||
<span>IT Security</span>
|
||||
<span>Due: Sept 30, 2024</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/30 dark:bg-black/10 rounded-lg p-3">
|
||||
<div className="flex justify-between mb-1">
|
||||
<div className="text-sm font-medium">Update privacy policies</div>
|
||||
<Badge variant="outline" className="text-xs bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300">High</Badge>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground flex items-center justify-between">
|
||||
<span>Legal Department</span>
|
||||
<span>Due: Aug 15, 2024</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/30 dark:bg-black/10 rounded-lg p-3">
|
||||
<div className="flex justify-between mb-1">
|
||||
<div className="text-sm font-medium">Implement data breach notification</div>
|
||||
<Badge variant="outline" className="text-xs bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300">High</Badge>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground flex items-center justify-between">
|
||||
<span>Security Team</span>
|
||||
<span>Due: Oct 20, 2024</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/30 dark:bg-black/10 rounded-lg p-3">
|
||||
<div className="flex justify-between mb-1">
|
||||
<div className="text-sm font-medium">Conduct annual risk assessment</div>
|
||||
<Badge variant="outline" className="text-xs bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300">High</Badge>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground flex items-center justify-between">
|
||||
<span>Compliance Officer</span>
|
||||
<span>Due: Dec 31, 2024</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features Section */}
|
||||
<section
|
||||
ref={refs.features}
|
||||
id="features"
|
||||
className={`py-16 md:py-24 transition-opacity duration-1000 ${isVisible.features ? 'opacity-100' : 'opacity-0'}`}
|
||||
>
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-3xl mx-auto text-center mb-16">
|
||||
<Badge variant="outline" className="mb-4 px-3 py-1 rounded-full">Features</Badge>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-6">
|
||||
Streamline Security Policy Implementation
|
||||
</h2>
|
||||
<p className="text-lg text-muted-foreground">
|
||||
SecuPolicy extracts tasks from complex policy documents, making compliance manageable and actionable.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-5xl mx-auto">
|
||||
{features.map((feature, index) => (
|
||||
<div key={index} className="glass-panel p-6 hover-lift">
|
||||
<div className="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center mb-4">
|
||||
{feature.icon}
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">{feature.title}</h3>
|
||||
<p className="text-muted-foreground">{feature.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* How It Works Section */}
|
||||
<section
|
||||
ref={refs.howItWorks}
|
||||
id="howItWorks"
|
||||
className={`py-16 md:py-24 bg-muted/30 transition-opacity duration-1000 ${isVisible.howItWorks ? 'opacity-100' : 'opacity-0'}`}
|
||||
>
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-3xl mx-auto text-center mb-16">
|
||||
<Badge variant="outline" className="mb-4 px-3 py-1 rounded-full">Process</Badge>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-6">
|
||||
How SecuPolicy Works
|
||||
</h2>
|
||||
<p className="text-lg text-muted-foreground">
|
||||
Our simplified workflow transforms complex policy documents into clear, actionable tasks.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-5xl mx-auto grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{steps.map((step, index) => (
|
||||
<div key={index} className="relative">
|
||||
<div className="glass-panel p-6 hover-lift h-full flex flex-col">
|
||||
<div className="text-3xl font-bold text-primary/20 mb-4">
|
||||
{step.number}
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-3">{step.title}</h3>
|
||||
<p className="text-muted-foreground">{step.description}</p>
|
||||
|
||||
{index < steps.length - 1 && (
|
||||
<div className="hidden lg:block absolute top-1/2 -right-4 transform -translate-y-1/2 z-10">
|
||||
<ArrowRight className="w-6 h-6 text-muted-foreground/40" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-16 text-center">
|
||||
<Button asChild size="lg" className="rounded-full">
|
||||
<Link to="/dashboard">Try It Now</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Pricing Section */}
|
||||
<section
|
||||
ref={refs.pricing}
|
||||
id="pricing"
|
||||
className={`py-16 md:py-24 transition-opacity duration-1000 ${isVisible.pricing ? 'opacity-100' : 'opacity-0'}`}
|
||||
>
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-3xl mx-auto text-center mb-16">
|
||||
<Badge variant="outline" className="mb-4 px-3 py-1 rounded-full">Pricing</Badge>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-6">
|
||||
Choose the Right Plan for Your Team
|
||||
</h2>
|
||||
<p className="text-lg text-muted-foreground">
|
||||
From individuals to enterprise teams, we have options to fit your needs and budget.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-6xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{plans.map((plan, index) => (
|
||||
<div key={index} className={`
|
||||
relative glass-panel p-6 flex flex-col
|
||||
${plan.highlight ? 'ring-2 ring-primary/20 shadow-lg' : ''}
|
||||
`}>
|
||||
{plan.highlight && (
|
||||
<div className="absolute top-0 right-0 transform translate-x-4 -translate-y-4">
|
||||
<Badge className="bg-primary text-white px-3 py-1 rounded-full">
|
||||
Most Popular
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-6">
|
||||
<h3 className="text-xl font-semibold mb-1">{plan.name}</h3>
|
||||
<div className="flex items-end gap-1 mb-2">
|
||||
<span className="text-3xl font-bold">{plan.price}</span>
|
||||
{plan.period && <span className="text-muted-foreground">{plan.period}</span>}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{plan.description}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 mb-8 flex-grow">
|
||||
<ul className="space-y-2">
|
||||
{plan.features.map((feature, fIndex) => (
|
||||
<li key={fIndex} className="flex items-start">
|
||||
<CheckCircle className="w-5 h-5 text-green-500 shrink-0 mr-2" />
|
||||
<span className="text-sm">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{plan.limitations.length > 0 && (
|
||||
<ul className="space-y-2 pt-4 border-t border-border">
|
||||
{plan.limitations.map((limitation, lIndex) => (
|
||||
<li key={lIndex} className="flex items-start">
|
||||
<X className="w-5 h-5 text-muted-foreground shrink-0 mr-2" />
|
||||
<span className="text-sm text-muted-foreground">{limitation}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
asChild
|
||||
variant={plan.highlight ? "default" : "outline"}
|
||||
className={`rounded-full w-full ${plan.highlight ? "shadow-md" : ""}`}
|
||||
>
|
||||
<Link to={plan.name === 'Enterprise' ? "#" : "/dashboard"}>
|
||||
{plan.cta}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-16 md:py-24 bg-muted/30">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-4xl mx-auto glass-panel p-10 rounded-3xl text-center">
|
||||
<div className="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<ShieldCheck className="w-8 h-8 text-primary" />
|
||||
</div>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4">
|
||||
Ready to Streamline Your Compliance?
|
||||
</h2>
|
||||
<p className="text-lg text-muted-foreground mb-8 max-w-2xl mx-auto">
|
||||
Start extracting actionable tasks from your security and compliance policies today.
|
||||
No credit card required to get started.
|
||||
</p>
|
||||
<Button asChild size="lg" className="rounded-full px-8">
|
||||
<Link to="/dashboard">Try SecuPolicy Free</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="py-12 border-t">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 mb-8">
|
||||
<div className="md:col-span-2">
|
||||
<div className="flex items-center space-x-2 mb-4">
|
||||
<ShieldCheck className="w-6 h-6 text-primary" />
|
||||
<span className="text-xl font-semibold">SecuPolicy</span>
|
||||
</div>
|
||||
<p className="text-muted-foreground max-w-sm mb-4">
|
||||
Turning IT security and compliance policies into actionable tasks for your team.
|
||||
</p>
|
||||
<div className="flex space-x-4">
|
||||
<a href="#" className="text-muted-foreground hover:text-foreground transition-colors">
|
||||
Twitter
|
||||
</a>
|
||||
<a href="#" className="text-muted-foreground hover:text-foreground transition-colors">
|
||||
LinkedIn
|
||||
</a>
|
||||
<a href="#" className="text-muted-foreground hover:text-foreground transition-colors">
|
||||
GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="font-medium mb-4">Product</h3>
|
||||
<ul className="space-y-2">
|
||||
<li><a href="#features" className="text-muted-foreground hover:text-foreground transition-colors">Features</a></li>
|
||||
<li><a href="#pricing" className="text-muted-foreground hover:text-foreground transition-colors">Pricing</a></li>
|
||||
<li><a href="#" className="text-muted-foreground hover:text-foreground transition-colors">Roadmap</a></li>
|
||||
<li><a href="#" className="text-muted-foreground hover:text-foreground transition-colors">Changelog</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="font-medium mb-4">Company</h3>
|
||||
<ul className="space-y-2">
|
||||
<li><a href="#" className="text-muted-foreground hover:text-foreground transition-colors">About</a></li>
|
||||
<li><a href="#" className="text-muted-foreground hover:text-foreground transition-colors">Blog</a></li>
|
||||
<li><a href="#" className="text-muted-foreground hover:text-foreground transition-colors">Careers</a></li>
|
||||
<li><a href="#" className="text-muted-foreground hover:text-foreground transition-colors">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t pt-8 flex flex-col md:flex-row justify-between items-center">
|
||||
<div className="text-sm text-muted-foreground mb-4 md:mb-0">
|
||||
© 2024 SecuPolicy. All rights reserved.
|
||||
</div>
|
||||
<div className="flex space-x-6">
|
||||
<a href="#" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
|
||||
Privacy Policy
|
||||
</a>
|
||||
<a href="#" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
|
||||
Terms of Service
|
||||
</a>
|
||||
<a href="#" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
|
||||
Cookie Policy
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
import { useLocation, Link } from "react-router-dom";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { FileQuestion, Home } from "lucide-react";
|
||||
|
||||
const NotFound = () => {
|
||||
const location = useLocation();
|
||||
@ -12,13 +15,28 @@ const NotFound = () => {
|
||||
}, [location.pathname]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-100">
|
||||
<div className="text-center">
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="glass-panel p-12 max-w-md w-full text-center">
|
||||
<div className="w-20 h-20 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-6 animate-pulse-subtle">
|
||||
<FileQuestion className="w-10 h-10 text-primary" />
|
||||
</div>
|
||||
<h1 className="text-4xl font-bold mb-4">404</h1>
|
||||
<p className="text-xl text-gray-600 mb-4">Oops! Page not found</p>
|
||||
<a href="/" className="text-blue-500 hover:text-blue-700 underline">
|
||||
Return to Home
|
||||
</a>
|
||||
<p className="text-xl text-muted-foreground mb-8">
|
||||
The page you are looking for doesn't exist or has been moved.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Button asChild className="rounded-full">
|
||||
<Link to="/">
|
||||
<Home className="w-4 h-4 mr-2" />
|
||||
Return Home
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="rounded-full">
|
||||
<Link to="/dashboard">
|
||||
Go to Dashboard
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
294
src/utils/mockData.ts
Normal file
294
src/utils/mockData.ts
Normal file
@ -0,0 +1,294 @@
|
||||
|
||||
export interface Task {
|
||||
id: string;
|
||||
description: string;
|
||||
department: string;
|
||||
deadline: string;
|
||||
priority: 'High' | 'Medium' | 'Low';
|
||||
status: 'Open' | 'In Progress' | 'Completed';
|
||||
context: string;
|
||||
}
|
||||
|
||||
// Mock data for demonstration purposes
|
||||
export const generateTasks = (documentName: string): Task[] => {
|
||||
// Generate different tasks based on document type/name
|
||||
if (documentName.toLowerCase().includes('gdpr')) {
|
||||
return gdprTasks;
|
||||
} else if (documentName.toLowerCase().includes('hipaa')) {
|
||||
return hipaaTasks;
|
||||
} else if (documentName.toLowerCase().includes('iso')) {
|
||||
return isoTasks;
|
||||
} else if (documentName.toLowerCase().includes('soc')) {
|
||||
return socTasks;
|
||||
} else {
|
||||
// Generic security policy tasks
|
||||
return defaultTasks;
|
||||
}
|
||||
};
|
||||
|
||||
const gdprTasks: Task[] = [
|
||||
{
|
||||
id: '1',
|
||||
description: 'Implement data subject access request (DSAR) process',
|
||||
department: 'Data Privacy Team',
|
||||
deadline: '2024-09-30',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'Article 15 - Right of access by the data subject'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
description: 'Update privacy policies to be GDPR compliant',
|
||||
department: 'Legal Department',
|
||||
deadline: '2024-08-15',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'Article 13 - Information to be provided'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
description: 'Implement data breach notification procedures',
|
||||
department: 'Security Team',
|
||||
deadline: '2024-10-20',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'Article 33 - Notification of a personal data breach'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
description: 'Conduct GDPR awareness training for all employees',
|
||||
department: 'HR Department',
|
||||
deadline: '2024-11-30',
|
||||
priority: 'Medium',
|
||||
status: 'Open',
|
||||
context: 'Article 39 - Tasks of the data protection officer'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
description: 'Implement data minimization principles in all systems',
|
||||
department: 'Engineering Team',
|
||||
deadline: '2024-12-15',
|
||||
priority: 'Medium',
|
||||
status: 'Open',
|
||||
context: 'Article 5 - Principles relating to processing of personal data'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
description: 'Appoint a Data Protection Officer (DPO)',
|
||||
department: 'Executive Team',
|
||||
deadline: '2024-07-31',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'Article 37 - Designation of the data protection officer'
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
description: 'Create data processing agreement templates',
|
||||
department: 'Legal Department',
|
||||
deadline: '2024-09-15',
|
||||
priority: 'Medium',
|
||||
status: 'Open',
|
||||
context: 'Article 28 - Processor'
|
||||
}
|
||||
];
|
||||
|
||||
const hipaaTasks: Task[] = [
|
||||
{
|
||||
id: '1',
|
||||
description: 'Implement encryption for all PHI data at rest',
|
||||
department: 'IT Security',
|
||||
deadline: '2024-10-15',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'HIPAA Security Rule - Technical Safeguards'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
description: 'Conduct annual HIPAA risk assessment',
|
||||
department: 'Compliance Officer',
|
||||
deadline: '2024-12-31',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'HIPAA Security Rule - Administrative Safeguards'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
description: 'Update Business Associate Agreements (BAAs)',
|
||||
department: 'Legal Department',
|
||||
deadline: '2024-09-30',
|
||||
priority: 'Medium',
|
||||
status: 'Open',
|
||||
context: 'HIPAA Privacy Rule - Business Associate Contracts'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
description: 'Implement audit logging for all PHI access',
|
||||
department: 'IT Security',
|
||||
deadline: '2024-11-30',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'HIPAA Security Rule - Audit Controls'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
description: 'Create and document data backup procedures',
|
||||
department: 'IT Department',
|
||||
deadline: '2024-08-31',
|
||||
priority: 'Medium',
|
||||
status: 'Open',
|
||||
context: 'HIPAA Security Rule - Contingency Plan'
|
||||
}
|
||||
];
|
||||
|
||||
const isoTasks: Task[] = [
|
||||
{
|
||||
id: '1',
|
||||
description: 'Develop and document Information Security Management System (ISMS)',
|
||||
department: 'Security Team',
|
||||
deadline: '2024-11-30',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'ISO 27001 - Clause 4'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
description: 'Conduct risk assessment and treatment',
|
||||
department: 'Risk Management',
|
||||
deadline: '2024-10-15',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'ISO 27001 - Clause 6.1'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
description: 'Define security roles and responsibilities',
|
||||
department: 'HR Department',
|
||||
deadline: '2024-09-30',
|
||||
priority: 'Medium',
|
||||
status: 'Open',
|
||||
context: 'ISO 27001 - Clause 5.3'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
description: 'Establish incident management procedures',
|
||||
department: 'Security Team',
|
||||
deadline: '2024-12-15',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'ISO 27001 - Annex A.16'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
description: 'Implement access control policy',
|
||||
department: 'IT Department',
|
||||
deadline: '2024-10-31',
|
||||
priority: 'Medium',
|
||||
status: 'Open',
|
||||
context: 'ISO 27001 - Annex A.9'
|
||||
}
|
||||
];
|
||||
|
||||
const socTasks: Task[] = [
|
||||
{
|
||||
id: '1',
|
||||
description: 'Document system boundaries for SOC 2 audit',
|
||||
department: 'Compliance Team',
|
||||
deadline: '2024-09-30',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'SOC 2 - Common Criteria'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
description: 'Implement employee security awareness program',
|
||||
department: 'HR Department',
|
||||
deadline: '2024-10-31',
|
||||
priority: 'Medium',
|
||||
status: 'Open',
|
||||
context: 'SOC 2 - CC1.4'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
description: 'Develop change management procedures',
|
||||
department: 'IT Operations',
|
||||
deadline: '2024-11-15',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'SOC 2 - CC8.1'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
description: 'Implement monitoring of system anomalies',
|
||||
department: 'Security Team',
|
||||
deadline: '2024-12-15',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'SOC 2 - CC7.2'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
description: 'Document vendor management process',
|
||||
department: 'Procurement',
|
||||
deadline: '2024-10-15',
|
||||
priority: 'Medium',
|
||||
status: 'Open',
|
||||
context: 'SOC 2 - CC9.2'
|
||||
}
|
||||
];
|
||||
|
||||
const defaultTasks: Task[] = [
|
||||
{
|
||||
id: '1',
|
||||
description: 'Implement multi-factor authentication for all employees',
|
||||
department: 'IT Security',
|
||||
deadline: '2024-09-30',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'Section 3.2 - Access Control Policy'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
description: 'Update firewall rules to comply with new standards',
|
||||
department: 'Network Security',
|
||||
deadline: '2024-10-15',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'Section 4.1 - Network Security'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
description: 'Conduct quarterly security awareness training',
|
||||
department: 'HR Department',
|
||||
deadline: '2024-11-30',
|
||||
priority: 'Medium',
|
||||
status: 'Open',
|
||||
context: 'Section 7.3 - Security Awareness'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
description: 'Review and update incident response plan',
|
||||
department: 'Security Team',
|
||||
deadline: '2024-12-15',
|
||||
priority: 'Medium',
|
||||
status: 'Open',
|
||||
context: 'Section 9.2 - Incident Management'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
description: 'Perform vulnerability scanning on all production systems',
|
||||
department: 'Security Team',
|
||||
deadline: '2024-08-31',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'Section 5.4 - Vulnerability Management'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
description: 'Implement data loss prevention (DLP) solution',
|
||||
department: 'IT Security',
|
||||
deadline: '2024-10-31',
|
||||
priority: 'High',
|
||||
status: 'Open',
|
||||
context: 'Section 6.1 - Data Protection'
|
||||
}
|
||||
];
|
@ -1,3 +1,4 @@
|
||||
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
export default {
|
||||
@ -63,6 +64,9 @@ export default {
|
||||
ring: 'hsl(var(--sidebar-ring))'
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
@ -84,11 +88,31 @@ export default {
|
||||
to: {
|
||||
height: '0'
|
||||
}
|
||||
},
|
||||
'fade-in': {
|
||||
'0%': { opacity: '0' },
|
||||
'100%': { opacity: '1' }
|
||||
},
|
||||
'fade-up': {
|
||||
'0%': { opacity: '0', transform: 'translateY(10px)' },
|
||||
'100%': { opacity: '1', transform: 'translateY(0)' }
|
||||
},
|
||||
'pulse-subtle': {
|
||||
'0%, 100%': { opacity: '1' },
|
||||
'50%': { opacity: '0.8' }
|
||||
},
|
||||
'scale-in': {
|
||||
'0%': { transform: 'scale(0.9)', opacity: '0' },
|
||||
'100%': { transform: 'scale(1)', opacity: '1' }
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||
'accordion-up': 'accordion-up 0.2s ease-out'
|
||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||
'fade-in': 'fade-in 0.5s ease-out',
|
||||
'fade-up': 'fade-up 0.5s ease-out',
|
||||
'pulse-subtle': 'pulse-subtle 2s ease-in-out infinite',
|
||||
'scale-in': 'scale-in 0.2s ease-out'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user