Mark-Lasfar commited on
Commit
fb023ba
·
1 Parent(s): f53960f

Update backend and server frontend for OAuth JSON response, client-side navigation, and add .gitignore

Browse files
Files changed (3) hide show
  1. api/auth.py +66 -58
  2. main.py +11 -4
  3. templates/login.html +229 -200
api/auth.py CHANGED
@@ -182,36 +182,40 @@ async def custom_oauth_callback(
182
  redirect_url: str = GOOGLE_REDIRECT_URL,
183
  response: Response = None,
184
  ):
185
- # جلب access token من Google
186
- token_data = await oauth_client.get_access_token(code, redirect_url)
187
- access_token = token_data["access_token"]
188
- user_info = await oauth_client.get_user_info(access_token)
189
-
190
- # استدعاء oauth_callback من UserManager
191
- user = await user_manager.oauth_callback(
192
- oauth_name="google",
193
- access_token=access_token,
194
- account_id=user_info["sub"],
195
- account_email=user_info["email"],
196
- expires_at=token_data.get("expires_in"),
197
- refresh_token=token_data.get("refresh_token"),
198
- associate_by_email=True,
199
- is_verified_by_default=True,
200
- )
201
-
202
- # إنشاء JWT token
203
- jwt_strategy = get_jwt_strategy()
204
- token = await jwt_strategy.write(user)
205
-
206
- # تعيين الـ token في الكوكيز
207
- cookie_transport.set_cookie(response, token)
208
-
209
- # رجع JSON بدل redirect – الـ frontend هيتحكم في التوجيه
210
- return JSONResponse(content={
211
- "message": "Google login successful",
212
- "access_token": token
213
- }, status_code=200)
214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  # تعديل الـ OAuth Callback لـ GitHub
216
  async def custom_github_oauth_callback(
217
  code: str,
@@ -220,36 +224,40 @@ async def custom_github_oauth_callback(
220
  redirect_url: str = GITHUB_REDIRECT_URL,
221
  response: Response = None,
222
  ):
223
- # جلب access token من GitHub
224
- token_data = await oauth_client.get_access_token(code, redirect_url)
225
- access_token = token_data["access_token"]
226
- user_info = await oauth_client.get_user_info(access_token)
227
-
228
- # استدعاء oauth_callback من UserManager
229
- user = await user_manager.oauth_callback(
230
- oauth_name="github",
231
- access_token=access_token,
232
- account_id=str(user_info["id"]),
233
- account_email=user_info.get("email") or f"{user_info['login']}@github.com",
234
- expires_at=token_data.get("expires_in"),
235
- refresh_token=token_data.get("refresh_token"),
236
- associate_by_email=True,
237
- is_verified_by_default=True,
238
- )
239
-
240
- # إنشاء JWT token
241
- jwt_strategy = get_jwt_strategy()
242
- token = await jwt_strategy.write(user)
243
-
244
- # تعيين الـ token في الكوكيز
245
- cookie_transport.set_cookie(response, token)
246
-
247
- # رجع JSON بدل redirect – الـ frontend هيتحكم في التوجيه
248
- return JSONResponse(content={
249
- "message": "GitHub login successful",
250
- "access_token": token
251
- }, status_code=200)
252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  # تضمين الراوترات داخل التطبيق
254
  def get_auth_router(app: FastAPI):
255
  app.include_router(fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"])
 
182
  redirect_url: str = GOOGLE_REDIRECT_URL,
183
  response: Response = None,
184
  ):
185
+ logger.debug(f"Processing Google callback with code: {code}")
186
+ try:
187
+ # جلب access token من Google
188
+ token_data = await oauth_client.get_access_token(code, redirect_url)
189
+ access_token = token_data["access_token"]
190
+ user_info = await oauth_client.get_user_info(access_token)
191
+
192
+ # استدعاء oauth_callback من UserManager
193
+ user = await user_manager.oauth_callback(
194
+ oauth_name="google",
195
+ access_token=access_token,
196
+ account_id=user_info["sub"],
197
+ account_email=user_info["email"],
198
+ expires_at=token_data.get("expires_in"),
199
+ refresh_token=token_data.get("refresh_token"),
200
+ associate_by_email=True,
201
+ is_verified_by_default=True,
202
+ )
 
 
 
 
 
 
 
 
 
 
 
203
 
204
+ # إنشاء JWT token
205
+ jwt_strategy = get_jwt_strategy()
206
+ token = await jwt_strategy.write(user)
207
+
208
+ # تعيين الـ token في الكوكيز
209
+ cookie_transport.set_cookie(response, token)
210
+
211
+ # رجع JSON بدل redirect
212
+ return JSONResponse(content={
213
+ "message": "Google login successful",
214
+ "access_token": token
215
+ }, status_code=200)
216
+ except Exception as e:
217
+ logger.error(f"Error in Google OAuth callback: {str(e)}")
218
+ raise HTTPException(status_code=400, detail=str(e))
219
  # تعديل الـ OAuth Callback لـ GitHub
220
  async def custom_github_oauth_callback(
221
  code: str,
 
224
  redirect_url: str = GITHUB_REDIRECT_URL,
225
  response: Response = None,
226
  ):
227
+ logger.debug(f"Processing GitHub callback with code: {code}")
228
+ try:
229
+ # جلب access token من GitHub
230
+ token_data = await oauth_client.get_access_token(code, redirect_url)
231
+ access_token = token_data["access_token"]
232
+ user_info = await oauth_client.get_user_info(access_token)
233
+
234
+ # استدعاء oauth_callback من UserManager
235
+ user = await user_manager.oauth_callback(
236
+ oauth_name="github",
237
+ access_token=access_token,
238
+ account_id=str(user_info["id"]),
239
+ account_email=user_info.get("email") or f"{user_info['login']}@github.com",
240
+ expires_at=token_data.get("expires_in"),
241
+ refresh_token=token_data.get("refresh_token"),
242
+ associate_by_email=True,
243
+ is_verified_by_default=True,
244
+ )
 
 
 
 
 
 
 
 
 
 
 
245
 
246
+ # إنشاء JWT token
247
+ jwt_strategy = get_jwt_strategy()
248
+ token = await jwt_strategy.write(user)
249
+
250
+ # تعيين الـ token في الكوكيز
251
+ cookie_transport.set_cookie(response, token)
252
+
253
+ # رجع JSON بدل redirect
254
+ return JSONResponse(content={
255
+ "message": "GitHub login successful",
256
+ "access_token": token
257
+ }, status_code=200)
258
+ except Exception as e:
259
+ logger.error(f"Error in GitHub OAuth callback: {str(e)}")
260
+ raise HTTPException(status_code=400, detail=str(e))
261
  # تضمين الراوترات داخل التطبيق
262
  def get_auth_router(app: FastAPI):
263
  app.include_router(fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"])
main.py CHANGED
@@ -13,7 +13,7 @@ from starlette.middleware.sessions import SessionMiddleware
13
  from fastapi.openapi.docs import get_swagger_ui_html
14
  from fastapi.middleware.cors import CORSMiddleware
15
  from api.endpoints import router as api_router
16
- from api.auth import fastapi_users, auth_backend, current_active_user, get_auth_router
17
  from api.database import User, Conversation, get_db, init_db
18
  from api.models import UserRead, UserCreate, UserUpdate
19
  from motor.motor_asyncio import AsyncIOMotorClient
@@ -102,7 +102,12 @@ async def lifespan(app: FastAPI):
102
  yield
103
  logger.info("Shutting down application...")
104
 
105
- app = FastAPI(title="MGZon Chatbot API", lifespan=lifespan)
 
 
 
 
 
106
 
107
  # Add SessionMiddleware
108
  app.add_middleware(SessionMiddleware, secret_key=JWT_SECRET)
@@ -136,8 +141,10 @@ logger.debug("CORS middleware configured with allowed origins")
136
 
137
  # Include routers
138
  app.include_router(api_router)
139
- get_auth_router(app)
140
- logger.debug("API and auth routers included")
 
 
141
 
142
  # Add logout endpoint
143
  @app.post("/logout")
 
13
  from fastapi.openapi.docs import get_swagger_ui_html
14
  from fastapi.middleware.cors import CORSMiddleware
15
  from api.endpoints import router as api_router
16
+ from api.auth import fastapi_users, auth_backend, current_active_user, get_auth_router , google_oauth_router, github_oauth_router
17
  from api.database import User, Conversation, get_db, init_db
18
  from api.models import UserRead, UserCreate, UserUpdate
19
  from motor.motor_asyncio import AsyncIOMotorClient
 
102
  yield
103
  logger.info("Shutting down application...")
104
 
105
+ app = FastAPI(
106
+ title="MGZon Chatbot API",
107
+ lifespan=lifespan,
108
+ docs_url=None, # تعطيل Swagger UI الافتراضي على /docs
109
+ redoc_url=None # تعطيل ReDoc الافتراضي لو مش عايزه
110
+ )
111
 
112
  # Add SessionMiddleware
113
  app.add_middleware(SessionMiddleware, secret_key=JWT_SECRET)
 
141
 
142
  # Include routers
143
  app.include_router(api_router)
144
+ app.include_router(google_oauth_router, prefix="/auth", tags=["auth"]) # إضافة Google OAuth router
145
+ app.include_router(github_oauth_router, prefix="/auth", tags=["auth"]) # إضافة GitHub OAuth router
146
+ get_auth_router(app) # الراوترات الأخرى (JWT, register, etc.)
147
+ logger.debug("API, Google OAuth, GitHub OAuth, and auth routers included")
148
 
149
  # Add logout endpoint
150
  @app.post("/logout")
templates/login.html CHANGED
@@ -378,85 +378,100 @@
378
  📲 Install MG Chat
379
  </button>
380
  </footer>
381
-
382
- <script>
383
- // Particles.js initialization
384
- particlesJS('particles-js', {
385
- particles: {
386
- number: { value: 80, density: { enable: true, value_area: 800 } },
387
- color: { value: ['#00f0ff', '#ff007a', '#6b21a8'] },
388
- shape: { type: 'circle' },
389
- opacity: { value: 0.5, random: true },
390
- size: { value: 3, random: true },
391
- line_linked: { enable: true, distance: 150, color: '#00f0ff', opacity: 0.4, width: 1 },
392
- move: { enable: true, speed: 3, direction: 'none', random: true }
393
- },
394
- interactivity: {
395
- detect_on: 'canvas',
396
- events: { onhover: { enable: true, mode: 'repulse' }, onclick: { enable: true, mode: 'push' } },
397
- modes: { repulse: { distance: 100 }, push: { particles_nb: 4 } }
398
- },
399
- retina_detect: true
400
- });
401
-
402
- function toggleTheme() {
403
- const html = document.documentElement;
404
- const button = document.querySelector('.theme-toggle');
405
- if (html.classList.contains('dark')) {
406
- html.classList.remove('dark');
407
- html.classList.add('light');
408
- button.textContent = '☀️';
409
- localStorage.setItem('theme', 'light');
410
- } else {
411
- html.classList.remove('light');
412
- html.classList.add('dark');
413
- button.textContent = '🌙';
414
- localStorage.setItem('theme', 'dark');
415
- }
416
- }
417
-
418
- // Load saved theme preference or system preference
419
- const savedTheme = localStorage.getItem('theme');
420
- if (savedTheme) {
421
- document.documentElement.classList.remove('dark', 'light');
422
- document.documentElement.classList.add(savedTheme);
423
- document.querySelector('.theme-toggle').textContent = savedTheme === 'dark' ? '🌙' : '☀️';
424
- } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
425
- document.documentElement.classList.remove('dark');
426
- document.documentElement.classList.add('light');
427
- document.querySelector('.theme-toggle').textContent = '☀️';
428
  localStorage.setItem('theme', 'light');
 
 
 
 
 
429
  }
 
430
 
431
- // Check authentication status on page load
432
- async function checkAuthStatus() {
433
- try {
434
- const response = await fetch('/api/check-auth', {
435
- method: 'GET',
436
- credentials: 'include',
437
- headers: { 'Accept': 'application/json' }
438
- });
439
- const data = await response.json();
440
- if (data.is_authenticated) {
441
- console.log('User is authenticated, redirecting to /chat');
442
- window.location.href = '/chat';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
  }
444
- } catch (error) {
445
- console.error('Error checking auth status:', error);
 
 
 
 
 
446
  }
 
 
 
447
  }
 
448
 
449
- // Handle email/password login
450
- const loginForm = document.getElementById('loginForm');
451
- const loginBtn = document.getElementById('loginBtn');
452
- const spinner = document.getElementById('spinner');
453
- const errorMsg = document.getElementById('errorMsg');
454
- const googleLoginBtn = document.getElementById('googleLoginBtn');
455
- const githubLoginBtn = document.getElementById('githubLoginBtn');
456
 
 
457
  loginForm.addEventListener('submit', async (e) => {
458
  e.preventDefault();
459
  console.log('Login form submitted');
 
 
 
 
 
460
  spinner.classList.remove('hidden');
461
  errorMsg.classList.add('hidden');
462
  const formData = new FormData(loginForm);
@@ -467,6 +482,8 @@
467
  });
468
  spinner.classList.add('hidden');
469
  if (response.ok) {
 
 
470
  console.log('Login successful, redirecting to /chat');
471
  window.location.href = '/chat';
472
  } else {
@@ -482,148 +499,160 @@
482
  console.error('Error during login:', error);
483
  }
484
  });
 
