Compare commits

..

4 Commits

Author SHA1 Message Date
47ac6f4c74 Merge pull request 'feat: added mock search results' (#3) from feature/mock-search-results into main
All checks were successful
Frontend CI / Lint and Build Frontend (push) Successful in 20s
Reviewed-on: http://git.naali.duckdns.org/jarno/vibing/pulls/3
2025-08-02 16:51:57 +00:00
928f65f9c5 build: downgraded actions for compatibility
All checks were successful
Frontend CI / Lint and Build Frontend (pull_request) Successful in 1m25s
2025-08-02 16:43:45 +03:00
19cba1606b fix: fixed eslint errors
Some checks failed
Frontend CI / Lint and Build Frontend (pull_request) Failing after 21s
2025-08-02 16:37:14 +03:00
9a11b4ea7e feat: added mock search results
Some checks failed
Frontend CI / Lint and Build Frontend (pull_request) Failing after 1m35s
2025-08-02 15:29:18 +03:00
9 changed files with 372 additions and 101 deletions

View File

@@ -17,10 +17,10 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v3
with: with:
node-version: '18' node-version: '18'
cache: 'npm' cache: 'npm'
@@ -39,7 +39,7 @@ jobs:
run: npm run build run: npm run build
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: frontend-build name: frontend-build
path: frontend/dist path: frontend/dist

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import './Footer.css'; import './Footer.css';
import { useLanguage } from '../contexts/LanguageContext'; import { useLanguage } from '../hooks/useLanguage';
const Footer = () => { const Footer = () => {
const { t } = useLanguage(); const { t } = useLanguage();

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useLanguage } from '../contexts/LanguageContext'; import { useLanguage } from '../hooks/useLanguage';
import LanguageSwitcher from './LanguageSwitcher'; import LanguageSwitcher from './LanguageSwitcher';
import './Header.css'; import './Header.css';

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { useLanguage } from '../contexts/LanguageContext'; import { useLanguage } from '../hooks/useLanguage';
import './LanguageSwitcher.css'; import './LanguageSwitcher.css';
const LanguageSwitcher = () => { const LanguageSwitcher = () => {

View File

@@ -1,86 +1,5 @@
import React, { createContext, useContext, useState } from 'react'; import React, { useState } from 'react';
import { LanguageContext, translations } from './languageContextData';
const LanguageContext = createContext();
export const useLanguage = () => {
const context = useContext(LanguageContext);
if (!context) {
throw new Error('useLanguage must be used within a LanguageProvider');
}
return context;
};
const translations = {
en: {
// Header
home: 'Home',
about: 'About',
contact: 'Contact',
// CTA Section
readyToStart: 'Ready to get started?',
ctaSubtitle: 'Join thousands of developers building amazing applications with our modern stack.',
startBuilding: 'Start Building',
// Language Switcher
language: 'Language',
english: 'English',
finnish: 'Finnish',
// Search Page
searchPlaceholder: 'Enter your search query...',
searchButton: 'Search',
searchResults: 'Search Results',
searchResultsSubtitle: 'Here are the results for your search query.',
searchResultsPlaceholder: 'Search results will appear here. Try entering a search term above to get started.',
// Search Filters
dogFriendly: 'Dog Friendly',
accessible: 'Accessible',
familyFriendly: 'Family Friendly',
peopleCount: 'People',
priceRange: 'Price',
any: 'Any',
// Footer
footerDescription: 'Find fun things to do!',
},
fi: {
// Header
home: 'Koti',
about: 'Tietoja',
contact: 'Yhteystiedot',
// CTA Section
readyToStart: 'Valmiina aloittamaan?',
ctaSubtitle: 'Liity tuhansien kehittäjien joukkoon, jotka rakentavat upeita sovelluksia modernilla teknologiapinoamme.',
startBuilding: 'Aloita rakentaminen',
// Language Switcher
language: 'Kieli',
english: 'Englanti',
finnish: 'Suomi',
// Search Page
searchPlaceholder: 'Syötä hakukyselysi...',
searchButton: 'Hae',
searchResults: 'Hakutulokset',
searchResultsSubtitle: 'Tässä ovat hakutulokset kyselysi perusteella.',
searchResultsPlaceholder: 'Hakutulokset näkyvät täällä. Kokeile syöttää hakusana yllä aloittaaksesi.',
// Search Filters
dogFriendly: 'Koiraystävällinen',
accessible: 'Esteetön',
familyFriendly: 'Lapsiystävällinen',
peopleCount: 'Henkilöt',
priceRange: 'Hinta',
any: 'Mikä tahansa',
// Footer
footerDescription: 'Löydä hauskaa tekemistä!',
}
};
export const LanguageProvider = ({ children }) => { export const LanguageProvider = ({ children }) => {
const [language, setLanguage] = useState('en'); const [language, setLanguage] = useState('en');

View File

@@ -0,0 +1,75 @@
import { createContext } from 'react';
export const LanguageContext = createContext();
export const translations = {
en: {
// Header
home: 'Home',
about: 'About',
contact: 'Contact',
// CTA Section
readyToStart: 'Ready to get started?',
ctaSubtitle: 'Join thousands of developers building amazing applications with our modern stack.',
startBuilding: 'Start Building',
// Language Switcher
language: 'Language',
english: 'English',
finnish: 'Finnish',
// Search Page
searchPlaceholder: 'Enter your search query...',
searchButton: 'Search',
searchResults: 'Search Results',
searchResultsSubtitle: 'Here are the results for your search query.',
searchResultsPlaceholder: 'Search results will appear here. Try entering a search term above to get started.',
// Search Filters
dogFriendly: 'Dog Friendly',
accessible: 'Accessible',
familyFriendly: 'Family Friendly',
peopleCount: 'People',
priceRange: 'Price',
any: 'Any',
// Footer
footerDescription: 'Find fun things to do!',
},
fi: {
// Header
home: 'Koti',
about: 'Tietoja',
contact: 'Yhteystiedot',
// CTA Section
readyToStart: 'Valmiina aloittamaan?',
ctaSubtitle: 'Liity tuhansien kehittäjien joukkoon, jotka rakentavat upeita sovelluksia modernilla teknologiapinoamme.',
startBuilding: 'Aloita rakentaminen',
// Language Switcher
language: 'Kieli',
english: 'Englanti',
finnish: 'Suomi',
// Search Page
searchPlaceholder: 'Syötä hakukyselysi...',
searchButton: 'Hae',
searchResults: 'Hakutulokset',
searchResultsSubtitle: 'Tässä ovat hakutulokset kyselysi perusteella.',
searchResultsPlaceholder: 'Hakutulokset näkyvät täällä. Kokeile syöttää hakusana yllä aloittaaksesi.',
// Search Filters
dogFriendly: 'Koiraystävällinen',
accessible: 'Esteetön',
familyFriendly: 'Lapsiystävällinen',
peopleCount: 'Henkilöt',
priceRange: 'Hinta',
any: 'Mikä tahansa',
// Footer
footerDescription: 'Löydä hauskaa tekemistä!',
}
};

View File

@@ -0,0 +1,10 @@
import { useContext } from 'react';
import { LanguageContext } from '../contexts/languageContextData';
export const useLanguage = () => {
const context = useContext(LanguageContext);
if (!context) {
throw new Error('useLanguage must be used within a LanguageProvider');
}
return context;
};

View File

@@ -205,6 +205,107 @@
gap: 2rem; gap: 2rem;
} }
/* Search Results Section */
.search-results {
padding: 4rem 0;
background: white;
}
.results-title {
text-align: center;
font-size: 2rem;
font-weight: 700;
margin: 0 0 3rem 0;
color: var(--color-text-primary);
}
.results-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.result-card {
height: 100%;
display: flex;
flex-direction: column;
}
.result-card .card-content {
flex: 1;
display: flex;
flex-direction: column;
}
.result-card .card-body {
flex: 1;
display: flex;
flex-direction: column;
gap: 1rem;
}
.result-card .card-body p {
margin: 0;
line-height: 1.6;
color: var(--color-text-secondary);
}
.amenities {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: auto;
}
.amenity-tag {
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
color: var(--color-dark);
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
box-shadow: 0 2px 8px var(--color-shadow-primary);
transition: all 0.3s ease;
}
.amenity-tag:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px var(--color-shadow-primary);
}
/* No Results Section */
.no-results {
padding: 4rem 0;
background: white;
text-align: center;
}
.no-results-content h2 {
font-size: 2rem;
font-weight: 700;
margin: 0 0 1rem 0;
color: var(--color-text-primary);
}
.no-results-content p {
font-size: 1.1rem;
color: var(--color-text-secondary);
margin: 0;
}
/* Loading State */
.search-button.searching {
background: linear-gradient(135deg, var(--color-secondary-dark) 0%, var(--color-dark) 100%);
cursor: not-allowed;
opacity: 0.8;
}
.search-button.searching:hover {
transform: none;
box-shadow: 0 4px 12px var(--color-shadow-secondary);
}
/* Responsive Design */ /* Responsive Design */
@media (max-width: 768px) { @media (max-width: 768px) {
.hero { .hero {
@@ -261,6 +362,27 @@
.features-grid { .features-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.search-results {
padding: 3rem 0;
}
.results-title {
font-size: 1.75rem;
}
.results-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.no-results {
padding: 3rem 0;
}
.no-results-content h2 {
font-size: 1.75rem;
}
} }
@media (max-width: 480px) { @media (max-width: 480px) {
@@ -284,6 +406,14 @@
min-width: 180px; min-width: 180px;
padding: 0.75rem; padding: 0.75rem;
} }
.results-title {
font-size: 1.5rem;
}
.no-results-content h2 {
font-size: 1.5rem;
}
} }
/* Dark Mode Support */ /* Dark Mode Support */
@@ -334,4 +464,24 @@
.features { .features {
background: var(--color-bg-dark); background: var(--color-bg-dark);
} }
.search-results {
background: var(--color-bg-dark);
}
.no-results {
background: var(--color-bg-dark);
}
.results-title {
color: var(--color-text-primary-light);
}
.no-results-content h2 {
color: var(--color-text-primary-light);
}
.no-results-content p {
color: var(--color-text-secondary);
}
} }

View File

@@ -1,6 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Header, Footer, Button, Card } from '../components'; import { Header, Footer, Button, Card } from '../components';
import { useLanguage } from '../contexts/LanguageContext'; import { useLanguage } from '../hooks/useLanguage';
import './Home.css'; import './Home.css';
const Home = () => { const Home = () => {
@@ -13,14 +13,62 @@ const Home = () => {
peopleCount: '', peopleCount: '',
priceRange: '' priceRange: ''
}); });
const [searchResults, setSearchResults] = useState([]);
const [isSearching, setIsSearching] = useState(false);
// Mockup data for search results
const mockResults = [
{
id: 1,
title: "Cozy Mountain Cabin",
subtitle: "€€ • 4 guests • Dog friendly",
image: "https://images.unsplash.com/photo-1449824913935-59a10b8d2000?q=80&w=1920&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
description: "Beautiful mountain cabin with stunning views, perfect for a peaceful getaway. Features a hot tub and fireplace.",
amenities: ["Dog friendly", "Fireplace", "Hot tub", "Mountain view"]
},
{
id: 2,
title: "Modern City Apartment",
subtitle: "€€€ • 2 guests • Accessible",
image: "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?q=80&w=1920&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
description: "Stylish apartment in the heart of the city with modern amenities and easy access to public transportation.",
amenities: ["Accessible", "City view", "Gym access", "Parking"]
},
{
id: 3,
title: "Family Beach House",
subtitle: "€€€€ • 6 guests • Family friendly",
image: "https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?q=80&w=1920&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
description: "Spacious beachfront property perfect for family vacations. Includes a pool, playground, and direct beach access.",
amenities: ["Family friendly", "Beach access", "Pool", "Playground"]
},
{
id: 4,
title: "Rustic Farmhouse",
subtitle: "€€ • 8 guests • Dog friendly",
image: "https://images.unsplash.com/photo-1571896349842-33c89424de2d?q=80&w=1920&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
description: "Charming farmhouse surrounded by nature. Perfect for large groups looking for a rural escape.",
amenities: ["Dog friendly", "Large groups", "Nature", "Fireplace"]
},
{
id: 5,
title: "Luxury Villa",
subtitle: "€€€€ • 4 guests • Accessible",
image: "https://images.unsplash.com/photo-1613490493576-7fde63acd811?q=80&w=1920&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
description: "Exclusive villa with premium amenities including a private pool, chef's kitchen, and stunning ocean views.",
amenities: ["Accessible", "Private pool", "Ocean view", "Chef's kitchen"]
},
{
id: 6,
title: "Cozy Studio",
subtitle: "€ • 2 guests • Family friendly",
image: "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?q=80&w=1920&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
description: "Affordable studio apartment perfect for couples or small families. Located in a quiet neighborhood.",
amenities: ["Family friendly", "Affordable", "Quiet", "Central location"]
}
];
const handleGetStarted = () => {
alert('Get Started button clicked!');
};
const handleLearnMore = () => {
alert('Learn More button clicked!');
};
const handleFilterChange = (filterName, value) => { const handleFilterChange = (filterName, value) => {
setFilters(prev => ({ setFilters(prev => ({
@@ -30,9 +78,30 @@ const Home = () => {
}; };
const handleSearch = () => { const handleSearch = () => {
// Handle search logic here if (!searchQuery.trim()) {
console.log('Search query:', searchQuery); alert('Please enter a search query');
console.log('Filters:', filters); return;
}
setIsSearching(true);
// Simulate API call delay
setTimeout(() => {
// Filter results based on search query and filters
let filteredResults = mockResults.filter(result => {
const matchesQuery = result.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
result.description.toLowerCase().includes(searchQuery.toLowerCase());
const matchesDogFriendly = !filters.dogFriendly || result.amenities.includes('Dog friendly');
const matchesAccessible = !filters.accessible || result.amenities.includes('Accessible');
const matchesFamilyFriendly = !filters.familyFriendly || result.amenities.includes('Family friendly');
return matchesQuery && matchesDogFriendly && matchesAccessible && matchesFamilyFriendly;
});
setSearchResults(filteredResults);
setIsSearching(false);
}, 1000);
}; };
return ( return (
@@ -57,8 +126,12 @@ const Home = () => {
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSearch()} onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
/> />
<button className="search-button" onClick={handleSearch}> <button
{t('searchButton')} className={`search-button ${isSearching ? 'searching' : ''}`}
onClick={handleSearch}
disabled={isSearching}
>
{isSearching ? 'Searching...' : t('searchButton')}
</button> </button>
</div> </div>
@@ -140,6 +213,50 @@ const Home = () => {
</div> </div>
</section> </section>
{/* Search Results Section */}
{searchResults.length > 0 && (
<section className="search-results">
<div className="container">
<h2 className="results-title">
Found {searchResults.length} result{searchResults.length !== 1 ? 's' : ''} for "{searchQuery}"
</h2>
<div className="results-grid">
{searchResults.map(result => (
<Card
key={result.id}
title={result.title}
subtitle={result.subtitle}
image={result.image}
imageAlt={result.title}
className="result-card"
>
<p>{result.description}</p>
<div className="amenities">
{result.amenities.map((amenity, index) => (
<span key={index} className="amenity-tag">
{amenity}
</span>
))}
</div>
</Card>
))}
</div>
</div>
</section>
)}
{/* No Results Message */}
{searchResults.length === 0 && !isSearching && searchQuery && (
<section className="no-results">
<div className="container">
<div className="no-results-content">
<h2>No results found for "{searchQuery}"</h2>
<p>Try adjusting your search terms or filters to find more options.</p>
</div>
</div>
</section>
)}
</main> </main>
<Footer /> <Footer />