Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3f55925
refactor: convert i18n locale files from JS to JSON for MI improvement
fraidakis Dec 26, 2025
2a7ae75
refactor: extract place type map, simplify placeTypes.js
fraidakis Dec 26, 2025
8c3b984
refactor: extract helpers in searchLocations.js
fraidakis Dec 26, 2025
070e4da
refactor: extract helpers in forwardGeocode.js
fraidakis Dec 26, 2025
7d5e6a1
refactor: extract helpers in reverseGeocode.js
fraidakis Dec 26, 2025
9ac4bf1
refactor(polyline): extract decodeValue helper to reduce complexity
fraidakis Dec 26, 2025
8cfd5cc
refactor(validation): split into schema factory functions
fraidakis Dec 26, 2025
0f7d52b
refactor(routing): extract response builder helpers
fraidakis Dec 26, 2025
72b10c7
refactor(constants): extract data to JSON for better MI
fraidakis Dec 26, 2025
954eb48
refactor(placeTypeMap): extract data to JSON for better MI
fraidakis Dec 26, 2025
21953b6
refactor: extract reusable field validators in validationSchemas.js
fraidakis Dec 26, 2025
6b02077
refactor: split handleSubmit and extract utilities in NavigationPage.jsx
fraidakis Dec 26, 2025
fcfc7c8
refactor: extract utilities and separate components in PlaceDetailsPa…
fraidakis Dec 26, 2025
201780d
refactor: split useEffect into smaller utility functions in RouteMap.jsx
fraidakis Dec 26, 2025
231fa55
refactor: extract class mappings to module level in Hero.jsx
fraidakis Dec 26, 2025
ab2b232
refactor: extract utilities and constants in SignupPage.jsx
fraidakis Dec 26, 2025
c1fe621
refactor: extract nav items and utilities in BottomNavigation.jsx
fraidakis Dec 26, 2025
5e28569
refactor: extract auth helpers in ProtectedRoute to improve MI
fraidakis Dec 26, 2025
716a337
refactor: extract class building helpers in Button to improve MI
fraidakis Dec 26, 2025
10b8cc5
refactor: split detectSeaCrossing into helpers to reduce CC
fraidakis Dec 26, 2025
9a388c3
refactor(PreferencesPage): extract handlers to usePreferencesActions …
fraidakis Dec 26, 2025
a6be619
refactor(RecommendationsPage): extract sort helpers outside component
fraidakis Dec 26, 2025
9a6342b
Revert last 2 commits
fraidakis Dec 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 31 additions & 53 deletions src/components/Hero.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@ import React from 'react';
import PropTypes from 'prop-types';
import './Hero.css';

/** Size class mappings */
const SIZE_CLASSES = {
small: 'hero--small',
medium: 'hero--medium',
large: 'hero--large',
};

/** Alignment class mappings */
const ALIGN_CLASSES = {
left: 'hero--align-left',
center: 'hero--align-center',
right: 'hero--align-right',
};

/** Build hero CSS classes from props */
const buildHeroClasses = ({ size, align, overlayColor, backgroundImage, className }) => [
'hero',
SIZE_CLASSES[size] || SIZE_CLASSES.large,
ALIGN_CLASSES[align] || ALIGN_CLASSES.center,
overlayColor === 'light' ? 'hero--overlay-light' : 'hero--overlay-dark',
backgroundImage ? 'hero--has-image' : '',
className,
].filter(Boolean).join(' ');