485
 
486
- // Handle Google OAuth login
487
- googleLoginBtn.addEventListener('click', async (e) => {
488
- e.preventDefault();
489
- console.log('Google login button clicked');
490
- spinner.classList.remove('hidden');
491
- errorMsg.classList.add('hidden');
492
- try {
493
- const response = await fetch('/auth/google/authorize', {
494
- method: 'GET',
495
- headers: { 'Accept': 'application/json' }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
  });
497
- if (!response.ok) {
498
- throw new Error(`HTTP error! Status: ${response.status}`);
499
- }
500
- const data = await response.json();
501
- if (data.authorization_url) {
502
- console.log('Redirecting to Google:', data.authorization_url);
503
- window.location.href = data.authorization_url;
504
- } else {
505
- throw new Error('No authorization URL received');
506
- }
507
- } catch (error) {
508
- spinner.classList.add('hidden');
509
- errorMsg.textContent = 'Failed to initiate Google login. Please try again.';
510
- errorMsg.classList.remove('hidden');
511
- console.error('Error initiating Google login:', error);
512
  }
513
- });
514
-
515
- // Handle GitHub OAuth login
516
- githubLoginBtn.addEventListener('click', async (e) => {
517
- e.preventDefault();
518
- console.log('GitHub login button clicked');
519
- spinner.classList.remove('hidden');
520
- errorMsg.classList.add('hidden');
521
- try {
522
- const response = await fetch('/auth/github/authorize', {
523
- method: 'GET',
524
- headers: { 'Accept': 'application/json' }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
  });
526
- if (!response.ok) {
527
- throw new Error(`HTTP error! Status: ${response.status}`);
528
- }
529
- const data = await response.json();
530
- if (data.authorization_url) {
531
- console.log('Redirecting to GitHub:', data.authorization_url);
532
- window.location.href = data.authorization_url;
533
- } else {
534
- throw new Error('No authorization URL received');
535
- }
536
- } catch (error) {
537
- spinner.classList.add('hidden');
538
- errorMsg.textContent = 'Failed to initiate GitHub login. Please try again.';
539
- errorMsg.classList.remove('hidden');
540
- console.error('Error initiating GitHub login:', error);
541
  }
542
- });
543
-
544
- // Check for OAuth callback on load (for web)
545
- window.addEventListener('load', async () => {
546
- const urlParams = new URLSearchParams(window.location.search);
547
- const code = urlParams.get('code');
548
- const error = urlParams.get('error');
549
- if (error) {
550
- errorMsg.textContent = decodeURIComponent(error);
551
- errorMsg.classList.remove('hidden');
552
- console.error('OAuth error from URL:', error);
553
- } else if (code) {
554
- console.log('OAuth code detected in URL, processing callback');
555
- const provider = window.location.pathname.includes('google') ? 'google' : 'github';
556
- const callbackEndpoint = `/auth/${provider}/callback?code=${code}`;
557
- try {
558
- const response = await fetch(callbackEndpoint, {
559
- method: 'GET',
560
- headers: { 'Accept': 'application/json' }
561
- });
562
- if (response.ok) {
563
- const data = await response.json();
564
- if (data.access_token) {
565
- localStorage.setItem('token', data.access_token); // Store token in localStorage for web
566
- console.log(`${provider} login successful, token saved`);
567
- window.location.href = '/chat'; // Redirect to server's /chat
568
- } else {
569
- throw new Error('No access token received');
570
- }
571
- } else {
572
- const errorData = await response.json();
573
- errorMsg.textContent = errorData.detail || `Failed to complete ${provider} login`;
574
- errorMsg.classList.remove('hidden');
575
- console.error(`Failed to complete ${provider} login:`, errorData);
576
- }
577
- } catch (error) {
578
- errorMsg.textContent = `Failed to process ${provider} login. Please try again.`;
579
- errorMsg.classList.remove('hidden');
580
- console.error(`Error processing ${provider} callback:`, error);
581
- }
582
- }
583
- });
584
 
