Compare commits

...

1 Commits

Author SHA1 Message Date
e993d93f03 шестое сохранение 2025-05-05 21:02:39 +03:00
24 changed files with 661 additions and 271 deletions

View File

@@ -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"

View File

@@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

View File

@@ -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;

View 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;
}

View 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;

View File

@@ -1,19 +1,12 @@
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 (
@@ -29,9 +22,8 @@ function ApartmentRentals() {
<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>

View File

@@ -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);
}; };

View 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 модального окна */
}

View File

@@ -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;

View 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>
);
};

View File

@@ -120,3 +120,16 @@
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;
}

View File

@@ -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 });
@@ -38,7 +43,28 @@ 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: '' });
@@ -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>

View File

@@ -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>
); );

View File

@@ -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>
); );

View 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>
);
};

View 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"
}
]

View 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"
}
]

View 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"
}]