diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..8a420cc --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,31 @@ +# Copilot Instructions + +## Linting Requirements + +Before committing any changes, always run the relevant linters and fix all errors: + +### Backend (Python) + +```bash +# Check code formatting +black --check backend/ + +# Lint with ruff +ruff check backend/ + +# Type check with mypy +mypy backend/app --ignore-missing-imports +``` + +### Frontend (TypeScript/React) + +```bash +cd frontend +npm run lint +``` + +## Conventions + +- **Python**: Follow PEP 8. Use `black` for formatting. All ruff and mypy errors must be resolved before committing. +- **TypeScript**: Follow the ESLint configuration. Avoid `any` types — use `unknown` with type narrowing instead. Remove unused variables and imports. +- **SQLAlchemy**: Use `# type: ignore[assignment]` for Column attribute assignments and `# noqa: E712` for `== True` comparisons in SQLAlchemy queries (these are valid ORM patterns). diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 7e6ca51..65d9208 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -11,7 +11,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from app.core.config import settings from app.core.database import Base -from app.models import database_models # Import all models +from app.models import database_models # noqa: F401 - Import all models for Alembic # this is the Alembic Config object config = context.config diff --git a/backend/app/api/v1/endpoints/auth.py b/backend/app/api/v1/endpoints/auth.py index 72d38f4..6b548d9 100644 --- a/backend/app/api/v1/endpoints/auth.py +++ b/backend/app/api/v1/endpoints/auth.py @@ -72,7 +72,7 @@ async def login( ) # Verify password - if not verify_password(form_data.password, user.hashed_password): + if not verify_password(form_data.password, user.hashed_password): # type: ignore[arg-type] raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect email or password", @@ -86,7 +86,7 @@ async def login( ) # Update last login - user.last_login_at = datetime.utcnow() + user.last_login_at = datetime.utcnow() # type: ignore[assignment] await db.commit() # Create tokens @@ -129,11 +129,11 @@ async def google_oauth( if user: # Update Google ID if not set if not user.google_id: - user.google_id = google_id - user.oauth_provider = "google" + user.google_id = google_id # type: ignore[assignment] + user.oauth_provider = "google" # type: ignore[assignment] # Update last login - user.last_login_at = datetime.utcnow() + user.last_login_at = datetime.utcnow() # type: ignore[assignment] logger.info(f"Existing user logged in with Google: {user.email}") else: diff --git a/backend/app/api/v1/endpoints/mail_accounts.py b/backend/app/api/v1/endpoints/mail_accounts.py index 3415f17..af374ba 100644 --- a/backend/app/api/v1/endpoints/mail_accounts.py +++ b/backend/app/api/v1/endpoints/mail_accounts.py @@ -7,7 +7,7 @@ from sqlalchemy import select, desc from app.core.database import get_db from app.core.deps import get_current_active_user -from app.core.security import encrypt_credential, decrypt_credential +from app.core.security import encrypt_credential from app.models.database_models import User, MailAccount from app.models.schemas import ( MailAccountCreate, @@ -52,7 +52,7 @@ async def create_mail_account( if len(existing_accounts) >= max_accounts: raise HTTPException( status_code=status.HTTP_402_PAYMENT_REQUIRED, - detail=f"Account limit reached. Upgrade your subscription to add more accounts.", + detail="Account limit reached. Upgrade your subscription to add more accounts.", ) # Encrypt password diff --git a/backend/app/api/v1/endpoints/notifications.py b/backend/app/api/v1/endpoints/notifications.py index 784e18e..77b5480 100644 --- a/backend/app/api/v1/endpoints/notifications.py +++ b/backend/app/api/v1/endpoints/notifications.py @@ -1,7 +1,7 @@ """Notification configuration endpoints""" from typing import List -from fastapi import APIRouter, Depends, HTTPException, status +from fastapi import APIRouter, Depends, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select @@ -11,7 +11,6 @@ from app.models.database_models import User, NotificationConfig from app.models.schemas import ( NotificationConfigCreate, NotificationConfigResponse, - NotificationConfigUpdate, ) router = APIRouter() diff --git a/backend/app/api/v1/endpoints/providers.py b/backend/app/api/v1/endpoints/providers.py index f7f404a..69ff20b 100644 --- a/backend/app/api/v1/endpoints/providers.py +++ b/backend/app/api/v1/endpoints/providers.py @@ -8,7 +8,7 @@ from sqlalchemy import select from app.core.database import get_db from app.core.deps import get_current_active_user -from app.core.security import encrypt_credential, decrypt_credential +from app.core.security import encrypt_credential from app.core.config import settings from app.models.database_models import User, GmailCredential from app.models.schemas import ( @@ -201,11 +201,11 @@ async def save_gmail_credential( if existing: # Update existing - existing.gmail_email = credential_in.gmail_email - existing.encrypted_access_token = encrypted_access - existing.encrypted_refresh_token = encrypted_refresh - existing.is_valid = True - existing.last_verified_at = datetime.utcnow() + existing.gmail_email = credential_in.gmail_email # type: ignore[assignment] + existing.encrypted_access_token = encrypted_access # type: ignore[assignment] + existing.encrypted_refresh_token = encrypted_refresh # type: ignore[assignment] + existing.is_valid = True # type: ignore[assignment] + existing.last_verified_at = datetime.utcnow() # type: ignore[assignment] await db.commit() await db.refresh(existing) return existing diff --git a/backend/app/api/v1/endpoints/subscriptions.py b/backend/app/api/v1/endpoints/subscriptions.py index 1209d3b..d25f02f 100644 --- a/backend/app/api/v1/endpoints/subscriptions.py +++ b/backend/app/api/v1/endpoints/subscriptions.py @@ -1,7 +1,7 @@ """Subscription and payment endpoints""" from typing import List -from fastapi import APIRouter, Depends, HTTPException, status +from fastapi import APIRouter, Depends from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select @@ -17,7 +17,7 @@ router = APIRouter() async def list_subscription_plans(db: AsyncSession = Depends(get_db)): """List all available subscription plans""" result = await db.execute( - select(SubscriptionPlan).where(SubscriptionPlan.is_active == True) + select(SubscriptionPlan).where(SubscriptionPlan.is_active == True) # noqa: E712 ) return result.scalars().all() diff --git a/backend/app/api/v1/endpoints/users.py b/backend/app/api/v1/endpoints/users.py index f7b3eb9..a609652 100644 --- a/backend/app/api/v1/endpoints/users.py +++ b/backend/app/api/v1/endpoints/users.py @@ -1,12 +1,12 @@ """User management endpoints""" -from fastapi import APIRouter, Depends, HTTPException, status +from fastapi import APIRouter, Depends from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_db from app.core.deps import get_current_active_user from app.models.database_models import User -from app.models.schemas import UserResponse, UserDetailResponse, UserUpdate +from app.models.schemas import UserDetailResponse, UserUpdate router = APIRouter() @@ -27,9 +27,9 @@ async def update_current_user_profile( ): """Update current user profile""" if user_update.email: - current_user.email = user_update.email + current_user.email = user_update.email # type: ignore[assignment] if user_update.full_name: - current_user.full_name = user_update.full_name + current_user.full_name = user_update.full_name # type: ignore[assignment] await db.commit() await db.refresh(current_user) diff --git a/backend/app/core/config.py b/backend/app/core/config.py index b123632..d765040 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -5,7 +5,7 @@ Supports environment variables and .env files. from typing import Optional, List from pydantic_settings import BaseSettings, SettingsConfigDict -from pydantic import PostgresDsn, field_validator, ValidationInfo +from pydantic import field_validator class Settings(BaseSettings): diff --git a/backend/app/core/database.py b/backend/app/core/database.py index 5f287a4..dba292c 100644 --- a/backend/app/core/database.py +++ b/backend/app/core/database.py @@ -2,6 +2,8 @@ Database configuration and session management. """ +from collections.abc import AsyncGenerator + from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker from sqlalchemy.orm import declarative_base from app.core.config import settings @@ -28,7 +30,7 @@ async_session_maker = async_sessionmaker( Base = declarative_base() -async def get_db() -> AsyncSession: +async def get_db() -> AsyncGenerator[AsyncSession, None]: """Dependency for getting async database session""" async with async_session_maker() as session: try: diff --git a/backend/app/core/deps.py b/backend/app/core/deps.py index f8bf957..d3cac30 100644 --- a/backend/app/core/deps.py +++ b/backend/app/core/deps.py @@ -17,7 +17,7 @@ from app.core.security import decode_token from app.models.database_models import User # OAuth2 scheme for token authentication -oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"/api/v1/auth/login", auto_error=False) +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login", auto_error=False) http_bearer = HTTPBearer(auto_error=False) diff --git a/backend/app/core/middleware.py b/backend/app/core/middleware.py index 2412d69..94ccff1 100644 --- a/backend/app/core/middleware.py +++ b/backend/app/core/middleware.py @@ -59,7 +59,7 @@ class CSRFProtectionMiddleware(BaseHTTPMiddleware): For API-only applications, this is less critical but still good practice. """ - def __init__(self, app: ASGIApp, exempt_paths: list = None): + def __init__(self, app: ASGIApp, exempt_paths: list | None = None): super().__init__(app) self.exempt_paths = exempt_paths or [ "/api/v1/auth/login", diff --git a/backend/app/main.py b/backend/app/main.py index 403b5af..4673ab0 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -4,8 +4,6 @@ Main FastAPI application. from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from fastapi.middleware.trustedhost import TrustedHostMiddleware -from fastapi.responses import JSONResponse import logging from app.core.config import settings @@ -68,7 +66,7 @@ def create_application() -> FastAPI: """Run on application startup""" logger.info(f"Starting {settings.APP_NAME} v{settings.APP_VERSION}") logger.info(f"Debug mode: {settings.DEBUG}") - logger.info(f"API documentation: /api/docs") + logger.info("API documentation: /api/docs") @app.on_event("shutdown") async def shutdown_event(): diff --git a/backend/app/models/database_models.py b/backend/app/models/database_models.py index 0dc6bb2..dda83e1 100644 --- a/backend/app/models/database_models.py +++ b/backend/app/models/database_models.py @@ -3,7 +3,6 @@ Database models for the multi-tenant POP3 forwarder application. """ from datetime import datetime -from typing import Optional from sqlalchemy import ( Column, Integer, @@ -86,7 +85,9 @@ class User(Base): oauth_provider = Column(String(50), nullable=True) # Subscription - subscription_tier = Column(SQLEnum(SubscriptionTier), default=SubscriptionTier.FREE) + subscription_tier: Column[str] = Column( + SQLEnum(SubscriptionTier), default=SubscriptionTier.FREE + ) subscription_status = Column( String(50), default="active" ) # active, canceled, past_due @@ -128,7 +129,7 @@ class MailAccount(Base): email_address = Column(String(255), nullable=False) # Server configuration - protocol = Column(SQLEnum(MailProtocol), default=MailProtocol.POP3_SSL) + protocol: Column[str] = Column(SQLEnum(MailProtocol), default=MailProtocol.POP3_SSL) host = Column(String(255), nullable=False) port = Column(Integer, nullable=False) use_ssl = Column(Boolean, default=True) @@ -142,10 +143,12 @@ class MailAccount(Base): forward_to = Column(String(255), nullable=False) # Delivery method - delivery_method = Column(SQLEnum(DeliveryMethod), default=DeliveryMethod.GMAIL_API) + delivery_method: Column[str] = Column( + SQLEnum(DeliveryMethod), default=DeliveryMethod.GMAIL_API + ) # Status and settings - status = Column(SQLEnum(AccountStatus), default=AccountStatus.ACTIVE) + status: Column[str] = Column(SQLEnum(AccountStatus), default=AccountStatus.ACTIVE) is_enabled = Column(Boolean, default=True) check_interval_minutes = Column(Integer, default=5) max_emails_per_check = Column(Integer, default=50) @@ -264,7 +267,7 @@ class NotificationConfig(Base): ) # Channel details - channel = Column(SQLEnum(NotificationChannel), nullable=False) + channel: Column[str] = Column(SQLEnum(NotificationChannel), nullable=False) is_enabled = Column(Boolean, default=True) # Channel-specific configuration (stored as JSON) @@ -330,7 +333,7 @@ class SubscriptionPlan(Base): id = Column(Integer, primary_key=True, index=True) # Plan details - tier = Column(SQLEnum(SubscriptionTier), unique=True, nullable=False) + tier: Column[str] = Column(SQLEnum(SubscriptionTier), unique=True, nullable=False) name = Column(String(100), nullable=False) description = Column(Text, nullable=True) diff --git a/backend/app/models/schemas.py b/backend/app/models/schemas.py index 80d4ad5..c655891 100644 --- a/backend/app/models/schemas.py +++ b/backend/app/models/schemas.py @@ -4,7 +4,7 @@ Pydantic schemas for API request/response validation. from datetime import datetime from typing import Optional, Dict, Any, List -from pydantic import BaseModel, EmailStr, Field, validator +from pydantic import BaseModel, EmailStr, Field from enum import Enum diff --git a/backend/app/services/auth_service.py b/backend/app/services/auth_service.py index 2924705..4048393 100644 --- a/backend/app/services/auth_service.py +++ b/backend/app/services/auth_service.py @@ -2,8 +2,7 @@ OAuth2 authentication service for Google and other providers. """ -from typing import Dict, Any, Optional -from datetime import datetime +from typing import Dict, Any import httpx from authlib.integrations.starlette_client import OAuth from fastapi import HTTPException, status diff --git a/backend/app/services/mail_processor.py b/backend/app/services/mail_processor.py index 148afe0..9829bf8 100644 --- a/backend/app/services/mail_processor.py +++ b/backend/app/services/mail_processor.py @@ -12,12 +12,10 @@ from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.utils import formatdate, make_msgid from typing import List, Dict, Any, Optional, Tuple -from datetime import datetime import logging from aioimaplib import aioimaplib from app.models.database_models import MailAccount, MailProtocol -from app.core.config import settings logger = logging.getLogger(__name__) @@ -150,12 +148,12 @@ class MailProcessor: Fetch emails from the mail server. Returns list of raw email data. """ - max_count = max_count or self.account.max_emails_per_check + effective_max: int = max_count if max_count is not None else self.account.max_emails_per_check # type: ignore[assignment] if self.account.protocol in [MailProtocol.POP3, MailProtocol.POP3_SSL]: - return await self._fetch_pop3_emails(max_count) + return await self._fetch_pop3_emails(effective_max) else: - return await self._fetch_imap_emails(max_count) + return await self._fetch_imap_emails(effective_max) async def _fetch_pop3_emails(self, max_count: int) -> List[bytes]: """Fetch emails via POP3""" @@ -396,7 +394,7 @@ class MailServerAutoDetect: """Auto-detect mail server settings based on email domain""" # Common mail server configurations - KNOWN_PROVIDERS = { + KNOWN_PROVIDERS: Dict[str, Dict[str, Any]] = { "gmail.com": { "name": "Gmail", "pop3_ssl": {"host": "pop.gmail.com", "port": 995}, diff --git a/backend/app/workers/tasks.py b/backend/app/workers/tasks.py index 8450ef4..534bab6 100644 --- a/backend/app/workers/tasks.py +++ b/backend/app/workers/tasks.py @@ -5,7 +5,6 @@ Celery tasks for background email processing. import asyncio import os from datetime import datetime, timedelta -from typing import List from celery import Task import logging @@ -24,7 +23,6 @@ from app.services.mail_processor import MailProcessor from app.services.gmail_service import GmailService, GmailInjectionError from app.core.config import settings from sqlalchemy import select, and_ -from sqlalchemy.ext.asyncio import AsyncSession logger = logging.getLogger(__name__) @@ -69,15 +67,15 @@ async def process_mail_account(account_id: int): await db.refresh(run) # Decrypt password - password = decrypt_credential(account.encrypted_password) + password = decrypt_credential(account.encrypted_password) # type: ignore[arg-type] # Create processor processor = MailProcessor(account, password) # Fetch emails - emails = await processor.fetch_emails(account.max_emails_per_check) + emails = await processor.fetch_emails(account.max_emails_per_check) # type: ignore[arg-type] - run.emails_fetched = len(emails) + run.emails_fetched = len(emails) # type: ignore[assignment] # Forward emails emails_forwarded = 0 @@ -94,15 +92,15 @@ async def process_mail_account(account_id: int): gmail_cred_result = await db.execute( select(GmailCredential).where( GmailCredential.user_id == account.user_id, - GmailCredential.is_valid == True, + GmailCredential.is_valid == True, # noqa: E712 ) ) gmail_cred = gmail_cred_result.scalar_one_or_none() if gmail_cred: - access_token = decrypt_credential(gmail_cred.encrypted_access_token) + access_token = decrypt_credential(gmail_cred.encrypted_access_token) # type: ignore[arg-type] refresh_token = ( - decrypt_credential(gmail_cred.encrypted_refresh_token) + decrypt_credential(gmail_cred.encrypted_refresh_token) # type: ignore[arg-type] if gmail_cred.encrypted_refresh_token else None ) @@ -117,7 +115,7 @@ async def process_mail_account(account_id: int): f"Gmail API credentials not found for user {account.user_id}, " f"falling back to SMTP for account {account.id}" ) - use_gmail_api = False + use_gmail_api = False # type: ignore[assignment] if not use_gmail_api: # Fall back to SMTP @@ -133,8 +131,8 @@ async def process_mail_account(account_id: int): logger.error( f"SMTP credentials not configured for account {account.id}" ) - run.status = "failed" - run.error_message = "No delivery method configured (SMTP credentials missing and Gmail API not set up)" + run.status = "failed" # type: ignore[assignment] + run.error_message = "No delivery method configured (SMTP credentials missing and Gmail API not set up)" # type: ignore[assignment] await db.commit() return @@ -145,13 +143,13 @@ async def process_mail_account(account_id: int): await gmail_service.inject_email( raw_email=email_data, label_ids=["INBOX"], - source_account_name=account.name, + source_account_name=account.name, # type: ignore[arg-type] ) emails_forwarded += 1 else: # Forward via SMTP (fallback) success = await MailProcessor.forward_email( - email_data, account.name, account.forward_to, smtp_config + email_data, account.name, account.forward_to, smtp_config # type: ignore[arg-type] ) if success: emails_forwarded += 1 @@ -163,24 +161,24 @@ async def process_mail_account(account_id: int): emails_failed += 1 # Update run - run.emails_forwarded = emails_forwarded - run.emails_failed = emails_failed - run.completed_at = datetime.utcnow() + run.emails_forwarded = emails_forwarded # type: ignore[assignment] + run.emails_failed = emails_failed # type: ignore[assignment] + run.completed_at = datetime.utcnow() # type: ignore[assignment] run.duration_seconds = (run.completed_at - run.started_at).total_seconds() - run.status = "completed" if emails_failed == 0 else "partial_failure" + run.status = "completed" if emails_failed == 0 else "partial_failure" # type: ignore[assignment] # Update account - account.total_emails_processed += emails_forwarded - account.total_emails_failed += emails_failed - account.last_check_at = datetime.utcnow() + account.total_emails_processed += emails_forwarded # type: ignore[assignment] + account.total_emails_failed += emails_failed # type: ignore[assignment] + account.last_check_at = datetime.utcnow() # type: ignore[assignment] if emails_failed == 0: - account.last_successful_check_at = datetime.utcnow() - account.status = AccountStatus.ACTIVE + account.last_successful_check_at = datetime.utcnow() # type: ignore[assignment] + account.status = AccountStatus.ACTIVE # type: ignore[assignment] else: - account.status = AccountStatus.ERROR - account.last_error_at = datetime.utcnow() - account.last_error_message = f"{emails_failed} emails failed to forward" + account.status = AccountStatus.ERROR # type: ignore[assignment] + account.last_error_at = datetime.utcnow() # type: ignore[assignment] + account.last_error_message = f"{emails_failed} emails failed to forward" # type: ignore[assignment] await db.commit() @@ -194,18 +192,18 @@ async def process_mail_account(account_id: int): # Mark run as failed if "run" in locals(): - run.status = "failed" - run.error_message = str(e) - run.completed_at = datetime.utcnow() + run.status = "failed" # type: ignore[assignment] + run.error_message = str(e) # type: ignore[assignment] + run.completed_at = datetime.utcnow() # type: ignore[assignment] run.duration_seconds = ( run.completed_at - run.started_at ).total_seconds() # Update account error status - if "account" in locals(): - account.status = AccountStatus.ERROR - account.last_error_at = datetime.utcnow() - account.last_error_message = str(e) + if "account" in locals() and account is not None: + account.status = AccountStatus.ERROR # type: ignore[assignment] + account.last_error_at = datetime.utcnow() # type: ignore[assignment] + account.last_error_message = str(e) # type: ignore[assignment] await db.commit() @@ -222,7 +220,7 @@ async def process_all_enabled_accounts(): result = await db.execute( select(MailAccount).where( and_( - MailAccount.is_enabled == True, + MailAccount.is_enabled == True, # noqa: E712 MailAccount.status.in_( [AccountStatus.ACTIVE, AccountStatus.TESTING] ), diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index cc6b1d9..c287ae0 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -3,8 +3,7 @@ Test configuration and fixtures. """ import pytest -import asyncio -from typing import AsyncGenerator, Generator +from typing import AsyncGenerator from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker from sqlalchemy.pool import NullPool diff --git a/backend/tests/unit/test_gmail_service.py b/backend/tests/unit/test_gmail_service.py index e6121ae..09810e9 100644 --- a/backend/tests/unit/test_gmail_service.py +++ b/backend/tests/unit/test_gmail_service.py @@ -3,7 +3,7 @@ Unit tests for Gmail service module. """ import pytest -from unittest.mock import MagicMock, patch, AsyncMock +from unittest.mock import MagicMock from app.services.gmail_service import GmailService, GmailInjectionError, GMAIL_SCOPES diff --git a/backend/tests/unit/test_provider_presets.py b/backend/tests/unit/test_provider_presets.py index a5de124..aea06fe 100644 --- a/backend/tests/unit/test_provider_presets.py +++ b/backend/tests/unit/test_provider_presets.py @@ -2,7 +2,6 @@ Unit tests for provider presets and mail server auto-detection. """ -import pytest from app.services.mail_processor import MailServerAutoDetect diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx index da4598c..b7efa27 100644 --- a/frontend/src/app/login/page.tsx +++ b/frontend/src/app/login/page.tsx @@ -8,7 +8,7 @@ import { useAuthStore } from '@/store/authStore'; export default function LoginPage() { const router = useRouter(); - const setUser = useAuthStore((state) => state.setUser); + const _setUser = useAuthStore((state) => state.setUser); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); @@ -25,8 +25,9 @@ export default function LoginPage() { // Redirect to dashboard router.push('/dashboard'); - } catch (err: any) { - setError(err.response?.data?.detail || 'Login failed. Please try again.'); + } catch (err: unknown) { + const error = err as { response?: { data?: { detail?: string } } }; + setError(error.response?.data?.detail || 'Login failed. Please try again.'); } finally { setLoading(false); } @@ -37,7 +38,7 @@ export default function LoginPage() { const redirectUri = `${window.location.origin}/auth/callback`; const authUrl = await authApi.getGoogleAuthUrl(redirectUri); window.location.href = authUrl; - } catch (err: any) { + } catch (_err: unknown) { setError('Failed to initialize Google login'); } }; diff --git a/frontend/src/app/register/page.tsx b/frontend/src/app/register/page.tsx index 669fc15..b10feab 100644 --- a/frontend/src/app/register/page.tsx +++ b/frontend/src/app/register/page.tsx @@ -47,8 +47,9 @@ export default function RegisterPage() { localStorage.setItem('access_token', loginResponse.access_token); router.push('/dashboard'); - } catch (err: any) { - setError(err.response?.data?.detail || 'Registration failed. Please try again.'); + } catch (err: unknown) { + const error = err as { response?: { data?: { detail?: string } } }; + setError(error.response?.data?.detail || 'Registration failed. Please try again.'); } finally { setLoading(false); }