ibrahimlasfar commited on
Commit
99ee13f
Β·
1 Parent(s): 538e152

Add MGZon logo to OAuth button

Browse files
Files changed (1) hide show
  1. views/index.ejs +131 -422
views/index.ejs CHANGED
@@ -8,30 +8,29 @@
8
  <meta name="keywords" content="portfolio, API, Node.js, Express, MongoDB, developer, OAuth, JWT">
9
  <meta name="author" content="Ibrahim Al-Asfar">
10
  <meta name="robots" content="index, follow">
11
-
12
  <!-- Open Graph / Social Media -->
13
  <meta property="og:title" content="Ibrahim Al-Asfar Portfolio API">
14
- <meta property="og:description"
15
- content="Full-stack web developer portfolio API with authentication and AI features">
16
  <meta property="og:type" content="website">
17
  <meta property="og:url" content="https://mgzon-server.hf.space">
18
  <meta property="og:image" content="https://mgzon-server.hf.space/images/logo.png">
19
-
20
  <!-- Twitter Card -->
21
  <meta name="twitter:card" content="summary_large_image">
22
  <meta name="twitter:title" content="Ibrahim Al-Asfar Portfolio API">
23
  <meta name="twitter:description" content="Full-stack web developer portfolio API">
24
  <meta name="twitter:image" content="https://mgzon-server.hf.space/images/logo.png">
25
-
26
  <!-- Favicon -->
27
  <link rel="icon" type="image/png" href="/images/logo.png">
28
  <link rel="apple-touch-icon" href="/images/logo.png">
29
-
30
  <title>Ibrahim Al-Asfar Portfolio API</title>
31
 
32
  <!-- Tailwind CSS -->
33
  <script src="https://cdn.tailwindcss.com"></script>
34
-
35
  <!-- Font Awesome -->
36
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
37
 
@@ -48,7 +47,6 @@
48
  opacity: 0;
49
  transform: translateY(30px);
50
  }
51
-
52
  to {
53
  opacity: 1;
54
  transform: translateY(0);
@@ -56,15 +54,8 @@
56
  }
57
 
58
  @keyframes float {
59
-
60
- 0%,
61
- 100% {
62
- transform: translateY(0px);
63
- }
64
-
65
- 50% {
66
- transform: translateY(-10px);
67
- }
68
  }
69
 
70
  .animate-fadeInUp {
@@ -155,342 +146,98 @@
155
  border-radius: 0.75rem;
156
  }
157
 
