Compare commits
1 Commits
b03b0997e2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e993d93f03 |
49
keynest/package-lock.json
generated
49
keynest/package-lock.json
generated
@@ -12,11 +12,12 @@
|
|||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.2.0",
|
"@testing-library/react": "^16.2.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"axios": "^1.9.0",
|
||||||
"lorem-ipsum": "^2.0.8",
|
"lorem-ipsum": "^2.0.8",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-modal": "^3.16.3",
|
"react-modal": "^3.16.3",
|
||||||
"react-router-dom": "^7.5.1",
|
"react-router-dom": "^7.5.3",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"styled-components": "^6.1.16",
|
"styled-components": "^6.1.16",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
@@ -4928,6 +4929,32 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/axios/node_modules/form-data": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||||
@@ -13726,6 +13753,12 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/psl": {
|
"node_modules/psl": {
|
||||||
"version": "1.15.0",
|
"version": "1.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
|
||||||
@@ -14046,9 +14079,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
"version": "7.5.1",
|
"version": "7.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz",
|
||||||
"integrity": "sha512-/jjU3fcYNd2bwz9Q0xt5TwyiyoO8XjSEFXJY4O/lMAlkGTHWuHRAbR9Etik+lSDqMC7A7mz3UlXzgYT6Vl58sA==",
|
"integrity": "sha512-3iUDM4/fZCQ89SXlDa+Ph3MevBrozBAI655OAfWQlTm9nBR0IKlrmNwFow5lPHttbwvITZfkeeeZFP6zt3F7pw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cookie": "^1.0.1",
|
"cookie": "^1.0.1",
|
||||||
@@ -14069,12 +14102,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router-dom": {
|
"node_modules/react-router-dom": {
|
||||||
"version": "7.5.1",
|
"version": "7.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.3.tgz",
|
||||||
"integrity": "sha512-5DPSPc7ENrt2tlKPq0FtpG80ZbqA9aIKEyqX6hSNJDlol/tr6iqCK4crqdsusmOSSotq6zDsn0y3urX9TuTNmA==",
|
"integrity": "sha512-cK0jSaTyW4jV9SRKAItMIQfWZ/D6WEZafgHuuCb9g+SjhLolY78qc+De4w/Cz9ybjvLzShAmaIMEXt8iF1Cm+A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react-router": "7.5.1"
|
"react-router": "7.5.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
|
|||||||
@@ -7,11 +7,12 @@
|
|||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.2.0",
|
"@testing-library/react": "^16.2.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"axios": "^1.9.0",
|
||||||
"lorem-ipsum": "^2.0.8",
|
"lorem-ipsum": "^2.0.8",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-modal": "^3.16.3",
|
"react-modal": "^3.16.3",
|
||||||
"react-router-dom": "^7.5.1",
|
"react-router-dom": "^7.5.3",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"styled-components": "^6.1.16",
|
"styled-components": "^6.1.16",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
|
|||||||
BIN
keynest/public/images/apartment1.jpg
Normal file
BIN
keynest/public/images/apartment1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
BIN
keynest/public/images/apartment2.jpg
Normal file
BIN
keynest/public/images/apartment2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
BIN
keynest/public/images/apartment3.jpg
Normal file
BIN
keynest/public/images/apartment3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
BIN
keynest/public/images/apartment4.jpg
Normal file
BIN
keynest/public/images/apartment4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 430 KiB |
BIN
keynest/public/images/apartment5.jpg
Normal file
BIN
keynest/public/images/apartment5.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
BIN
keynest/public/images/apartment6.jpg
Normal file
BIN
keynest/public/images/apartment6.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 268 KiB |
@@ -8,43 +8,47 @@ import { AuthProvider, useAuth } from './components/AuthContext';
|
|||||||
import BookingPage from './components/BookingPage';
|
import BookingPage from './components/BookingPage';
|
||||||
import MyRentals from './components/MyRentals';
|
import MyRentals from './components/MyRentals';
|
||||||
import Footer from './components/Footer';
|
import Footer from './components/Footer';
|
||||||
|
import ApartmentList from './components/ApartmentList';
|
||||||
|
import { RentalProvider } from './components/RentalContext'; // Corrected import path
|
||||||
|
|
||||||
function AppContent() {
|
function AppContent() {
|
||||||
const { isLoggedIn, logout, user } = useAuth();
|
const { isLoggedIn, logout, user } = useAuth();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='app'>
|
<div className='app'>
|
||||||
<Header />
|
<Header />
|
||||||
{isLoggedIn ? (
|
{isLoggedIn ? (
|
||||||
<>
|
<>
|
||||||
<ApartmentRentals />
|
<ApartmentRentals />
|
||||||
<ApartmentOptions />
|
<ApartmentOptions />
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<ApartmentRentals />
|
<ApartmentRentals />
|
||||||
<ApartmentOptions />
|
<ApartmentOptions />
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<Routes>
|
<RentalProvider> {/* Added RentalProvider here */}
|
||||||
<Route path="/" element={<AppContent />} />
|
<Routes>
|
||||||
<Route path="/" element={<ApartmentRentals />} />
|
<Route path="/" element={<AppContent />} />
|
||||||
<Route path="/booking" element={<BookingPage />} />
|
<Route path="/apartment-list" element={<ApartmentList />} />
|
||||||
<Route path="/myrentals" element={<MyRentals />} />
|
<Route path="/booking/:apartmentId" element={<BookingPage />} />
|
||||||
</Routes>
|
<Route path="/myrentals" element={<MyRentals />} />
|
||||||
</AuthProvider>
|
</Routes>
|
||||||
</Router>
|
</RentalProvider> {/* RentalProvider wraps Routes */}
|
||||||
);
|
</AuthProvider>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
46
keynest/src/components/ApartmentList.css
Normal file
46
keynest/src/components/ApartmentList.css
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
.apartment-list-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apartment-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(300px, 1fr)); /* Всегда два столбца */
|
||||||
|
gap: 20px;
|
||||||
|
padding-top: 60px;
|
||||||
|
padding-right: 300px;
|
||||||
|
padding-left: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apartment-card {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.apartment-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
object-fit: contain; /* Изображение отображается полностью, сохраняя пропорции */
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apartment-price {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apartment-card button {
|
||||||
|
background-color: #242323;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apartment-card button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
31
keynest/src/components/ApartmentList.js
Normal file
31
keynest/src/components/ApartmentList.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import './ApartmentList.css';
|
||||||
|
import apartments from '../../src/storage/apartments.json'; // Импортируйте JSON-файл
|
||||||
|
|
||||||
|
function ApartmentList() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleBookApartment = (apartmentId) => {
|
||||||
|
navigate(`/booking/${apartmentId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="apartment-list-container">
|
||||||
|
<h1>Available Apartments</h1>
|
||||||
|
<div className="apartment-grid">
|
||||||
|
{apartments.map(apartment => (
|
||||||
|
<div key={apartment.id} className="apartment-card">
|
||||||
|
<img src={process.env.PUBLIC_URL + apartment.imageUrl} alt={apartment.name} className="apartment-image" />
|
||||||
|
<h2>{apartment.name}</h2>
|
||||||
|
<p>{apartment.description}</p>
|
||||||
|
<p className="apartment-price">{apartment.price}</p>
|
||||||
|
<button onClick={() => handleBookApartment(apartment.id)}>Book Now</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ApartmentList;
|
||||||
@@ -1,37 +1,29 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import './ApartmentRentals.css';
|
import './ApartmentRentals.css';
|
||||||
|
|
||||||
function ApartmentRentals() {
|
function ApartmentRentals() {
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleBookNowClick = () => {
|
const handleBookNowClick = () => {
|
||||||
setLoading(true);
|
navigate('/apartment-list'); // Переход на страницу ApartmentList
|
||||||
// Simulate loading delay
|
|
||||||
setTimeout(() => {
|
|
||||||
setLoading(false);
|
|
||||||
// Redirect to the booking page using navigate
|
|
||||||
navigate('/booking');
|
|
||||||
}, 2000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className='apartment'>
|
<header className='apartment'>
|
||||||
<img
|
<img
|
||||||
className='backround__section'
|
className='backround__section'
|
||||||
src={process.env.PUBLIC_URL + '/images/Section.png'}
|
src={process.env.PUBLIC_URL + '/images/Section.png'}
|
||||||
alt='изображение'
|
alt='изображение'
|
||||||
/>
|
/>
|
||||||
<div className='Apartament__content'>
|
<div className='Apartament__content'>
|
||||||
<h1 className='heading'>Short-term and Long-term Apartment Rentals</h1>
|
<h1 className='heading'>Short-term and Long-term Apartment Rentals</h1>
|
||||||
<p className='info'>Discover the perfect apartment for your needs, whether it's a quick stay or a long-term investment</p>
|
<p className='info'>Discover the perfect apartment for your needs, whether it's a quick stay or a long-term investment</p>
|
||||||
<button
|
<button
|
||||||
className='content__button'
|
className='content__button'
|
||||||
onClick={handleBookNowClick}
|
onClick={handleBookNowClick}
|
||||||
disabled={loading}
|
|
||||||
>
|
>
|
||||||
{loading ? 'Booking...' : 'Book Now'}
|
Book Now
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -1,57 +1,41 @@
|
|||||||
import React, { createContext, useState, useContext } from 'react';
|
import React, { createContext, useState, useEffect, useContext } from 'react';
|
||||||
|
|
||||||
const AuthContext = createContext();
|
const AuthContext = createContext();
|
||||||
|
|
||||||
export const AuthProvider = ({ children }) => { // ONE declaration of AuthProvider
|
export const AuthProvider = ({ children }) => {
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
const [user, setUser] = useState(() => {
|
||||||
const [user, setUser] = useState(null);
|
// При инициализации проверяем, есть ли данные в localStorage
|
||||||
|
const storedUser = localStorage.getItem('user');
|
||||||
|
return storedUser ? JSON.parse(storedUser) : null;
|
||||||
|
});
|
||||||
|
|
||||||
const login = async (credentials) => {
|
useEffect(() => {
|
||||||
// Mock authentication logic
|
// При изменении user сохраняем его в localStorage
|
||||||
if (credentials.username === 'user' && credentials.password === 'password') {
|
if (user) {
|
||||||
setIsLoggedIn(true);
|
localStorage.setItem('user', JSON.stringify(user));
|
||||||
setUser({ username: credentials.username });
|
} else {
|
||||||
return Promise.resolve(); // Resolve promise on successful login
|
// Если user становится null, удаляем данные из localStorage
|
||||||
} else {
|
localStorage.removeItem('user');
|
||||||
setIsLoggedIn(false);
|
}
|
||||||
setUser(null);
|
}, [user]);
|
||||||
return Promise.reject(new Error('Invalid credentials')); // Reject promise on invalid credentials
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const register = async (credentials) => {
|
const login = (userData) => {
|
||||||
// Mock registration logic
|
setUser(userData);
|
||||||
if (credentials.username && credentials.password) {
|
};
|
||||||
setIsLoggedIn(true);
|
|
||||||
setUser({ username: credentials.username });
|
|
||||||
return Promise.resolve(); // Resolve promise on successful registration
|
|
||||||
} else {
|
|
||||||
setIsLoggedIn(false);
|
|
||||||
setUser(null);
|
|
||||||
return Promise.reject(new Error('Registration failed')); // Reject promise on failed registration
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
setIsLoggedIn(false);
|
setUser(null);
|
||||||
setUser(null);
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const value = {
|
const isLoggedIn = !!user;
|
||||||
isLoggedIn,
|
|
||||||
user,
|
|
||||||
login,
|
|
||||||
register,
|
|
||||||
logout
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={value}>
|
<AuthContext.Provider value={{ user, isLoggedIn, login, logout }}>
|
||||||
{children}
|
{children}
|
||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAuth = () => {
|
export const useAuth = () => {
|
||||||
return useContext(AuthContext);
|
return useContext(AuthContext);
|
||||||
};
|
};
|
||||||
18
keynest/src/components/AuthModal.css
Normal file
18
keynest/src/components/AuthModal.css
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
.auth-modal {
|
||||||
|
position: fixed; /* Или absolute, если нужно позиционировать относительно родителя */
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%); /* Центрирование */
|
||||||
|
z-index: 1000; /* Убедитесь, что z-index достаточно высок */
|
||||||
|
/* Другие стили */
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5); /* Полупрозрачный фон */
|
||||||
|
z-index: 999; /* Меньше, чем z-index модального окна */
|
||||||
|
}
|
||||||
@@ -1,128 +1,105 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import Modal from 'react-modal';
|
import Modal from 'react-modal';
|
||||||
import { useAuth } from './AuthContext';
|
|
||||||
|
|
||||||
Modal.setAppElement('#root');
|
Modal.setAppElement('#root');
|
||||||
|
|
||||||
const AuthModal = ({ isOpen, onRequestClose }) => {
|
function AuthModal({ isOpen, onRequestClose, onLogin }) {
|
||||||
const [isLogin, setIsLogin] = useState(true);
|
const [username, setUsername] = useState('');
|
||||||
const [username, setUsername] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [isLogin, setIsLogin] = useState(true);
|
||||||
const { login, register } = useAuth();
|
|
||||||
const [error, setError] = useState('');
|
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const modalStyles = {
|
||||||
e.preventDefault();
|
overlay: {
|
||||||
setError('');
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
try {
|
zIndex: 1000
|
||||||
if (isLogin) {
|
},
|
||||||
await login({ username, password });
|
content: {
|
||||||
} else {
|
top: '50%',
|
||||||
await register({ username, password });
|
left: '50%',
|
||||||
}
|
right: 'auto',
|
||||||
onRequestClose();
|
bottom: 'auto',
|
||||||
} catch (err) {
|
marginRight: '-50%',
|
||||||
setError(err.message);
|
transform: 'translate(-50%, -50%)',
|
||||||
}
|
padding: '20px',
|
||||||
};
|
width: '300px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)',
|
||||||
|
border: 'none'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const toggleAuthMode = () => {
|
const formStyles = {
|
||||||
setIsLogin(!isLogin);
|
display: 'flex',
|
||||||
setUsername('');
|
flexDirection: 'column',
|
||||||
setPassword('');
|
gap: '15px'
|
||||||
setError('');
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const modalStyles = {
|
const inputStyles = {
|
||||||
overlay: {
|
padding: '10px',
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.5)'
|
borderRadius: '4px',
|
||||||
},
|
border: '1px solid #ccc',
|
||||||
content: {
|
fontSize: '16px'
|
||||||
top: '50%',
|
};
|
||||||
left: '50%',
|
|
||||||
right: 'auto',
|
|
||||||
bottom: 'auto',
|
|
||||||
marginRight: '-50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
padding: '20px',
|
|
||||||
width: '300px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)',
|
|
||||||
border: 'none'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const formStyles = {
|
const buttonStyles = {
|
||||||
display: 'flex',
|
padding: '12px',
|
||||||
flexDirection: 'column',
|
borderRadius: '4px',
|
||||||
gap: '15px'
|
border: 'none',
|
||||||
};
|
backgroundColor: '#242323',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: '16px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
':hover': {
|
||||||
|
backgroundColor: '#0056b3'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const inputStyles = {
|
const toggleButtonStyles = {
|
||||||
padding: '10px',
|
background: 'none',
|
||||||
borderRadius: '4px',
|
border: 'none',
|
||||||
border: '1px solid #ccc',
|
color: '#007bff',
|
||||||
fontSize: '16px'
|
cursor: 'pointer',
|
||||||
};
|
fontSize: '14px',
|
||||||
|
textDecoration: 'underline'
|
||||||
|
};
|
||||||
|
|
||||||
const buttonStyles = {
|
const handleSubmit = (e) => {
|
||||||
padding: '12px',
|
e.preventDefault();
|
||||||
borderRadius: '4px',
|
onLogin(username, password);
|
||||||
border: 'none',
|
};
|
||||||
backgroundColor: '#242323',
|
|
||||||
color: 'white',
|
|
||||||
fontSize: '16px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
':hover': {
|
|
||||||
backgroundColor: '#0056b3'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleButtonStyles = {
|
return (
|
||||||
background: 'none',
|
<Modal
|
||||||
border: 'none',
|
isOpen={isOpen}
|
||||||
color: '#007bff',
|
onRequestClose={onRequestClose}
|
||||||
cursor: 'pointer',
|
style={modalStyles}
|
||||||
fontSize: '14px',
|
contentLabel={isLogin ? "Login Modal" : "Register Modal"}
|
||||||
textDecoration: 'underline'
|
>
|
||||||
};
|
<h2>{isLogin ? 'Login' : 'Register'}</h2>
|
||||||
|
<form style={formStyles} onSubmit={handleSubmit}>
|
||||||
return (
|
<input
|
||||||
<Modal
|
type="text"
|
||||||
isOpen={isOpen}
|
placeholder="Username"
|
||||||
onRequestClose={onRequestClose}
|
value={username}
|
||||||
style={modalStyles}
|
onChange={e => setUsername(e.target.value)}
|
||||||
>
|
style={inputStyles}
|
||||||
<h2 style={{ textAlign: 'center', marginBottom: '20px' }}>{isLogin ? 'Login' : 'Register'}</h2>
|
/>
|
||||||
{error && <p style={{ color: 'red', textAlign: 'center' }}>{error}</p>}
|
<input
|
||||||
<form onSubmit={handleSubmit} style={formStyles}>
|
type="password"
|
||||||
<div>
|
placeholder="Password"
|
||||||
<label htmlFor="username" style={{ marginBottom: '5px', display: 'block' }}>Username:</label>
|
value={password}
|
||||||
<input
|
onChange={e => setPassword(e.target.value)}
|
||||||
type="text"
|
style={inputStyles}
|
||||||
id="username"
|
/>
|
||||||
value={username}
|
<button type="submit" style={buttonStyles}>
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
{isLogin ? 'Login' : 'Register'}
|
||||||
style={inputStyles}
|
</button>
|
||||||
/>
|
</form>
|
||||||
</div>
|
<button style={toggleButtonStyles} onClick={() => setIsLogin(!isLogin)}>
|
||||||
<div>
|
{isLogin ? 'Create an account' : 'Already have an account?'}
|
||||||
<label htmlFor="password" style={{ marginBottom: '5px', display: 'block' }}>Password:</label>
|
</button>
|
||||||
<input
|
</Modal>
|
||||||
type="password"
|
);
|
||||||
id="password"
|
}
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
style={inputStyles}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button type="submit" style={buttonStyles}>{isLogin ? 'Log In' : 'Register'}</button>
|
|
||||||
</form>
|
|
||||||
<button onClick={toggleAuthMode} style={toggleButtonStyles}>
|
|
||||||
{isLogin ? 'Need an account? Register' : 'Already have an account? Login'}
|
|
||||||
</button>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthModal;
|
export default AuthModal;
|
||||||
104
keynest/src/components/AuthProvider.js
Normal file
104
keynest/src/components/AuthProvider.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import React, { createContext, useState, useEffect, useContext } from 'react';
|
||||||
|
// import axios from 'axios'; // Убираем axios
|
||||||
|
import users from './storage/users.json'; // Импортируем JSON
|
||||||
|
|
||||||
|
const AuthContext = createContext();
|
||||||
|
|
||||||
|
export const useAuth = () => useContext(AuthContext);
|
||||||
|
|
||||||
|
export const AuthProvider = ({ children }) => {
|
||||||
|
const [user, setUser] = useState(null);
|
||||||
|
const [token, setToken] = useState(localStorage.getItem('token') || null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initializeAuth = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
if (token) {
|
||||||
|
// axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; // Убираем axios
|
||||||
|
try {
|
||||||
|
// Замените на реальный запрос к API для получения данных пользователя
|
||||||
|
// const response = await axios.get('/api/user'); // Убираем axios
|
||||||
|
// setUser(response.data); // Убираем axios
|
||||||
|
// Ищем пользователя в JSON файле по токену (username)
|
||||||
|
const storedUsername = localStorage.getItem('username');
|
||||||
|
const foundUser = users.find(u => u.username === storedUsername);
|
||||||
|
if (foundUser) {
|
||||||
|
setUser(foundUser);
|
||||||
|
} else {
|
||||||
|
logout();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при аутентификации:', error);
|
||||||
|
logout(); // Очищаем токен, если он недействителен
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
initializeAuth();
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
const login = async ({ username, password }) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const foundUser = users.find(u => u.username === username && u.password === password);
|
||||||
|
if (foundUser) {
|
||||||
|
const newToken = username; // В качестве токена используем username
|
||||||
|
localStorage.setItem('token', newToken);
|
||||||
|
localStorage.setItem('username', username); // Сохраняем username
|
||||||
|
setToken(newToken);
|
||||||
|
// axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`; // Убираем axios
|
||||||
|
setUser(foundUser);
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error('Неверный логин или пароль'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const register = async ({ username, password }) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Проверяем, существует ли уже пользователь с таким именем
|
||||||
|
const existingUser = users.find(u => u.username === username);
|
||||||
|
if (existingUser) {
|
||||||
|
reject(new Error('Пользователь с таким именем уже существует'));
|
||||||
|
} else {
|
||||||
|
// В реальном приложении нужно добавить пользователя в базу данных
|
||||||
|
// Здесь мы просто эмулируем добавление
|
||||||
|
const newUser = {
|
||||||
|
id: users.length + 1,
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
};
|
||||||
|
users.push(newUser);
|
||||||
|
// Автоматически логиним после регистрации
|
||||||
|
login({ username, password })
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
localStorage.removeItem('username');
|
||||||
|
setToken(null);
|
||||||
|
setUser(null);
|
||||||
|
// delete axios.defaults.headers.common['Authorization']; // Убираем axios
|
||||||
|
};
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
user,
|
||||||
|
token,
|
||||||
|
login,
|
||||||
|
register,
|
||||||
|
logout,
|
||||||
|
loading,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={value}>
|
||||||
|
{!loading && children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -119,4 +119,17 @@
|
|||||||
.back-to-home-button:focus {
|
.back-to-home-button:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 0 0 0 3px rgba(220, 220, 220, 0.5);
|
box-shadow: 0 0 0 3px rgba(220, 220, 220, 0.5);
|
||||||
|
}
|
||||||
|
.apartment-price {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.booking-form h2 {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 24px;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import './BookingPage.css';
|
import './BookingPage.css';
|
||||||
|
import apartments from '../../src/storage/apartments.json';
|
||||||
|
import { useRentalContext } from './RentalContext'; // Import useRentalContext
|
||||||
|
|
||||||
function BookingPage() {
|
function BookingPage() {
|
||||||
|
const { apartmentId } = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const apartment = apartments.find(apartment => apartment.id === parseInt(apartmentId));
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
email: '',
|
email: '',
|
||||||
@@ -11,7 +16,7 @@ function BookingPage() {
|
|||||||
});
|
});
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const [isSuccess, setIsSuccess] = useState(false);
|
const [isSuccess, setIsSuccess] = useState(false);
|
||||||
const navigate = useNavigate();
|
const { addRental } = useRentalContext(); // Use addRental from RentalContext
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||||
@@ -23,7 +28,7 @@ function BookingPage() {
|
|||||||
const checkinDate = new Date(formData.checkin);
|
const checkinDate = new Date(formData.checkin);
|
||||||
const checkoutDate = new Date(formData.checkout);
|
const checkoutDate = new Date(formData.checkout);
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
today.setHours(0, 0, 0, 0);
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
if (checkinDate < today) {
|
if (checkinDate < today) {
|
||||||
setMessage('Check-in date cannot be in the past.');
|
setMessage('Check-in date cannot be in the past.');
|
||||||
@@ -38,10 +43,31 @@ function BookingPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await submitForm(formData);
|
const userId = localStorage.getItem('user_id');
|
||||||
|
if (!userId) {
|
||||||
|
setMessage('Please log in to make a booking.');
|
||||||
|
setIsSuccess(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newRental = {
|
||||||
|
id: Date.now(),
|
||||||
|
userId: userId,
|
||||||
|
apartmentId: apartmentId,
|
||||||
|
name: formData.name,
|
||||||
|
email: formData.email,
|
||||||
|
checkin: formData.checkin,
|
||||||
|
checkout: formData.checkout,
|
||||||
|
apartmentName: apartment.name, // Include apartment name
|
||||||
|
apartmentPrice: apartment.price, // Include apartment price
|
||||||
|
status: 'Pending'
|
||||||
|
};
|
||||||
|
|
||||||
|
addRental(newRental); // Add the new rental to the context
|
||||||
|
|
||||||
setMessage('Booking successful!');
|
setMessage('Booking successful!');
|
||||||
setIsSuccess(true);
|
setIsSuccess(true);
|
||||||
setFormData({ name: '', email: '', checkin: '', checkout: '' });
|
setFormData({ name: '', email: '', checkin: '', checkout: '' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setMessage('Booking failed. Please try again.');
|
setMessage('Booking failed. Please try again.');
|
||||||
setIsSuccess(false);
|
setIsSuccess(false);
|
||||||
@@ -52,13 +78,17 @@ function BookingPage() {
|
|||||||
navigate('/');
|
navigate('/');
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitForm = (data) => {
|
if (!apartment) {
|
||||||
return new Promise((resolve) => {
|
return (
|
||||||
setTimeout(() => {
|
<div className="booking-page dark-theme">
|
||||||
resolve();
|
<button className="back-to-home-button" onClick={handleBackToHome}>
|
||||||
}, 2000);
|
Back to Home
|
||||||
});
|
</button>
|
||||||
};
|
<h1>Apartment Not Found</h1>
|
||||||
|
<p>The requested apartment could not be found.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="booking-page dark-theme">
|
<div className="booking-page dark-theme">
|
||||||
@@ -70,6 +100,8 @@ function BookingPage() {
|
|||||||
<p>Please fill out the form below to complete your booking.</p>
|
<p>Please fill out the form below to complete your booking.</p>
|
||||||
|
|
||||||
<form className="booking-form" onSubmit={handleSubmit}>
|
<form className="booking-form" onSubmit={handleSubmit}>
|
||||||
|
<h2 className='name'>{apartment.name}</h2>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label htmlFor="name">Name:</label>
|
<label htmlFor="name">Name:</label>
|
||||||
<input type="text" id="name" name="name" required value={formData.name} onChange={handleChange} />
|
<input type="text" id="name" name="name" required value={formData.name} onChange={handleChange} />
|
||||||
@@ -90,6 +122,8 @@ function BookingPage() {
|
|||||||
<input type="date" id="checkout" name="checkout" required value={formData.checkout} onChange={handleChange} />
|
<input type="date" id="checkout" name="checkout" required value={formData.checkout} onChange={handleChange} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p className="apartment-price">{apartment.price}</p>
|
||||||
|
|
||||||
<button type="submit" className="submit-button">Complete Booking</button>
|
<button type="submit" className="submit-button">Complete Booking</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,31 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import './Header.css';
|
import './Header.css';
|
||||||
import AuthModal from './AuthModal';
|
import AuthModal from './AuthModal.js';
|
||||||
import { useAuth } from './AuthContext';
|
import { useAuth } from './AuthContext.js';
|
||||||
|
import { useRentalContext } from './RentalContext'; // Import useRentalContext
|
||||||
|
|
||||||
function Header() {
|
function Header() {
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const { isLoggedIn, logout } = useAuth();
|
const { isLoggedIn, login, logout } = useAuth();
|
||||||
|
const [users, setUsers] = useState([]);
|
||||||
|
const { clearRentals } = useRentalContext(); // Use clearRentals from RentalContext
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchUsers();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchUsers = async () => {
|
||||||
|
try {
|
||||||
|
const data = require('../../src/storage/users.json');
|
||||||
|
setUsers(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при загрузке пользователей:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const openModal = () => {
|
const openModal = () => {
|
||||||
|
console.log('openModal called');
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -16,8 +33,25 @@ function Header() {
|
|||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLogin = async (username, password) => {
|
||||||
|
console.log('handleLogin called', username, password);
|
||||||
|
const user = users.find(user => user.username === username && user.password === password);
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
// Успешная аутентификация
|
||||||
|
login(user); // Сохраняем данные пользователя в AuthContext
|
||||||
|
localStorage.setItem('user_id', user.user_id.toString()); // Сохраняем user_id в localStorage
|
||||||
|
closeModal();
|
||||||
|
} else {
|
||||||
|
// Неверные учетные данные
|
||||||
|
alert('Неверное имя пользователя или пароль');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
logout();
|
logout();
|
||||||
|
localStorage.removeItem('user_id'); // Удаляем user_id из localStorage при выходе
|
||||||
|
clearRentals(); // Clear rentals from context
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -30,7 +64,7 @@ function Header() {
|
|||||||
<nav className="menu">
|
<nav className="menu">
|
||||||
<Link to="/">Home</Link>
|
<Link to="/">Home</Link>
|
||||||
<Link to="/about">About</Link>
|
<Link to="/about">About</Link>
|
||||||
<Link to="/myrentals">Rentals</Link> {/* Updated link */}
|
<Link to="/myrentals">Rentals</Link>
|
||||||
<Link to="/contact">Contact</Link>
|
<Link to="/contact">Contact</Link>
|
||||||
</nav>
|
</nav>
|
||||||
{isLoggedIn ? (
|
{isLoggedIn ? (
|
||||||
@@ -38,7 +72,7 @@ function Header() {
|
|||||||
) : (
|
) : (
|
||||||
<button className="login-button button" onClick={openModal}>Login</button>
|
<button className="login-button button" onClick={openModal}>Login</button>
|
||||||
)}
|
)}
|
||||||
<AuthModal isOpen={isModalOpen} onRequestClose={closeModal} />
|
<AuthModal isOpen={isModalOpen} onRequestClose={closeModal} onLogin={handleLogin} />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,34 +1,36 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Link } from 'react-router-dom'; // Import Link
|
import { Link } from 'react-router-dom';
|
||||||
import './MyRentals.css';
|
import './MyRentals.css';
|
||||||
|
import { useRentalContext } from './RentalContext';
|
||||||
|
import rentalsData from '../../src/storage/rentals.json'; // Import rentals data
|
||||||
|
|
||||||
function MyRentals() {
|
function MyRentals() {
|
||||||
const rentals = [
|
const { rentals } = useRentalContext();
|
||||||
{
|
const [rentalsFromJson, setRentalsFromJson] = useState([]);
|
||||||
id: 1,
|
const currentUserId = localStorage.getItem('user_id') || null;
|
||||||
title: 'Cozy Apartment in Downtown',
|
|
||||||
location: '123 Main St, Cityville',
|
useEffect(() => {
|
||||||
checkin: '2024-05-01',
|
// Load rentals from JSON
|
||||||
checkout: '2024-05-07',
|
setRentalsFromJson(rentalsData);
|
||||||
status: 'Confirmed'
|
}, []);
|
||||||
},
|
|
||||||
{
|
// Filter rentals from JSON for the current user
|
||||||
id: 2,
|
const userRentalsFromJson = rentalsFromJson.filter(rental => rental.userId === currentUserId);
|
||||||
title: 'Luxury Condo with Ocean View',
|
|
||||||
location: '456 Ocean Ave, Beach City',
|
// Combine rentals from context and JSON
|
||||||
checkin: '2024-06-10',
|
const combinedRentals = [...rentals.filter(rental => rental.userId === currentUserId), ...userRentalsFromJson];
|
||||||
checkout: '2024-06-17',
|
|
||||||
status: 'Pending'
|
if (!currentUserId) {
|
||||||
},
|
return (
|
||||||
{
|
<div className="my-rentals-page">
|
||||||
id: 3,
|
<Link to="/" className="back-to-home-button">
|
||||||
title: 'Modern Loft in Arts District',
|
Back to Home
|
||||||
location: '789 Art Lane, Culturetown',
|
</Link>
|
||||||
checkin: '2024-07-01',
|
<h1>My Rentals</h1>
|
||||||
checkout: '2024-07-08',
|
<p>Please log in to view your rentals.</p>
|
||||||
status: 'Completed'
|
</div>
|
||||||
}
|
);
|
||||||
];
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="my-rentals-page">
|
<div className="my-rentals-page">
|
||||||
@@ -37,15 +39,19 @@ function MyRentals() {
|
|||||||
</Link>
|
</Link>
|
||||||
<h1>My Rentals</h1>
|
<h1>My Rentals</h1>
|
||||||
<div className="rentals-list">
|
<div className="rentals-list">
|
||||||
{rentals.map((rental) => (
|
{combinedRentals.length === 0 ? (
|
||||||
<div key={rental.id} className="rental-item">
|
<p>No rentals found.</p>
|
||||||
<h3>{rental.title}</h3>
|
) : (
|
||||||
<p>Location: {rental.location}</p>
|
combinedRentals.map((rental) => (
|
||||||
<p>Check-in: {rental.checkin}</p>
|
<div key={rental.id} className="rental-item">
|
||||||
<p>Check-out: {rental.checkout}</p>
|
<h3>{rental.apartmentName || rental.title}</h3>
|
||||||
<p>Status: {rental.status}</p>
|
<p>Name: {rental.name}</p>
|
||||||
</div>
|
<p>Check-in: {rental.checkin}</p>
|
||||||
))}
|
<p>Check-out: {rental.checkout}</p>
|
||||||
|
<p>Status: {rental.status}</p>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
24
keynest/src/components/RentalContext.js
Normal file
24
keynest/src/components/RentalContext.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// RentalContext.js
|
||||||
|
import React, { createContext, useState, useContext } from 'react';
|
||||||
|
|
||||||
|
const RentalContext = createContext();
|
||||||
|
|
||||||
|
export const useRentalContext = () => useContext(RentalContext);
|
||||||
|
|
||||||
|
export const RentalProvider = ({ children }) => {
|
||||||
|
const [rentals, setRentals] = useState([]);
|
||||||
|
|
||||||
|
const addRental = (newRental) => {
|
||||||
|
setRentals([...rentals, newRental]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearRentals = () => {
|
||||||
|
setRentals([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RentalContext.Provider value={{ rentals, addRental, clearRentals }}>
|
||||||
|
{children}
|
||||||
|
</RentalContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
44
keynest/src/storage/apartments.json
Normal file
44
keynest/src/storage/apartments.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Cozy Studio Apartment",
|
||||||
|
"description": "A perfect studio for solo travelers or couples.",
|
||||||
|
"imageUrl": "/images/apartment1.jpg",
|
||||||
|
"price": "$80/night"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Spacious Family Apartment",
|
||||||
|
"description": "Ideal for families with kids, featuring multiple bedrooms.",
|
||||||
|
"imageUrl": "/images/apartment2.jpg",
|
||||||
|
"price": "$150/night"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Luxury Penthouse",
|
||||||
|
"description": "Experience the ultimate in luxury living with stunning city views.",
|
||||||
|
"imageUrl": "/images/apartment3.jpg",
|
||||||
|
"price": "$300/night"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "Modern Loft Apartment",
|
||||||
|
"description": "Stylish loft apartment in the heart of the city.",
|
||||||
|
"imageUrl": "/images/apartment4.jpg",
|
||||||
|
"price": "$120/night"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "Beachfront Villa",
|
||||||
|
"description": "Enjoy breathtaking ocean views from this stunning beachfront villa.",
|
||||||
|
"imageUrl": "/images/apartment5.jpg",
|
||||||
|
"price": "$500/night"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "Mountain Retreat Cabin",
|
||||||
|
"description": "Escape to the mountains in this cozy and secluded cabin.",
|
||||||
|
"imageUrl": "/images/apartment6.jpg",
|
||||||
|
"price": "$180/night"
|
||||||
|
}
|
||||||
|
]
|
||||||
30
keynest/src/storage/rentals.json
Normal file
30
keynest/src/storage/rentals.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"rentals_id": "1",
|
||||||
|
"userId": "2",
|
||||||
|
"title": "Cozy Apartment in Downtown",
|
||||||
|
"location": "123 Main St, Cityville",
|
||||||
|
"checkin": "2024-05-01",
|
||||||
|
"checkout": "2024-05-07",
|
||||||
|
"status": "Confirmed"
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rentals_id": "2",
|
||||||
|
"userId": "2",
|
||||||
|
"title": "Luxury Condo with Ocean View",
|
||||||
|
"location": "456 Ocean Ave, Beach City",
|
||||||
|
"checkin": "2024-06-10",
|
||||||
|
"checkout": "2024-06-17",
|
||||||
|
"status": "Pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rentals_id": "3",
|
||||||
|
"userId": "3",
|
||||||
|
"title": "Modern Loft in Arts District",
|
||||||
|
"location": "789 Art Lane, Culturetown",
|
||||||
|
"checkin": "2024-07-01",
|
||||||
|
"checkout": "2024-07-08",
|
||||||
|
"status": "Completed"
|
||||||
|
}
|
||||||
|
]
|
||||||
15
keynest/src/storage/users.json
Normal file
15
keynest/src/storage/users.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[{
|
||||||
|
"user_id": "1",
|
||||||
|
"password":"pass",
|
||||||
|
"username": "login"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": "2",
|
||||||
|
"password":"123",
|
||||||
|
"username": "123"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": "3",
|
||||||
|
"password":"321",
|
||||||
|
"username": "321"
|
||||||
|
}]
|
||||||
Reference in New Issue
Block a user