585
- // Handle card details toggle
586
- function showCardDetails(cardId) {
587
- console.log('Showing card details:', cardId);
588
- document.getElementById(`${cardId}-details`).classList.remove('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
  }
 
590
 
591
- function closeCardDetails(cardId) {
592
- console.log('Closing card details:', cardId);
593
- document.getElementById(`${cardId}-details`).classList.add('hidden');
594
- }
 
595
 
596
- // Service Worker for PWA
597
- if ('serviceWorker' in navigator) {
598
- console.log('Registering service worker');
599
- navigator.serviceWorker.register('/static/js/sw.js')
600
- .then(reg => console.log('✅ Service Worker Registered', reg))
601
- .catch(err => console.error('❌ Service Worker registration failed', err));
602
- }
603
 
604
- // Handle PWA install prompt
605
- let deferredPrompt;
606
- window.addEventListener('beforeinstallprompt', (e) => {
607
- console.log('Before install prompt triggered');
608
- e.preventDefault();
609
- deferredPrompt = e;
610
- const installBtn = document.getElementById('installAppBtn');
611
- if (installBtn) {
612
- installBtn.style.display = 'block';
613
- installBtn.addEventListener('click', () => {
614
- console.log('Install button clicked');
615
- deferredPrompt.prompt();
616
- deferredPrompt.userChoice.then(choice => {
617
- if (choice.outcome === 'accepted') {
618
- console.log('✅ User accepted the install prompt');
619
- } else {
620
- console.log('❌ User dismissed the install prompt');
621
- }
622
- deferredPrompt = null;
623
- });
 
 
 
 
 
 
 
624
  });
625
- }
626
- });
627
- </script>
 
628
  </body>
629
  </html>
 
378
  📲 Install MG Chat
379
  </button>
380
  </footer>
381
+ <script>
382
+ // Particles.js initialization
383
+ particlesJS('particles-js', {
384
+ particles: {
385
+ number: { value: 80, density: { enable: true, value_area: 800 } },
386
+ color: { value: ['#00f0ff', '#ff007a', '#6b21a8'] },
387
+ shape: { type: 'circle' },
388
+ opacity: { value: 0.5, random: true },
389
+ size: { value: 3, random: true },
390
+ line_linked: { enable: true, distance: 150, color: '#00f0ff', opacity: 0.4, width: 1 },
391
+ move: { enable: true, speed: 3, direction: 'none', random: true }
392
+ },
393
+ interactivity: {
394
+ detect_on: 'canvas',
395
+ events: { onhover: { enable: true, mode: 'repulse' }, onclick: { enable: true, mode: 'push' } },
396
+ modes: { repulse: { distance: 100 }, push: { particles_nb: 4 } }
397
+ },
398
+ retina_detect: true
399
+ });
400
+
401
+ function toggleTheme() {
402
+ const html = document.documentElement;
403
+ const button = document.querySelector('.theme-toggle');
404
+ if (html.classList.contains('dark')) {
405
+ html.classList.remove('dark');
406
+ html.classList.add('light');
407
+ button.textContent = '☀️';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  localStorage.setItem('theme', 'light');
409
+ } else {
410
+ html.classList.remove('light');
411
+ html.classList.add('dark');
412
+ button.textContent = '🌙';
413
+ localStorage.setItem('theme', 'dark');
414
  }
415
+ }
416
 
417
+ // Load saved theme preference or system preference
418
+ const savedTheme = localStorage.getItem('theme');
419
+ if (savedTheme) {
420
+ document.documentElement.classList.remove('dark', 'light');
421
+ document.documentElement.classList.add(savedTheme);
422
+ document.querySelector('.theme-toggle').textContent = savedTheme === 'dark' ? '🌙' : '☀️';
423
+ } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
424
+ document.documentElement.classList.remove('dark');
425
+ document.documentElement.classList.add('light');
426
+ document.querySelector('.theme-toggle').textContent = '☀️';
427
+ localStorage.setItem('theme', 'light');
428
+ }
429
+
430
+ // Check authentication status on page load
431
+ async function checkAuthStatus() {
432
+ try {
433
+ const token = localStorage.getItem('token');
434
+ if (!token) {
435
+ console.log('No token found in localStorage');
436
+ return;
437
+ }
438
+ const response = await fetch('/api/verify-token', {
439
+ method: 'GET',
440
+ headers: {
441
+ 'Authorization': `Bearer ${token}`,
442
+ 'Accept': 'application/json'
443
  }
444
+ });
445
+ if (response.ok) {
446
+ console.log('User is authenticated, redirecting to /chat');
447
+ window.location.href = '/chat';
448
+ } else {
449
+ console.log('Token verification failed, clearing token');
450
+ localStorage.removeItem('token');
451
  }
452
+ } catch (error) {
453
+ console.error('Error checking auth status:', error);
454
+ localStorage.removeItem('token');
455
  }
456
+ }
457
 
