From 3d2df14d77c1718e1e84b1798e895b64b5a9782f Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 9 Mar 2025 11:16:09 +0000 Subject: [PATCH] Implement SecuPolicy MVP Implement core features for SecuPolicy MVP, including document upload, automated task extraction, task overview, and export functionality. --- src/components/ExportOptions.tsx | 76 +++++ src/components/Header.tsx | 95 ++++++ src/components/TaskCard.tsx | 157 +++++++++ src/components/TaskList.tsx | 410 ++++++++++++++++++++++ src/components/UploadSection.tsx | 171 ++++++++++ src/index.css | 131 ++++--- src/pages/Dashboard.tsx | 169 +++++++++ src/pages/Index.tsx | 567 ++++++++++++++++++++++++++++++- src/pages/NotFound.tsx | 32 +- src/utils/mockData.ts | 294 ++++++++++++++++ tailwind.config.ts | 26 +- 11 files changed, 2059 insertions(+), 69 deletions(-) create mode 100644 src/components/ExportOptions.tsx create mode 100644 src/components/Header.tsx create mode 100644 src/components/TaskCard.tsx create mode 100644 src/components/TaskList.tsx create mode 100644 src/components/UploadSection.tsx create mode 100644 src/pages/Dashboard.tsx create mode 100644 src/utils/mockData.ts diff --git a/src/components/ExportOptions.tsx b/src/components/ExportOptions.tsx new file mode 100644 index 0000000..7ff4ead --- /dev/null +++ b/src/components/ExportOptions.tsx @@ -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 ( +
+ +
+ ); +}; + +export default ExportOptions; diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..b4290ab --- /dev/null +++ b/src/components/Header.tsx @@ -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 ( +
+
+
+ {/* Logo */} + + + SecuPolicy + + + {/* Desktop Navigation */} + + + {/* Desktop CTA */} +
+ {isHome ? ( + + ) : ( + + )} +
+ + {/* Mobile Menu Button */} + +
+
+ + {/* Mobile Menu */} + {mobileMenuOpen && ( +
+
+ setMobileMenuOpen(false)}> + Home + + setMobileMenuOpen(false)}> + Dashboard + + setMobileMenuOpen(false)}> + Features + + setMobileMenuOpen(false)}> + Pricing + + {isHome && ( + + )} +
+
+ )} +
+ ); +}; + +export default Header; diff --git a/src/components/TaskCard.tsx b/src/components/TaskCard.tsx new file mode 100644 index 0000000..8eac699 --- /dev/null +++ b/src/components/TaskCard.tsx @@ -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 ( +
setIsExpanded(!isExpanded)} + > +
+
+

{task.description}