/**
* Hero Component - Reusable hero section with background image support
*
Expand All @@ -28,48 +52,16 @@ const Hero = ({
className = '',
...rest
}) => {
const sizeClasses = {
small: 'hero--small',
medium: 'hero--medium',
large: 'hero--large',
};

const alignClasses = {
left: 'hero--align-left',
center: 'hero--align-center',
right: 'hero--align-right',
};

const overlayStyle = {
'--hero-overlay-opacity': overlayOpacity,
};

const heroClasses = [
'hero',
sizeClasses[size] || sizeClasses.large,
alignClasses[align] || alignClasses.center,
overlayColor === 'light' ? 'hero--overlay-light' : 'hero--overlay-dark',
backgroundImage ? 'hero--has-image' : '',
className,
].filter(Boolean).join(' ');

const heroClasses = buildHeroClasses({ size, align, overlayColor, backgroundImage, className });
const overlayStyle = { '--hero-overlay-opacity': overlayOpacity };
const TravelGlobe = React.useMemo(() => React.lazy(() => import('./3d/TravelGlobe')), []);

return (
<section
className={heroClasses}
style={overlayStyle}
{...rest}
>
<section className={heroClasses} style={overlayStyle} {...rest}>
{/* Background Layer */}
<div className="hero__background">
{backgroundImage ? (
<img
src={backgroundImage}
alt=""
className="hero__bg-image"
aria-hidden="true"
/>
<img src={backgroundImage} alt="" className="hero__bg-image" aria-hidden="true" />
) : (
<div className="hero__bg-gradient" aria-hidden="true" />
)}
Expand All @@ -90,23 +82,9 @@ const Hero = ({
{/* Content Layer */}
<div className="hero__container container">
<div className="hero__content">
{title && (
<h1 className="hero__title animate-fadeInUp">
{title}
</h1>
)}

{subtitle && (
<p className="hero__subtitle animate-fadeInUp stagger-1">
{subtitle}
</p>
)}

{children && (
<div className="hero__actions animate-fadeInUp stagger-2">
{children}
</div>
)}
{title && <h1 className="hero__title animate-fadeInUp">{title}</h1>}
{subtitle && <p className="hero__subtitle animate-fadeInUp stagger-1">{subtitle}</p>}
{children && <div className="hero__actions animate-fadeInUp stagger-2">{children}</div>}
</div>
</div>

Expand Down
210 changes: 85 additions & 125 deletions src/components/RouteMap.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,77 @@ import './RouteMap.css';

import L from 'leaflet';

// Route line colors for different transport modes
/** Route line colors for different transport modes */
const ROUTE_LINE_COLORS = {
'WALKING': '#10b981', // Green for walking
'DRIVING': '#3b82f6', // Blue for driving
'PUBLIC_TRANSPORT': '#f59e0b', // Orange for public transport
'WALKING': '#10b981',
'DRIVING': '#3b82f6',
'PUBLIC_TRANSPORT': '#f59e0b',
};

/** Default map configuration */
const DEFAULT_CENTER = [39.0742, 21.8243];
const DEFAULT_ZOOM = 6;

/** Create custom marker icon */
const createMarkerIcon = (color, label) => L.divIcon({
className: 'custom-marker',
html: `<div class="marker-pin marker-pin--${color}"><span class="marker-label">${label}</span></div>`,
iconSize: [30, 42],
iconAnchor: [15, 42],
popupAnchor: [0, -42],
});

/** Create popup content for marker */
const createPopupContent = (title, name, lat, lng) => `
<div class="map-popup">
<strong>${title}</strong>
<span class="popup-name">${name || 'Loading...'}</span>
<span class="popup-coords">${lat.toFixed(4)}, ${lng.toFixed(4)}</span>
</div>
`;

/** Add route polyline to map */
const addRouteLine = (map, geometry, transportMode) => {
const lineColor = ROUTE_LINE_COLORS[transportMode] || ROUTE_LINE_COLORS['DRIVING'];

// Shadow line for visibility
const shadowLine = L.polyline(geometry, {
color: '#1e293b', weight: 8, opacity: 0.3, lineCap: 'round', lineJoin: 'round',
}).addTo(map);

// Main route line
const routeLine = L.polyline(geometry, {
color: lineColor, weight: 5, opacity: 0.8, lineCap: 'round', lineJoin: 'round',
}).addTo(map);

return { routeLine, shadowLine };
};

/** Add fallback straight line when no route geometry available */
const addStraightLine = (map, start, end) => {
return L.polyline(
[[start.lat, start.lng], [end.lat, end.lng]],
{ color: '#94a3b8', weight: 3, opacity: 0.6, dashArray: '10, 10' }
).addTo(map);
};

/** Add marker to map with popup */
const addMarker = (map, position, name, color, label, title) => {
const marker = L.marker([position.lat, position.lng], {
icon: createMarkerIcon(color, label),
zIndexOffset: 1000,
}).addTo(map);

marker.bindPopup(createPopupContent(title, name, position.lat, position.lng));
return marker;
};

/** Parse coordinate point object */
const parsePoint = (point) => {
if (point?.latitude && point?.longitude) {
return { lat: parseFloat(point.latitude), lng: parseFloat(point.longitude) };
}
return null;
};

/**
Expand All @@ -32,7 +98,7 @@ const RouteMap = ({
endPoint,
startName,
endName,
routeGeometry = null, // Array of [lat, lng] coordinates for route line
routeGeometry = null,
transportMode = 'DRIVING',
className = '',
}) => {
Expand All @@ -42,43 +108,19 @@ const RouteMap = ({
const routeLineRef = useRef(null);

// Parse coordinates
const start = useMemo(() => {
if (startPoint?.latitude && startPoint?.longitude) {
return {
lat: parseFloat(startPoint.latitude),
lng: parseFloat(startPoint.longitude),
};
}
return null;
}, [startPoint]);

const end = useMemo(() => {
if (endPoint?.latitude && endPoint?.longitude) {
return {
lat: parseFloat(endPoint.latitude),
lng: parseFloat(endPoint.longitude),
};
}
return null;
}, [endPoint]);
const start = useMemo(() => parsePoint(startPoint), [startPoint]);
const end = useMemo(() => parsePoint(endPoint), [endPoint]);

// Initialize map
useEffect(() => {
if (!L || !mapRef.current || mapInstanceRef.current) return;

// Default center: Greece
const defaultCenter = [39.0742, 21.8243];
const defaultZoom = 6;

// Initialize map
mapInstanceRef.current = L.map(mapRef.current).setView(defaultCenter, defaultZoom);
mapInstanceRef.current = L.map(mapRef.current).setView(DEFAULT_CENTER, DEFAULT_ZOOM);

// Add tile layer
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(mapInstanceRef.current);

// Cleanup on unmount
return () => {
if (mapInstanceRef.current) {
mapInstanceRef.current.remove();
Expand All @@ -93,117 +135,36 @@ const RouteMap = ({

const map = mapInstanceRef.current;

// Remove existing markers and route line
// Cleanup existing layers
markersRef.current.forEach(marker => marker.remove());
markersRef.current = [];

if (routeLineRef.current) {
routeLineRef.current.remove();
routeLineRef.current = null;
}

// Create custom icons
const createIcon = (color, label) => {
return L.divIcon({
className: 'custom-marker',
html: `
<div class="marker-pin marker-pin--${color}">
<span class="marker-label">${label}</span>
</div>
`,
iconSize: [30, 42],
iconAnchor: [15, 42],
popupAnchor: [0, -42],
});
};

// Add route polyline if geometry is available
// Add route polyline
if (routeGeometry && routeGeometry.length > 0) {
const lineColor = ROUTE_LINE_COLORS[transportMode] || ROUTE_LINE_COLORS['DRIVING'];

routeLineRef.current = L.polyline(routeGeometry, {
color: lineColor,
weight: 5,
opacity: 0.8,
lineCap: 'round',
lineJoin: 'round',
}).addTo(map);

// Add a subtle shadow/outline for better visibility
const shadowLine = L.polyline(routeGeometry, {
color: '#1e293b',
weight: 8,
opacity: 0.3,
lineCap: 'round',
lineJoin: 'round',
}).addTo(map);

// Add shadow to cleanup ref
const { routeLine, shadowLine } = addRouteLine(map, routeGeometry, transportMode);
routeLineRef.current = routeLine;
markersRef.current.push(shadowLine);

// Fit bounds to route
map.fitBounds(routeLineRef.current.getBounds(), {
padding: [50, 50],
maxZoom: 14
});
map.fitBounds(routeLine.getBounds(), { padding: [50, 50], maxZoom: 14 });
} else if (start && end) {
// Fallback: draw a dashed straight line if no route geometry
const straightLine = L.polyline(
[[start.lat, start.lng], [end.lat, end.lng]],
{
color: '#94a3b8',
weight: 3,
opacity: 0.6,
dashArray: '10, 10',
}
).addTo(map);
const straightLine = addStraightLine(map, start, end);
markersRef.current.push(straightLine);

// Fit bounds
const bounds = L.latLngBounds([
[start.lat, start.lng],
[end.lat, end.lng],
]);
const bounds = L.latLngBounds([[start.lat, start.lng], [end.lat, end.lng]]);
map.fitBounds(bounds, { padding: [50, 50], maxZoom: 10 });
}

// Add start marker
// Add markers
if (start) {
const startMarker = L.marker([start.lat, start.lng], {
icon: createIcon('green', 'A'),
zIndexOffset: 1000, // Make sure markers are above route line
}).addTo(map);

startMarker.bindPopup(`
<div class="map-popup">
<strong>Start Point</strong>
<span class="popup-name">${startName || 'Loading...'}</span>
<span class="popup-coords">${start.lat.toFixed(4)}, ${start.lng.toFixed(4)}</span>
</div>
`);

markersRef.current.push(startMarker);
markersRef.current.push(addMarker(map, start, startName, 'green', 'A', 'Start Point'));
}

// Add end marker
if (end) {
const endMarker = L.marker([end.lat, end.lng], {
icon: createIcon('red', 'B'),
zIndexOffset: 1000,
}).addTo(map);

endMarker.bindPopup(`
<div class="map-popup">
<strong>Destination</strong>
<span class="popup-name">${endName || 'Loading...'}</span>
<span class="popup-coords">${end.lat.toFixed(4)}, ${end.lng.toFixed(4)}</span>
</div>
`);

markersRef.current.push(endMarker);
markersRef.current.push(addMarker(map, end, endName, 'red', 'B', 'Destination'));
}

// If only start or end, center on that
// Center on single point if only one available
if (start && !end && !routeGeometry) {
map.setView([start.lat, start.lng], 10);
} else if (end && !start && !routeGeometry) {
Expand All @@ -214,7 +175,6 @@ const RouteMap = ({
return (
<div className={`route-map-container ${className}`}>
<div ref={mapRef} className="route-map" />

{!start && !end && (
<div className="map-empty-overlay">
<span className="map-empty-icon">🗺️</span>
Expand Down
Loading
Loading