diff --git a/cypress/e2e/auth_login_logout.cy.js b/cypress/e2e/auth_login_logout.cy.js index fb515d32..c768650b 100644 --- a/cypress/e2e/auth_login_logout.cy.js +++ b/cypress/e2e/auth_login_logout.cy.js @@ -1,18 +1,32 @@ /** - * E2E Tests for Login & Logout Flows + * @fileoverview E2E Tests for Login & Logout Flows + * Tests authentication workflow, session persistence, and protected routes. * Split from auth_happy_unhappy.cy.js for file size optimization + * @module cypress/e2e/auth_login_logout */ + import { visitLogin, expectUrlToEqual, expectUrlToContain, fillLoginForm, submitForm, logMessage } from '../support/helpers'; +/** + * Test suite for authentication flows. + * Covers login, logout, session persistence, and route protection. + */ describe('Login & Logout Flows', () => { + // Clear state before each test for isolation beforeEach(() => { cy.clearLocalStorage(); cy.clearCookies(); }); + /** + * Login Page Tests + * Verify login form renders and authentication succeeds with valid credentials. + */ describe('Login Page', () => { + // Test: Page loads correctly it('should load the login page', () => { visitLogin(); cy.contains(/login/i).should('be.visible'); }); + // Test: Login with custom test user fixture it('should successfully log in with valid credentials', () => { cy.loginAsTestUser('validUser'); expectUrlToEqual('/'); @@ -20,6 +34,7 @@ describe('Login & Logout Flows', () => { logMessage('✅ Successfully logged in with test user'); }); + // Test: Login with hardcoded demo credentials it('should log in with demo user credentials', () => { cy.login('user1@example.com', 'password123'); cy.url().should('not.include', '/login'); @@ -28,7 +43,12 @@ describe('Login & Logout Flows', () => { }); }); + /** + * Logout Tests + * Verify logout clears session and redirects appropriately. + */ describe('Logout', () => { + // Test: Programmatic logout it('should successfully log out', () => { cy.loginAsTestUser('validUser'); cy.url().should('not.include', '/login'); @@ -37,6 +57,7 @@ describe('Login & Logout Flows', () => { logMessage('✅ Successfully logged out'); }); + // Test: Logout via UI dropdown button it('should log out via UI logout button', () => { cy.loginAsTestUser('validUser'); cy.get('[data-cy="user-dropdown-trigger"]').should('be.visible').click(); @@ -47,7 +68,12 @@ describe('Login & Logout Flows', () => { }); }); + /** + * Session Persistence Tests + * Verify session survives page reloads and data is stored correctly. + */ describe('Session Persistence', () => { + // Test: Session survives reload it('should maintain session after page reload', () => { cy.loginAsTestUser('validUser'); expectUrlToEqual('/'); @@ -57,6 +83,7 @@ describe('Login & Logout Flows', () => { logMessage('✅ Session persists after page reload'); }); + // Test: User data in localStorage is correct it('should restore user data from localStorage', () => { cy.loginAsTestUser('validUser'); cy.window().its('localStorage.user').should('exist'); @@ -69,7 +96,12 @@ describe('Login & Logout Flows', () => { }); }); + /** + * Protected Route Tests + * Verify authenticated users can access protected routes. + */ describe('Protected Routes', () => { + // Test: Authenticated access to profile page it('should allow access to protected route when authenticated', () => { cy.loginAsTestUser('validUser'); cy.visit('/profile'); diff --git a/cypress/e2e/auth_validation.cy.js b/cypress/e2e/auth_validation.cy.js index 6a90c146..c1001c2e 100644 --- a/cypress/e2e/auth_validation.cy.js +++ b/cypress/e2e/auth_validation.cy.js @@ -1,13 +1,26 @@ /** - * E2E Tests for Authentication Validation Errors + * @fileoverview E2E Tests for Authentication Validation Errors + * Tests form validation, error messages, and protected route access. * Split from auth_happy_unhappy.cy.js for file size optimization + * @module cypress/e2e/auth_validation */ + import { visitLogin, visitSignup, expectUrlToContain, fillLoginForm, fillSignupForm, submitForm, getRandomEmail, logMessage } from '../support/helpers'; +/** + * Test suite for authentication validation. + * Covers signup validation, login errors, and route protection. + */ describe('Authentication Validation Errors', () => { + // Clear state before each test for isolation beforeEach(() => { cy.clearLocalStorage(); cy.clearCookies(); }); + /** + * Signup Validation Tests + * Tests email format, password strength, and required fields. + */ describe('Signup Validation Errors', () => { + // Test: Invalid email format validation it('should show email validation error for invalid email format', () => { cy.intercept('POST', '**/auth/signup').as('signupRequest'); visitSignup(); @@ -18,6 +31,7 @@ describe('Authentication Validation Errors', () => { cy.window().its('localStorage.token').should('not.exist'); }); + // Test: Weak password validation it('should show password strength error for weak password', () => { visitSignup(); fillSignupForm({ name: 'Test User', email: getRandomEmail(), password: 'weak', confirmPassword: 'weak' }); @@ -26,6 +40,7 @@ describe('Authentication Validation Errors', () => { expectUrlToContain('/signup'); }); + // Test: Password confirmation mismatch it('should show error for mismatched password confirmation', () => { visitSignup(); cy.get('[data-cy="input-name"]').clear().type('Test User'); @@ -37,6 +52,7 @@ describe('Authentication Validation Errors', () => { expectUrlToContain('/signup'); }); + // Test: Required fields validation it('should show required field errors for empty form submission', () => { visitSignup(); submitForm(); @@ -61,7 +77,12 @@ describe('Authentication Validation Errors', () => { }); }); + /** + * Login Authentication Error Tests + * Tests wrong credentials, required fields, and invalid email. + */ describe('Login Authentication Errors', () => { + // Test: Invalid credentials returns auth error it('should show auth error for incorrect credentials', () => { cy.intercept('POST', '**/auth/login', { statusCode: 401, body: { success: false, message: 'Invalid credentials' } }).as('login'); visitLogin(); @@ -88,7 +109,12 @@ describe('Authentication Validation Errors', () => { }); }); + /** + * Protected Route Access Control Tests + * Verifies unauthenticated users are redirected to login. + */ describe('Protected Route Access Control - Unauthenticated', () => { + // Routes requiring authentication const protectedRoutes = [ { path: '/recommendations', name: 'Recommendations' }, { path: '/profile', name: 'User Profile' }, diff --git a/src/App.jsx b/src/App.jsx index f9981ff4..5aa8ef3e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,3 +1,26 @@ +/** + * @fileoverview Root Application Component. + * + * This is the main application component that sets up the entire application + * structure including: + * - Context providers (Theme, Language, Toast) + * - React Router configuration + * - Layout structure (Header, Main, Footer, Bottom Navigation) + * - Authentication state management + * + * The component hierarchy ensures all child components have access to: + * - Theme context for light/dark mode + * - Language context for i18n translations + * - Toast context for notifications + * - Router context for navigation + * + * @module App + * @requires react + * @requires react-router-dom + * @requires ./i18n - Language provider + * @requires ./context - Theme provider + */ + import React, { useState, useEffect } from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; import { LanguageProvider } from './i18n'; @@ -9,26 +32,51 @@ import ScrollToTop from './components/ScrollToTop.jsx'; import AppRoutes from './router/index.jsx'; import './App.css'; +/** + * App Component + * + * The root component that wraps the entire application with necessary + * providers and sets up the main layout structure. Tracks authentication + * state for conditional rendering of navigation elements. + * + * Provider hierarchy (outermost to innermost): + * 1. ThemeProvider - Dark/light mode + * 2. LanguageProvider - Internationalization + * 3. ToastProvider - Notification system + * 4. Router - Navigation + * + * @function App + * @returns {React.ReactElement} The complete application structure + */ function App() { + // Track user authentication state for conditional UI const [isAuthenticated, setIsAuthenticated] = useState(false); + /** + * Sets up authentication state tracking on mount. + * Listens for login/logout events and storage changes. + */ useEffect(() => { // Check for existing token on mount const token = localStorage.getItem('token'); setIsAuthenticated(!!token); - // Listen for login/logout events + // Event handlers for auth state changes const handleLogin = () => setIsAuthenticated(true); const handleLogout = () => setIsAuthenticated(false); + // Listen for custom login/logout events window.addEventListener('user:login', handleLogin); window.addEventListener('user:logout', handleLogout); + + // Listen for storage changes (handles logout from other tabs) window.addEventListener('storage', (e) => { if (e.key === 'token') { setIsAuthenticated(!!e.newValue); } }); + // Cleanup event listeners on unmount return () => { window.removeEventListener('user:login', handleLogin); window.removeEventListener('user:logout', handleLogout); @@ -36,17 +84,26 @@ function App() { }, []); return ( + // Theme provider for dark/light mode support + {/* Language provider for internationalization */} + {/* Toast provider for notification system */} + {/* React Router for client-side navigation */} + {/* Scroll restoration on route change */}
+ {/* Site header with navigation */}
+ {/* Main content area with routes */}
+ {/* Site footer */}
+ {/* Mobile bottom navigation bar */}
@@ -57,3 +114,4 @@ function App() { } export default App; + diff --git a/src/components/3d/TravelGlobe.jsx b/src/components/3d/TravelGlobe.jsx index 07cca5b6..2b6f36fb 100644 --- a/src/components/3d/TravelGlobe.jsx +++ b/src/components/3d/TravelGlobe.jsx @@ -1,3 +1,9 @@ +/** + * @fileoverview 3D Travel Globe component. + * Interactive globe with orbiting satellites using Three.js. + * @module components/3d/TravelGlobe + */ + import React, { useRef } from 'react'; import { Canvas, useFrame } from '@react-three/fiber'; import { Icosahedron, OrbitControls, Stars } from '@react-three/drei'; diff --git a/src/components/Carousel3D.jsx b/src/components/Carousel3D.jsx index 25173fd7..60896744 100644 --- a/src/components/Carousel3D.jsx +++ b/src/components/Carousel3D.jsx @@ -1,3 +1,9 @@ +/** + * @fileoverview 3D Coverflow-style carousel component. + * Features smooth animations, drag support, and keyboard navigation. + * @module components/Carousel3D + */ + import React, { useState, useEffect, useRef, useCallback } from 'react'; import PropTypes from 'prop-types'; import Icon from './ui/Icon'; diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx index a90fb81c..5d1565a6 100644 --- a/src/components/Footer.jsx +++ b/src/components/Footer.jsx @@ -1,3 +1,9 @@ +/** + * @fileoverview Site footer component. + * Contains branding, navigation links, and copyright. + * @module components/Footer + */ + import React from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from '../i18n'; diff --git a/src/components/Header.jsx b/src/components/Header.jsx index b24d4db9..582d18de 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -1,3 +1,9 @@ +/** + * @fileoverview Site header component. + * Professional navigation with glassmorphism, user dropdown, and scroll effects. + * @module components/Header + */ + import React, { useState, useEffect } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { useAuth } from '../hooks'; diff --git a/src/components/LanguageSwitcher.jsx b/src/components/LanguageSwitcher.jsx index 8858ba14..aaf7db91 100644 --- a/src/components/LanguageSwitcher.jsx +++ b/src/components/LanguageSwitcher.jsx @@ -1,19 +1,52 @@ +/** + * @fileoverview Language Switcher Component for internationalization support. + * + * Provides a button that allows users to toggle between English and Greek + * languages in the application. Uses the translation context to manage + * the current language state. + * + * @module components/LanguageSwitcher + * @requires react + * @requires ../i18n - Translation context and hooks + */ + import React from 'react'; import { useTranslation } from '../i18n'; import './LanguageSwitcher.css'; +/** + * LanguageSwitcher Component + * + * A button component that toggles the application language between + * English (EN) and Greek (EL). The button displays the opposite language + * code from the currently active language, indicating what language + * the user will switch to upon clicking. + * + * @component + * @example + * // Basic usage in a navigation bar + * + * + * @returns {React.ReactElement} A styled button for language toggling + * + * @see {@link module:i18n/LanguageContext} for language context implementation + */ const LanguageSwitcher = () => { + // Get current language and toggle function from translation context const { language, toggleLanguage } = useTranslation(); return ( - ); }; export default LanguageSwitcher; + diff --git a/src/components/UserDropdown.jsx b/src/components/UserDropdown.jsx index 73ea0f1c..461ab33c 100644 --- a/src/components/UserDropdown.jsx +++ b/src/components/UserDropdown.jsx @@ -1,3 +1,9 @@ +/** + * @fileoverview User dropdown menu component. + * Avatar with dropdown for profile, preferences, and logout. + * @module components/UserDropdown + */ + import React, { useState, useEffect, useRef } from 'react'; import { Link } from 'react-router-dom'; import { useAuth } from '../hooks'; diff --git a/src/components/favourites/FavouriteListItem.jsx b/src/components/favourites/FavouriteListItem.jsx index 02c458c0..1bd8b4e4 100644 --- a/src/components/favourites/FavouriteListItem.jsx +++ b/src/components/favourites/FavouriteListItem.jsx @@ -1,3 +1,9 @@ +/** + * @fileoverview Favourite list item component. + * Renders individual favourite/disliked place with remove action. + * @module components/favourites/FavouriteListItem + */ + import React from 'react'; import PlaceCard from '../PlaceCard.jsx'; import { Button, Icon } from '../ui'; diff --git a/src/components/home/CTASection.jsx b/src/components/home/CTASection.jsx index 35b83ab0..1ef93b53 100644 --- a/src/components/home/CTASection.jsx +++ b/src/components/home/CTASection.jsx @@ -1,3 +1,9 @@ +/** + * @fileoverview Homepage call-to-action section component. + * Displays CTA banner with link to explore destinations. + * @module components/home/CTASection + */ + import React from 'react'; import { Link } from 'react-router-dom'; import { Button, Icon } from '../ui'; diff --git a/src/components/home/FeaturesSection.jsx b/src/components/home/FeaturesSection.jsx index 5b4f8800..37babcfa 100644 --- a/src/components/home/FeaturesSection.jsx +++ b/src/components/home/FeaturesSection.jsx @@ -1,3 +1,9 @@ +/** + * @fileoverview Homepage features section component. + * Displays feature highlights: recommendations, reviews, favourites. + * @module components/home/FeaturesSection + */ + import React from 'react'; /** diff --git a/src/components/navigation/RouteForm.jsx b/src/components/navigation/RouteForm.jsx index b635048a..91d39eb7 100644 --- a/src/components/navigation/RouteForm.jsx +++ b/src/components/navigation/RouteForm.jsx @@ -1,3 +1,9 @@ +/** + * @fileoverview Route planning form component. + * Used in NavigationPage for entering origin/destination and transport mode. + * @module components/navigation/RouteForm + */ + import React from 'react'; import { Button, Icon, Spinner, AutocompleteInput } from '../ui'; import { searchLocations } from '../../utils/geocoding'; @@ -15,11 +21,13 @@ const RouteForm = ({ error, onSubmit, }) => { + /** Handle input field changes and update form state */ const handleInputChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; + /** Map transport mode to display icon */ const getTransportIcon = (mode) => { const icons = { 'WALKING': '🚶', 'DRIVING': '🚗', 'PUBLIC_TRANSPORT': '🚌' }; return icons[mode] || '🗺️'; @@ -32,7 +40,9 @@ const RouteForm = ({

{t('calculateRoute')}

+ {/* Route form with autocomplete inputs */}
+ {/* Start location section */}
📍 @@ -61,6 +71,7 @@ const RouteForm = ({
+ {/* Destination location section */}
🎯 @@ -83,6 +94,7 @@ const RouteForm = ({
+ {/* Transport mode selection */}
@@ -104,11 +116,13 @@ const RouteForm = ({
+ {/* Submit button with loading state */} + {/* Error display */} {error && (
diff --git a/src/components/navigation/RouteResults.jsx b/src/components/navigation/RouteResults.jsx index aa9c3ccb..a3761dc6 100644 --- a/src/components/navigation/RouteResults.jsx +++ b/src/components/navigation/RouteResults.jsx @@ -1,3 +1,9 @@ +/** + * @fileoverview Route results display component. + * Shows route stats, waypoints, warnings, and interactive map. + * @module components/navigation/RouteResults + */ + import React from 'react'; import { Icon } from '../ui'; import RouteMap from '../RouteMap'; @@ -17,11 +23,13 @@ const RouteResults = ({ routeGeometry, routeError, }) => { + /** Map transport mode to display icon */ const getTransportIcon = (mode) => { const icons = { 'WALKING': '🚶', 'DRIVING': '🚗', 'PUBLIC_TRANSPORT': '🚌' }; return icons[mode] || '🗺️'; }; + /** Format duration in hours and minutes */ const formatDuration = (minutes) => { if (minutes < 60) return `${Math.round(minutes)} ${t('minutes')}`; const hours = Math.floor(minutes / 60); @@ -29,6 +37,7 @@ const RouteResults = ({ return `${hours}h ${mins}m`; }; + /** Render location name with loading state and coordinates */ const renderLocationName = (name, isLoading, lat, lng) => { if (isLoading) { return ( @@ -51,6 +60,7 @@ const RouteResults = ({ ); }; + // Empty state when no route calculated yet if (!routeData) { return (
@@ -68,6 +78,7 @@ const RouteResults = ({

{t('routeResults')}

+ {/* Route statistics cards */}
📏
@@ -98,6 +109,7 @@ const RouteResults = ({
+ {/* Start and end point markers */}
A @@ -109,6 +121,7 @@ const RouteResults = ({
+ {/* Route warnings (ferry required, route not possible) */} {routeError && (
{routeError.includes('⛴️') ? '⛴️' : '⚠️'}
@@ -123,6 +136,7 @@ const RouteResults = ({
)} + {/* Interactive map with route polyline */} { + /** Generate star display string from numeric rating */ const getRatingStars = (rating) => { return '★'.repeat(Math.floor(rating)) + '☆'.repeat(5 - Math.floor(rating)); }; @@ -30,6 +37,7 @@ const ReviewsSection = ({
+ {/* Review submission form - toggleable */} {showReviewForm && (
@@ -68,6 +76,7 @@ const ReviewsSection = ({ )} + {/* Reviews list with empty state */}
{reviews.length > 0 ? ( reviews.map((review, index) => ( diff --git a/src/components/preferences/PreferenceForm.jsx b/src/components/preferences/PreferenceForm.jsx index 89be5ac1..039d172e 100644 --- a/src/components/preferences/PreferenceForm.jsx +++ b/src/components/preferences/PreferenceForm.jsx @@ -1,8 +1,16 @@ +/** + * @fileoverview Preference profile form component. + * Form for creating/editing preference profiles with category selection. + * @module components/preferences/PreferenceForm + */ + import React from 'react'; import { Button, Icon } from '../ui'; +// Available place categories for preferences const AVAILABLE_CATEGORIES = ['MUSEUM', 'RESTAURANT', 'BEACH', 'CULTURE', 'PARK', 'NIGHTLIFE', 'SHOPPING', 'SPORTS']; +// Emoji icons for each category const CATEGORY_ICONS = { 'MUSEUM': '🏛️', 'RESTAURANT': '🍽️', 'BEACH': '🏖️', 'CULTURE': '🎭', 'PARK': '🌳', 'NIGHTLIFE': '🌙', 'SHOPPING': '🛍️', 'SPORTS': '⚽', diff --git a/src/components/preferences/ProfileCard.jsx b/src/components/preferences/ProfileCard.jsx index d7836b96..9c08c6e9 100644 --- a/src/components/preferences/ProfileCard.jsx +++ b/src/components/preferences/ProfileCard.jsx @@ -1,3 +1,9 @@ +/** + * @fileoverview Preference profile card component. + * Displays profile info with activate, edit, and delete actions. + * @module components/preferences/ProfileCard + */ + import React from 'react'; import { Button, Icon } from '../ui'; import { CATEGORY_ICONS } from './PreferenceForm'; diff --git a/src/components/profile/ProfileInfoCard.jsx b/src/components/profile/ProfileInfoCard.jsx index ec7eaf33..4c9862e3 100644 --- a/src/components/profile/ProfileInfoCard.jsx +++ b/src/components/profile/ProfileInfoCard.jsx @@ -1,3 +1,9 @@ +/** + * @fileoverview User profile information card component. + * Displays and allows editing of personal information. + * @module components/profile/ProfileInfoCard + */ + import React from 'react'; import { Button, Icon } from '../ui'; diff --git a/src/components/profile/SettingsCard.jsx b/src/components/profile/SettingsCard.jsx index 0cebe6cc..1bee7a14 100644 --- a/src/components/profile/SettingsCard.jsx +++ b/src/components/profile/SettingsCard.jsx @@ -1,3 +1,9 @@ +/** + * @fileoverview User settings card component. + * Displays and allows editing of notification settings. + * @module components/profile/SettingsCard + */ + import React from 'react'; import { Button, Icon } from '../ui'; @@ -5,6 +11,7 @@ import { Button, Icon } from '../ui'; * Settings card for editing/displaying user settings */ const SettingsCard = ({ t, settings, setSettings, editMode, setEditMode, onSubmit }) => { + // Render settings card with edit/display modes return (
@@ -20,8 +27,10 @@ const SettingsCard = ({ t, settings, setSettings, editMode, setEditMode, onSubmi )}
+ {/* Edit mode: form with toggle switches */} {editMode ? (
+ {/* Email notifications toggle */}