+ + {task.priority} + +
+ +
+
+ + {task.department} +
+
+ + + {formatDate(task.deadline)} + {isUrgent && ` (${daysLeft} days left)`} + {isOverdue && ` (Overdue by ${Math.abs(daysLeft)} days)`} + +
+
+ +
+ + {task.status} + + +
e.stopPropagation()}> + + + +
+
+ + {isExpanded && ( +
+
+ +
+

Context from Policy:

+

{task.context}

+
+
+
+ )} +
+
+ ); +}; + +export default TaskCard; diff --git a/src/components/TaskList.tsx b/src/components/TaskList.tsx new file mode 100644 index 0000000..4c8ecee --- /dev/null +++ b/src/components/TaskList.tsx @@ -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(tasks); + const [searchQuery, setSearchQuery] = useState(''); + const [sortBy, setSortBy] = useState('deadline'); + const [filterPriority, setFilterPriority] = useState([]); + const [filterStatus, setFilterStatus] = useState([]); + const [filterDepartment, setFilterDepartment] = useState([]); + 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 ( +
+
+
+
+

Tasks

+

+ {tasks.length} tasks extracted, {completedTasksCount} completed ({taskCompletion}%) +

+
+ +
+ + + +
+
+ +
+
+ + setSearchQuery(e.target.value)} + className="pl-9 rounded-full" + /> + {searchQuery && ( + + )} +
+ +
+ + + + + + { + if (checked) { + setFilterPriority([...filterPriority, 'High']); + } else { + setFilterPriority(filterPriority.filter(p => p !== 'High')); + } + }} + > + High + + { + if (checked) { + setFilterPriority([...filterPriority, 'Medium']); + } else { + setFilterPriority(filterPriority.filter(p => p !== 'Medium')); + } + }} + > + Medium + + { + if (checked) { + setFilterPriority([...filterPriority, 'Low']); + } else { + setFilterPriority(filterPriority.filter(p => p !== 'Low')); + } + }} + > + Low + + + + + + + + + + { + if (checked) { + setFilterStatus([...filterStatus, 'Open']); + } else { + setFilterStatus(filterStatus.filter(s => s !== 'Open')); + } + }} + > + Open + + { + if (checked) { + setFilterStatus([...filterStatus, 'In Progress']); + } else { + setFilterStatus(filterStatus.filter(s => s !== 'In Progress')); + } + }} + > + In Progress + + { + if (checked) { + setFilterStatus([...filterStatus, 'Completed']); + } else { + setFilterStatus(filterStatus.filter(s => s !== 'Completed')); + } + }} + > + Completed + + + + + + + + + + {uniqueDepartments.map(dept => ( + { + if (checked) { + setFilterDepartment([...filterDepartment, dept]); + } else { + setFilterDepartment(filterDepartment.filter(d => d !== dept)); + } + }} + > + {dept} + + ))} + + +
+
+ + {isFiltering && ( +
+ {filterPriority.map(priority => ( + { + setFilterPriority(filterPriority.filter(p => p !== priority)); + }} + > + {priority} + + ))} + + {filterStatus.map(status => ( + { + setFilterStatus(filterStatus.filter(s => s !== status)); + }} + > + {status} + + ))} + + {filterDepartment.map(dept => ( + { + setFilterDepartment(filterDepartment.filter(d => d !== dept)); + }} + > + {dept} + + ))} + + {searchQuery && ( + setSearchQuery('')} + > + Search: {searchQuery} + + )} + + +
+ )} +
+ + {filteredTasks.length === 0 ? ( +
+

No tasks match your filters

+

Try adjusting your search or filters

+ +
+ ) : ( +
+ {filteredTasks.map((task) => ( + + ))} +
+ )} +
+ ); +}; + +export default TaskList; diff --git a/src/components/UploadSection.tsx b/src/components/UploadSection.tsx new file mode 100644 index 0000000..0897f5e --- /dev/null +++ b/src/components/UploadSection.tsx @@ -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(null); + const [isUploading, setIsUploading] = useState(false); + const fileInputRef = useRef(null); + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(true); + }; + + const handleDragLeave = () => { + setIsDragging(false); + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(false); + + const droppedFile = e.dataTransfer.files[0]; + if (isValidFile(droppedFile)) { + setFile(droppedFile); + } + }; + + const handleFileSelect = (e: React.ChangeEvent) => { + 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 ( +
+
+

Upload Your Policy Document

+

Upload a PDF or Word document to extract actionable tasks

+
+ +
+ + + {!file ? ( + <> +
+ +
+

Drag and drop your file here

+

+ Support for PDF and Word documents up to 10MB +

+ + + ) : ( +
+
+
+ +
+
+
+

+ {file.name} + +

+

+ {(file.size / 1024 / 1024).toFixed(2)} MB +

+
+
+ + +
+
+ )} +
+ +
+ + Your documents are processed securely and never stored permanently +
+
+ ); +}; + +export default UploadSection; diff --git a/src/index.css b/src/index.css index 33fdf9d..b8ce308 100644 --- a/src/index.css +++ b/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; } -} \ No newline at end of file +} + +@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; +} diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx new file mode 100644 index 0000000..aaa9aa4 --- /dev/null +++ b/src/pages/Dashboard.tsx @@ -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([]); + + 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 ( +
+
+ +
+
+ +
+ + + Upload Document + + { + if (!uploadCompleted) { + toast.error("Please upload a document first"); + } + }} + > + Review Tasks + + { + if (!uploadCompleted) { + toast.error("Please upload a document first"); + } + }} + > + Export + + +
+ + + + + {uploadCompleted && ( +
+
+ + Document processed successfully! Continue to Review Tasks. +
+
+ )} +
+ + + {uploadCompleted ? ( + <> +
+
+ Extracted tasks from +
+

{documentName}

+
+ + + ) : ( +
+

Please upload a document first

+
+ )} +
+ + + {uploadCompleted ? ( +
+
+

Export Task List

+

+ Download your tasks to share with your team or import into your project management tool +

+
+ +
+
+

Export Summary

+
    +
  • Document: {documentName}
  • +
  • Total Tasks: {tasks.length}
  • +
  • Open Tasks: {tasks.filter(t => t.status === 'Open').length}
  • +
  • In Progress Tasks: {tasks.filter(t => t.status === 'In Progress').length}
  • +
  • Completed Tasks: {tasks.filter(t => t.status === 'Completed').length}
  • +
  • High Priority Tasks: {tasks.filter(t => t.priority === 'High').length}
  • +
+
+ + +
+ +

+ Need to make changes? Go back to the Review Tasks tab. +

+
+ ) : ( +
+

Please upload a document first

+
+ )} +
+
+
+
+ + +
+ ); +}; + +export default Dashboard; diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 52ea22c..24e0d14 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -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(null), + features: useRef(null), + howItWorks: useRef(null), + pricing: useRef(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: , + title: 'Document Upload', + description: 'Upload your policy documents in PDF or DOCX format for instant processing.' + }, + { + icon: , + title: 'Task Extraction', + description: 'Our smart algorithm identifies and extracts actionable tasks from complex documents.' + }, + { + icon: , + title: 'Task Prioritization', + description: 'Review, edit, and prioritize extracted tasks to create an effective action plan.' + }, + { + icon: , + 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 ( -
-
-

Welcome to Your Blank App

-

Start building your amazing project here!

-
+
+
+ + {/* Hero Section */} +
+
+
+ + Streamline Your Security Compliance + + +

+ Turn Policy Documents Into Actionable Tasks +

+ +

+ SecuPolicy extracts actionable tasks from IT security and compliance policies, + helping your team implement requirements quickly and accurately. +

+ +
+ + +
+
+
+
+ + {/* Illustration */} +
+
+
+
+
+
+
+
+
+
+
SecuPolicy Dashboard
+
+
+
+
+
+
+

Task Overview

+
+
+
Total Tasks
+
24
+
+
+
High Priority
+
8
+
+
+
In Progress
+
10
+
+
+
Completed
+
6
+
+
+
+
+
+

Recent Uploads

+ +
+
+
+ +
+
GDPR_Compliance.pdf
+
7 tasks extracted
+
+
+
+ +
+
HIPAA_Security.docx
+
12 tasks extracted
+
+
+
+ +
+
ISO27001_Controls.pdf
+
5 tasks extracted
+
+
+
+
+
+
+
+

High Priority Tasks

+ +
+
+
+
+
Implement MFA for all employees
+ High +
+
+ IT Security + Due: Sept 30, 2024 +
+
+
+
+
Update privacy policies
+ High +
+
+ Legal Department + Due: Aug 15, 2024 +
+
+
+
+
Implement data breach notification
+ High +
+
+ Security Team + Due: Oct 20, 2024 +
+
+
+
+
Conduct annual risk assessment
+ High +
+
+ Compliance Officer + Due: Dec 31, 2024 +
+
+
+
+
+
+
+
+
+
+ + {/* Features Section */} +
+
+
+ Features +

+ Streamline Security Policy Implementation +

+

+ SecuPolicy extracts tasks from complex policy documents, making compliance manageable and actionable. +

+
+ +
+ {features.map((feature, index) => ( +
+
+ {feature.icon} +
+

{feature.title}

+

{feature.description}

+
+ ))} +
+
+
+ + {/* How It Works Section */} +
+
+
+ Process +

+ How SecuPolicy Works +

+

+ Our simplified workflow transforms complex policy documents into clear, actionable tasks. +

+
+ +
+ {steps.map((step, index) => ( +
+
+
+ {step.number} +
+

{step.title}

+

{step.description}

+ + {index < steps.length - 1 && ( +
+ +
+ )} +
+
+ ))} +
+ +
+ +
+
+
+ + {/* Pricing Section */} +
+
+
+ Pricing +

+ Choose the Right Plan for Your Team +

+

+ From individuals to enterprise teams, we have options to fit your needs and budget. +

+
+ +
+ {plans.map((plan, index) => ( +
+ {plan.highlight && ( +
+ + Most Popular + +
+ )} + +
+

{plan.name}

+
+ {plan.price} + {plan.period && {plan.period}} +
+

{plan.description}

+
+ +
+
    + {plan.features.map((feature, fIndex) => ( +
  • + + {feature} +
  • + ))} +
+ + {plan.limitations.length > 0 && ( +
    + {plan.limitations.map((limitation, lIndex) => ( +
  • + + {limitation} +
  • + ))} +
+ )} +
+ + +
+ ))} +
+
+
+ + {/* CTA Section */} +
+
+
+
+ +
+

+ Ready to Streamline Your Compliance? +

+

+ Start extracting actionable tasks from your security and compliance policies today. + No credit card required to get started. +

+ +
+
+
+ + {/* Footer */} +
); }; diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx index cda36da..06666a0 100644 --- a/src/pages/NotFound.tsx +++ b/src/pages/NotFound.tsx @@ -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 ( -
-
+
+
+
+ +

404

-

Oops! Page not found

- - Return to Home - +

+ The page you are looking for doesn't exist or has been moved. +

+
+ + +
); diff --git a/src/utils/mockData.ts b/src/utils/mockData.ts new file mode 100644 index 0000000..0123278 --- /dev/null +++ b/src/utils/mockData.ts @@ -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' + } +]; diff --git a/tailwind.config.ts b/tailwind.config.ts index 8706086..d0dee78 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -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' } } },