458
+ // Handle email/password login
459
+ const loginForm = document.getElementById('loginForm');
460
+ const loginBtn = document.getElementById('loginBtn');
461
+ const spinner = document.getElementById('spinner');
462
+ const errorMsg = document.getElementById('errorMsg');
463
+ const googleLoginBtn = document.getElementById('googleLoginBtn');
464
+ const githubLoginBtn = document.getElementById('githubLoginBtn');
465
 
466
+ if (loginForm) {
467
  loginForm.addEventListener('submit', async (e) => {
468
  e.preventDefault();
469
  console.log('Login form submitted');
470
+ if (!navigator.onLine) {
471
+ errorMsg.textContent = 'You are offline. Please connect to the internet and try again.';
472
+ errorMsg.classList.remove('hidden');
473
+ return;
474
+ }
475
  spinner.classList.remove('hidden');
476
  errorMsg.classList.add('hidden');
477
  const formData = new FormData(loginForm);
 
482
  });
483
  spinner.classList.add('hidden');
484
  if (response.ok) {
485
+ const data = await response.json();
486
+ localStorage.setItem('token', data.access_token);
487
  console.log('Login successful, redirecting to /chat');
488
  window.location.href = '/chat';
489
  } else {
 
499
  console.error('Error during login:', error);
500
  }
501
  });
