Tareex commited on
Commit
c0e8080
·
verified ·
1 Parent(s): 6f0ed4f

Build a REACT projct for an IT infrastructure documentation and troubleshooting knowledge management platform called "MapIT".

Browse files

This web application allows IT teams to inventory servers, virtual machines, network devices, and other infrastructure elements, map their relationships/dependencies, and record problems encountered with their solutions.

TARGET USERS:
- IT administrators and network engineers (primary)
- System administrators and DevOps teams
- IT managers viewing dashboards and reports

DESIGN REQUIREMENTS:

Visual Style:
- Modern, professional dashboard aesthetic similar to Tailwind UI or Vercel design system
- Clean, minimalist interface with ample white space
- Professional color palette: Primary (deep blue #1E40AF), Secondary (slate gray #475569), Accent (emerald green #10B981 for success states), Alert (amber #F59E0B for warnings)
- Consistent rounded corners (8px for cards, 6px for buttons)
- Subtle shadows and smooth transitions for depth

Typography:
- Font family: Inter or system fonts for readability
- Clear hierarchy: H1 (32px bold), H2 (24px semibold), Body (16px regular), Small text (14px)
- High contrast text for accessibility (WCAG AA compliant)

Layout & Responsiveness:
- Mobile-first responsive design (breakpoints: 640px, 768px, 1024px, 1280px)
- Sidebar navigation on desktop, collapsible hamburger menu on mobile
- Grid-based layouts with flexible containers
- Touch-friendly buttons (min 44x44px) for mobile

PAGES TO BUILD:

1. Login Page:
- Centered card layout with logo at top
- Email and password fields with icon prefixes
- "Remember me" checkbox and "Forgot password?" link
- Primary CTA button with loading state animation
- Optional: SSO buttons (Google, Microsoft) below divider
- Background: Subtle gradient or geometric pattern

2. Dashboard (Home):
- Top navigation bar: App logo, search bar, notifications bell, user avatar dropdown
- Left sidebar: Navigation menu with icons (Dashboard, Assets, Problems, Relationships, Reports, Settings)
- Main content area:
* KPI cards showing: Total Assets, Active Servers, Open Problems, Resolved This Month (with trend indicators)
* Interactive infrastructure topology visualization (network graph or tree view)
* Recent activity timeline
* Quick action buttons: Add Asset, Log Problem, View Map

3. Asset Inventory Page:
- Search and filter toolbar (by type, location, status, tags)
- Data table with sortable columns: Name, Type, IP Address, Location, Status, Last Updated
- Inline actions per row: View, Edit, Delete icons
- Pagination controls at bottom
- "Add New Asset" floating action button (FAB)
- Bulk actions checkbox for multi-select operations

4. Asset Detail Page:
- Header: Asset name, type badge, status indicator (online/offline dot)
- Tabbed interface: Overview, Specifications, Relationships, Problem History, Activity Log
- Overview tab: Key-value pairs in card layout (IP, OS, CPU, Memory, etc.)
- Relationships section: Visual graph showing connected assets with link labels
- Problem history: Accordion list of past issues with timestamps and resolution status

5. Problem/Solution Knowledge Base Page:
- Split view: Problem list on left (filterable by asset, severity, status), detail panel on right
- Each problem card: Title, affected asset tags, severity badge, timestamp, solved/unsolved indicator
- Detail panel: Problem description, affected assets list, solution steps (rich text with code blocks support), attachments section
- "Add New Problem" button with modal form

6. Relationship Map Page:
- Full-screen interactive network diagram showing asset dependencies
- Node types: Servers, VMs, Network devices, Services (color-coded)
- Zoom and pan controls
- Filter panel: Show/hide by asset type, location, or service
- Click node to view quick info tooltip, double-click to navigate to asset detail

7. Settings Page:
- Vertical tab navigation: Profile, Security, Notifications, Integrations, API Keys
- Form layouts with clear labels, input validation feedback
- Save/Cancel buttons with confirmation toasts

UI COMPONENTS TO INCLUDE:
- Reusable button variants (primary, secondary, danger, ghost)
- Input fields with floating labels or placeholder text, validation states
- Toast notifications for success/error feedback (top-right corner)
- Modal dialogs for confirmations and forms
- Dropdown menus with search capability
- Badge components for status indicators
- Loading skeletons for async content
- Empty states with illustrations and CTAs
- Breadcrumb navigation for deep pages

TECHNICAL SPECIFICATIONS:
- Framework: [React with TypeScript / Vue.js / Next.js - specify your preference]
- Styling: Tailwind CSS or styled-components
- Icons: Heroicons, Lucide Icons, or Font Awesome
- Charts/Graphs: Recharts or Chart.js for dashboards, Cytoscape.js or D3.js for network topology
- State management: Context API or Zustand for auth and global state
- Routing: React Router or Next.js built-in routing
- Form handling: React Hook Form with Zod validation
- API integration: Axios with mock endpoints (provide sample JSON structure)

ANIMATIONS & INTERACTIONS:
- Smooth page transitions (fade-in on mount)
- Hover effects on interactive elements (scale, color shift)
- Loading spinners during data fetches
- Success checkmark animations after form submissions
- Smooth expand/collapse for accordions and dropdowns

ACCESSIBILITY:
- Semantic HTML5 elements
- ARIA labels for screen readers
- Keyboard navigation support (tab order, focus indicators)
- Skip-to-content link
- Color contrast ratio ≥ 4.5:1

DO:
- Use consistent spacing (4px, 8px, 16px, 24px, 32px scale)
- Implement proper error boundaries
- Add proper TypeScript types for props
- Include helpful loading and error states
- Make forms accessible with proper labels

DON'T:
- Hardcode data - use mock API calls or props
- Mix design patterns - stay consistent
- Ignore mobile breakpoints
- Overcomplicate navigation structure
- Use low-contrast colors

OUTPUT FORMAT:
Provide complete, production-ready component code with:
- Organized folder structure (components/, pages/, utils/, styles/)
- Reusable component library
- README with setup instructions
- Responsive at all breakpoints
- Commented code for complex logic

README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Mapit Infrastructure Intelligence Hub
3
- emoji: 😻
4
- colorFrom: blue
5
- colorTo: indigo
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: MapIT - Infrastructure Intelligence Hub 🗺️💻
3
+ colorFrom: purple
4
+ colorTo: green
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://huggingface.co/deepsite).
package.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ json
2
+ {
3
+ "name": "mapit-infrastructure-docs",
4
+ "version": "1.0.0",
5
+ "description": "IT infrastructure documentation and troubleshooting knowledge management platform",
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "vite",
9
+ "build": "vite build",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "react": "^18.2.0",
14
+ "react-dom": "^18.2.0",
15
+ "react-router-dom": "^6.8.1",
16
+ "lucide-react": "^0.294.0",
17
+ "axios": "^1.6.2",
18
+ "react-hook-form": "^7.48.2",
19
+ "zod": "^3.22.4",
20
+ "recharts": "^2.8.0",
21
+ "cytoscape": "^3.26.0",
22
+ "react-cytoscapejs": "^2.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/react": "^18.2.43",
26
+ "@types/react-dom": "^18.2.17",
27
+ "@vitejs/plugin-react": "^4.2.1",
28
+ "autoprefixer": "^10.4.16",
29
+ "postcss": "^8.4.32",
30
+ "tailwindcss": "^3.3.6",
31
+ "typescript": "^5.2.2",
32
+ "vite": "^5.0.8"
33
+ }
34
+ }
35
+
36
+ </html>
src/App.tsx ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ typescript
2
+ import React from 'react'
3
+ import { Routes, Route } from 'react-router-dom'
4
+ import { AuthProvider } from './contexts/AuthContext'
5
+ import { ToastProvider } from './contexts/ToastContext'
6
+ import ProtectedRoute from './components/ProtectedRoute'
7
+ import Layout from './components/Layout'
8
+ import Login from './pages/Login'
9
+ import Dashboard from './pages/Dashboard'
10
+ import AssetInventory from './pages/AssetInventory'
11
+ import AssetDetail from './pages/AssetDetail'
12
+ import KnowledgeBase from './pages/KnowledgeBase'
13
+ import RelationshipMap from './pages/RelationshipMap'
14
+ import Settings from './pages/Settings'
15
+
16
+ function App() {
17
+ return (
18
+ <ToastProvider>
19
+ <AuthProvider>
20
+ <Routes>
21
+ <Route path="/login" element={<Login />} />
22
+ <Route path="/" element={<ProtectedRoute><Layout /></ProtectedRoute>}>
23
+ <Route index element={<Dashboard />} />
24
+ <Route path="assets" element={<AssetInventory />} />
25
+ <Route path="assets/:id" element={<AssetDetail />} />
26
+ <Route path="knowledge" element={<KnowledgeBase />} />
27
+ <Route path="map" element={<RelationshipMap />} />
28
+ <Route path="settings" element={<Settings />} />
29
+ </Route>
30
+ </Routes>
31
+ </AuthProvider>
32
+ </ToastProvider>
33
+ )
34
+ }
35
+
36
+ export default App
37
+
38
+ </html>
src/components/ActivityTimeline.tsx ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ typescript
2
+ import React from 'react'
3
+ import { CheckCircle, AlertTriangle, Info } from 'lucide-react'
4
+
5
+ interface ActivityItem {
6
+ id: string
7
+ type: 'success' | 'warning' | 'info'
8
+ title: string
9
+ description: string
10
+ timestamp: string
11
+ asset?: string
12
+ }
13
+
14
+ const ActivityTimeline: React.FC = () => {
15
+ const activities: ActivityItem[] = [
16
+ {
17
+ id: '1',
18
+ type: 'success',
19
+ title: 'Database server restored',
20
+ description: 'Backup restoration completed successfully',
21
+ timestamp: '2 hours ago',
22
+ asset: 'db-prod-01'
23
+ },
24
+ {
25
+ id: '2',
26
+ type: 'warning',
27
+ title: 'High memory usage detected',
28
+ description: 'Web server memory usage above 90% threshold',
29
+ timestamp: '4 hours ago',
30
+ asset: 'web-staging-02'
31
+ },
32
+ {
33
+ id: '3',
34
+ type: 'info',
35
+ title: 'New asset added',
36
+ description: 'Added new load balancer to inventory',
37
+ timestamp: '6 hours ago'
38
+ },
39
+ {
40
+ id: '4',
41
+ type: 'success',
42
+ title: 'Network switch configured',
43
+ description: 'VLAN configuration updated successfully',
44
+ timestamp: '1 day ago',
45
+ asset: 'switch-dc-03'
46
+ }
47
+ ]
48
+
49
+ const getActivityIcon = (type: string) => {
50
+ switch (type) {
51
+ case 'success':
52
+ return <CheckCircle className="h-4 w-4 text-accent-500" />
53
+ case 'warning':
54
+ return <AlertTriangle className="h-4 w-4 text-alert-500" />
55
+ case 'info':
56
+ return <Info className="h-4 w-4 text-primary-500" />
57
+ default:
58
+ return <Info className="h-4 w-4 text-primary-500" />
59
+ }
60
+ }
61
+
62
+ const getActivityColor = (type: string) => {
63
+ switch (type) {
64
+ case 'success':
65
+ return 'border-accent-200'
66
+ case 'warning':
67
+ return 'border-alert-200'
68
+ case 'info':
69
+ return 'border-primary-200'
70
+ default:
71
+ return 'border-gray-200'
72
+ }
73
+ }
74
+
75
+ return (
76
+ <div className="flow-root">
77
+ <ul className="-mb-8">
78
+ {activities.map((activity, activityIdx) => (
79
+ <li key={activity.id}>
80
+ <div className="relative pb-8">
81
+ {activityIdx !== activities.length - 1 && (
82
+ <span
83
+ className="absolute top-4 left-4 -ml-px h-full w-0.5 bg-gray-200"
84
+ aria-hidden="true"
85
+ />
86
+ )}
87
+ <div className="relative flex space-x-3">
88
+ <div>
89
+ <span className={`
90
+ h-8 w-8 rounded-full flex items-center justify-center ring-8 ring-white
91
+ ${getActivityColor(activity.type)}
92
+ `}>
93
+ {getActivityIcon(activity.type)}
94
+ </span>
95
+ </div>
96
+ <div className="min-w-0 flex-1 pt-1.5 flex justify-between space-x-4">
97
+ <div>
98
+ <p className="text-sm text-gray-900">{activity.title}</p>
99
+ <p className="text-sm text-gray-500">{activity.description}</p>
100
+ {activity.asset && (
101
+ <span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
102
+ {activity.asset}
103
+ </span>
104
+ )}
105
+ </div>
106
+ <div className="text-right text-sm whitespace-nowrap text-gray-500">
107
+ {activity.timestamp}
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ </li>
113
+ ))}
114
+ </ul>
115
+ </div>
116
+ )
117
+ }
118
+
119
+ export default ActivityTimeline
120
+
121
+ </html>
src/components/KPICard.tsx ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ typescript
2
+ import React from 'react'
3
+ import { LucideIcon } from 'lucide-react'
4
+
5
+ interface KPICardProps {
6
+ title: string
7
+ value: string
8
+ change: string
9
+ trend: 'up' | 'down'
10
+ icon: LucideIcon
11
+ }
12
+
13
+ const KPICard: React.FC<KPICardProps> = ({ title, value, change, trend, icon: Icon }) => {
14
+ return (
15
+ <div className="card p-6 hover:shadow-md transition-shadow duration-200">
16
+ <div className="flex items-center">
17
+ <div className="flex-shrink-0">
18
+ <div className="h-12 w-12 bg-primary-100 rounded-full flex items-center justify-center">
19
+ <Icon className="h-6 w-6 text-primary-600" />
20
+ </div>
21
+ </div>
22
+
23
+ <div className="ml-4">
24
+ <p className="text-sm font-medium text-gray-600">{title}</p>
25
+ <p className="text-2xl font-bold text-gray-900">{value}</p>
26
+ <div className={`flex items-center text-sm ${trend === 'up' ? 'text-accent-600' : 'text-red-600'}`}>
27
+ {trend === 'up' ? (
28
+ <TrendingUp className="h-4 w-4 mr-1" />
29
+ ) : (
30
+ <TrendingDown className="h-4 w-4 mr-1" />
31
+ )}
32
+ <span>{change} from last week</span>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ )
38
+ }
39
+
40
+ export default KPICard
41
+
42
+ </html>
src/components/Layout.tsx ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ typescript
2
+ import React, { useState } from 'react'
3
+ import { Outlet } from 'react-router-dom'
4
+ import Sidebar from './Sidebar'
5
+ import TopNav from './TopNav'
6
+ import ToastContainer from './ToastContainer'
7
+
8
+ const Layout: React.FC = () => {
9
+ const [sidebarOpen, setSidebarOpen] = useState(false)
10
+
11
+ return (
12
+ <div className="flex h-screen bg-gray-50">
13
+ <Sidebar open={sidebarOpen} onClose={() => setSidebarOpen(false)} />
14
+
15
+ <div className="flex-1 flex flex-col overflow-hidden">
16
+ <TopNav onMenuClick={() => setSidebarOpen(true)} />
17
+
18
+ <main className="flex-1 overflow-y-auto p-4 md:p-6">
19
+ <Outlet />
20
+ </main>
21
+ </div>
22
+
23
+ <ToastContainer />
24
+ </div>
25
+ )
26
+ }
27
+
28
+ export default Layout
29
+
30
+ </html>
src/components/ProtectedRoute.tsx ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ typescript
2
+ import React from 'react'
3
+ import { Navigate } from 'react-router-dom'
4
+ import { useAuth } from '../contexts/AuthContext'
5
+
6
+ interface ProtectedRouteProps {
7
+ children: React.ReactNode
8
+ }
9
+
10
+ const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
11
+ const { user, isLoading } = useAuth()
12
+
13
+ if (isLoading) {
14
+ return (
15
+ <div className="min-h-screen flex items-center justify-center">
16
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></div>
17
+ </div>
18
+ )
19
+ }
20
+
21
+ if (!user) {
22
+ return <Navigate to="/login" replace />
23
+ }
24
+
25
+ return <>{children}</>
26
+ }
27
+
28
+ export default ProtectedRoute
29
+
30
+ </html>
src/components/Sidebar.tsx ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ typescript
2
+ import React from 'react'
3
+ import { Link, useLocation } from 'react-router-dom'
4
+ import {
5
+ LayoutDashboard,
6
+ Server,
7
+ BookOpen,
8
+ Network,
9
+ Settings,
10
+ X
11
+ } from 'lucide-react'
12
+
13
+ interface SidebarProps {
14
+ open: boolean
15
+ onClose: () => void
16
+ }
17
+
18
+ const navigation = [
19
+ { name: 'Dashboard', href: '/', icon: LayoutDashboard },
20
+ { name: 'Asset Inventory', href: '/assets', icon: Server },
21
+ { name: 'Knowledge Base', href: '/knowledge', icon: BookOpen },
22
+ { name: 'Relationship Map', href: '/map', icon: Network },
23
+ { name: 'Settings', href: '/settings', icon: Settings },
24
+ ]
25
+
26
+ const Sidebar: React.FC<SidebarProps> = ({ open, onClose }) => {
27
+ const location = useLocation()
28
+
29
+ return (
30
+ <>
31
+ {/* Mobile overlay */}
32
+ {open && (
33
+ <div
34
+ className="fixed inset-0 z-40 bg-gray-600 bg-opacity-75 lg:hidden"
35
+ onClick={onClose}
36
+ />
37
+ )}
38
+
39
+ {/* Sidebar */}
40
+ <div className={`
41
+ fixed inset-y-0 left-0 z-50 w-64 bg-white shadow-xl transform transition-transform duration-300 ease-in-out lg:translate-x-0 lg:static lg:inset-0
42
+ ${open ? 'translate-x-0' : '-translate-x-full'}
43
+ `}>
44
+ <div className="flex items-center justify-between h-16 px-4 border-b border-gray-200">
45
+ <div className="flex items-center">
46
+ <div className="flex-shrink-0">
47
+ <div className="h-8 w-8 bg-primary-600 rounded-button flex items-center justify-center">
48
+ <span className="text-white font-bold text-sm">MI</span>
49
+ </div>
50
+ <span className="ml-3 text-lg font-semibold text-gray-900">MapIT</span>
51
+ </div>
52
+ </div>
53
+
54
+ <button
55
+ onClick={onClose}
56
+ className="lg:hidden p-2 rounded-button text-gray-400 hover:text-gray-500 hover:bg-gray-100"
57
+ >
58
+ <X className="h-5 w-5" />
59
+ </button>
60
+ </div>
61
+
62
+ <nav className="mt-8 px-4 space-y-2">
63
+ {navigation.map((item) => {
64
+ const isActive = location.pathname === item.href
65
+ return (
66
+ <Link
67
+ key={item.name}
68
+ to={item.href}
69
+ className={`
70
+ group flex items-center px-3 py-2 text-sm font-medium rounded-button transition-colors duration-200
71
+ ${isActive
72
+ ? 'bg-primary-50 text-primary-700 border border-primary-200'
73
+ : 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'
74
+ }
75
+ `}
76
+ onClick={() => window.innerWidth < 1024 && onClose()}
77
+ >
78
+ <item.icon className={`
79
+ mr-3 h-5 w-5 flex-shrink-0
80
+ ${isActive ? 'text-primary-500' : 'text-gray-400 group-hover:text-gray-500'}
81
+ `} />
82
+ {item.name}
83
+ </Link>
84
+ )
85
+ })}
86
+ </nav>
87
+ </div>
88
+ </>
89
+ )
90
+ }
91
+
92
+ export default Sidebar
93
+
94
+ </html>
src/components/ToastContainer.tsx ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ typescript
2
+ import React from 'react'
3
+ import { useToast } from '../contexts/ToastContext'
4
+ import { CheckCircle, XCircle, AlertTriangle, Info, X } from 'lucide-react'
5
+
6
+ const ToastContainer: React.FC = () => {
7
+ const { toasts, removeToast } = useToast()
8
+
9
+ const getToastIcon = (type: string) => {
10
+ switch (type) {
11
+ case 'success':
12
+ return <CheckCircle className="h-5 w-5 text-accent-500" />
13
+ case 'error':
14
+ return <XCircle className="h-5 w-5 text-red-500" />
15
+ case 'warning':
16
+ return <AlertTriangle className="h-5 w-5 text-alert-500" />
17
+ case 'info':
18
+ return <Info className="h-5 w-5 text-primary-500" />
19
+ default:
20
+ return <Info className="h-5 w-5 text-primary-500" />
21
+ }
22
+ }
23
+
24
+ const getToastStyles = (type: string) => {
25
+ switch (type) {
26
+ case 'success':
27
+ return 'bg-accent-50 border-accent-200'
28
+ case 'error':
29
+ return 'bg-red-50 border-red-200'
30
+ case 'warning':
31
+ return 'bg-alert-50 border-alert-200'
32
+ case 'info':
33
+ return 'bg-primary-50 border-primary-200'
34
+ default:
35
+ return 'bg-gray-50 border-gray-200'
36
+ }
37
+ }
38
+
39
+ return (
40
+ <div className="fixed top-4 right-4 z-50 space-y-3 max-w-sm">
41
+ {toasts.map((toast) => (
42
+ <div
43
+ key={toast.id}
44
+ className={`
45
+ card p-4 transform transition-all duration-300 ease-in-out
46
+ ${getToastStyles(toast.type)}
47
+ `}
48
+ >
49
+ <div className="flex items-start">
50
+ <div className="flex-shrink-0">
51
+ {getToastIcon(toast.type)}
52
+ </div>
53
+ <div className="ml-3 flex-1">
54
+ <p className="text-sm font-medium text-gray-900">
55
+ {toast.title}
56
+ </p>
57
+ {toast.message && (
58
+ <p className="mt-1 text-sm text-gray-500">
59
+ {toast.message}
60
+ </p>
61
+ </div>
62
+ <button
63
+ onClick={() => removeToast(toast.id)}
64
+ className="ml-4 flex-shrink-0 p-1 rounded-button text-gray-400 hover:text-gray-500 hover:bg-gray-100"
65
+ >
66
+ <X className="h-4 w-4" />
67
+ </button>
68
+ </div>
69
+ </div>
70
+ ))}
71
+ </div>
72
+ )
73
+ }
74
+
75
+ export default ToastContainer
76
+
77
+ </html>
src/components/TopNav.tsx ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ typescript
2
+ import React, { useState } from 'react'
3
+ import { useAuth } from '../contexts/AuthContext'
4
+ import { Search, Bell, Menu, User, LogOut } from 'lucide-react'
5
+
6
+ interface TopNavProps {
7
+ onMenuClick: () => void
8
+ }
9
+
10
+ const TopNav: React.FC<TopNavProps> = ({ onMenuClick }) => {
11
+ const { user, logout } = useAuth()
12
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false)
13
+
14
+ return (
15
+ <header className="bg-white shadow-sm border-b border-gray-200 z-30">
16
+ <div className="flex items-center justify-between h-16 px-4 md:px-6">
17
+ <div className="flex items-center">
18
+ <button
19
+ onClick={onMenuClick}
20
+ className="lg:hidden p-2 rounded-button text-gray-400 hover:text-gray-500 hover:bg-gray-100"
21
+ >
22
+ <Menu className="h-5 w-5" />
23
+ </button>
24
+
25
+ <div className="hidden md:block ml-4">
26
+ <div className="relative">
27
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
28
+ <Search className="h-4 w-4 text-gray-400" />
29
+ </div>
30
+ <input
31
+ type="text"
32
+ placeholder="Search assets, problems..."
33
+ className="input-field pl-10 pr-4 w-80"
34
+ />
35
+ </div>
36
+ </div>
37
+ </div>
38
+
39
+ <div className="flex items-center space-x-4">
40
+ <button className="relative p-2 rounded-button text-gray-400 hover:text-gray-500 hover:bg-gray-100">
41
+ <Bell className="h-5 w-5" />
42
+ <span className="absolute top-1 right-1 h-2 w-2 bg-alert-500 rounded-full"></span>
43
+ </button>
44
+
45
+ <div className="relative">
46
+ <button
47
+ onClick={() => setIsDropdownOpen(!isDropdownOpen)}
48
+ className="flex items-center space-x-3 p-2 rounded-button hover:bg-gray-100"
49
+ >
50
+ <div className="h-8 w-8 bg-primary-100 rounded-full flex items-center justify-center">
51
+ <User className="h-4 w-4 text-primary-600" />
52
+ </div>
53
+ <span className="hidden md:block text-sm font-medium text-gray-700">
54
+ {user?.name}
55
+ </span>
56
+ </button>
57
+
58
+ {isDropdownOpen && (
59
+ <div className="absolute right-0 mt-2 w-48 bg-white rounded-card shadow-lg border border-gray-200 py-1">
60
+ <button
61
+ onClick={logout}
62
+ className="flex items-center w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-50"
63
+ >
64
+ <LogOut className="mr-3 h-4 w-4" />
65
+ Sign out
66
+ </button>
67
+ </div>
68
+ )}
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </header>
73
+ )
74
+ }
75
+
76
+ export default TopNav
77
+
78
+ </html>
src/contexts/AuthContext.tsx ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ typescript
2
+ import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'
3
+
4
+ interface User {
5
+ id: string
6
+ name: string
7
+ email: string
8
+ role: string
9
+ avatar?: string
10
+ }
11
+
12
+ interface AuthContextType {
13
+ user: User | null
14
+ login: (email: string, password: string) => Promise<void>
15
+ logout: () => void
16
+ isLoading: boolean
17
+ }
18
+
19
+ const AuthContext = createContext<AuthContextType | undefined>(undefined)
20
+
21
+ export const useAuth = () => {
22
+ const context = useContext(AuthContext)
23
+ if (context === undefined) {
24
+ throw new Error('useAuth must be used within an AuthProvider')
25
+ }
26
+ return context
27
+ }
28
+
29
+ interface AuthProviderProps {
30
+ children: ReactNode
31
+ }
32
+
33
+ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
34
+ const [user, setUser] = useState<User | null>(null)
35
+ const [isLoading, setIsLoading] = useState(true)
36
+
37
+ useEffect(() => {
38
+ // Check for stored auth token on app load
39
+ const token = localStorage.getItem('auth_token')
40
+ if (token) {
41
+ // Mock user data - in real app, verify token with backend
42
+ setUser({
43
+ id: '1',
44
+ name: 'John Doe',
45
+ email: 'john.doe@company.com',
46
+ role: 'admin'
47
+ })
48
+ }
49
+ setIsLoading(false)
50
+ }, [])
51
+
52
+ const login = async (email: string, password: string): Promise<void> => {
53
+ setIsLoading(true)
54
+ try {
55
+ // Mock API call
56
+ await new Promise(resolve => setTimeout(resolve, 1000))
57
+
58
+ if (email === 'admin@mapit.com' && password === 'password') {
59
+ const userData: User = {
60
+ id: '1',
61
+ name: 'John Doe',
62
+ email: email,
63
+ role: 'admin'
64
+ }
65
+ setUser(userData)
66
+ localStorage.setItem('auth_token', 'mock_jwt_token')
67
+ } else {
68
+ throw new Error('Invalid credentials')
69
+ }
70
+ } finally {
71
+ setIsLoading(false)
72
+ }
73
+ }
74
+
75
+ const logout = () => {
76
+ setUser(null)
77
+ localStorage.removeItem('auth_token')
78
+ }
79
+
80
+ return (
81
+ <AuthContext.Provider value={{ user, login, logout, isLoading }}>
82
+ {children}
83
+ </AuthContext.Provider>
84
+ )
85
+ }
86
+
87
+ </html>
src/contexts/ToastContext.tsx ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ typescript
2
+ import React, { createContext, useContext, useState, ReactNode } from 'react'
3
+
4
+ export type ToastType = 'success' | 'error' | 'warning' | 'info'
5
+
6
+ interface Toast {
7
+ id: string
8
+ type: ToastType
9
+ title: string
10
+ message?: string
11
+ }
12
+
13
+ interface ToastContextType {
14
+ toasts: Toast[]
15
+ addToast: (toast: Omit<Toast, 'id'>) => void
16
+ removeToast: (id: string) => void
17
+ }
18
+
19
+ const ToastContext = createContext<ToastContextType | undefined>(undefined)
20
+
21
+ export const useToast = () => {
22
+ const context = useContext(ToastContext)
23
+ if (context === undefined) {
24
+ throw new Error('useToast must be used within a ToastProvider')
25
+ }
26
+ return context
27
+ }
28
+
29
+ interface ToastProviderProps {
30
+ children: ReactNode
31
+ }
32
+
33
+ export const ToastProvider: React.FC<ToastProviderProps> = ({ children }) => {
34
+ const [toasts, setToasts] = useState<Toast[]>([])
35
+
36
+ const addToast = (toast: Omit<Toast, 'id'>) => {
37
+ const id = Math.random().toString(36).substr(2, 9)
38
+ setToasts(prev => [...prev, { ...toast, id }])
39
+
40
+ // Auto remove after 5 seconds
41
+ setTimeout(() => {
42
+ removeToast(id)
43
+ }, 5000)
44
+ }
45
+
46
+ const removeToast = (id: string) => {
47
+ setToasts(prev => prev.filter(toast => toast.id !== id))
48
+ }
49
+
50
+ return (
51
+ <ToastContext.Provider value={{ toasts, addToast, removeToast }}>
52
+ {children}
53
+ </ToastContext.Provider>
54
+ )
55
+ }
56
+
57
+ </html>
src/index.css ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
2
+ @tailwind base;
3
+ @tailwind components;
4
+ @tailwind utilities;
5
+
6
+ @layer base {
7
+ html {
8
+ font-family: 'Inter', system-ui, sans-serif;
9
+ }
10
+
11
+ body {
12
+ @apply bg-gray-50 text-gray-900 antialiased;
13
+ }
14
+
15
+ h1 {
16
+ @apply text-3xl font-bold;
17
+ }
18
+
19
+ h2 {
20
+ @apply text-2xl font-semibold;
21
+ }
22
+
23
+ p {
24
+ @apply text-base;
25
+ }
26
+
27
+ small {
28
+ @apply text-sm;
29
+ }
30
+ }
31
+
32
+ @layer components {
33
+ .btn {
34
+ @apply inline-flex items-center justify-center px-4 py-2 rounded-button font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2;
35
+ }
36
+
37
+ .btn-primary {
38
+ @apply btn bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500;
39
+ }
40
+
41
+ .btn-secondary {
42
+ @apply btn bg-secondary-200 text-secondary-800 hover:bg-secondary-300 focus:ring-secondary-500;
43
+ }
44
+
45
+ .btn-danger {
46
+ @apply btn bg-red-600 text-white hover:bg-red-700 focus:ring-red-500;
47
+ }
48
+
49
+ .btn-ghost {
50
+ @apply btn text-secondary-600 hover:text-secondary-800 hover:bg-secondary-100 focus:ring-secondary-500;
51
+ }
52
+
53
+ .input-field {
54
+ @apply block w-full px-3 py-2 border border-gray-300 rounded-button placeholder-gray-400 focus:outline-none focus:ring-primary-500 focus:border-primary-500;
55
+ }
56
+
57
+ .card {
58
+ @apply bg-white rounded-card shadow-sm border border-gray-200;
59
+ }
60
+
61
+ .badge {
62
+ @apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium;
63
+ }
64
+
65
+ .badge-success {
66
+ @apply badge bg-accent-100 text-accent-800;
67
+ }
68
+
69
+ .badge-warning {
70
+ @apply badge bg-alert-100 text-alert-800;
71
+ }
72
+
73
+ .badge-error {
74
+ @apply badge bg-red-100 text-red-800;
75
+ }
76
+
77
+ .badge-info {
78
+ @apply badge bg-blue-100 text-blue-800;
79
+ }
80
+ }
81
+
82
+ /* Smooth page transitions */
83
+ .page-enter {
84
+ opacity: 0;
85
+ transform: translateY(10px);
86
+ }
87
+
88
+ .page-enter-active {
89
+ opacity: 1;
90
+ transform: translateY(0);
91
+ transition: opacity 300ms, transform 300ms;
92
+ }
93
+
94
+ /* Loading skeleton animation */
95
+ @keyframes pulse {
96
+ 0%, 100% {
97
+ opacity: 1;
98
+ }
99
+ 50% {
100
+ opacity: 0.5;
101
+ }
102
+ }
103
+
104
+ .skeleton {
105
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
106
+ }
src/main.tsx ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ typescript
2
+ import React from 'react'
3
+ import ReactDOM from 'react-dom/client'
4
+ import { BrowserRouter } from 'react-router-dom'
5
+ import App from './App.tsx'
6
+ import './index.css'
7
+
8
+ ReactDOM.createRoot(document.getElementById('root')!).render(
9
+ <React.StrictMode>
10
+ <BrowserRouter>
11
+ <App />
12
+ </BrowserRouter>
13
+ </React.StrictMode>,
14
+ )
15
+
16
+ </html>
src/pages/Dashboard.tsx ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ typescript
2
+ import React from 'react'
3
+ import { Link } from 'react-router-dom'
4
+ import { TrendingUp, TrendingDown, Plus, AlertTriangle } from 'lucide-react'
5
+ import KPICard from '../components/KPICard'
6
+ import ActivityTimeline from '../components/ActivityTimeline'
7
+
8
+ const Dashboard: React.FC = () => {
9
+ const mockKPIs = [
10
+ {
11
+ title: 'Total Assets',
12
+ value: '247',
13
+ change: '+12',
14
+ trend: 'up',
15
+ icon: Server
16
+ },
17
+ {
18
+ title: 'Active Servers',
19
+ value: '89',
20
+ change: '+3',
21
+ trend: 'up',
22
+ icon: TrendingUp
23
+ },
24
+ {
25
+ title: 'Open Problems',
26
+ value: '14',
27
+ change: '-2',
28
+ trend: 'down',
29
+ icon: TrendingDown
30
+ },
31
+ {
32
+ title: 'Resolved This Month',
33
+ value: '42',
34
+ change: '+8',
35
+ trend: 'up',
36
+ icon: CheckCircle
37
+ }
38
+ ]
39
+
40
+ return (
41
+ <div className="space-y-6">
42
+ {/* Page Header */}
43
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between">
44
+ <div>
45
+ <h1 className="text-2xl font-bold text-gray-900">Dashboard</h1>
46
+ <p className="mt-1 text-sm text-gray-600">
47
+ Overview of your IT infrastructure and recent activity
48
+ </p>
49
+ </div>
50
+
51
+ <div className="mt-4 sm:mt-0 flex space-x-3">
52
+ <Link to="/assets/new" className="btn-primary">
53
+ <Plus className="h-4 w-4 mr-2" />
54
+ Add Asset
55
+ </Link>
56
+ <Link to="/knowledge/new" className="btn-secondary">
57
+ <AlertTriangle className="h-4 w-4 mr-2" />
58
+ Log Problem
59
+ </Link>
60
+ </div>
61
+ </div>
62
+
63
+ {/* KPI Grid */}
64
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
65
+ {mockKPIs.map((kpi, index) => (
66
+ <KPICard key={index} {...kpi} />
67
+ ))}
68
+ </div>
69
+
70
+ {/* Main Content Grid */}
71
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
72
+ {/* Infrastructure Visualization */}
73
+ <div className="lg:col-span-2 card p-6">
74
+ <h2 className="text-lg font-semibold text-gray-900 mb-4">
75
+ Infrastructure Topology
76
+ </h2>
77
+ <div className="h-80 bg-gray-50 rounded-card border-2 border-dashed border-gray-300 flex items-center justify-center">
78
+ <div className="text-center">
79
+ <Network className="mx-auto h-12 w-12 text-gray-400" />
80
+ <p className="mt-2 text-sm text-gray-500">
81
+ Interactive network graph visualization
82
+ </p>
83
+ <Link to="/map" className="mt-3 btn-primary">
84
+ View Full Map
85
+ </Link>
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ {/* Recent Activity */}
91
+ <div className="card p-6">
92
+ <h2 className="text-lg font-semibold text-gray-900 mb-4">
93
+ Recent Activity
94
+ </h2>
95
+ <ActivityTimeline />
96
+ </div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+ )
101
+ }
102
+
103
+ export default Dashboard
104
+
105
+ </html>
src/pages/Login.tsx ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ typescript
2
+ import React, { useState } from 'react'
3
+ import { useNavigate } from 'react-router-dom'
4
+ import { useAuth } from '../contexts/AuthContext'
5
+ import { useToast } from '../contexts/ToastContext'
6
+ import { Server, Mail, Lock, Loader } from 'lucide-react'
7
+
8
+ const Login: React.FC = () => {
9
+ const [email, setEmail] = useState('')
10
+ const [password, setPassword] = useState('')
11
+ const [rememberMe, setRememberMe] = useState(false)
12
+ const { login, isLoading } = useAuth()
13
+ const { addToast } = useToast()
14
+ const navigate = useNavigate()
15
+
16
+ const handleSubmit = async (e: React.FormEvent) => {
17
+ e.preventDefault()
18
+ try {
19
+ await login(email, password)
20
+ addToast({
21
+ type: 'success',
22
+ title: 'Welcome back!',
23
+ message: 'Successfully logged in.'
24
+ })
25
+ navigate('/')
26
+ } catch (error) {
27
+ addToast({
28
+ type: 'error',
29
+ title: 'Login failed',
30
+ message: 'Invalid email or password.'
31
+ })
32
+ }
33
+ }
34
+
35
+ return (
36
+ <div className="min-h-screen bg-gradient-to-br from-primary-50 via-white to-accent-50 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
37
+ <div className="max-w-md w-full space-y-8">
38
+ {/* Logo */}
39
+ <div className="text-center">
40
+ <div className="mx-auto h-16 w-16 bg-primary-600 rounded-card flex items-center justify-center">
41
+ <Server className="h-8 w-8 text-white" />
42
+ </div>
43
+ <h2 className="mt-6 text-3xl font-bold text-gray-900">MapIT</h2>
44
+ <p className="mt-2 text-sm text-gray-600">
45
+ IT Infrastructure Documentation Platform
46
+ </p>
47
+ </div>
48
+
49
+ {/* Login Form */}
50
+ <form className="mt-8 space-y-6" onSubmit={handleSubmit}>
51
+ <div className="card p-6 shadow-xl">
52
+ <div className="space-y-4">
53
+ {/* Email Field */}
54
+ <div>
55
+ <label htmlFor="email" className="sr-only">
56
+ Email address
57
+ </label>
58
+ <div className="relative">
59
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
60
+ <Mail className="h-5 w-5 text-gray-400" />
61
+ </div>
62
+ <input
63
+ id="email"
64
+ name="email"
65
+ type="email"
66
+ autoComplete="email"
67
+ required
68
+ value={email}
69
+ onChange={(e) => setEmail(e.target.value)}
70
+ className="input-field pl-10"
71
+ placeholder="Email address"
72
+ />
73
+ </div>
74
+ </div>
75
+
76
+ {/* Password Field */}
77
+ <div>
78
+ <label htmlFor="password" className="sr-only">
79
+ Password
80
+ </label>
81
+ <div className="relative">
82
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
83
+ <Lock className="h-5 w-5 text-gray-400" />
84
+ </div>
85
+ <input
86
+ id="password"
87
+ name="password"
88
+ type="password"
89
+ autoComplete="current-password"
90
+ required
91
+ value={password}
92
+ onChange={(e) => setPassword(e.target.value)}
93
+ className="input-field pl-10"
94
+ placeholder="Password"
95
+ />
96
+ </div>
97
+ </div>
98
+
99
+ {/* Remember Me & Forgot Password */}
100
+ <div className="flex items-center justify-between">
101
+ <div className="flex items-center">
102
+ <input
103
+ id="remember-me"
104
+ name="remember-me"
105
+ type="checkbox"
106
+ checked={rememberMe}
107
+ onChange={(e) => setRememberMe(e.target.checked)}
108
+ className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
109
+ />
110
+ <label htmlFor="remember-me" className="ml-2 block text-sm text-gray-900">
111
+ Remember me
112
+ </label>
113
+ </div>
114
+
115
+ <div className="text-sm">
116
+ <a href="#" className="font-medium text-primary-600 hover:text-primary-500">
117
+ Forgot your password?
118
+ </a>
119
+ </div>
120
+ </div>
121
+
122
+ {/* Submit Button */}
123
+ <button
124
+ type="submit"
125
+ disabled={isLoading}
126
+ className="group relative w-full flex justify-center py-3 px-4 btn-primary disabled:opacity-50 disabled:cursor-not-allowed"
127
+ >
128
+ {isLoading ? (
129
+ <>
130
+ <Loader className="animate-spin -ml-1 mr-3 h-5 w-5" />
131
+ Signing in...
132
+ </>
133
+ ) : (
134
+ 'Sign in to your account'
135
+ )}
136
+ </button>
137
+
138
+ {/* Divider */}
139
+ <div className="relative">
140
+ <div className="absolute inset-0 flex items-center">
141
+ <div className="w-full border-t border-gray-300"></div>
142
+ </div>
143
+ <div className="relative flex justify-center text-sm">
144
+ <span className="px-2 bg-white text-gray-500">Or continue with</span>
145
+ </div>
146
+ </div>
147
+
148
+ {/* SSO Options */}
149
+ <div className="grid grid-cols-2 gap-3">
150
+ <button
151
+ type="button"
152
+ className="w-full inline-flex justify-center py-2 px-4 btn-secondary">
153
+ Google
154
+ </button>
155
+ <button
156
+ type="button"
157
+ className="w-full inline-flex justify-center py-2 px-4 btn-secondary">
158
+ Microsoft
159
+ </button>
160
+ </div>
161
+ </form>
162
+ </div>
163
+ </div>
164
+ )
165
+ }
166
+
167
+ export default Login
168
+
169
+ </html>
tailwind.config.js ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./index.html",
5
+ "./src/**/*.{js,ts,jsx,tsx}",
6
+ ],
7
+ theme: {
8
+ extend: {
9
+ colors: {
10
+ primary: {
11
+ 50: '#eff6ff',
12
+ 100: '#dbeafe',
13
+ 200: '#bfdbfe',
14
+ 300: '#93c5fd',
15
+ 400: '#60a5fa',
16
+ 500: '#3b82f6',
17
+ 600: '#2563eb',
18
+ 700: '#1d4ed8',
19
+ 800: '#1e40af',
20
+ 900: '#1e3a8a',
21
+ },
22
+ secondary: {
23
+ 50: '#f8fafc',
24
+ 100: '#f1f5f9',
25
+ 200: '#e2e8f0',
26
+ 300: '#cbd5e1',
27
+ 400: '#94a3b8',
28
+ 500: '#64748b',
29
+ 600: '#475569',
30
+ 700: '#334155',
31
+ 800: '#1e293b',
32
+ 900: '#0f172a',
33
+ },
34
+ accent: {
35
+ 50: '#ecfdf5',
36
+ 100: '#d1fae5',
37
+ 200: '#a7f3d0',
38
+ 300: '#6ee7b7',
39
+ 400: '#34d399',
40
+ 500: '#10b981',
41
+ 600: '#059669',
42
+ 700: '#047857',
43
+ 800: '#065f46',
44
+ 900: '#064e3b',
45
+ },
46
+ alert: {
47
+ 50: '#fffbeb',
48
+ 100: '#fef3c7',
49
+ 200: '#fde68a',
50
+ 300: '#fcd34d',
51
+ 400: '#fbbf24',
52
+ 500: '#f59e0b',
53
+ 600: '#d97706',
54
+ 700: '#b45309',
55
+ 800: '#92400e',
56
+ 900: '#78350f',
57
+ }
58
+ },
59
+ fontFamily: {
60
+ sans: ['Inter', 'system-ui', 'sans-serif'],
61
+ },
62
+ borderRadius: {
63
+ 'card': '8px',
64
+ 'button': '6px',
65
+ },
66
+ spacing: {
67
+ '18': '4.5rem',
68
+ '88': '22rem',
69
+ }
70
+ },
71
+ },
72
+ plugins: [],
73
+ }