jaivial commited on
Commit
d502d70
·
verified ·
1 Parent(s): 8ea4121

Okay now add nitro as ssr and add an express backend with a simple login and authentication system that creates session id and stores session id as http only cookie and in the ssr middleware for all routes we do have a check for the http only cookie to check if is present or expired to redirect to home page or to login page

Browse files
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Nitro Auth Express Flow
3
- emoji: 📉
4
- colorFrom: gray
5
- colorTo: gray
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: Nitro Auth Express Flow
3
+ colorFrom: yellow
4
+ colorTo: pink
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).
components/navbar.js ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class AppNavbar extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+ this.shadowRoot.innerHTML = `
5
+ <style>
6
+ :host {
7
+ display: block;
8
+ background-color: #1e293b; /* slate-800 */
9
+ border-bottom: 1px solid #334155; /* slate-700 */
10
+ }
11
+ .container {
12
+ max-width: 100%;
13
+ padding: 0.75rem 1.5rem;
14
+ display: flex;
15
+ justify-content: space-between;
16
+ align-items: center;
17
+ }
18
+ .logo {
19
+ display: flex;
20
+ align-items: center;
21
+ gap: 0.5rem;
22
+ font-weight: 700;
23
+ font-size: 1.25rem;
24
+ color: white;
25
+ text-decoration: none;
26
+ }
27
+ .logo span {
28
+ color: #6366f1; /* indigo-500 */
29
+ }
30
+ .nav-links {
31
+ display: none;
32
+ gap: 2rem;
33
+ }
34
+ .nav-link {
35
+ color: #94a3b8; /* slate-400 */
36
+ text-decoration: none;
37
+ font-weight: 500;
38
+ font-size: 0.875rem;
39
+ transition: color 0.2s;
40
+ }
41
+ .nav-link:hover, .nav-link.active {
42
+ color: white;
43
+ }
44
+ .user-menu {
45
+ display: flex;
46
+ align-items: center;
47
+ gap: 1rem;
48
+ }
49
+ .icon-btn {
50
+ background: none;
51
+ border: none;
52
+ color: #94a3b8;
53
+ cursor: pointer;
54
+ padding: 0.25rem;
55
+ }
56
+ .icon-btn:hover {
57
+ color: white;
58
+ }
59
+ .logout-btn {
60
+ background-color: #ef4444;
61
+ color: white;
62
+ border: none;
63
+ padding: 0.5rem 1rem;
64
+ border-radius: 0.5rem;
65
+ font-size: 0.875rem;
66
+ cursor: pointer;
67
+ transition: background-color 0.2s;
68
+ display: flex;
69
+ align-items: center;
70
+ gap: 0.5rem;
71
+ }
72
+ .logout-btn:hover {
73
+ background-color: #dc2626;
74
+ }
75
+ @media (min-width: 768px) {
76
+ .nav-links {
77
+ display: flex;
78
+ }
79
+ }
80
+ </style>
81
+ <div class="container">
82
+ <a href="/" class="logo">
83
+ <svg height="24" viewBox="0 0 32 32" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M29,1H3A2,2,0,0,0,1,3V29a2,2,0,0,0,2,2H29a2,2,0,0,0,2-2V3A2,2,0,0,0,29,1ZM11,27H7V17h4ZM9,15.5A2.5,2.5,0,1,1,11.5,13,2.5,2.5,0,0,1,9,15.5ZM27,27H23V21c0-1.38-.5-2.5-2-2.5s-2,1.12-2,2.5v6H15V17h4v2.08a4,4,0,0,1,3.5-1.75c2.51,0,4.5,1.75,4.5,5.17Z" fill="#6366f1"/></svg>
84
+ Vite<span>ReactTS</span>
85
+ </a>
86
+ <nav class="nav-links">
87
+ <a href="#" class="nav-link active">Overview</a>
88
+ <a href="#" class="nav-link">Components</a>
89
+ <a href="#" class="nav-link">Documentation</a>
90
+ <a href="#" class="nav-link">Settings</a>
91
+ </nav>
92
+ <div class="user-menu">
93
+ <button class="icon-btn">
94
+ <i data-feather="search" width="18" height="18"></i>
95
+ </button>
96
+ <button class="icon-btn">
97
+ <i data-feather="bell" width="18" height="18"></i>
98
+ </button>
99
+ <button id="logoutBtn" class="logout-btn">
100
+ <i data-feather="log-out" width="16" height="16"></i>
101
+ <span>Logout</span>
102
+ </button>
103
+ <button class="icon-btn md:hidden">
104
+ <i data-feather="menu" width="18" height="18"></i>
105
+ </button>
106
+ </div>
107
+ <script>
108
+ document.getElementById('logoutBtn').addEventListener('click', async () => {
109
+ try {
110
+ const response = await fetch('/api/logout', {
111
+ method: 'POST',
112
+ credentials: 'include'
113
+ });
114
+ if (response.ok) {
115
+ window.location.href = '/login.html';
116
+ }
117
+ } catch (error) {
118
+ console.error('Logout error:', error);
119
+ }
120
+ });
121
+ </script>
122
+ </div>
123
+ `;
124
+ // Re-initialize feather icons inside shadow DOM
125
+ feather.replace();
126
+ }
127
+ }
128
+ customElements.define('app-navbar', AppNavbar);
components/sidebar.js ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class AppSidebar extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+ this.shadowRoot.innerHTML = `
5
+ <style>
6
+ :host {
7
+ display: block;
8
+ width: 250px;
9
+ background-color: #0f172a; /* slate-900 */
10
+ border-right: 1px solid #1e293b; /* slate-800 */
11
+ padding: 1.5rem 1rem;
12
+ display: none; /* Hidden on mobile by default */
13
+ flex-direction: column;
14
+ gap: 1rem;
15
+ }
16
+ @media (min-width: 1024px) {
17
+ :host {
18
+ display: flex;
19
+ }
20
+ }
21
+ .menu-label {
22
+ font-size: 0.75rem;
23
+ text-transform: uppercase;
24
+ letter-spacing: 0.05em;
25
+ color: #64748b; /* slate-500 */
26
+ margin-bottom: 0.5rem;
27
+ padding-left: 0.75rem;
28
+ }
29
+ .nav-item {
30
+ display: flex;
31
+ align-items: center;
32
+ gap: 0.75rem;
33
+ padding: 0.75rem;
34
+ color: #cbd5e1; /* slate-300 */
35
+ text-decoration: none;
36
+ border-radius: 0.5rem;
37
+ transition: background 0.2s, color 0.2s;
38
+ font-size: 0.9rem;
39
+ }
40
+ .nav-item:hover {
41
+ background-color: #1e293b; /* slate-800 */
42
+ color: white;
43
+ }
44
+ .nav-item.active {
45
+ background-color: #4f46e5; /* indigo-600 */
46
+ color: white;
47
+ }
48
+ .nav-item i {
49
+ width: 18px;
50
+ height: 18px;
51
+ }
52
+ </style>
53
+ <div class="menu-label">Main</div>
54
+ <a href="#" class="nav-item active">
55
+ <i data-feather="grid"></i>
56
+ <span>Dashboard</span>
57
+ </a>
58
+ <a href="#" class="nav-item">
59
+ <i data-feather="layers"></i>
60
+ <span>Projects</span>
61
+ </a>
62
+ <a href="#" class="nav-item">
63
+ <i data-feather="users"></i>
64
+ <span>Team</span>
65
+ </a>
66
+
67
+ <div class="menu-label" style="margin-top: 1rem;">Development</div>
68
+ <a href="#" class="nav-item">
69
+ <i data-feather="code"></i>
70
+ <span>Components</span>
71
+ </a>
72
+ <a href="#" class="nav-item">
73
+ <i data-feather="database"></i>
74
+ <span>API Keys</span>
75
+ </a>
76
+ <a href="#" class="nav-item">
77
+ <i data-feather="terminal"></i>
78
+ <span>Logs</span>
79
+ </a>
80
+ `;
81
+ feather.replace();
82
+ }
83
+ }
84
+ customElements.define('app-sidebar', AppSidebar);
components/stat-card.js ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class StatCard extends HTMLElement {
2
+ connectedCallback() {
3
+ const title = this.getAttribute('title') || 'Metric';
4
+ const value = this.getAttribute('value') || '0';
5
+ const icon = this.getAttribute('icon') || 'activity';
6
+ const color = this.getAttribute('color') || 'text-white';
7
+ const trend = this.getAttribute('trend') || '';
8
+
9
+ this.attachShadow({ mode: 'open' });
10
+ this.shadowRoot.innerHTML = `
11
+ <style>
12
+ :host {
13
+ display: block;
14
+ background-color: #1e293b; /* slate-800 */
15
+ border-radius: 0.75rem;
16
+ padding: 1.5rem;
17
+ border: 1px solid #334155; /* slate-700 */
18
+ transition: transform 0.2s, box-shadow 0.2s;
19
+ height: 100%;
20
+ }
21
+ :host:hover {
22
+ transform: translateY(-4px);
23
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
24
+ border-color: #475569;
25
+ }
26
+ .card-header {
27
+ display: flex;
28
+ justify-content: space-between;
29
+ align-items: flex-start;
30
+ margin-bottom: 0.5rem;
31
+ }
32
+ .icon-box {
33
+ padding: 0.5rem;
34
+ background-color: #0f172a; /* slate-900 */
35
+ border-radius: 0.5rem;
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ }
40
+ .icon-box i {
41
+ width: 20px;
42
+ height: 20px;
43
+ }
44
+ .value {
45
+ font-size: 1.875rem;
46
+ font-weight: 700;
47
+ color: white;
48
+ line-height: 1;
49
+ }
50
+ .title {
51
+ color: #94a3b8; /* slate-400 */
52
+ font-size: 0.875rem;
53
+ margin-top: 0.25rem;
54
+ }
55
+ .trend {
56
+ font-size: 0.75rem;
57
+ margin-top: 0.75rem;
58
+ display: inline-block;
59
+ padding: 0.125rem 0.5rem;
60
+ border-radius: 9999px;
61
+ background-color: rgba(16, 185, 129, 0.1); /* emerald low opacity */
62
+ color: #34d399; /* emerald-400 */
63
+ }
64
+ </style>
65
+ <div class="card-header">
66
+ <div>
67
+ <div class="value">${value}</div>
68
+ <div class="title">${title}</div>
69
+ </div>
70
+ <div class="icon-box ${color}">
71
+ <i data-feather="${icon}"></i>
72
+ </div>
73
+ </div>
74
+ ${trend ? `<div class="trend">${trend}</div>` : ''}
75
+ `;
76
+ feather.replace();
77
+ }
78
+ }
79
+ customElements.define('stat-card', StatCard);
index.html CHANGED
@@ -1,19 +1,161 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>React Vite TS Page 1</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
10
+ <script src="https://unpkg.com/feather-icons"></script>
11
+ <script>
12
+ tailwind.config = {
13
+ darkMode: 'class',
14
+ theme: {
15
+ extend: {
16
+ colors: {
17
+ primary: '#4f46e5', // Indigo-600
18
+ secondary: '#ec4899', // Pink-500
19
+ dark: '#0f172a', // Slate-900
20
+ darker: '#020617', // Slate-950
21
+ surface: '#1e293b', // Slate-800
22
+ }
23
+ }
24
+ }
25
+ }
26
+ </script>
27
+ </head>
28
+ <body class="bg-darker text-slate-200 font-sans antialiased overflow-hidden h-screen flex flex-col">
29
+
30
+ <!-- Navbar Web Component -->
31
+ <app-navbar></app-navbar>
32
+
33
+ <div class="flex flex-1 overflow-hidden">
34
+ <!-- Sidebar Web Component -->
35
+ <app-sidebar></app-sidebar>
36
+
37
+ <!-- Main Content Area -->
38
+ <main class="flex-1 overflow-y-auto p-4 md:p-8 relative">
39
+ <!-- Background decorative blobs -->
40
+ <div class="absolute top-0 left-0 w-96 h-96 bg-primary/20 rounded-full blur-3xl -translate-x-1/2 -translate-y-1/2 pointer-events-none"></div>
41
+ <div class="absolute bottom-0 right-0 w-96 h-96 bg-secondary/10 rounded-full blur-3xl translate-x-1/2 translate-y-1/2 pointer-events-none"></div>
42
+
43
+ <div class="max-w-5xl mx-auto relative z-10 space-y-8">
44
+ <!-- Header Section -->
45
+ <header class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
46
+ <div>
47
+ <h1 class="text-3xl font-bold text-white mb-2">Dashboard Overview</h1>
48
+ <p class="text-slate-400">Welcome back to your Vite React TS project.</p>
49
+ </div>
50
+ <button class="flex items-center gap-2 bg-primary hover:bg-indigo-700 text-white px-4 py-2 rounded-lg transition-all shadow-lg shadow-primary/20 active:scale-95">
51
+ <i data-feather="plus"></i>
52
+ <span>New Project</span>
53
+ </button>
54
+ </header>
55
+
56
+ <!-- Stats Grid -->
57
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
58
+ <stat-card title="Build Time" value="142ms" icon="zap" color="text-yellow-400" trend="+12% faster"></stat-card>
59
+ <stat-card title="Bundle Size" value="42kb" icon="package" color="text-primary" trend="Optimized"></stat-card>
60
+ <stat-card title="Type Coverage" value="100%" icon="check-circle" color="text-emerald-400" trend="Strict Mode"></stat-card>
61
+ </div>
62
+
63
+ <!-- Content Section -->
64
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
65
+ <!-- Main Article/List -->
66
+ <div class="lg:col-span-2 bg-surface border border-slate-700/50 rounded-xl p-6 shadow-xl">
67
+ <div class="flex justify-between items-center mb-6">
68
+ <h2 class="text-xl font-semibold text-white">Recent Activity</h2>
69
+ <a href="#" class="text-sm text-primary hover:text-white transition-colors">View All</a>
70
+ </div>
71
+ <div class="space-y-4">
72
+ <!-- Item 1 -->
73
+ <div class="flex items-center gap-4 p-4 bg-dark/50 rounded-lg border border-slate-700/50 hover:border-primary/50 transition-colors">
74
+ <div class="p-3 bg-surface rounded-full text-secondary">
75
+ <i data-feather="git-commit"></i>
76
+ </div>
77
+ <div class="flex-1">
78
+ <h3 class="font-medium text-white">Updated Dependencies</h3>
79
+ <p class="text-sm text-slate-400">React 18.3.0 and Vite 5.0.0 installed</p>
80
+ </div>
81
+ <span class="text-xs text-slate-500">2m ago</span>
82
+ </div>
83
+ <!-- Item 2 -->
84
+ <div class="flex items-center gap-4 p-4 bg-dark/50 rounded-lg border border-slate-700/50 hover:border-primary/50 transition-colors">
85
+ <div class="p-3 bg-surface rounded-full text-blue-400">
86
+ <i data-feather="file-text"></i>
87
+ </div>
88
+ <div class="flex-1">
89
+ <h3 class="font-medium text-white">Page 1 Created</h3>
90
+ <p class="text-sm text-slate-400">Initial setup of the main dashboard page</p>
91
+ </div>
92
+ <span class="text-xs text-slate-500">1h ago</span>
93
+ </div>
94
+ <!-- Item 3 -->
95
+ <div class="flex items-center gap-4 p-4 bg-dark/50 rounded-lg border border-slate-700/50 hover:border-primary/50 transition-colors">
96
+ <div class="p-3 bg-surface rounded-full text-purple-400">
97
+ <i data-feather="code"></i>
98
+ </div>
99
+ <div class="flex-1">
100
+ <h3 class="font-medium text-white">Configured Tailwind</h3>
101
+ <p class="text-sm text-slate-400">Dark mode enabled by default</p>
102
+ </div>
103
+ <span class="text-xs text-slate-500">3h ago</span>
104
+ </div>
105
+ </div>
106
+ </div>
107
+
108
+ <!-- Side Widget -->
109
+ <div class="bg-surface border border-slate-700/50 rounded-xl p-6 shadow-xl">
110
+ <h2 class="text-xl font-semibold text-white mb-4">System Status</h2>
111
+ <div class="space-y-6">
112
+ <div>
113
+ <div class="flex justify-between text-sm mb-2">
114
+ <span class="text-slate-400">Server Load</span>
115
+ <span class="text-white">24%</span>
116
+ </div>
117
+ <div class="h-2 bg-dark rounded-full overflow-hidden">
118
+ <div class="h-full bg-primary w-1/4 rounded-full"></div>
119
+ </div>
120
+ </div>
121
+ <div>
122
+ <div class="flex justify-between text-sm mb-2">
123
+ <span class="text-slate-400">Memory Usage</span>
124
+ <span class="text-white">512MB</span>
125
+ </div>
126
+ <div class="h-2 bg-dark rounded-full overflow-hidden">
127
+ <div class="h-full bg-secondary w-1/2 rounded-full"></div>
128
+ </div>
129
+ </div>
130
+
131
+ <div class="pt-4 border-t border-slate-700">
132
+ <div class="flex items-center gap-3 mb-3">
133
+ <img src="http://static.photos/people/100x100/12" alt="User" class="w-10 h-10 rounded-full border-2 border-surface">
134
+ <div>
135
+ <p class="text-sm font-medium text-white">Developer One</p>
136
+ <p class="text-xs text-green-400">Online</p>
137
+ </div>
138
+ </div>
139
+ <div class="flex gap-2">
140
+ <button class="flex-1 py-2 bg-dark hover:bg-darker text-xs rounded border border-slate-700 transition-colors">Profile</button>
141
+ <button class="flex-1 py-2 bg-dark hover:bg-darker text-xs rounded border border-slate-700 transition-colors">Settings</button>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </div>
148
+ </main>
149
+ </div>
150
+
151
+ <!-- Web Components Scripts -->
152
+ <script src="components/navbar.js"></script>
153
+ <script src="components/sidebar.js"></script>
154
+ <script src="components/stat-card.js"></script>
155
+
156
+ <!-- Main JS -->
157
+ <script src="script.js"></script>
158
+ <script>feather.replace();</script>
159
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
160
+ </body>
161
+ </html>
login.html ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Login - Vite React TS</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
10
+ <script>
11
+ tailwind.config = {
12
+ darkMode: 'class',
13
+ theme: {
14
+ extend: {
15
+ colors: {
16
+ primary: '#4f46e5',
17
+ secondary: '#ec4899',
18
+ dark: '#0f172a',
19
+ darker: '#020617',
20
+ surface: '#1e293b',
21
+ }
22
+ }
23
+ }
24
+ }
25
+ </script>
26
+ </head>
27
+ <body class="bg-darker text-slate-200 font-sans antialiased min-h-screen flex items-center justify-center relative overflow-hidden">
28
+
29
+ <!-- Background decorative blobs -->
30
+ <div class="absolute top-0 left-0 w-96 h-96 bg-primary/20 rounded-full blur-3xl -translate-x-1/2 -translate-y-1/2 pointer-events-none"></div>
31
+ <div class="absolute bottom-0 right-0 w-96 h-96 bg-secondary/10 rounded-full blur-3xl translate-x-1/2 translate-y-1/2 pointer-events-none"></div>
32
+
33
+ <div class="relative z-10 w-full max-w-md p-8">
34
+ <!-- Logo -->
35
+ <div class="text-center mb-8">
36
+ <a href="/" class="inline-flex items-center gap-2 text-2xl font-bold text-white">
37
+ <svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><path d="M29,1H3A2,2,0,0,0,1,3V29a2,2,0,0,0,2,2H29a2,2,0,0,0,2-2V3A2,2,0,0,0,29,1ZM11,27H7V17h4ZM9,15.5A2.5,2.5,0,1,1,11.5,13,2.5,2.5,0,0,1,9,15.5ZM27,27H23V21c0-1.38-.5-2.5-2-2.5s-2,1.12-2,2.5v6H15V17h4v2.08a4,4,0,0,1,3.5-1.75c2.51,0,4.5,1.75,4.5,5.17Z" fill="#6366f1"/></svg>
38
+ Vite<span>ReactTS</span>
39
+ </a>
40
+ <p class="text-slate-400 mt-2">Sign in to your account</p>
41
+ </div>
42
+
43
+ <!-- Login Form -->
44
+ <div class="bg-surface border border-slate-700/50 rounded-2xl p-8 shadow-2xl">
45
+ <form id="loginForm" class="space-y-6">
46
+ <!-- Email Input -->
47
+ <div>
48
+ <label for="email" class="block text-sm font-medium text-slate-300 mb-2">Email Address</label>
49
+ <div class="relative">
50
+ <i data-feather="mail" class="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500 w-5 h-5"></i>
51
+ <input
52
+ type="email"
53
+ id="email"
54
+ name="email"
55
+ required
56
+ class="w-full bg-dark border border-slate-700 rounded-lg py-3 pl-10 pr-4 text-white placeholder-slate-500 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-colors"
57
+ placeholder="admin@vite.com"
58
+ >
59
+ </div>
60
+ </div>
61
+
62
+ <!-- Password Input -->
63
+ <div>
64
+ <label for="password" class="block text-sm font-medium text-slate-300 mb-2">Password</label>
65
+ <div class="relative">
66
+ <i data-feather="lock" class="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500 w-5 h-5"></i>
67
+ <input
68
+ type="password"
69
+ id="password"
70
+ name="password"
71
+ required
72
+ class="w-full bg-dark border border-slate-700 rounded-lg py-3 pl-10 pr-4 text-white placeholder-slate-500 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-colors"
73
+ placeholder="••••••••"
74
+ >
75
+ </div>
76
+ </div>
77
+
78
+ <!-- Error Message -->
79
+ <div id="errorMessage" class="hidden bg-red-500/10 border border-red-500/50 text-red-400 px-4 py-3 rounded-lg text-sm"></div>
80
+
81
+ <!-- Submit Button -->
82
+ <button
83
+ type="submit"
84
+ id="submitBtn"
85
+ class="w-full bg-primary hover:bg-indigo-700 text-white font-semibold py-3 px-4 rounded-lg transition-all shadow-lg shadow-primary/20 active:scale-[0.98] flex items-center justify-center gap-2"
86
+ >
87
+ <i data-feather="log-in"></i>
88
+ <span>Sign In</span>
89
+ </button>
90
+ </form>
91
+
92
+ <!-- Demo Credentials -->
93
+ <div class="mt-6 p-4 bg-dark/50 rounded-lg border border-slate-700/50">
94
+ <p class="text-xs text-slate-500 mb-2">Demo Credentials:</p>
95
+ <div class="text-xs space-y-1">
96
+ <p class="text-slate-400">Admin: <span class="text-primary font-mono">admin@vite.com / admin123</span></p>
97
+ <p class="text-slate-400">User: <span class="text-primary font-mono">user@vite.com / user123</span></p>
98
+ </div>
99
+ </div>
100
+ </div>
101
+
102
+ <!-- Footer -->
103
+ <p class="text-center text-sm text-slate-500 mt-8">
104
+ Protected by Nitro SSR + Express Sessions
105
+ </p>
106
+ </div>
107
+
108
+ <script>
109
+ feather.replace();
110
+
111
+ const loginForm = document.getElementById('loginForm');
112
+ const errorMessage = document.getElementById('errorMessage');
113
+ const submitBtn = document.getElementById('submitBtn');
114
+
115
+ loginForm.addEventListener('submit', async (e) => {
116
+ e.preventDefault();
117
+
118
+ const email = document.getElementById('email').value;
119
+ const password = document.getElementById('password').value;
120
+
121
+ // Show loading state
122
+ submitBtn.disabled = true;
123
+ submitBtn.innerHTML = `
124
+ <svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
125
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
126
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
127
+ </svg>
128
+ <span>Signing in...</span>
129
+ `;
130
+
131
+ try {
132
+ const response = await fetch('/api/login', {
133
+ method: 'POST',
134
+ headers: {
135
+ 'Content-Type': 'application/json'
136
+ },
137
+ credentials: 'include',
138
+ body: JSON.stringify({ email, password })
139
+ });
140
+
141
+ const data = await response.json();
142
+
143
+ if (response.ok) {
144
+ // Redirect to dashboard on success
145
+ window.location.href = '/';
146
+ } else {
147
+ // Show error message
148
+ errorMessage.textContent = data.message || 'Login failed. Please try again.';
149
+ errorMessage.classList.remove('hidden');
150
+ }
151
+ } catch (error) {
152
+ errorMessage.textContent = 'Network error. Please try again.';
153
+ errorMessage.classList.remove('hidden');
154
+ } finally {
155
+ // Reset button state
156
+ submitBtn.disabled = false;
157
+ submitBtn.innerHTML = `
158
+ <i data-feather="log-in"></i>
159
+ <span>Sign In</span>
160
+ `;
161
+ feather.replace();
162
+ }
163
+ });
164
+
165
+ // Hide error message when user starts typing
166
+ document.getElementById('email').addEventListener('input', () => {
167
+ errorMessage.classList.add('hidden');
168
+ });
169
+ document.getElementById('password').addEventListener('input', () => {
170
+ errorMessage.classList.add('hidden');
171
+ });
172
+ </script>
173
+ </body>
174
+ </html>
nitro.config.ts ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```typescript
2
+ import { defineNitroConfig } from 'nitropack'
3
+
4
+ export default defineNitroConfig({
5
+ srcDir: './server',
6
+ publicAssets: {
7
+ dir: './',
8
+ maxAge: 60 * 60 * 24 * 365 // 1 year
9
+ },
10
+ prerender: {
11
+ failOnError: false,
12
+ routes: ['/']
13
+ },
14
+ runtimeConfig: {
15
+ sessionSecret: 'your-secret-key-change-in-production-min-32-chars',
16
+ // Session will be stored in memory for development
17
+ // In production, use a proper store like Redis
18
+ }
19
+ })
20
+ ```
package.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```json
2
+ {
3
+ "name": "nitro-auth-express",
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "npx nitropack dev",
8
+ "build": "npx nitropack build",
9
+ "preview": "node .output/server/index.mjs"
10
+ },
11
+ "dependencies": {
12
+ "nitropack": "^2.9.0",
13
+ "h3": "^1.11.0",
14
+ "express-session": "^1.18.0",
15
+ "cookie-parser": "^1.4.6"
16
+ },
17
+ "devDependencies": {
18
+ "@types/express-session": "^1.18.0"
19
+ }
20
+ }
21
+ ```
script.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ console.log('Project loaded: Vite React TS Velocity');
3
+
4
+ // Simulating some interaction logic that would normally be in React useEffect
5
+ const cards = document.querySelectorAll('stat-card');
6
+
7
+ cards.forEach(card => {
8
+ card.addEventListener('mouseenter', () => {
9
+ card.style.transform = 'translateY(-2px)';
10
+ });
11
+ card.addEventListener('mouseleave', () => {
12
+ card.style.transform = 'translateY(0)';
13
+ });
14
+ });
15
+
16
+ // Initialize tooltips or other interactions here
17
+ });
server/api/login.post.ts ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```typescript
2
+ import { eventHandler, readBody, setCookie, createError } from 'h3'
3
+ import { sessionStore, sessionDuration } from '../middleware/auth'
4
+
5
+ // Mock user database (use a real database in production)
6
+ const users = [
7
+ { id: 1, email: 'admin@vite.com', password: 'admin123', name: 'Admin User' },
8
+ { id: 2, email: 'user@vite.com', password: 'user123', name: 'Regular User' }
9
+ ]
10
+
11
+ export default eventHandler(async (event) => {
12
+ const body = await readBody(event)
13
+ const { email, password } = body
14
+
15
+ // Validate input
16
+ if (!email || !password) {
17
+ throw createError({
18
+ statusCode: 400,
19
+ message: 'Email and password are required'
20
+ })
21
+ }
22
+
23
+ // Find user
24
+ const user = users.find(u => u.email === email && u.password === password)
25
+
26
+ if (!user) {
27
+ throw createError({
28
+ statusCode: 401,
29
+ message: 'Invalid email or password'
30
+ })
31
+ }
32
+
33
+ // Generate session ID
34
+ const sessionId = Buffer.from(`${user.id}-${Date.now()}-${Math.random()}`).toString('base64')
35
+
36
+ // Store session
37
+ sessionStore.set(sessionId, {
38
+ userId: user.id,
39
+ email: user.email,
40
+ createdAt: Date.now()
41
+ })
42
+
43
+ // Set HTTP-only cookie
44
+ const cookieOptions = {
45
+ httpOnly: true,
46
+ secure: process.env.NODE_ENV === 'production',
47
+ sameSite: 'lax' as const,
48
+ maxAge: sessionDuration / 1000,
49
+ path: '/'
50
+ }
51
+
52
+ setCookie(event, 'session_id', sessionId, cookieOptions)
53
+
54
+ return {
55
+ success: true,
56
+ user: {
57
+ id: user.id,
58
+ email: user.email,
59
+ name: user.name
60
+ }
61
+ }
62
+ })
63
+ ```
server/api/logout.post.ts ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```typescript
2
+ import { eventHandler, deleteCookie } from 'h3'
3
+ import { sessionStore } from '../middleware/auth'
4
+
5
+ export default eventHandler((event) => {
6
+ const sessionId = getCookie(event, 'session_id')
7
+
8
+ if (sessionId) {
9
+ sessionStore.delete(sessionId)
10
+ deleteCookie(event, 'session_id', {
11
+ path: '/',
12
+ httpOnly: true
13
+ })
14
+ }
15
+
16
+ return { success: true }
17
+ })
18
+
19
+ import { getCookie } from 'h3'
20
+ ```
server/api/session.get.ts ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```typescript
2
+ import { eventHandler, getCookie } from 'h3'
3
+ import { sessionStore } from '../middleware/auth'
4
+
5
+ export default eventHandler((event) => {
6
+ const sessionId = getCookie(event, 'session_id')
7
+
8
+ if (!sessionId || !sessionStore.has(sessionId)) {
9
+ return { authenticated: false }
10
+ }
11
+
12
+ const session = sessionStore.get(sessionId)!
13
+ const now = Date.now()
14
+
15
+ if (now - session.createdAt > 24 * 60 * 60 * 1000) {
16
+ sessionStore.delete(sessionId)
17
+ return { authenticated: false }
18
+ }
19
+
20
+ return {
21
+ authenticated: true,
22
+ user: {
23
+ id: session.userId,
24
+ email: session.email
25
+ }
26
+ }
27
+ })
28
+ ```
server/middleware/auth.ts ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```typescript
2
+ import { eventHandler, setCookie, getCookie, deleteCookie, createError } from 'h3'
3
+
4
+ // Simple in-memory session store (use Redis in production)
5
+ const sessionStore = new Map<string, { userId: number; email: string; createdAt: number }>()
6
+
7
+ export const sessionDuration = 24 * 60 * 60 * 1000 // 24 hours in milliseconds
8
+
9
+ // Middleware to check authentication
10
+ export default eventHandler((event) => {
11
+ const path = event.node.req.url || ''
12
+ const sessionId = getCookie(event, 'session_id')
13
+
14
+ // Skip auth check for login page and login API
15
+ const publicPaths = ['/login.html', '/api/login', '/api/logout', '/_nuxt']
16
+ const isPublicPath = publicPaths.some(p => path.startsWith(p))
17
+
18
+ if (isPublicPath) {
19
+ return
20
+ }
21
+
22
+ // Check if user is authenticated
23
+ if (!sessionId || !sessionStore.has(sessionId)) {
24
+ // Redirect to login page
25
+ event.node.res.writeHead(302, {
26
+ 'Location': '/login.html'
27
+ })
28
+ event.node.res.end()
29
+ return
30
+ }
31
+
32
+ // Check if session is expired
33
+ const session = sessionStore.get(sessionId)!
34
+ const now = Date.now()
35
+ if (now - session.createdAt > sessionDuration) {
36
+ sessionStore.delete(sessionId)
37
+ deleteCookie(event, 'session_id')
38
+ event.node.res.writeHead(302, {
39
+ 'Location': '/login.html'
40
+ })
41
+ event.node.res.end()
42
+ return
43
+ }
44
+
45
+ // Attach session data to event context
46
+ event.context.session = {
47
+ id: sessionId,
48
+ ...session
49
+ }
50
+ })
51
+
52
+ // Export session store for use in API routes
53
+ export { sessionStore }
54
+ ```
style.css CHANGED
@@ -1,28 +1,40 @@
 
 
1
  body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
28
  }
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
2
+
3
  body {
4
+ font-family: 'Inter', sans-serif;
5
+ }
6
+
7
+ /* Custom Scrollbar */
8
+ ::-webkit-scrollbar {
9
+ width: 8px;
10
+ height: 8px;
11
  }
12
 
13
+ ::-webkit-scrollbar-track {
14
+ background: #0f172a;
 
15
  }
16
 
17
+ ::-webkit-scrollbar-thumb {
18
+ background: #334155;
19
+ border-radius: 4px;
 
 
20
  }
21
 
22
+ ::-webkit-scrollbar-thumb:hover {
23
+ background: #475569;
 
 
 
 
24
  }
25
 
26
+ /* Smooth transitions for links and buttons */
27
+ a, button {
28
+ transition: all 0.2s ease-in-out;
29
  }
30
+
31
+ /* Selection color */
32
+ ::selection {
33
+ background-color: #4f46e5;
34
+ color: white;
35
+ }
36
+
37
+ ::-moz-selection {
38
+ background-color: #4f46e5;
39
+ color: white;
40
+ }