158
- .p-2 {
159
- padding: 0.5rem;
160
- }
161
-
162
- .p-3 {
163
- padding: 0.75rem;
164
- }
165
-
166
- .p-4 {
167
- padding: 1rem;
168
- }
169
-
170
- .p-5 {
171
- padding: 1.25rem;
172
- }
173
-
174
- .p-6 {
175
- padding: 1.5rem;
176
- }
177
-
178
- .py-8 {
179
- padding-top: 2rem;
180
- padding-bottom: 2rem;
181
- }
182
-
183
- .py-12 {
184
- padding-top: 3rem;
185
- padding-bottom: 3rem;
186
- }
187
-
188
- .py-16 {
189
- padding-top: 4rem;
190
- padding-bottom: 4rem;
191
- }
192
-
193
- .py-20 {
194
- padding-top: 5rem;
195
- padding-bottom: 5rem;
196
- }
197
-
198
- .px-4 {
199
- padding-left: 1rem;
200
- padding-right: 1rem;
201
- }
202
-
203
- .px-8 {
204
- padding-left: 2rem;
205
- padding-right: 2rem;
206
- }
207
-
208
- .text-center {
209
- text-align: center;
210
- }
211
-
212
- .text-white {
213
- color: white;
214
- }
215
-
216
- .text-gray-600 {
217
- color: #4b5563;
218
- }
219
-
220
- .text-gray-800 {
221
- color: #1f2937;
222
- }
223
-
224
- .text-xl {
225
- font-size: 1.25rem;
226
- }
227
-
228
- .text-2xl {
229
- font-size: 1.5rem;
230
- }
231
-
232
- .text-3xl {
233
- font-size: 1.875rem;
234
- }
235
-
236
- .text-5xl {
237
- font-size: 3rem;
238
- }
239
-
240
- .font-bold {
241
- font-weight: 700;
242
- }
243
-
244
- .font-semibold {
245
- font-weight: 600;
246
- }
247
-
248
- .bg-white {
249
- background-color: white;
250
- }
251
-
252
- .bg-gray-800 {
253
- background-color: #1f2937;
254
- }
255
-
256
- .bg-green-500 {
257
- background-color: #10b981;
258
- }
259
-
260
- .bg-red-500 {
261
- background-color: #ef4444;
262
- }
263
-
264
- .mb-2 {
265
- margin-bottom: 0.5rem;
266
- }
267
-
268
- .mb-4 {
269
- margin-bottom: 1rem;
270
- }
271
-
272
- .mb-6 {
273
- margin-bottom: 1.5rem;
274
- }
275
-
276
- .mb-8 {
277
- margin-bottom: 2rem;
278
- }
279
-
280
- .mb-12 {
281
- margin-bottom: 3rem;
282
- }
283
-
284
- .mt-2 {
285
- margin-top: 0.5rem;
286
- }
287
-
288
- .mr-2 {
289
- margin-right: 0.5rem;
290
- }
291
-
292
- .ml-2 {
293
- margin-left: 0.5rem;
294
- }
295
-
296
- .flex {
297
- display: flex;
298
- }
299
-
300
- .inline-flex {
301
- display: inline-flex;
302
- }
303
-
304
- .grid {
305
- display: grid;
306
- }
307
-
308
- .items-center {
309
- align-items: center;
310
- }
311
-
312
- .justify-center {
313
- justify-content: center;
314
- }
315
-
316
- .justify-between {
317
- justify-content: space-between;
318
- }
319
-
320
- .gap-4 {
321
- gap: 1rem;
322
- }
323
-
324
- .gap-6 {
325
- gap: 1.5rem;
326
- }
327
-
328
- .gap-8 {
329
- gap: 2rem;
330
- }
331
-
332
- .max-w-2xl {
333
- max-width: 42rem;
334
- }
335
-
336
- .max-w-7xl {
337
- max-width: 80rem;
338
- }
339
-
340
- .mx-auto {
341
- margin-left: auto;
342
- margin-right: auto;
343
- }
344
-
345
- .w-12 {
346
- width: 3rem;
347
- }
348
-
349
- .h-12 {
350
- height: 3rem;
351
- }
352
-
353
- .w-16 {
354
- width: 4rem;
355
- }
356
-
357
- .h-16 {
358
- height: 4rem;
359
- }
360
-
361
- .w-3 {
362
- width: 0.75rem;
363
- }
364
-
365
- .h-3 {
366
- height: 0.75rem;
367
- }
368
-
369
- .rounded-full {
370
- border-radius: 9999px;
371
- }
372
-
373
- .cursor-pointer {
374
- cursor: pointer;
375
- }
376
-
377
- .animate-pulse {
378
- animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
379
- }
380
-
381
  @keyframes pulse {
382
-
383
- 0%,
384
- 100% {
385
- opacity: 1;
386
- }
387
-
388
- 50% {
389
- opacity: .5;
390
- }
391
- }
392
-
393
- .sticky {
394
- position: sticky;
395
- }
396
-
397
- .top-0 {
398
- top: 0;
399
- }
400
-
401
- .z-50 {
402
- z-index: 50;
403
- }
404
-
405
- .absolute {
406
- position: absolute;
407
- }
408
-
409
- .relative {
410
- position: relative;
411
- }
412
-
413
- .inset-0 {
414
- top: 0;
415
- right: 0;
416
- bottom: 0;
417
- left: 0;
418
- }
419
-
420
- .overflow-hidden {
421
- overflow: hidden;
422
- }
423
-
424
- .bg-opacity-10 {
425
- opacity: 0.1;
426
- }
427
-
428
- .transition {
429
- transition-property: all;
430
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
431
- transition-duration: 150ms;
432
- }
433
-
434
- .hover\:shadow-xl:hover {
435
- box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
436
- }
437
-
438
- .hover\:shadow-2xl:hover {
439
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
440
- }
441
-
442
- .hover\:bg-gray-900:hover {
443
- background-color: #111827;
444
- }
445
-
446
- .hover\:text-purple-600:hover {
447
- color: #7c3aed;
448
- }
449
-
450
- .hover\:scale-105:hover {
451
- transform: scale(1.05);
452
- }
453
-
454
  @media (min-width: 640px) {
455
- .sm\:px-6 {
456
- padding-left: 1.5rem;
457
- padding-right: 1.5rem;
458
- }
459
  }
460
-
461
  @media (min-width: 768px) {
462
- .md\:grid-cols-2 {
463
- grid-template-columns: repeat(2, minmax(0, 1fr));
464
- }
465
-
466
- .md\:grid-cols-3 {
467
- grid-template-columns: repeat(3, minmax(0, 1fr));
468
- }
469
-
470
- .md\:grid-cols-4 {
471
- grid-template-columns: repeat(4, minmax(0, 1fr));
472
- }
473
-
474
- .md\:text-6xl {
475
- font-size: 3.75rem;
476
- }
477
  }
