Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/components/home/FeaturesSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const FeaturesSection = ({ t }) => {
<div className="container">
<div className="features-header">
<h2 className="section-title">{t('whatWeOffer')}</h2>
<p className="section-subtitle">Everything you need for your perfect travel experience</p>
<p className="section-subtitle">{t('whatWeOfferSubtitle')}</p>
</div>

<div className="features-grid">
Expand Down
29 changes: 23 additions & 6 deletions src/components/preferences/PreferenceForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const CATEGORY_ICONS = {
/**
* Preference form for creating/editing preference profiles
*/
const PreferenceForm = ({ t, formData, setFormData, editingProfile, onSubmit, onClose }) => {
const PreferenceForm = ({ t, formData, setFormData, editingProfile, onSubmit, onClose, showError }) => {
const toggleCategory = (category) => {
setFormData(prev => {
const categories = prev.categories || [];
Expand All @@ -32,6 +32,24 @@ const PreferenceForm = ({ t, formData, setFormData, editingProfile, onSubmit, on
});
};

const handleSubmit = (e) => {
e.preventDefault();

// Validate name
if (!formData.name || !formData.name.trim()) {
showError(t('profileNameRequired'));
return;
}

// Validate categories
if (!formData.categories || formData.categories.length === 0) {
showError(t('categoryMinOne'));
return;
}

onSubmit(e);
};

return (
<div className="preference-form animate-fadeInUp">
<div className="form-header">
Expand All @@ -41,18 +59,16 @@ const PreferenceForm = ({ t, formData, setFormData, editingProfile, onSubmit, on
</button>
</div>

<form onSubmit={onSubmit}>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label className="form-label">{t('profileName')}</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="form-input"
placeholder="e.g., Weekend Getaway"
placeholder={t('profileNamePlaceholder')}
required
pattern=".*\S+.*"
title="Profile name cannot be empty or contain only whitespace"
/>
</div>

Expand All @@ -67,7 +83,7 @@ const PreferenceForm = ({ t, formData, setFormData, editingProfile, onSubmit, on
onClick={() => toggleCategory(cat)}
>
<span className="category-icon">{CATEGORY_ICONS[cat]}</span>
<span className="category-name">{cat}</span>
<span className="category-name">{t(cat)}</span>
</button>
))}
</div>
Expand All @@ -88,3 +104,4 @@ const PreferenceForm = ({ t, formData, setFormData, editingProfile, onSubmit, on

export { PreferenceForm, CATEGORY_ICONS };
export default PreferenceForm;

4 changes: 2 additions & 2 deletions src/components/preferences/ProfileCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ const ProfileCard = ({ t, profile, isActive, index, onActivate, onEdit, onDelete
<div className="profile-tags">
{(profile.categories || []).map(cat => (
<span key={cat} className="profile-tag">
{CATEGORY_ICONS[cat]} {cat}
{CATEGORY_ICONS[cat]} {t(cat)}
</span>
))}
{(!profile.categories || profile.categories.length === 0) && (
<span className="no-data">No categories selected</span>
<span className="no-data">{t('noCategoriesSelected')}</span>
)}
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/components/recommendations/RecommendationFilters.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ const RecommendationFilters = ({
/>
</div>
<div className="filter-group filter-group--sort">
<label className="filter-label">{t('sortBy') || 'Sort By'}</label>
<label className="filter-label">{t('sortBy')}</label>
<select value={sortBy} onChange={onSortChange} className="filter-select">
<option value="distance">{t('sortByDistance') || 'Distance'}</option>
<option value="rating">{t('sortByRating') || 'Rating'}</option>
<option value="distance">{t('sortByDistance')}</option>
<option value="rating">{t('sortByRating')}</option>
</select>
</div>
<div className="filter-group filter-group--action">
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/el/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"ctaDescription": "Γίνετε μέλος χιλιάδων ταξιδιωτών που ανακαλύπτουν εκπληκτικούς προορισμούς κάθε μέρα.",
"exploreDestinations": "Εξερευνήστε Προορισμούς",
"whatWeOffer": "Τι προσφέρουμε",
"whatWeOfferSubtitle": "Ό,τι χρειάζεστε για μια τέλεια ταξιδιωτική εμπειρία",
"personalizedRecommendations": "Εξατομικευμένες Προτάσεις",
"personalizedRecommendationsDesc": "Λάβετε προτάσεις βασισμένες στις προτιμήσεις σας",
"reviews": "Αξιολογήσεις",
Expand Down Expand Up @@ -68,7 +69,8 @@
"haveAccount": "Έχετε ήδη λογαριασμό;",
"loginLink": "Συνδεθείτε εδώ",
"signupError": "Σφάλμα εγγραφής. Το email μπορεί να χρησιμοποιείται ήδη",
"passwordRequirement": "Τουλάχιστον 6 χαρακτήρες (κεφαλαίο, πεζό, αριθμό)"
"passwordRequirement": "Τουλάχιστον 6 χαρακτήρες (κεφαλαίο, πεζό, αριθμό)",
"namePlaceholder": "Γιάννης Παπαδόπουλος"
},
"validation": {
"invalidEmail": "Μη έγκυρη διεύθυνση email",
Expand Down
16 changes: 14 additions & 2 deletions src/i18n/locales/el/features.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"from": "Από",
"to": "Προς",
"getRoute": "Εύρεση Διαδρομής",
"navigationTitle": "Πλοήγηση Διαδρομής",
"navigationSubtitle": "Βρείτε τη διαδρομή προς τον προορισμό σας",
"calculateRoute": "Υπολογισμός Διαδρομής",
"startPoint": "Σημείο Εκκίνησης",
Expand All @@ -22,6 +23,9 @@
"minutes": "λεπτά",
"mapView": "Προβολή χάρτη (θα ενσωματωθεί σε μελλοντική έκδοση)",
"quickRoutes": "Γρήγορες Διαδρομές",
"routeAthensToThessaloniki": "Αθήνα → Θεσσαλονίκη",
"routeThessalonikiToAthens": "Θεσσαλονίκη → Αθήνα",
"routeHeraklionToAthens": "Ηράκλειο → Αθήνα",
"resolvingLocation": "Εύρεση τοποθεσίας...",
"enterLocation": "Τοποθεσία",
"searchLocationPlaceholder": "π.χ., Αθήνα, Ελλάδα",
Expand All @@ -31,7 +35,7 @@
"enterLocationsToStart": "Εισάγετε τοποθεσίες ή επιλέξτε μια γρήγορη διαδρομή"
},
"recommendationsPage": {
"recommendationsTitle": "Προτάσεις για Εσάς",
"recommendationsTitle": "Προτάσεις για Εσάς",
"recommendationsSubtitle": "Εξατομικευμένες προτάσεις βασισμένες στις προτιμήσεις σας",
"filtersTitle": "Φίλτρα Αναζήτησης",
"latitude": "Γεωγραφικό Πλάτος",
Expand All @@ -42,8 +46,14 @@
"errorLoadingRecommendations": "Σφάλμα κατά τη φόρτωση των προτάσεων",
"noRecommendationsFound": "Δεν βρέθηκαν προτάσεις",
"tryAgain": "Προσπάθεια Ξανά",
"createProfileForRecommendations": "Δημιουργήστε ένα προφίλ προτιμήσεων για να δείτε προτάσεις",
"goToPreferences": "Πηγαίνετε στο \"⚙️ Προφίλ Προτιμήσεων\" για να δημιουργήσετε ένα προφίλ",
"tryDifferentFilters": "Δοκιμάστε να αλλάξετε τα φίλτρα αναζήτησης ή τις κατηγορίες στο προφίλ σας"
"tryDifferentFilters": "Δοκιμάστε να αλλάξετε τα φίλτρα αναζήτησης ή τις κατηγορίες στο προφίλ σας",
"sortBy": "Ταξινόμηση",
"sortByDistance": "Απόσταση",
"sortByRating": "Αξιολόγηση",
"recommendationSingular": "Πρόταση",
"recommendationPlural": "Προτάσεις"
},
"placeDetails": {
"location": "Τοποθεσία",
Expand Down Expand Up @@ -105,6 +115,8 @@
"HOTEL": "Ξενοδοχείο",
"CAFE": "Καφετέρια",
"BAR": "Μπαρ",
"NIGHTLIFE": "Νυχτερινή Ζωή",
"SPORTS": "Αθλητισμός",
"cheap": "Οικονομικό",
"moderate": "Μέτριο",
"expensive": "Ακριβό"
Expand Down
5 changes: 4 additions & 1 deletion src/i18n/locales/el/pages.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@
"errorUpdatingProfile": "Σφάλμα κατά την ενημέρωση του προφίλ",
"confirmDeleteProfile": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το προφίλ;",
"errorDeletingProfile": "Σφάλμα κατά τη διαγραφή του προφίλ",
"errorActivatingProfile": "Σφάλμα κατά την ενεργοποίηση του προφίλ"
"errorActivatingProfile": "Σφάλμα κατά την ενεργοποίηση του προφίλ",
"noCategoriesSelected": "Δεν έχουν επιλεγεί κατηγορίες",
"profileNamePlaceholder": "π.χ., Σαββατοκύριακο",
"profileNameRequired": "Το όνομα προφίλ δεν μπορεί να είναι κενό ή να περιέχει μόνο κενά"
},
"favouritesPage": {
"favouritesTitle": "Τα Αγαπημένα σας Μέρη",
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"ctaDescription": "Join thousands of travelers discovering amazing destinations every day.",
"exploreDestinations": "Explore Destinations",
"whatWeOffer": "What We Offer",
"whatWeOfferSubtitle": "Everything you need for your perfect travel experience",
"personalizedRecommendations": "Personalized Recommendations",
"personalizedRecommendationsDesc": "Get recommendations based on your preferences",
"reviews": "Reviews",
Expand Down Expand Up @@ -68,7 +69,8 @@
"haveAccount": "Already have an account?",
"loginLink": "Login here",
"signupError": "Signup error. Email may already be in use",
"passwordRequirement": "At least 6 characters (uppercase, lowercase, number)"
"passwordRequirement": "At least 6 characters (uppercase, lowercase, number)",
"namePlaceholder": "John Doe"
},
"validation": {
"invalidEmail": "Invalid email address",
Expand Down
16 changes: 14 additions & 2 deletions src/i18n/locales/en/features.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"from": "From",
"to": "To",
"getRoute": "Get Route",
"navigationTitle": "Route Navigation",
"navigationSubtitle": "Find the route to your destination",
"calculateRoute": "Calculate Route",
"startPoint": "Starting Point",
Expand All @@ -22,6 +23,9 @@
"minutes": "minutes",
"mapView": "Map view (will be integrated in future version)",
"quickRoutes": "Quick Routes",
"routeAthensToThessaloniki": "Athens → Thessaloniki",
"routeThessalonikiToAthens": "Thessaloniki → Athens",
"routeHeraklionToAthens": "Heraklion → Athens",
"resolvingLocation": "Resolving location...",
"enterLocation": "Location",
"searchLocationPlaceholder": "e.g., Athens, Greece",
Expand All @@ -31,7 +35,7 @@
"enterLocationsToStart": "Enter locations or select a quick route to get started"
},
"recommendationsPage": {
"recommendationsTitle": "Recommendations for You",
"recommendationsTitle": "Recommendations for You",
"recommendationsSubtitle": "Personalized recommendations based on your preferences",
"filtersTitle": "Search Filters",
"latitude": "Latitude",
Expand All @@ -42,8 +46,14 @@
"errorLoadingRecommendations": "Error loading recommendations",
"noRecommendationsFound": "No recommendations found",
"tryAgain": "Try Again",
"createProfileForRecommendations": "Create a preference profile to see recommendations",
"goToPreferences": "Go to \"⚙️ Preference Profile\" to create a profile",
"tryDifferentFilters": "Try changing the search filters or categories in your profile"
"tryDifferentFilters": "Try changing the search filters or categories in your profile",
"sortBy": "Sort By",
"sortByDistance": "Distance",
"sortByRating": "Rating",
"recommendationSingular": "Recommendation",
"recommendationPlural": "Recommendations"
},
"placeDetails": {
"location": "Location",
Expand Down Expand Up @@ -105,6 +115,8 @@
"HOTEL": "Hotel",
"CAFE": "Cafe",
"BAR": "Bar",
"NIGHTLIFE": "Nightlife",
"SPORTS": "Sports",
"cheap": "Cheap",
"moderate": "Moderate",
"expensive": "Expensive"
Expand Down
5 changes: 4 additions & 1 deletion src/i18n/locales/en/pages.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@
"errorUpdatingProfile": "Error updating profile",
"confirmDeleteProfile": "Are you sure you want to delete this profile?",
"errorDeletingProfile": "Error deleting profile",
"errorActivatingProfile": "Error activating profile"
"errorActivatingProfile": "Error activating profile",
"noCategoriesSelected": "No categories selected",
"profileNamePlaceholder": "e.g., Weekend Getaway",
"profileNameRequired": "Profile name cannot be empty or contain only whitespace"
},
"favouritesPage": {
"favouritesTitle": "Your Favourite Places",
Expand Down
2 changes: 1 addition & 1 deletion src/pages/LoginPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const LoginPage = () => {
)}

{error && (
<Alert variant="error" title="Error" data-testid="auth-error">
<Alert variant="error" title={t('error')} data-testid="auth-error">
{error}
</Alert>
)}
Expand Down
10 changes: 5 additions & 5 deletions src/pages/NavigationPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ import './NavigationPage.css';

/** Predefined quick routes for popular destinations */
const QUICK_ROUTES = [
{ name: 'Athens → Thessaloniki', icon: '🏛️', from: 'Athens, Greece', to: 'Thessaloniki, Greece' },
{ name: 'Thessaloniki → Athens', icon: '🏛️', from: 'Thessaloniki, Greece', to: 'Athens, Greece' },
{ name: 'Heraklion → Athens', icon: '🏖️', from: 'Heraklion, Crete, Greece', to: 'Athens, Greece' },
{ nameKey: 'routeAthensToThessaloniki', icon: '🏛️', from: 'Athens, Greece', to: 'Thessaloniki, Greece' },
{ nameKey: 'routeThessalonikiToAthens', icon: '🏛️', from: 'Thessaloniki, Greece', to: 'Athens, Greece' },
{ nameKey: 'routeHeraklionToAthens', icon: '🏖️', from: 'Heraklion, Crete, Greece', to: 'Athens, Greece' },
];

/**
Expand Down Expand Up @@ -95,7 +95,7 @@ const NavigationPage = () => {
return (
<div className="navigation-page">
{/* Hero section with page title */}
<Hero title={t('Route Navigation')} subtitle={t('navigationSubtitle')} size="small" align="center" />
<Hero title={t('navigationTitle')} subtitle={t('navigationSubtitle')} size="small" align="center" />

<div className="container">
{/* Main navigation layout with form and results columns */}
Expand All @@ -120,7 +120,7 @@ const NavigationPage = () => {
<button key={index} className="quick-route-btn"
onClick={() => setFormData({ fromLocation: route.from, toLocation: route.to, transportMode: 'DRIVING' })}>
<span className="route-emoji">{route.icon}</span>
<span className="route-name">{route.name}</span>
<span className="route-name">{t(route.nameKey)}</span>
<Icon name="arrow" size="sm" className="route-arrow" />
</button>
))}
Expand Down
13 changes: 7 additions & 6 deletions src/pages/PreferencesPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ const PreferencesPage = () => {
if (loading) {
return (
<div className="preferences-page">
<Hero title={t('preferenceProfiles')} subtitle={t('managePreferenceProfiles')}
size="small" align="center" />
<Hero title={t('preferenceProfiles')} subtitle={t('managePreferenceProfiles')}
size="small" align="center" />
<div className="container">
<div className="loading-state">
<Spinner size="lg" />
Expand All @@ -89,8 +89,8 @@ const PreferencesPage = () => {
if (error) {
return (
<div className="preferences-page">
<Hero title={t('preferenceProfiles')} subtitle={t('managePreferenceProfiles')}
size="small" align="center" />
<Hero title={t('preferenceProfiles')} subtitle={t('managePreferenceProfiles')}
size="small" align="center" />
<div className="container">
<div className="error-state">
<Icon name="alert" size="2xl" />
Expand All @@ -103,8 +103,8 @@ const PreferencesPage = () => {

return (
<div className="preferences-page">
<Hero title={t('preferenceProfiles')} subtitle={t('managePreferenceProfiles')}
size="small" align="center" />
<Hero title={t('preferenceProfiles')} subtitle={t('managePreferenceProfiles')}
size="small" align="center" />

<div className="container">
{/* Create new profile button */}
Expand All @@ -124,6 +124,7 @@ const PreferencesPage = () => {
editingProfile={editingProfile}
onSubmit={editingProfile ? handleUpdate : handleCreate}
onClose={closeForm}
showError={showError}
/>
)}

Expand Down
4 changes: 2 additions & 2 deletions src/pages/RecommendationsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ const RecommendationsPage = () => {
// Info state: API message (e.g., no active profile)
<div className="info-state">
<div className="info-icon">ℹ️</div>
<h3>{message}</h3>
<h3>{t('createProfileForRecommendations')}</h3>
<p>{t('goToPreferences')}</p>
<Button variant="primary" onClick={() => window.location.href = '/preferences'}>{t('preferences')}</Button>
</div>
) : recommendations.length > 0 ? (
// Success state: show recommendations grid
<>
<div className="results-header">
<h2 className="results-title">{recommendations.length} {recommendations.length === 1 ? 'Recommendation' : 'Recommendations'}</h2>
<h2 className="results-title">{recommendations.length} {recommendations.length === 1 ? t('recommendationSingular') : t('recommendationPlural')}</h2>
</div>
<div className="recommendations-grid">
{recommendations.map((rec, index) => (
Expand Down
Loading
Loading