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:
gpt-engineer-app[bot] 2025-03-09 11:16:09 +00:00
parent 48e6d3bdb0
commit 3d2df14d77
11 changed files with 2059 additions and 69 deletions

View 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
View 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
View 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
View 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;

View 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;

View File

@ -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
View 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;

View File

@ -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>
);
};

View File

@ -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
View 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'
}
];

View File

@ -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'
}
}
},