From 34d4f5a5653a0796ccb5e80a63b80c3fcfa8fcfe Mon Sep 17 00:00:00 2001 From: Giannis Fraidakis Date: Mon, 29 Dec 2025 13:48:17 +0200 Subject: [PATCH 1/2] feat(i18n): add missing translations and improve form validation UX --- src/components/home/FeaturesSection.jsx | 2 +- src/components/preferences/PreferenceForm.jsx | 30 ++++++++++++++----- src/components/preferences/ProfileCard.jsx | 4 +-- .../recommendations/RecommendationFilters.jsx | 6 ++-- src/i18n/locales/el/common.json | 4 ++- src/i18n/locales/el/features.json | 16 ++++++++-- src/i18n/locales/el/pages.json | 5 +++- src/i18n/locales/en/common.json | 4 ++- src/i18n/locales/en/features.json | 16 ++++++++-- src/i18n/locales/en/pages.json | 5 +++- src/pages/LoginPage.jsx | 2 +- src/pages/NavigationPage.jsx | 10 +++---- src/pages/PreferencesPage.jsx | 13 ++++---- src/pages/RecommendationsPage.jsx | 4 +-- src/pages/SignupPage.jsx | 6 ++-- 15 files changed, 89 insertions(+), 38 deletions(-) diff --git a/src/components/home/FeaturesSection.jsx b/src/components/home/FeaturesSection.jsx index 37babcfa..cb4cb0e4 100644 --- a/src/components/home/FeaturesSection.jsx +++ b/src/components/home/FeaturesSection.jsx @@ -16,7 +16,7 @@ const FeaturesSection = ({ t }) => {

{t('whatWeOffer')}

-

Everything you need for your perfect travel experience

+

{t('whatWeOfferSubtitle')}

diff --git a/src/components/preferences/PreferenceForm.jsx b/src/components/preferences/PreferenceForm.jsx index 26a7fb66..66d7126b 100644 --- a/src/components/preferences/PreferenceForm.jsx +++ b/src/components/preferences/PreferenceForm.jsx @@ -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 || []; @@ -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 (
@@ -41,7 +59,7 @@ const PreferenceForm = ({ t, formData, setFormData, editingProfile, onSubmit, on
-
+
setFormData({ ...formData, name: e.target.value })} className="form-input" - placeholder="e.g., Weekend Getaway" - required - pattern=".*\S+.*" - title="Profile name cannot be empty or contain only whitespace" + placeholder={t('profileNamePlaceholder')} />
@@ -67,7 +82,7 @@ const PreferenceForm = ({ t, formData, setFormData, editingProfile, onSubmit, on onClick={() => toggleCategory(cat)} > {CATEGORY_ICONS[cat]} - {cat} + {t(cat)} ))}
@@ -88,3 +103,4 @@ const PreferenceForm = ({ t, formData, setFormData, editingProfile, onSubmit, on export { PreferenceForm, CATEGORY_ICONS }; export default PreferenceForm; + diff --git a/src/components/preferences/ProfileCard.jsx b/src/components/preferences/ProfileCard.jsx index 9c08c6e9..caaee90c 100644 --- a/src/components/preferences/ProfileCard.jsx +++ b/src/components/preferences/ProfileCard.jsx @@ -35,11 +35,11 @@ const ProfileCard = ({ t, profile, isActive, index, onActivate, onEdit, onDelete
{(profile.categories || []).map(cat => ( - {CATEGORY_ICONS[cat]} {cat} + {CATEGORY_ICONS[cat]} {t(cat)} ))} {(!profile.categories || profile.categories.length === 0) && ( - No categories selected + {t('noCategoriesSelected')} )}
diff --git a/src/components/recommendations/RecommendationFilters.jsx b/src/components/recommendations/RecommendationFilters.jsx index 84c2f05b..6c1325c0 100644 --- a/src/components/recommendations/RecommendationFilters.jsx +++ b/src/components/recommendations/RecommendationFilters.jsx @@ -58,10 +58,10 @@ const RecommendationFilters = ({ />
- +
diff --git a/src/i18n/locales/el/common.json b/src/i18n/locales/el/common.json index 9c405930..fa7d7b91 100644 --- a/src/i18n/locales/el/common.json +++ b/src/i18n/locales/el/common.json @@ -41,6 +41,7 @@ "ctaDescription": "Γίνετε μέλος χιλιάδων ταξιδιωτών που ανακαλύπτουν εκπληκτικούς προορισμούς κάθε μέρα.", "exploreDestinations": "Εξερευνήστε Προορισμούς", "whatWeOffer": "Τι προσφέρουμε", + "whatWeOfferSubtitle": "Ό,τι χρειάζεστε για μια τέλεια ταξιδιωτική εμπειρία", "personalizedRecommendations": "Εξατομικευμένες Προτάσεις", "personalizedRecommendationsDesc": "Λάβετε προτάσεις βασισμένες στις προτιμήσεις σας", "reviews": "Αξιολογήσεις", @@ -68,7 +69,8 @@ "haveAccount": "Έχετε ήδη λογαριασμό;", "loginLink": "Συνδεθείτε εδώ", "signupError": "Σφάλμα εγγραφής. Το email μπορεί να χρησιμοποιείται ήδη", - "passwordRequirement": "Τουλάχιστον 6 χαρακτήρες (κεφαλαίο, πεζό, αριθμό)" + "passwordRequirement": "Τουλάχιστον 6 χαρακτήρες (κεφαλαίο, πεζό, αριθμό)", + "namePlaceholder": "Γιάννης Παπαδόπουλος" }, "validation": { "invalidEmail": "Μη έγκυρη διεύθυνση email", diff --git a/src/i18n/locales/el/features.json b/src/i18n/locales/el/features.json index 22a47b14..d2020a2b 100644 --- a/src/i18n/locales/el/features.json +++ b/src/i18n/locales/el/features.json @@ -3,6 +3,7 @@ "from": "Από", "to": "Προς", "getRoute": "Εύρεση Διαδρομής", + "navigationTitle": "Πλοήγηση Διαδρομής", "navigationSubtitle": "Βρείτε τη διαδρομή προς τον προορισμό σας", "calculateRoute": "Υπολογισμός Διαδρομής", "startPoint": "Σημείο Εκκίνησης", @@ -22,6 +23,9 @@ "minutes": "λεπτά", "mapView": "Προβολή χάρτη (θα ενσωματωθεί σε μελλοντική έκδοση)", "quickRoutes": "Γρήγορες Διαδρομές", + "routeAthensToThessaloniki": "Αθήνα → Θεσσαλονίκη", + "routeThessalonikiToAthens": "Θεσσαλονίκη → Αθήνα", + "routeHeraklionToAthens": "Ηράκλειο → Αθήνα", "resolvingLocation": "Εύρεση τοποθεσίας...", "enterLocation": "Τοποθεσία", "searchLocationPlaceholder": "π.χ., Αθήνα, Ελλάδα", @@ -31,7 +35,7 @@ "enterLocationsToStart": "Εισάγετε τοποθεσίες ή επιλέξτε μια γρήγορη διαδρομή" }, "recommendationsPage": { - "recommendationsTitle": "✨ Προτάσεις για Εσάς", + "recommendationsTitle": "Προτάσεις για Εσάς", "recommendationsSubtitle": "Εξατομικευμένες προτάσεις βασισμένες στις προτιμήσεις σας", "filtersTitle": "Φίλτρα Αναζήτησης", "latitude": "Γεωγραφικό Πλάτος", @@ -42,8 +46,14 @@ "errorLoadingRecommendations": "Σφάλμα κατά τη φόρτωση των προτάσεων", "noRecommendationsFound": "Δεν βρέθηκαν προτάσεις", "tryAgain": "Προσπάθεια Ξανά", + "createProfileForRecommendations": "Δημιουργήστε ένα προφίλ προτιμήσεων για να δείτε προτάσεις", "goToPreferences": "Πηγαίνετε στο \"⚙️ Προφίλ Προτιμήσεων\" για να δημιουργήσετε ένα προφίλ", - "tryDifferentFilters": "Δοκιμάστε να αλλάξετε τα φίλτρα αναζήτησης ή τις κατηγορίες στο προφίλ σας" + "tryDifferentFilters": "Δοκιμάστε να αλλάξετε τα φίλτρα αναζήτησης ή τις κατηγορίες στο προφίλ σας", + "sortBy": "Ταξινόμηση", + "sortByDistance": "Απόσταση", + "sortByRating": "Αξιολόγηση", + "recommendationSingular": "Πρόταση", + "recommendationPlural": "Προτάσεις" }, "placeDetails": { "location": "Τοποθεσία", @@ -105,6 +115,8 @@ "HOTEL": "Ξενοδοχείο", "CAFE": "Καφετέρια", "BAR": "Μπαρ", + "NIGHTLIFE": "Νυχτερινή Ζωή", + "SPORTS": "Αθλητισμός", "cheap": "Οικονομικό", "moderate": "Μέτριο", "expensive": "Ακριβό" diff --git a/src/i18n/locales/el/pages.json b/src/i18n/locales/el/pages.json index 5930a20c..d072fca6 100644 --- a/src/i18n/locales/el/pages.json +++ b/src/i18n/locales/el/pages.json @@ -51,7 +51,10 @@ "errorUpdatingProfile": "Σφάλμα κατά την ενημέρωση του προφίλ", "confirmDeleteProfile": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το προφίλ;", "errorDeletingProfile": "Σφάλμα κατά τη διαγραφή του προφίλ", - "errorActivatingProfile": "Σφάλμα κατά την ενεργοποίηση του προφίλ" + "errorActivatingProfile": "Σφάλμα κατά την ενεργοποίηση του προφίλ", + "noCategoriesSelected": "Δεν έχουν επιλεγεί κατηγορίες", + "profileNamePlaceholder": "π.χ., Σαββατοκύριακο", + "profileNameRequired": "Το όνομα προφίλ δεν μπορεί να είναι κενό ή να περιέχει μόνο κενά" }, "favouritesPage": { "favouritesTitle": "Τα Αγαπημένα σας Μέρη", diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 3d308a95..680ae611 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -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", @@ -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", diff --git a/src/i18n/locales/en/features.json b/src/i18n/locales/en/features.json index 6ad1ba4d..feeec099 100644 --- a/src/i18n/locales/en/features.json +++ b/src/i18n/locales/en/features.json @@ -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", @@ -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", @@ -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", @@ -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", @@ -105,6 +115,8 @@ "HOTEL": "Hotel", "CAFE": "Cafe", "BAR": "Bar", + "NIGHTLIFE": "Nightlife", + "SPORTS": "Sports", "cheap": "Cheap", "moderate": "Moderate", "expensive": "Expensive" diff --git a/src/i18n/locales/en/pages.json b/src/i18n/locales/en/pages.json index 8db8f443..0bbe9a83 100644 --- a/src/i18n/locales/en/pages.json +++ b/src/i18n/locales/en/pages.json @@ -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", diff --git a/src/pages/LoginPage.jsx b/src/pages/LoginPage.jsx index 185ca714..950e1c20 100644 --- a/src/pages/LoginPage.jsx +++ b/src/pages/LoginPage.jsx @@ -112,7 +112,7 @@ const LoginPage = () => { )} {error && ( - + {error} )} diff --git a/src/pages/NavigationPage.jsx b/src/pages/NavigationPage.jsx index f926aa92..8da9e9e7 100644 --- a/src/pages/NavigationPage.jsx +++ b/src/pages/NavigationPage.jsx @@ -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' }, ]; /** @@ -95,7 +95,7 @@ const NavigationPage = () => { return (
{/* Hero section with page title */} - +
{/* Main navigation layout with form and results columns */} @@ -120,7 +120,7 @@ const NavigationPage = () => { ))} diff --git a/src/pages/PreferencesPage.jsx b/src/pages/PreferencesPage.jsx index 23b92d23..d7238218 100644 --- a/src/pages/PreferencesPage.jsx +++ b/src/pages/PreferencesPage.jsx @@ -73,8 +73,8 @@ const PreferencesPage = () => { if (loading) { return (
- +
@@ -89,8 +89,8 @@ const PreferencesPage = () => { if (error) { return (
- +
@@ -103,8 +103,8 @@ const PreferencesPage = () => { return (
- +
{/* Create new profile button */} @@ -124,6 +124,7 @@ const PreferencesPage = () => { editingProfile={editingProfile} onSubmit={editingProfile ? handleUpdate : handleCreate} onClose={closeForm} + showError={showError} /> )} diff --git a/src/pages/RecommendationsPage.jsx b/src/pages/RecommendationsPage.jsx index 3a16dc4a..bd8f456c 100644 --- a/src/pages/RecommendationsPage.jsx +++ b/src/pages/RecommendationsPage.jsx @@ -99,7 +99,7 @@ const RecommendationsPage = () => { // Info state: API message (e.g., no active profile)
ℹ️
-

{message}

+

{t('createProfileForRecommendations')}

{t('goToPreferences')}

@@ -107,7 +107,7 @@ const RecommendationsPage = () => { // Success state: show recommendations grid <>
-

{recommendations.length} {recommendations.length === 1 ? 'Recommendation' : 'Recommendations'}

+

{recommendations.length} {recommendations.length === 1 ? t('recommendationSingular') : t('recommendationPlural')}

{recommendations.map((rec, index) => ( diff --git a/src/pages/SignupPage.jsx b/src/pages/SignupPage.jsx index 1a1f2fe3..3312cf9a 100644 --- a/src/pages/SignupPage.jsx +++ b/src/pages/SignupPage.jsx @@ -63,7 +63,7 @@ const SignupPage = () => { }); storeUserSession(response); - navigate('/'); + navigate('/preferences'); } catch (err) { console.error('Signup error:', err); setError(extractErrorMessage(err, t('signupError'))); @@ -89,7 +89,7 @@ const SignupPage = () => { {({ isSubmitting, errors, touched }) => ( {error && ( - + {error} )} @@ -100,7 +100,7 @@ const SignupPage = () => { type="text" id="name" name="name" - placeholder="John Doe" + placeholder={t('namePlaceholder')} className={errors.name && touched.name ? 'error' : ''} data-cy="input-name" /> From 232098391419a705fd2f6c3203335f091564bcbc Mon Sep 17 00:00:00 2001 From: Giannis Fraidakis Date: Sun, 4 Jan 2026 20:46:31 +0200 Subject: [PATCH 2/2] fix(test): unhappy_paths.cy --- src/components/preferences/PreferenceForm.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/preferences/PreferenceForm.jsx b/src/components/preferences/PreferenceForm.jsx index 66d7126b..57f00e49 100644 --- a/src/components/preferences/PreferenceForm.jsx +++ b/src/components/preferences/PreferenceForm.jsx @@ -68,6 +68,7 @@ const PreferenceForm = ({ t, formData, setFormData, editingProfile, onSubmit, on onChange={(e) => setFormData({ ...formData, name: e.target.value })} className="form-input" placeholder={t('profileNamePlaceholder')} + required />