maylinejix commited on
Commit
50c1c55
·
verified ·
1 Parent(s): 1178956

Upload 7 files

Browse files
public/admin.html ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Admin Panel - ShortLink</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
+ <link rel="stylesheet" href="/styles.css">
9
+ </head>
10
+ <body>
11
+ <div class="container">
12
+ <header class="header">
13
+ <div>
14
+ <h1 class="title">
15
+ <i class="fas fa-user-shield"></i> Admin Panel
16
+ </h1>
17
+ <p class="subtitle">Create and manage short links</p>
18
+ </div>
19
+ <button onclick="location.href='/home'" class="btn btn-secondary">
20
+ <i class="fas fa-home"></i> Back to Home
21
+ </button>
22
+ </header>
23
+
24
+ <div id="login-section" class="card">
25
+ <h2 class="card-title">
26
+ <i class="fas fa-key"></i> Login
27
+ </h2>
28
+ <form id="login-form">
29
+ <div class="form-group">
30
+ <label><i class="fas fa-lock"></i> Password</label>
31
+ <input type="password" id="password" class="input" placeholder="Enter admin password" required>
32
+ </div>
33
+ <button type="submit" class="btn btn-primary btn-full">
34
+ <i class="fas fa-sign-in-alt"></i> Login
35
+ </button>
36
+ </form>
37
+ </div>
38
+
39
+ <div id="create-section" class="card" style="display: none;">
40
+ <h2 class="card-title">
41
+ <i class="fas fa-plus-circle"></i> Make Short URL
42
+ </h2>
43
+ <form id="create-form">
44
+ <div class="form-group">
45
+ <label><i class="fas fa-heading"></i> Title</label>
46
+ <input type="text" id="title" class="input" placeholder="SKIP SFL.GL" required>
47
+ </div>
48
+
49
+ <div class="form-group">
50
+ <label><i class="fas fa-external-link-alt"></i> Redirect URL</label>
51
+ <input type="url" id="redirect" class="input" placeholder="https://github.com/herzonly" required>
52
+ </div>
53
+
54
+ <div class="form-group">
55
+ <label><i class="fas fa-tag"></i> Short Name</label>
56
+ <input type="text" id="name" class="input" placeholder="skipsfl" required>
57
+ <small class="help-text">Access: yourdomain.com/home/url/<strong>skipsfl</strong>/id</small>
58
+ </div>
59
+
60
+ <div class="form-group">
61
+ <label class="switch-label">
62
+ <span><i class="fas fa-check-circle"></i> Require Verification</span>
63
+ <label class="switch">
64
+ <input type="checkbox" id="verify">
65
+ <span class="slider"></span>
66
+ </label>
67
+ </label>
68
+ </div>
69
+
70
+ <button type="submit" class="btn btn-primary btn-full">
71
+ <i class="fas fa-plus"></i> Create Short Link
72
+ </button>
73
+ </form>
74
+
75
+ <button onclick="logout()" class="btn btn-danger btn-full" style="margin-top: 1rem;">
76
+ <i class="fas fa-sign-out-alt"></i> Logout
77
+ </button>
78
+ </div>
79
+ </div>
80
+
81
+ <div id="alert-container"></div>
82
+ <script src="/admin.js"></script>
83
+ </body>
84
+ </html>
public/admin.js ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // admin.js - Admin Panel JavaScript
2
+ const loginSection = document.getElementById('login-section');
3
+ const createSection = document.getElementById('create-section');
4
+ const loginForm = document.getElementById('login-form');
5
+ const createForm = document.getElementById('create-form');
6
+
7
+ // Check if already logged in
8
+ if (localStorage.getItem('admin_token')) {
9
+ loginSection.style.display = 'none';
10
+ createSection.style.display = 'block';
11
+ }
12
+
13
+ loginForm.addEventListener('submit', async (e) => {
14
+ e.preventDefault();
15
+ const password = document.getElementById('password').value;
16
+
17
+ try {
18
+ const response = await fetch('/api/login', {
19
+ method: 'POST',
20
+ headers: { 'Content-Type': 'application/json' },
21
+ body: JSON.stringify({ password })
22
+ });
23
+
24
+ const data = await response.json();
25
+
26
+ if (data.success) {
27
+ localStorage.setItem('admin_token', data.token);
28
+ loginSection.style.display = 'none';
29
+ createSection.style.display = 'block';
30
+ showAlert('success', 'Login successful!');
31
+ } else {
32
+ showAlert('error', 'Invalid password');
33
+ }
34
+ } catch (error) {
35
+ showAlert('error', 'Login failed');
36
+ }
37
+ });
38
+
39
+ createForm.addEventListener('submit', async (e) => {
40
+ e.preventDefault();
41
+
42
+ const title = document.getElementById('title').value;
43
+ const redirect = document.getElementById('redirect').value;
44
+ const name = document.getElementById('name').value;
45
+ const verify = document.getElementById('verify').checked;
46
+
47
+ const token = localStorage.getItem('admin_token');
48
+
49
+ try {
50
+ const response = await fetch('/api/create', {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/json',
54
+ 'Authorization': `Bearer ${token}`
55
+ },
56
+ body: JSON.stringify({ title, redirect, name, verify })
57
+ });
58
+
59
+ const data = await response.json();
60
+
61
+ if (data.success) {
62
+ showAlert('success', 'Short link created successfully!');
63
+ createForm.reset();
64
+ setTimeout(() => {
65
+ window.location.href = '/home';
66
+ }, 2000);
67
+ } else {
68
+ showAlert('error', 'Failed to create link');
69
+ }
70
+ } catch (error) {
71
+ showAlert('error', 'Failed to create link');
72
+ }
73
+ });
74
+
75
+ function logout() {
76
+ localStorage.removeItem('admin_token');
77
+ window.location.href = '/home';
78
+ }
79
+
80
+ function showAlert(type, message) {
81
+ const container = document.getElementById('alert-container');
82
+ const alert = document.createElement('div');
83
+ alert.className = `alert alert-${type}`;
84
+
85
+ let icon = 'fa-check-circle';
86
+ if (type === 'error') icon = 'fa-exclamation-circle';
87
+ else if (type === 'info') icon = 'fa-info-circle';
88
+
89
+ alert.innerHTML = `
90
+ <span class="alert-icon"><i class="fas ${icon}"></i></span>
91
+ <span>${message}</span>
92
+ <button class="alert-close" onclick="this.parentElement.remove()">
93
+ <i class="fas fa-times"></i>
94
+ </button>
95
+ `;
96
+ container.appendChild(alert);
97
+ setTimeout(() => alert.remove(), 5000);
98
+ }
public/home.html ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ShortLink Manager</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
+ <link rel="stylesheet" href="/styles.css">
9
+ </head>
10
+ <body>
11
+ <div class="container">
12
+ <header class="header">
13
+ <div>
14
+ <h1 class="title">
15
+ <i class="fas fa-link"></i> ShortLink Manager
16
+ </h1>
17
+ <p class="subtitle">Secure URL shortening with OCR verification</p>
18
+ </div>
19
+ <button onclick="location.href='/home/admin/create'" class="btn btn-primary">
20
+ <i class="fas fa-shield-alt"></i> Admin Panel
21
+ </button>
22
+ </header>
23
+
24
+ <div id="links-container" class="grid">
25
+ <div class="loading">
26
+ <i class="fas fa-spinner fa-spin fa-2x"></i>
27
+ <p>Loading links...</p>
28
+ </div>
29
+ </div>
30
+ </div>
31
+
32
+ <div id="alert-container"></div>
33
+ <script src="/home.js"></script>
34
+ </body>
35
+ </html>
public/home.js ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // home.js - Homepage JavaScript
2
+ async function loadLinks() {
3
+ try {
4
+ const response = await fetch('/api/links');
5
+ const links = await response.json();
6
+
7
+ const container = document.getElementById('links-container');
8
+
9
+ if (links.length === 0) {
10
+ container.innerHTML = `
11
+ <div class="empty-state">
12
+ <div class="empty-state-icon">
13
+ <i class="fas fa-link fa-4x"></i>
14
+ </div>
15
+ <p>No short links created yet</p>
16
+ </div>
17
+ `;
18
+ return;
19
+ }
20
+
21
+ container.innerHTML = links.map(link => `
22
+ <div class="card">
23
+ <div class="card-header">
24
+ <h3 class="card-title">
25
+ <i class="fas fa-file-alt"></i> ${escapeHtml(link.title)}
26
+ </h3>
27
+ ${link.verify ? '<span class="badge"><i class="fas fa-lock"></i> Verified</span>' : ''}
28
+ </div>
29
+ <p class="card-text">
30
+ <i class="fas fa-external-link-alt"></i> ${escapeHtml(link.redirect)}
31
+ </p>
32
+ <div class="code-box">
33
+ <i class="fas fa-code"></i> /home/url/${escapeHtml(link.name)}/${link.id}
34
+ </div>
35
+ <button onclick="goToLink('${link.name}', '${link.id}')" class="btn btn-primary btn-full">
36
+ <i class="fas fa-rocket"></i> Access Link
37
+ </button>
38
+ </div>
39
+ `).join('');
40
+ } catch (error) {
41
+ console.error('Error loading links:', error);
42
+ showAlert('error', 'Failed to load links');
43
+ }
44
+ }
45
+
46
+ function goToLink(name, id) {
47
+ window.location.href = `/home/url/${name}/${id}`;
48
+ }
49
+
50
+ function escapeHtml(text) {
51
+ const div = document.createElement('div');
52
+ div.textContent = text;
53
+ return div.innerHTML;
54
+ }
55
+
56
+ function showAlert(type, message) {
57
+ const container = document.getElementById('alert-container');
58
+ const alert = document.createElement('div');
59
+ alert.className = `alert alert-${type}`;
60
+
61
+ let icon = 'fa-check-circle';
62
+ if (type === 'error') icon = 'fa-exclamation-circle';
63
+ else if (type === 'info') icon = 'fa-info-circle';
64
+
65
+ alert.innerHTML = `
66
+ <span class="alert-icon"><i class="fas ${icon}"></i></span>
67
+ <span>${message}</span>
68
+ <button class="alert-close" onclick="this.parentElement.remove()">
69
+ <i class="fas fa-times"></i>
70
+ </button>
71
+ `;
72
+ container.appendChild(alert);
73
+ setTimeout(() => alert.remove(), 5000);
74
+ }
75
+
76
+ // Load links on page load
77
+ loadLinks();
public/styles.css ADDED
@@ -0,0 +1,520 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* styles.css - Complete Styling */
2
+
3
+ * {
4
+ margin: 0;
5
+ padding: 0;
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
11
+ background: linear-gradient(135deg, #1e293b 0%, #1e40af 50%, #1e293b 100%);
12
+ min-height: 100vh;
13
+ color: #f8fafc;
14
+ padding: 20px;
15
+ }
16
+
17
+ .container {
18
+ max-width: 1200px;
19
+ margin: 0 auto;
20
+ }
21
+
22
+ .verify-container {
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ min-height: 100vh;
27
+ padding: 20px;
28
+ }
29
+
30
+ .header {
31
+ display: flex;
32
+ justify-content: space-between;
33
+ align-items: center;
34
+ margin-bottom: 2rem;
35
+ flex-wrap: wrap;
36
+ gap: 1rem;
37
+ }
38
+
39
+ .title {
40
+ font-size: 2.5rem;
41
+ font-weight: 700;
42
+ color: #fff;
43
+ margin-bottom: 0.5rem;
44
+ }
45
+
46
+ .title i {
47
+ color: #60a5fa;
48
+ margin-right: 0.5rem;
49
+ }
50
+
51
+ .subtitle {
52
+ color: #93c5fd;
53
+ font-size: 1.1rem;
54
+ }
55
+
56
+ .btn {
57
+ padding: 0.75rem 1.5rem;
58
+ border: none;
59
+ border-radius: 0.5rem;
60
+ font-size: 1rem;
61
+ cursor: pointer;
62
+ display: inline-flex;
63
+ align-items: center;
64
+ gap: 0.5rem;
65
+ transition: all 0.3s;
66
+ font-weight: 600;
67
+ }
68
+
69
+ .btn i {
70
+ font-size: 1.1rem;
71
+ }
72
+
73
+ .btn-primary {
74
+ background: #2563eb;
75
+ color: white;
76
+ }
77
+
78
+ .btn-primary:hover {
79
+ background: #1d4ed8;
80
+ transform: translateY(-2px);
81
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.4);
82
+ }
83
+
84
+ .btn-secondary {
85
+ background: #475569;
86
+ color: white;
87
+ }
88
+
89
+ .btn-secondary:hover {
90
+ background: #334155;
91
+ box-shadow: 0 4px 12px rgba(71, 85, 105, 0.4);
92
+ }
93
+
94
+ .btn-danger {
95
+ background: #dc2626;
96
+ color: white;
97
+ }
98
+
99
+ .btn-danger:hover {
100
+ background: #b91c1c;
101
+ box-shadow: 0 4px 12px rgba(220, 38, 38, 0.4);
102
+ }
103
+
104
+ .btn-success {
105
+ background: #16a34a;
106
+ color: white;
107
+ }
108
+
109
+ .btn-success:hover {
110
+ background: #15803d;
111
+ box-shadow: 0 4px 12px rgba(22, 163, 74, 0.4);
112
+ }
113
+
114
+ .btn-full {
115
+ width: 100%;
116
+ justify-content: center;
117
+ }
118
+
119
+ .grid {
120
+ display: grid;
121
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
122
+ gap: 1.5rem;
123
+ }
124
+
125
+ .card {
126
+ background: rgba(30, 41, 59, 0.6);
127
+ backdrop-filter: blur(10px);
128
+ border: 1px solid rgba(148, 163, 184, 0.3);
129
+ border-radius: 1rem;
130
+ padding: 1.5rem;
131
+ transition: all 0.3s;
132
+ }
133
+
134
+ .card:hover {
135
+ border-color: #3b82f6;
136
+ transform: translateY(-4px);
137
+ box-shadow: 0 8px 24px rgba(59, 130, 246, 0.2);
138
+ }
139
+
140
+ .verify-card {
141
+ max-width: 600px;
142
+ width: 100%;
143
+ }
144
+
145
+ .card-title {
146
+ font-size: 1.5rem;
147
+ margin-bottom: 1rem;
148
+ color: #fff;
149
+ display: flex;
150
+ align-items: center;
151
+ gap: 0.5rem;
152
+ }
153
+
154
+ .card-title i {
155
+ color: #60a5fa;
156
+ }
157
+
158
+ .card-header {
159
+ display: flex;
160
+ justify-content: space-between;
161
+ align-items: start;
162
+ margin-bottom: 1rem;
163
+ }
164
+
165
+ .badge {
166
+ background: #2563eb;
167
+ color: white;
168
+ font-size: 0.75rem;
169
+ padding: 0.25rem 0.75rem;
170
+ border-radius: 0.25rem;
171
+ font-weight: 600;
172
+ display: inline-flex;
173
+ align-items: center;
174
+ gap: 0.25rem;
175
+ }
176
+
177
+ .card-text {
178
+ color: #cbd5e1;
179
+ font-size: 0.9rem;
180
+ margin-bottom: 1rem;
181
+ word-break: break-all;
182
+ display: flex;
183
+ align-items: center;
184
+ gap: 0.5rem;
185
+ }
186
+
187
+ .card-text i {
188
+ color: #60a5fa;
189
+ flex-shrink: 0;
190
+ }
191
+
192
+ .code-box {
193
+ background: #0f172a;
194
+ color: #60a5fa;
195
+ padding: 0.75rem;
196
+ border-radius: 0.5rem;
197
+ font-family: 'Courier New', monospace;
198
+ font-size: 0.9rem;
199
+ margin-bottom: 1rem;
200
+ display: flex;
201
+ align-items: center;
202
+ gap: 0.5rem;
203
+ }
204
+
205
+ .code-box i {
206
+ color: #93c5fd;
207
+ }
208
+
209
+ .form-group {
210
+ margin-bottom: 1.5rem;
211
+ }
212
+
213
+ .form-group label {
214
+ display: block;
215
+ margin-bottom: 0.5rem;
216
+ color: #e2e8f0;
217
+ font-weight: 500;
218
+ display: flex;
219
+ align-items: center;
220
+ gap: 0.5rem;
221
+ }
222
+
223
+ .form-group label i {
224
+ color: #60a5fa;
225
+ }
226
+
227
+ .input {
228
+ width: 100%;
229
+ padding: 0.75rem;
230
+ background: rgba(15, 23, 42, 0.6);
231
+ border: 1px solid rgba(148, 163, 184, 0.3);
232
+ border-radius: 0.5rem;
233
+ color: #fff;
234
+ font-size: 1rem;
235
+ transition: all 0.3s;
236
+ }
237
+
238
+ .input:focus {
239
+ outline: none;
240
+ border-color: #3b82f6;
241
+ background: rgba(15, 23, 42, 0.8);
242
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
243
+ }
244
+
245
+ .input::placeholder {
246
+ color: #64748b;
247
+ }
248
+
249
+ .help-text {
250
+ display: block;
251
+ margin-top: 0.5rem;
252
+ color: #94a3b8;
253
+ font-size: 0.875rem;
254
+ }
255
+
256
+ .switch-label {
257
+ display: flex;
258
+ justify-content: space-between;
259
+ align-items: center;
260
+ }
261
+
262
+ .switch-label span {
263
+ display: flex;
264
+ align-items: center;
265
+ gap: 0.5rem;
266
+ }
267
+
268
+ .switch {
269
+ position: relative;
270
+ display: inline-block;
271
+ width: 60px;
272
+ height: 34px;
273
+ }
274
+
275
+ .switch input {
276
+ opacity: 0;
277
+ width: 0;
278
+ height: 0;
279
+ }
280
+
281
+ .slider {
282
+ position: absolute;
283
+ cursor: pointer;
284
+ top: 0;
285
+ left: 0;
286
+ right: 0;
287
+ bottom: 0;
288
+ background-color: #475569;
289
+ transition: 0.4s;
290
+ border-radius: 34px;
291
+ }
292
+
293
+ .slider:before {
294
+ position: absolute;
295
+ content: "";
296
+ height: 26px;
297
+ width: 26px;
298
+ left: 4px;
299
+ bottom: 4px;
300
+ background-color: white;
301
+ transition: 0.4s;
302
+ border-radius: 50%;
303
+ }
304
+
305
+ input:checked + .slider {
306
+ background-color: #2563eb;
307
+ }
308
+
309
+ input:checked + .slider:before {
310
+ transform: translateX(26px);
311
+ }
312
+
313
+ .loading {
314
+ text-align: center;
315
+ padding: 3rem;
316
+ color: #94a3b8;
317
+ font-size: 1.1rem;
318
+ }
319
+
320
+ .loading i {
321
+ display: block;
322
+ margin: 0 auto 1rem;
323
+ color: #60a5fa;
324
+ }
325
+
326
+ .loading p {
327
+ margin-top: 1rem;
328
+ }
329
+
330
+ .empty-state {
331
+ text-align: center;
332
+ padding: 4rem 2rem;
333
+ color: #64748b;
334
+ }
335
+
336
+ .empty-state-icon {
337
+ font-size: 4rem;
338
+ margin-bottom: 1rem;
339
+ color: #475569;
340
+ }
341
+
342
+ .empty-state-icon i {
343
+ color: #475569;
344
+ }
345
+
346
+ .alert {
347
+ position: fixed;
348
+ top: 20px;
349
+ right: 20px;
350
+ background: #1e293b;
351
+ border: 2px solid;
352
+ border-radius: 0.75rem;
353
+ padding: 1rem 1.5rem;
354
+ max-width: 400px;
355
+ display: flex;
356
+ align-items: center;
357
+ gap: 1rem;
358
+ animation: slideIn 0.3s ease-out;
359
+ z-index: 1000;
360
+ box-shadow: 0 10px 25px rgba(0,0,0,0.3);
361
+ }
362
+
363
+ .alert-success {
364
+ border-color: #16a34a;
365
+ }
366
+
367
+ .alert-error {
368
+ border-color: #dc2626;
369
+ }
370
+
371
+ .alert-info {
372
+ border-color: #2563eb;
373
+ }
374
+
375
+ .alert-icon {
376
+ font-size: 1.5rem;
377
+ }
378
+
379
+ .alert-success .alert-icon {
380
+ color: #16a34a;
381
+ }
382
+
383
+ .alert-error .alert-icon {
384
+ color: #dc2626;
385
+ }
386
+
387
+ .alert-info .alert-icon {
388
+ color: #2563eb;
389
+ }
390
+
391
+ .alert-close {
392
+ background: none;
393
+ border: none;
394
+ color: #fff;
395
+ font-size: 1.2rem;
396
+ cursor: pointer;
397
+ margin-left: auto;
398
+ padding: 0;
399
+ line-height: 1;
400
+ transition: all 0.3s;
401
+ }
402
+
403
+ .alert-close:hover {
404
+ color: #cbd5e1;
405
+ }
406
+
407
+ @keyframes slideIn {
408
+ from {
409
+ transform: translateX(100%);
410
+ opacity: 0;
411
+ }
412
+ to {
413
+ transform: translateX(0);
414
+ opacity: 1;
415
+ }
416
+ }
417
+
418
+ .upload-area {
419
+ border: 2px dashed rgba(148, 163, 184, 0.3);
420
+ border-radius: 0.75rem;
421
+ padding: 3rem 2rem;
422
+ text-align: center;
423
+ cursor: pointer;
424
+ transition: all 0.3s;
425
+ background: rgba(15, 23, 42, 0.3);
426
+ }
427
+
428
+ .upload-area:hover {
429
+ border-color: #3b82f6;
430
+ background: rgba(37, 99, 235, 0.1);
431
+ }
432
+
433
+ .upload-area.dragover {
434
+ border-color: #3b82f6;
435
+ background: rgba(37, 99, 235, 0.2);
436
+ transform: scale(1.02);
437
+ }
438
+
439
+ .upload-icon {
440
+ margin-bottom: 1rem;
441
+ color: #60a5fa;
442
+ }
443
+
444
+ .file-input {
445
+ display: none;
446
+ }
447
+
448
+ .verify-title {
449
+ font-size: 2rem;
450
+ margin-bottom: 1rem;
451
+ text-align: center;
452
+ display: flex;
453
+ align-items: center;
454
+ justify-content: center;
455
+ gap: 0.75rem;
456
+ }
457
+
458
+ .verify-title i {
459
+ color: #60a5fa;
460
+ }
461
+
462
+ .verify-subtitle {
463
+ color: #cbd5e1;
464
+ text-align: center;
465
+ margin-bottom: 2rem;
466
+ line-height: 1.6;
467
+ }
468
+
469
+ .verify-subtitle a {
470
+ color: #60a5fa;
471
+ text-decoration: none;
472
+ display: inline-flex;
473
+ align-items: center;
474
+ gap: 0.25rem;
475
+ }
476
+
477
+ .verify-subtitle a:hover {
478
+ text-decoration: underline;
479
+ }
480
+
481
+ .countdown {
482
+ text-align: center;
483
+ font-size: 3rem;
484
+ color: #3b82f6;
485
+ font-weight: 700;
486
+ margin: 2rem 0;
487
+ display: flex;
488
+ align-items: center;
489
+ justify-content: center;
490
+ gap: 0.5rem;
491
+ }
492
+
493
+ @media (max-width: 768px) {
494
+ .title {
495
+ font-size: 2rem;
496
+ }
497
+
498
+ .header {
499
+ flex-direction: column;
500
+ align-items: stretch;
501
+ }
502
+
503
+ .grid {
504
+ grid-template-columns: 1fr;
505
+ }
506
+
507
+ .alert {
508
+ right: 10px;
509
+ left: 10px;
510
+ max-width: none;
511
+ }
512
+
513
+ .countdown {
514
+ font-size: 2rem;
515
+ }
516
+
517
+ .verify-title {
518
+ font-size: 1.5rem;
519
+ }
520
+ }
public/verify.html ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Verification Required</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
+ <link rel="stylesheet" href="/styles.css">
9
+ </head>
10
+ <body>
11
+ <div class="container verify-container">
12
+ <div class="card verify-card">
13
+ <div id="verify-content">
14
+ <div class="loading">
15
+ <i class="fas fa-spinner fa-spin fa-3x"></i>
16
+ <p>Loading...</p>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </div>
21
+
22
+ <div id="alert-container"></div>
23
+ <script src="/verify.js"></script>
24
+ </body>
25
+ </html>
public/verify.js ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // verify.js - Verification Page JavaScript
2
+ let linkData = null;
3
+
4
+ async function init() {
5
+ const pathParts = window.location.pathname.split('/');
6
+ const name = pathParts[3];
7
+ const id = pathParts[4];
8
+
9
+ try {
10
+ const response = await fetch(`/api/link/${name}/${id}`);
11
+ linkData = await response.json();
12
+
13
+ if (linkData.error) {
14
+ showError('Link not found');
15
+ return;
16
+ }
17
+
18
+ if (!linkData.verify) {
19
+ // No verification required, just redirect
20
+ showDirectRedirect();
21
+ } else {
22
+ // Show verification form
23
+ showVerificationForm();
24
+ }
25
+ } catch (error) {
26
+ showError('Failed to load link');
27
+ }
28
+ }
29
+
30
+ function showDirectRedirect() {
31
+ document.getElementById('verify-content').innerHTML = `
32
+ <h2 class="verify-title">
33
+ <i class="fas fa-link"></i> ${escapeHtml(linkData.title)}
34
+ </h2>
35
+ <p class="verify-subtitle">Click the button below to continue</p>
36
+ <button onclick="redirect()" class="btn btn-success btn-full">
37
+ <i class="fas fa-rocket"></i> Go to Link
38
+ </button>
39
+ `;
40
+ }
41
+
42
+ function showVerificationForm() {
43
+ document.getElementById('verify-content').innerHTML = `
44
+ <h2 class="verify-title">
45
+ <i class="fas fa-shield-alt"></i> ${escapeHtml(linkData.title)}
46
+ </h2>
47
+ <p class="verify-subtitle">
48
+ This URL requires verification to access. Please upload a screenshot of joining our WhatsApp channel.<br>
49
+ If you are not joining yet, <a href="https://whatsapp.com/channel/0029VaGVOvq1iUxY6WgHLv2R" target="_blank">
50
+ <i class="fab fa-whatsapp"></i> click here
51
+ </a>.
52
+ </p>
53
+
54
+ <div class="upload-area" id="upload-area">
55
+ <div class="upload-icon">
56
+ <i class="fas fa-cloud-upload-alt fa-4x"></i>
57
+ </div>
58
+ <p><strong>Drop image here</strong> or click to upload</p>
59
+ <p style="color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem;">
60
+ <i class="fas fa-image"></i> PNG, JPG up to 10MB
61
+ </p>
62
+ <input type="file" id="file-input" class="file-input" accept="image/*">
63
+ </div>
64
+
65
+ <div id="upload-status" style="margin-top: 1.5rem; text-align: center;"></div>
66
+ `;
67
+
68
+ setupUpload();
69
+ }
70
+
71
+ function setupUpload() {
72
+ const uploadArea = document.getElementById('upload-area');
73
+ const fileInput = document.getElementById('file-input');
74
+
75
+ uploadArea.addEventListener('click', () => fileInput.click());
76
+
77
+ uploadArea.addEventListener('dragover', (e) => {
78
+ e.preventDefault();
79
+ uploadArea.classList.add('dragover');
80
+ });
81
+
82
+ uploadArea.addEventListener('dragleave', () => {
83
+ uploadArea.classList.remove('dragover');
84
+ });
85
+
86
+ uploadArea.addEventListener('drop', (e) => {
87
+ e.preventDefault();
88
+ uploadArea.classList.remove('dragover');
89
+ const file = e.dataTransfer.files[0];
90
+ if (file) handleFile(file);
91
+ });
92
+
93
+ fileInput.addEventListener('change', (e) => {
94
+ const file = e.target.files[0];
95
+ if (file) handleFile(file);
96
+ });
97
+ }
98
+
99
+ async function handleFile(file) {
100
+ if (!file.type.startsWith('image/')) {
101
+ showAlert('error', 'Please upload an image file');
102
+ return;
103
+ }
104
+
105
+ const statusDiv = document.getElementById('upload-status');
106
+ statusDiv.innerHTML = `
107
+ <div class="loading">
108
+ <i class="fas fa-spinner fa-spin fa-2x"></i>
109
+ <p>Verifying image with OCR...</p>
110
+ </div>
111
+ `;
112
+
113
+ const formData = new FormData();
114
+ formData.append('image', file);
115
+
116
+ try {
117
+ const response = await fetch('/api/verify', {
118
+ method: 'POST',
119
+ body: formData
120
+ });
121
+
122
+ const result = await response.json();
123
+
124
+ if (result.success) {
125
+ if (result.verified) {
126
+ statusDiv.innerHTML = `
127
+ <p style="color: #16a34a; font-weight: 600;">
128
+ <i class="fas fa-check-circle fa-2x"></i><br>
129
+ Verification successful!
130
+ </p>
131
+ `;
132
+ startCountdown();
133
+ } else {
134
+ if (result.channelName !== 'NOTMEBOTZ') {
135
+ showSweetAlert("That's not our channel :(");
136
+ statusDiv.innerHTML = `
137
+ <p style="color: #dc2626;">
138
+ <i class="fas fa-times-circle fa-2x"></i><br>
139
+ Wrong channel. Please join NOTMEBOTZ channel.
140
+ </p>
141
+ `;
142
+ } else if (result.hasFollowButton || result.hasPrivacy) {
143
+ statusDiv.innerHTML = `
144
+ <p style="color: #dc2626;">
145
+ <i class="fas fa-exclamation-triangle fa-2x"></i><br>
146
+ You haven't joined the channel yet. Please join first!
147
+ </p>
148
+ `;
149
+ showSweetAlert("Please join our WhatsApp channel first!");
150
+ }
151
+ }
152
+ } else {
153
+ showAlert('error', 'Verification failed');
154
+ statusDiv.innerHTML = `
155
+ <p style="color: #dc2626;">
156
+ <i class="fas fa-times-circle fa-2x"></i><br>
157
+ Verification failed. Please try again.
158
+ </p>
159
+ `;
160
+ }
161
+ } catch (error) {
162
+ showAlert('error', 'Verification error');
163
+ statusDiv.innerHTML = `
164
+ <p style="color: #dc2626;">
165
+ <i class="fas fa-times-circle fa-2x"></i><br>
166
+ Error occurred. Please try again.
167
+ </p>
168
+ `;
169
+ }
170
+ }
171
+
172
+ function startCountdown() {
173
+ let seconds = 5;
174
+ const statusDiv = document.getElementById('upload-status');
175
+
176
+ const interval = setInterval(() => {
177
+ statusDiv.innerHTML = `
178
+ <p style="color: #16a34a; margin-bottom: 1rem;">
179
+ <i class="fas fa-check-circle fa-2x"></i><br>
180
+ Verification successful!
181
+ </p>
182
+ <div class="countdown">
183
+ <i class="fas fa-clock"></i> ${seconds}
184
+ </div>
185
+ <p style="color: #cbd5e1;">Redirecting...</p>
186
+ `;
187
+
188
+ seconds--;
189
+
190
+ if (seconds < 0) {
191
+ clearInterval(interval);
192
+ redirect();
193
+ }
194
+ }, 1000);
195
+ }
196
+
197
+ function redirect() {
198
+ window.location.href = linkData.redirect;
199
+ }
200
+
201
+ function showError(message) {
202
+ document.getElementById('verify-content').innerHTML = `
203
+ <div class="empty-state">
204
+ <div class="empty-state-icon">
205
+ <i class="fas fa-exclamation-triangle fa-4x"></i>
206
+ </div>
207
+ <p>${message}</p>
208
+ <button onclick="location.href='/home'" class="btn btn-primary" style="margin-top: 1.5rem;">
209
+ <i class="fas fa-home"></i> Back to Home
210
+ </button>
211
+ </div>
212
+ `;
213
+ }
214
+
215
+ function showSweetAlert(message) {
216
+ showAlert('error', message);
217
+ }
218
+
219
+ function showAlert(type, message) {
220
+ const container = document.getElementById('alert-container');
221
+ if (!container) return;
222
+
223
+ let icon = 'fa-check-circle';
224
+ if (type === 'error') icon = 'fa-exclamation-circle';
225
+ else if (type === 'info') icon = 'fa-info-circle';
226
+
227
+ const alert = document.createElement('div');
228
+ alert.className = `alert alert-${type}`;
229
+ alert.innerHTML = `
230
+ <span class="alert-icon"><i class="fas ${icon}"></i></span>
231
+ <span>${message}</span>
232
+ <button class="alert-close" onclick="this.parentElement.remove()">
233
+ <i class="fas fa-times"></i>
234
+ </button>
235
+ `;
236
+ container.appendChild(alert);
237
+ setTimeout(() => alert.remove(), 5000);
238
+ }
239
+
240
+ function escapeHtml(text) {
241
+ const div = document.createElement('div');
242
+ div.textContent = text;
243
+ return div.innerHTML;
244
+ }
245
+
246
+ // Initialize page
247
+ init();