502
+ }
503
 
504
+ // Handle Google OAuth login
505
+ if (googleLoginBtn) {
506
+ googleLoginBtn.addEventListener('click', async (e) => {
507
+ e.preventDefault();
508
+ console.log('Google login button clicked');
509
+ spinner.classList.remove('hidden');
510
+ errorMsg.classList.add('hidden');
511
+ try {
512
+ const response = await fetch('/auth/google/authorize', {
513
+ method: 'GET',
514
+ headers: { 'Accept': 'application/json' }
515
+ });
516
+ if (!response.ok) {
517
+ throw new Error(`HTTP error! Status: ${response.status}`);
518
+ }
519
+ const data = await response.json();
520
+ if (data.authorization_url) {
521
+ console.log('Redirecting to Google:', data.authorization_url);
522
+ window.location.href = data.authorization_url;
523
+ } else {
524
+ throw new Error('No authorization URL received');
525
+ }
526
+ } catch (error) {
527
+ spinner.classList.add('hidden');
528
+ errorMsg.textContent = 'Failed to initiate Google login. Please try again.';
529
+ errorMsg.classList.remove('hidden');
530
+ console.error('Error initiating Google login:', error);
531
+ }
532
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
533
  }
534
+
535
+ // Handle GitHub OAuth login
536
+ if (githubLoginBtn) {
537
+ githubLoginBtn.addEventListener('click', async (e) => {
538
+ e.preventDefault();
539
+ console.log('GitHub login button clicked');
540
+ spinner.classList.remove('hidden');
541
+ errorMsg.classList.add('hidden');
542
+ try {
543
+ const response = await fetch('/auth/github/authorize', {
544
+ method: 'GET',
545
+ headers: { 'Accept': 'application/json' }
546
+ });
547
+ if (!response.ok) {
548
+ throw new Error(`HTTP error! Status: ${response.status}`);
549
+ }
550
+ const data = await response.json();
551
+ if (data.authorization_url) {
552
+ console.log('Redirecting to GitHub:', data.authorization_url);
553
+ window.location.href = data.authorization_url;
554
+ } else {
555
+ throw new Error('No authorization URL received');
556
+ }
557
+ } catch (error) {
558
+ spinner.classList.add('hidden');
559
+ errorMsg.textContent = 'Failed to initiate GitHub login. Please try again.';
560
+ errorMsg.classList.remove('hidden');
561
+ console.error('Error initiating GitHub login:', error);
562
+ }
563
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
565
 
566
+ // Check for OAuth callback on load (for web)
567
+ window.addEventListener('load', async () => {
568
+ console.log('Page loaded, checking for auth status and OAuth callback');
569
+ if (!navigator.onLine) {
570
+ errorMsg.textContent = 'You are offline. Please connect to the internet and try again.';
571
+ errorMsg.classList.remove('hidden');
572
+ return;
573
+ }
574
+ await checkAuthStatus();
575
+ const urlParams = new URLSearchParams(window.location.search);
576
+ const code = urlParams.get('code');
577
+ const error = urlParams.get('error');
578
+ if (error) {
579
+ errorMsg.textContent = decodeURIComponent(error);
580
+ errorMsg.classList.remove('hidden');
581
+ console.error('OAuth error from URL:', error);
582
+ } else if (code) {
583
+ console.log('OAuth code detected in URL, processing callback');
584
+ const provider = window.location.pathname.includes('google') ? 'google' : 'github';
585
+ const callbackEndpoint = `/auth/${provider}/callback?code=${code}`;
586
+ try {
587
+ const response = await fetch(callbackEndpoint, {
588
+ method: 'GET',
589
+ headers: { 'Accept': 'application/json' }
590
+ });
591
+ if (response.ok) {
592
+ const data = await response.json();
593
+ if (data.access_token) {
594
+ localStorage.setItem('token', data.access_token);
595
+ console.log(`${provider} login successful, token saved`);
596
+ window.location.href = '/chat';
597
+ } else {
598
+ throw new Error('No access token received');
599
+ }
600
+ } else {
601
+ const errorData = await response.json();
602
+ errorMsg.textContent = errorData.detail || `Failed to complete ${provider} login`;
603
+ errorMsg.classList.remove('hidden');
604
+ console.error(`Failed to complete ${provider} login:`, errorData);
605
+ }
606
+ } catch (error) {
607
+ errorMsg.textContent = `Failed to process ${provider} login. Please try again.`;
608
+ errorMsg.classList.remove('hidden');
609
+ console.error(`Error processing ${provider} callback:`, error);
610
+ }
611
  }
612
+ });
613
 
614
+ // Handle card details toggle
615
+ function showCardDetails(cardId) {
616
+ console.log('Showing card details:', cardId);
617
+ document.getElementById(`${cardId}-details`).classList.remove('hidden');
618
+ }
619
 
620
+ function closeCardDetails(cardId) {
621
+ console.log('Closing card details:', cardId);
622
+ document.getElementById(`${cardId}-details`).classList.add('hidden');
623
+ }
 
 
 
624
 
625
+ // Service Worker for PWA
626
+ if ('serviceWorker' in navigator) {
627
+ console.log('Registering service worker');
628
+ navigator.serviceWorker.register('/static/js/sw.js')
629
+ .then(reg => console.log('✅ Service Worker Registered', reg))
630
+ .catch(err => console.error('❌ Service Worker registration failed', err));
631
+ }
632
+
633
+ // Handle PWA install prompt
634
+ let deferredPrompt;
635
+ window.addEventListener('beforeinstallprompt', (e) => {
636
+ console.log('Before install prompt triggered');
637
+ e.preventDefault();
638
+ deferredPrompt = e;
639
+ const installBtn = document.getElementById('installAppBtn');
640
+ if (installBtn) {
641
+ installBtn.style.display = 'block';
642
+ installBtn.addEventListener('click', () => {
643
+ console.log('Install button clicked');
644
+ deferredPrompt.prompt();
645
+ deferredPrompt.userChoice.then(choice => {
646
+ if (choice.outcome === 'accepted') {
647
+ console.log('✅ User accepted the install prompt');
648
+ } else {
649
+ console.log('❌ User dismissed the install prompt');
650
+ }
651
+ deferredPrompt = null;
652
  });
653
+ });
654
+ }
655
+ });
656
+ </script>
657
  </body>
658
  </html>