478
-
479
  @media (min-width: 1024px) {
480
- .lg\:grid-cols-3 {
481
- grid-template-columns: repeat(3, minmax(0, 1fr));
482
- }
483
-
484
- .lg\:grid-cols-4 {
485
- grid-template-columns: repeat(4, minmax(0, 1fr));
486
- }
487
-
488
- .lg\:px-8 {
489
- padding-left: 2rem;
490
- padding-right: 2rem;
491
- }
492
  }
493
-
494
  /* Toast notification */
495
  .toast {
496
  position: fixed;
@@ -499,34 +246,25 @@
499
  background: white;
500
  border-radius: 8px;
501
  padding: 12px 20px;
502
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
503
  z-index: 1000;
504
  animation: slideIn 0.3s ease;
505
  }
506
-
507
  @keyframes slideIn {
508
  from {
509
  transform: translateX(100%);
510
  opacity: 0;
511
  }
512
-
513
  to {
514
  transform: translateX(0);
515
  opacity: 1;
516
  }
517
  }
518
-
519
- .toast.success {
520
- border-left: 4px solid #10b981;
521
- }
522
-
523
- .toast.error {
524
- border-left: 4px solid #ef4444;
525
- }
526
-
527
- .toast.info {
528
- border-left: 4px solid #3b82f6;
529
- }
530
  </style>
531
  </head>
532
 
@@ -545,16 +283,13 @@
545
  </h1>
546
  </div>
547
  <div class="flex items-center space-x-4">
548
- <a href="/api-docs" target="_blank" class="text-gray-600 hover:text-purple-600 transition"
549
- title="API Documentation">
550
  <i class="fas fa-book text-lg"></i>
551
  </a>
552
- <a href="https://github.com/Mark-Lasfar" target="_blank"
553
- class="text-gray-600 hover:text-purple-600 transition" title="GitHub">
554
  <i class="fab fa-github text-lg"></i>
555
  </a>
556
- <a href="https://linkedin.com/in/ibrahim-elasfar" target="_blank"
557
- class="text-gray-600 hover:text-purple-600 transition" title="LinkedIn">
558
  <i class="fab fa-linkedin text-lg"></i>
559
  </a>
560
  </div>
@@ -575,18 +310,14 @@
575
  Welcome to My Portfolio API
576
  </h1>
577
  <p class="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
578
- Full-stack web developer passionate about building modern web applications with cutting-edge
579
- technologies.
580
  </p>
581
  <div class="flex flex-wrap justify-center gap-4">
582
- <a href="/api-docs"
583
- class="gradient-bg text-white px-8 py-3 rounded-lg font-semibold btn-hover inline-flex items-center">
584
  <i class="fas fa-rocket mr-2"></i>
585
  Explore API
586
  </a>
587
- <a href="#test-api"
588
- class="bg-gray-800 text-white px-8 py-3 rounded-lg font-semibold btn-hover inline-flex items-center"
589
- id="test-api-btn">
590
  <i class="fas fa-vial mr-2"></i>
591
  Test API
592
  </a>
@@ -632,56 +363,45 @@
632
  <h2 class="text-3xl font-bold text-center text-gray-800 mb-12">πŸ” Authentication Providers</h2>
633
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
634
  <!-- Google OAuth -->
635
- <a href="/auth/google" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group"
636
- target="_blank">
637
  <div class="w-16 h-16 mx-auto mb-4">
638
  <i class="fab fa-google text-5xl text-red-500 group-hover:scale-110 transition-transform"></i>
639
  </div>
640
  <h3 class="font-semibold text-gray-800">Sign in with Google</h3>
641
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
642
- <span
643
- class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click
644
- to test β†’</span>
645
  </a>
646
 
647
  <!-- Facebook OAuth -->
648
- <a href="/auth/facebook" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group"
649
- target="_blank">
650
  <div class="w-16 h-16 mx-auto mb-4">
651
  <i class="fab fa-facebook text-5xl text-blue-600 group-hover:scale-110 transition-transform"></i>
652
  </div>
653
  <h3 class="font-semibold text-gray-800">Sign in with Facebook</h3>
654
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
655
- <span
656
- class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click
657
- to test β†’</span>
658
  </a>
659
 
660
  <!-- GitHub OAuth -->
661
- <a href="/auth/github" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group"
662
- target="_blank">
663
  <div class="w-16 h-16 mx-auto mb-4">
664
  <i class="fab fa-github text-5xl text-gray-800 group-hover:scale-110 transition-transform"></i>
665
  </div>
666
  <h3 class="font-semibold text-gray-800">Sign in with GitHub</h3>
667
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
668
- <span
669
- class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click
670
- to test β†’</span>
671
  </a>
672
 
673
- <!-- MGZon OAuth -->
674
  <a href="/auth/mgz" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group" target="_blank">
675
  <div class="w-16 h-16 mx-auto mb-4">
676
- <img src="https://mgzon-server.hf.space/images/logo.png" alt="MGZon"
677
  class="w-16 h-16 rounded-full mx-auto group-hover:scale-110 transition-transform"
678
  onerror="this.onerror=null; this.parentElement.innerHTML='<div class=\'w-16 h-16 gradient-bg rounded-full flex items-center justify-center mx-auto group-hover:scale-110 transition-transform\'><i class=\'fas fa-globe text-white text-2xl\'></i></div>'">
679
  </div>
680
  <h3 class="font-semibold text-gray-800">Sign in with MGZon</h3>
681
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
682
- <span
683
- class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click
684
- to test β†’</span>
685
  </a>
686
  </div>
687
  </div>
@@ -693,8 +413,7 @@
693
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
694
  <div>
695
  <label class="block text-sm font-medium text-gray-700 mb-2">Endpoint</label>
696
- <select id="api-endpoint"
697
- class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
698
  <option value="/api/health">GET /api/health</option>
699
  <option value="/api/projects">GET /api/projects</option>
700
  <option value="/api/skills">GET /api/skills</option>
@@ -704,9 +423,7 @@
704
  </div>
705
  <div>
706
  <label class="block text-sm font-medium text-gray-700 mb-2">Request Body (JSON)</label>
707
- <textarea id="api-body" rows="3"
708
- class="w-full px-4 py-2 border border-gray-300 rounded-lg font-mono text-sm"
709
- placeholder='{"email": "test@example.com", "password": "12345678"}'></textarea>
710
  </div>
711
  </div>
712
  <div class="mt-4 flex justify-center">
@@ -717,8 +434,7 @@
717
  </div>
718
  <div class="mt-6">
719
  <label class="block text-sm font-medium text-gray-700 mb-2">Response</label>
720
- <pre id="api-response"
721
- class="bg-gray-900 text-green-400 p-4 rounded-lg overflow-x-auto font-mono text-sm max-h-96">Click "Send Request" to test API...</pre>
722
  </div>
723
  </div>
724
  </div>
@@ -734,8 +450,7 @@
734
  </div>
735
  <code class="text-sm text-gray-700">/api/projects</code>
736
  <p class="text-gray-500 text-sm mt-2">Get all public projects</p>
737
- <button onclick="testEndpoint('/api/projects')"
738
- class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
739
  </div>
740
 
741
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
@@ -745,8 +460,7 @@
745
  </div>
746
  <code class="text-sm text-gray-700">/api/skills</code>
747
  <p class="text-gray-500 text-sm mt-2">Get all skills</p>
748
- <button onclick="testEndpoint('/api/skills')" class="mt-3 text-purple-600 text-sm hover:underline">Test
749
- β†’</button>
750
  </div>
751
 
752
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
@@ -756,8 +470,7 @@
756
  </div>
757
  <code class="text-sm text-gray-700">/api/health</code>
758
  <p class="text-gray-500 text-sm mt-2">Check system health</p>
759
- <button onclick="testEndpoint('/api/health')" class="mt-3 text-purple-600 text-sm hover:underline">Test
760
- β†’</button>
761
  </div>
762
 
763
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
@@ -767,9 +480,7 @@
767
  </div>
768
  <code class="text-sm text-gray-700">/api/register</code>
769
  <p class="text-gray-500 text-sm mt-2">Create new account</p>
770
- <button
771
- onclick="testEndpoint('/api/register', 'POST', {email: 'test@example.com', password: '12345678', username: 'testuser'})"
772
- class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
773
  </div>
774
 
775
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
@@ -779,8 +490,7 @@
779
  </div>
780
  <code class="text-sm text-gray-700">/api/login</code>
781
  <p class="text-gray-500 text-sm mt-2">Authenticate user</p>
782
- <button onclick="testEndpoint('/api/login', 'POST', {email: 'admin@elasfar.com', password: 'admin123'})"
783
- class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
784
  </div>
785
 
786
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
@@ -790,8 +500,7 @@
790
  </div>
791
  <code class="text-sm text-gray-700">/auth/google</code>
792
  <p class="text-gray-500 text-sm mt-2">Google OAuth Login</p>
793
- <a href="/auth/google" target="_blank"
794
- class="mt-3 text-purple-600 text-sm hover:underline inline-block">Open β†’</a>
795
  </div>
796
  </div>
797
  </div>
@@ -871,13 +580,13 @@
871
  async function testEndpoint(url, method = 'GET', body = null) {
872
  const responseDiv = document.getElementById('api-response');
873
  responseDiv.textContent = 'Loading...';
874
-
875
  try {
876
  const options = { method, headers: { 'Content-Type': 'application/json' } };
877
  if (body && method !== 'GET') {
878
  options.body = JSON.stringify(body);
879
  }
880
-
881
  const response = await fetch(url, options);
882
  const data = await response.json();
883
  responseDiv.textContent = JSON.stringify(data, null, 2);
@@ -897,10 +606,10 @@
897
 
898
  document.getElementById('db-status').innerHTML = data.mongodb === 'connected' ? 'MongoDB Connected βœ…' : 'MongoDB Error ❌';
899
  document.getElementById('db-status').className = data.mongodb === 'connected' ? 'text-green-600' : 'text-red-600';
900
-
901
  document.getElementById('cloudinary-status').innerHTML = data.cloudinary === 'configured' ? 'Cloudinary Active βœ…' : 'Cloudinary Not Configured ⚠️';
902
  document.getElementById('cloudinary-status').className = data.cloudinary === 'configured' ? 'text-green-600' : 'text-yellow-600';
903
-
904
  document.getElementById('sentry-status').innerHTML = data.sentry === 'configured' ? 'Sentry Active βœ…' : 'Sentry Not Configured ⚠️';
905
  document.getElementById('sentry-status').className = data.sentry === 'configured' ? 'text-green-600' : 'text-yellow-600';
906
  } catch (error) {
@@ -916,7 +625,7 @@
916
  const endpoint = document.getElementById('api-endpoint').value;
917
  const bodyText = document.getElementById('api-body').value;
918
  let body = null;
919
-
920
  if (bodyText && (endpoint.includes('/register') || endpoint.includes('/login'))) {
921
  try {
922
  body = JSON.parse(bodyText);
@@ -925,14 +634,14 @@
925
  return;
926
  }
927
  }
928
-
929
  const method = endpoint.includes('/register') || endpoint.includes('/login') ? 'POST' : 'GET';
930
  await testEndpoint(endpoint, method, body);
931
  });
932
 
933
  // Load stats on page load
934
  fetchStats();
935
-
936
  // Refresh stats every 30 seconds
937
  setInterval(fetchStats, 30000);
938
 
@@ -957,7 +666,7 @@
957
  document.body.style.opacity = '1';
958
  }, 100);
959
  });
960
-
961
  // Show welcome toast
962
  setTimeout(() => {
963
  showToast('Welcome to Portfolio API! πŸš€', 'info');
 
8
  <meta name="keywords" content="portfolio, API, Node.js, Express, MongoDB, developer, OAuth, JWT">
9
  <meta name="author" content="Ibrahim Al-Asfar">
10
  <meta name="robots" content="index, follow">
11
+
12
  <!-- Open Graph / Social Media -->
13
  <meta property="og:title" content="Ibrahim Al-Asfar Portfolio API">
14
+ <meta property="og:description" content="Full-stack web developer portfolio API with authentication and AI features">
 
15
  <meta property="og:type" content="website">
16
  <meta property="og:url" content="https://mgzon-server.hf.space">
17
  <meta property="og:image" content="https://mgzon-server.hf.space/images/logo.png">
18
+
19
  <!-- Twitter Card -->
20
  <meta name="twitter:card" content="summary_large_image">
21
  <meta name="twitter:title" content="Ibrahim Al-Asfar Portfolio API">
22
  <meta name="twitter:description" content="Full-stack web developer portfolio API">
23
  <meta name="twitter:image" content="https://mgzon-server.hf.space/images/logo.png">
24
+
25
  <!-- Favicon -->
26
  <link rel="icon" type="image/png" href="/images/logo.png">
27
  <link rel="apple-touch-icon" href="/images/logo.png">
28
+
29
  <title>Ibrahim Al-Asfar Portfolio API</title>
30
 
31
  <!-- Tailwind CSS -->
32
  <script src="https://cdn.tailwindcss.com"></script>
33
+
34
  <!-- Font Awesome -->
35
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
36
 
 
47
  opacity: 0;
48
  transform: translateY(30px);
49
  }
 
50
  to {
51
  opacity: 1;
52
  transform: translateY(0);
 
54
  }
55
 
56
  @keyframes float {
57
+ 0%, 100% { transform: translateY(0px); }
58
+ 50% { transform: translateY(-10px); }
 
 
 
 
 
 
 
59
  }
60
 
61
  .animate-fadeInUp {
 
146
  border-radius: 0.75rem;
147
  }
148
 
149
+ .p-2 { padding: 0.5rem; }
150
+ .p-3 { padding: 0.75rem; }
151
+ .p-4 { padding: 1rem; }
152
+ .p-5 { padding: 1.25rem; }
153
+ .p-6 { padding: 1.5rem; }
154
+ .py-8 { padding-top: 2rem; padding-bottom: 2rem; }
155
+ .py-12 { padding-top: 3rem; padding-bottom: 3rem; }
156
+ .py-16 { padding-top: 4rem; padding-bottom: 4rem; }
157
+ .py-20 { padding-top: 5rem; padding-bottom: 5rem; }
158
+ .px-4 { padding-left: 1rem; padding-right: 1rem; }
159
+ .px-8 { padding-left: 2rem; padding-right: 2rem; }
160
+ .text-center { text-align: center; }
161
+ .text-white { color: white; }
162
+ .text-gray-600 { color: #4b5563; }
163
+ .text-gray-800 { color: #1f2937; }
164
+ .text-xl { font-size: 1.25rem; }
165
+ .text-2xl { font-size: 1.5rem; }
166
+ .text-3xl { font-size: 1.875rem; }
167
+ .text-5xl { font-size: 3rem; }
168
+ .font-bold { font-weight: 700; }
169
+ .font-semibold { font-weight: 600; }
170
+ .bg-white { background-color: white; }
171
+ .bg-gray-800 { background-color: #1f2937; }
172
+ .bg-green-500 { background-color: #10b981; }
173
+ .bg-red-500 { background-color: #ef4444; }
174
+ .mb-2 { margin-bottom: 0.5rem; }
175
+ .mb-4 { margin-bottom: 1rem; }
176
+ .mb-6 { margin-bottom: 1.5rem; }
177
+ .mb-8 { margin-bottom: 2rem; }
178
+ .mb-12 { margin-bottom: 3rem; }
179
+ .mt-2 { margin-top: 0.5rem; }
180
+ .mr-2 { margin-right: 0.5rem; }
181
+ .ml-2 { margin-left: 0.5rem; }
182
+ .flex { display: flex; }
183
+ .inline-flex { display: inline-flex; }
184
+ .grid { display: grid; }
185
+ .items-center { align-items: center; }
186
+ .justify-center { justify-content: center; }
187
+ .justify-between { justify-content: space-between; }
188
+ .gap-4 { gap: 1rem; }
189
+ .gap-6 { gap: 1.5rem; }
190
+ .gap-8 { gap: 2rem; }
191
+ .max-w-2xl { max-width: 42rem; }
192
+ .max-w-7xl { max-width: 80rem; }
193
+ .mx-auto { margin-left: auto; margin-right: auto; }
194
+ .w-12 { width: 3rem; }
195
+ .h-12 { height: 3rem; }
196
+ .w-16 { width: 4rem; }
197
+ .h-16 { height: 4rem; }
198
+ .w-3 { width: 0.75rem; }
199
+ .h-3 { height: 0.75rem; }
200
+ .rounded-full { border-radius: 9999px; }
201
+ .cursor-pointer { cursor: pointer; }
202
+ .animate-pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
203
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  @keyframes pulse {
205
+ 0%, 100% { opacity: 1; }
206
+ 50% { opacity: .5; }
207
+ }
208
+
209
+ .sticky { position: sticky; }
210
+ .top-0 { top: 0; }
211
+ .z-50 { z-index: 50; }
212
+ .absolute { position: absolute; }
213
+ .relative { position: relative; }
214
+ .inset-0 { top: 0; right: 0; bottom: 0; left: 0; }
215
+ .overflow-hidden { overflow: hidden; }
216
+ .bg-opacity-10 { opacity: 0.1; }
217
+ .transition { transition-property: all; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; }
218
+ .hover\:shadow-xl:hover { box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); }
219
+ .hover\:shadow-2xl:hover { box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); }
220
+ .hover\:bg-gray-900:hover { background-color: #111827; }
221
+ .hover\:text-purple-600:hover { color: #7c3aed; }
222
+ .hover\:scale-105:hover { transform: scale(1.05); }
223
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  @media (min-width: 640px) {
225
+ .sm\:px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
 
 
 
226
  }
227
+
228
  @media (min-width: 768px) {
229
+ .md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
230
+ .md\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
231
+ .md\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
232
+ .md\:text-6xl { font-size: 3.75rem; }
 
 
 
 
 
 
 
 
 
 
 
233
  }
234
+
235
  @media (min-width: 1024px) {
236
+ .lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
237
+ .lg\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
238
+ .lg\:px-8 { padding-left: 2rem; padding-right: 2rem; }
 
 
 
 
 
 
 
 
 
239
  }
240
+
241
  /* Toast notification */
242
  .toast {
243
  position: fixed;
 
246
  background: white;
247
  border-radius: 8px;
248
  padding: 12px 20px;
249
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
250
  z-index: 1000;
251
  animation: slideIn 0.3s ease;
252
  }
253
+
254
  @keyframes slideIn {
255
  from {
256
  transform: translateX(100%);
257
  opacity: 0;
258
  }
 
259
  to {
260
  transform: translateX(0);
261
  opacity: 1;
262
  }
263
  }
264
+
265
+ .toast.success { border-left: 4px solid #10b981; }
266
+ .toast.error { border-left: 4px solid #ef4444; }
267
+ .toast.info { border-left: 4px solid #3b82f6; }
 
 
 
 
 
 
 
 
268
  </style>
269
  </head>
270
 
 
283
  </h1>
284
  </div>
285
  <div class="flex items-center space-x-4">
286
+ <a href="/api-docs" target="_blank" class="text-gray-600 hover:text-purple-600 transition" title="API Documentation">
 
287
  <i class="fas fa-book text-lg"></i>
288
  </a>
289
+ <a href="https://github.com/Mark-Lasfar" target="_blank" class="text-gray-600 hover:text-purple-600 transition" title="GitHub">
 
290
  <i class="fab fa-github text-lg"></i>
291
  </a>
292
+ <a href="https://linkedin.com/in/ibrahim-elasfar" target="_blank" class="text-gray-600 hover:text-purple-600 transition" title="LinkedIn">
 
293
  <i class="fab fa-linkedin text-lg"></i>
294
  </a>
295
  </div>
 
310
  Welcome to My Portfolio API
311
  </h1>
312
  <p class="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
313
+ Full-stack web developer passionate about building modern web applications with cutting-edge technologies.
 
314
  </p>
315
  <div class="flex flex-wrap justify-center gap-4">
316
+ <a href="/api-docs" class="gradient-bg text-white px-8 py-3 rounded-lg font-semibold btn-hover inline-flex items-center">
 
317
  <i class="fas fa-rocket mr-2"></i>
318
  Explore API
319
  </a>
320
+ <a href="#test-api" class="bg-gray-800 text-white px-8 py-3 rounded-lg font-semibold btn-hover inline-flex items-center" id="test-api-btn">
 
 
321
  <i class="fas fa-vial mr-2"></i>
322
  Test API
323
  </a>
 
363
  <h2 class="text-3xl font-bold text-center text-gray-800 mb-12">πŸ” Authentication Providers</h2>
364
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
365
  <!-- Google OAuth -->
366
+ <a href="/auth/google" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group" target="_blank">
 
367
  <div class="w-16 h-16 mx-auto mb-4">
368
  <i class="fab fa-google text-5xl text-red-500 group-hover:scale-110 transition-transform"></i>
369
  </div>
370
  <h3 class="font-semibold text-gray-800">Sign in with Google</h3>
371
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
372
+ <span class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click to test β†’</span>
 
 
373
  </a>
374
 
375
  <!-- Facebook OAuth -->
376
+ <a href="/auth/facebook" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group" target="_blank">
 
377
  <div class="w-16 h-16 mx-auto mb-4">
378
  <i class="fab fa-facebook text-5xl text-blue-600 group-hover:scale-110 transition-transform"></i>
379
  </div>
380
  <h3 class="font-semibold text-gray-800">Sign in with Facebook</h3>
381
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
382
+ <span class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click to test β†’</span>
 
 
383
  </a>
384
 
385
  <!-- GitHub OAuth -->
386
+ <a href="/auth/github" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group" target="_blank">
 
387
  <div class="w-16 h-16 mx-auto mb-4">
388
  <i class="fab fa-github text-5xl text-gray-800 group-hover:scale-110 transition-transform"></i>
389
  </div>
390
  <h3 class="font-semibold text-gray-800">Sign in with GitHub</h3>
391
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
392
+ <span class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click to test β†’</span>
 
 
393
  </a>
394
 
395
+ <!-- MGZon OAuth -->
396
  <a href="/auth/mgz" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group" target="_blank">
397
  <div class="w-16 h-16 mx-auto mb-4">
398
+ <img src="https://mgzon-server.hf.space/images/logo.png" alt="MGZon"
399
  class="w-16 h-16 rounded-full mx-auto group-hover:scale-110 transition-transform"
400
  onerror="this.onerror=null; this.parentElement.innerHTML='<div class=\'w-16 h-16 gradient-bg rounded-full flex items-center justify-center mx-auto group-hover:scale-110 transition-transform\'><i class=\'fas fa-globe text-white text-2xl\'></i></div>'">
401
  </div>
402
  <h3 class="font-semibold text-gray-800">Sign in with MGZon</h3>
403
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
404
+ <span class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click to test β†’</span>
 
 
405
  </a>
406
  </div>
407
  </div>
 
413
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
414
  <div>
415
  <label class="block text-sm font-medium text-gray-700 mb-2">Endpoint</label>
416
+ <select id="api-endpoint" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
 
417
  <option value="/api/health">GET /api/health</option>
418
  <option value="/api/projects">GET /api/projects</option>
419
  <option value="/api/skills">GET /api/skills</option>
 
423
  </div>
424
  <div>
425
  <label class="block text-sm font-medium text-gray-700 mb-2">Request Body (JSON)</label>
426
+ <textarea id="api-body" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg font-mono text-sm" placeholder='{"email": "test@example.com", "password": "12345678"}'></textarea>
 
 
427
  </div>
428
  </div>
429
  <div class="mt-4 flex justify-center">
 
434
  </div>
435
  <div class="mt-6">
436
  <label class="block text-sm font-medium text-gray-700 mb-2">Response</label>
437
+ <pre id="api-response" class="bg-gray-900 text-green-400 p-4 rounded-lg overflow-x-auto font-mono text-sm max-h-96">Click "Send Request" to test API...</pre>
 
438
  </div>
439
  </div>
440
  </div>
 
450
  </div>
451
  <code class="text-sm text-gray-700">/api/projects</code>
452
  <p class="text-gray-500 text-sm mt-2">Get all public projects</p>
453
+ <button onclick="testEndpoint('/api/projects')" class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
 
454
  </div>
455
 
456
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
 
460
  </div>
461
  <code class="text-sm text-gray-700">/api/skills</code>
462
  <p class="text-gray-500 text-sm mt-2">Get all skills</p>
463
+ <button onclick="testEndpoint('/api/skills')" class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
 
464
  </div>
465
 
466
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
 
470
  </div>
471
  <code class="text-sm text-gray-700">/api/health</code>
472
  <p class="text-gray-500 text-sm mt-2">Check system health</p>
473
+ <button onclick="testEndpoint('/api/health')" class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
 
474
  </div>
475
 
476
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
 
480
  </div>
481
  <code class="text-sm text-gray-700">/api/register</code>
482
  <p class="text-gray-500 text-sm mt-2">Create new account</p>
483
+ <button onclick="testEndpoint('/api/register', 'POST', {email: 'test@example.com', password: '12345678', username: 'testuser'})" class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
 
 
484
  </div>
485
 
486
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
 
490
  </div>
491
  <code class="text-sm text-gray-700">/api/login</code>
492
  <p class="text-gray-500 text-sm mt-2">Authenticate user</p>
493
+ <button onclick="testEndpoint('/api/login', 'POST', {email: 'admin@elasfar.com', password: 'admin123'})" class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
 
494
  </div>
495
 
496
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
 
500
  </div>
501
  <code class="text-sm text-gray-700">/auth/google</code>
502
  <p class="text-gray-500 text-sm mt-2">Google OAuth Login</p>
503
+ <a href="/auth/google" target="_blank" class="mt-3 text-purple-600 text-sm hover:underline inline-block">Open β†’</a>
 
504
  </div>
505
  </div>
506
  </div>
 
580
  async function testEndpoint(url, method = 'GET', body = null) {
581
  const responseDiv = document.getElementById('api-response');
582
  responseDiv.textContent = 'Loading...';
583
+
584
  try {
585
  const options = { method, headers: { 'Content-Type': 'application/json' } };
586
  if (body && method !== 'GET') {
587
  options.body = JSON.stringify(body);
588
  }
589
+
590
  const response = await fetch(url, options);
591
  const data = await response.json();
592
  responseDiv.textContent = JSON.stringify(data, null, 2);
 
606
 
607
  document.getElementById('db-status').innerHTML = data.mongodb === 'connected' ? 'MongoDB Connected βœ…' : 'MongoDB Error ❌';
608
  document.getElementById('db-status').className = data.mongodb === 'connected' ? 'text-green-600' : 'text-red-600';
609
+
610
  document.getElementById('cloudinary-status').innerHTML = data.cloudinary === 'configured' ? 'Cloudinary Active βœ…' : 'Cloudinary Not Configured ⚠️';
611
  document.getElementById('cloudinary-status').className = data.cloudinary === 'configured' ? 'text-green-600' : 'text-yellow-600';
612
+
613
  document.getElementById('sentry-status').innerHTML = data.sentry === 'configured' ? 'Sentry Active βœ…' : 'Sentry Not Configured ⚠️';
614
  document.getElementById('sentry-status').className = data.sentry === 'configured' ? 'text-green-600' : 'text-yellow-600';
615
  } catch (error) {
 
625
  const endpoint = document.getElementById('api-endpoint').value;
626
  const bodyText = document.getElementById('api-body').value;
627
  let body = null;
628
+
629
  if (bodyText && (endpoint.includes('/register') || endpoint.includes('/login'))) {
630
  try {
631
  body = JSON.parse(bodyText);
 
634
  return;
635
  }
636
  }
637
+
638
  const method = endpoint.includes('/register') || endpoint.includes('/login') ? 'POST' : 'GET';
639
  await testEndpoint(endpoint, method, body);
640
  });
641
 
642
  // Load stats on page load
643
  fetchStats();
644
+
645
  // Refresh stats every 30 seconds
646
  setInterval(fetchStats, 30000);
647
 
 
666
  document.body.style.opacity = '1';
667
  }, 100);
668
  });
669
+
670
  // Show welcome toast
671
  setTimeout(() => {
672
  showToast('Welcome to Portfolio API! πŸš€', 'info');