Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/EditProfile and MyReservation Page #52

Merged
merged 6 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import HostingImage from '@/routes/HostingImageUpload';
import Redirect from '@/routes/Redirect';

import RegisterPage from './components/home/Topbar/Menu/RegisterPage';
import MyReservationDetails from './components/profile/MyReservationDetails';
import MyReservations from './routes/MyReservations';
import Profile from './routes/Profile';
import ProfileEdit from './routes/ProfileEdit';
import { Roomdetail } from './routes/roomDetail';

export const App = () => {
Expand All @@ -25,7 +28,13 @@ export const App = () => {
<Route path="/hosting" element={<Hosting />} />
<Route path="/hosting/images" element={<HostingImage />} />
<Route path="/profile" element={<Profile />} />
<Route path="/profile/edit" element={<ProfileEdit />} />
<Route path="/register" element={<RegisterPage />} />
<Route path="/MyReservations" element={<MyReservations />} />
<Route
path="/reservations/:reservationId"
element={<MyReservationDetails />}
/>
<Route path="*" element={<h1>404 Not Found</h1>} />
</Routes>
</SearchProvider>
Expand Down
1 change: 1 addition & 0 deletions src/components/home/Topbar/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const Topbar = () => {
setDropdownOpen(false);
localStorage.removeItem('token');
alert('λ‘œκ·Έμ•„μ›ƒ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.');
void navigate('/');
};

return (
Expand Down
6 changes: 4 additions & 2 deletions src/components/home/Topbar/Menu/RegisterPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const RegisterPage: React.FC = () => {
bio: '',
showMyReviews: false,
showMyReservations: false,
showMyWishlist: false,
});
const [profileImage, setProfileImage] = useState<File | null>(null);
const [showPassword, setShowPassword] = useState(false);
Expand Down Expand Up @@ -61,12 +62,13 @@ const RegisterPage: React.FC = () => {
'showMyReservations',
formData.showMyReservations.toString(),
);
formDataToSend.append('showMyWishlist', formData.showMyWishlist.toString());
if (profileImage !== null) {
formDataToSend.append('profileImage', profileImage);
}

type RegisterResponseData = {
imageUploadurl: string;
imageUploadUrl: string;
};

try {
Expand All @@ -82,7 +84,7 @@ const RegisterPage: React.FC = () => {

alert('νšŒμ›κ°€μž… 성곡');

const presignedUrl = RegisterResponse.data.imageUploadurl;
const presignedUrl = RegisterResponse.data.imageUploadUrl;

if (profileImage !== null && presignedUrl !== '') {
const uploadSuccess = await uploadImageWithPresignedUrl(
Expand Down
91 changes: 91 additions & 0 deletions src/components/profile/MyReservationDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import axios from 'axios';
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';

type ReservationDetail = {
reservationId: number;
roomId: number;
startDate: string;
endDate: string;
place: string;
numberOfGuests: number;
imageUrl: string;
};

const MyReservationDetails = () => {
const { reservationId } = useParams<{ reservationId: string }>();
const [reservation, setReservation] = useState<ReservationDetail | null>(
null,
);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(true);

useEffect(() => {
const fetchReservationDetail = async () => {
const token = localStorage.getItem('token');
if (token === null || (typeof token === 'string' && token === '')) {
setError('λ‘œκ·ΈμΈλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.');
return;
}

if (reservationId === undefined) {
setError('μ˜ˆμ•½ IDκ°€ μœ νš¨ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.');
setLoading(false);
return;
}

try {
const response = await axios.get<ReservationDetail>(
`/api/v1/reservations/${reservationId}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
setReservation(response.data);
} catch (err) {
console.error(err);
setError('μ˜ˆμ•½ 정보λ₯Ό κ°€μ Έμ˜€λŠ” 데 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.');
} finally {
setLoading(false);
}
};

void fetchReservationDetail();
}, [reservationId]);

if (loading) {
return <p>λ‘œλ”© 쀑...</p>;
}

if (error !== null) {
return <p className="text-red-500">{error}</p>;
}

if (reservation === null) {
return <p>μ˜ˆμ•½ 정보λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.</p>;
}

return (
<div className="flex flex-col items-center min-h-screen bg-gray-100">
<h1 className="text-2xl font-bold my-4">{reservation.place}</h1>
<img
src={reservation.imageUrl}
alt={reservation.place}
className="w-3/4 h-64 object-cover rounded-lg shadow-md"
/>
<div className="mt-4 p-4 bg-white rounded-lg shadow-md w-3/4">
<p className="text-lg font-semibold">
μ—¬ν–‰ κΈ°κ°„: {reservation.startDate} ~ {reservation.endDate}
</p>
<p className="mt-2 text-gray-700">
게슀트 수: {reservation.numberOfGuests}λͺ…
</p>
<p className="mt-2 text-gray-700">객싀 ID: {reservation.roomId}</p>
</div>
</div>
);
};

export default MyReservationDetails;
170 changes: 170 additions & 0 deletions src/components/profile/MyReservationItems.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import axios from 'axios';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';

type Reservation = {
reservationId: number;
place: string;
startDate: string;
endDate: string;
imageUrl: string;
};

type MyReservationItemsResponse = {
content: Reservation[];
};

type ProfileInfo = {
userId: number;
nickname: string;
};

const MyReservationItems = () => {
const navigate = useNavigate();
const [pastMyReservationItems, setPastMyReservationItems] = useState<
Reservation[]
>([]);
const [upcomingMyReservationItems, setUpcomingMyReservationItems] = useState<
Reservation[]
>([]);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(true);

useEffect(() => {
const fetchMyReservationItems = async () => {
const token = localStorage.getItem('token');
if (token === null || (typeof token === 'string' && token === '')) {
setError('λ‘œκ·ΈμΈλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.');
return;
}

try {
const profileResponse = await axios.get<ProfileInfo>(
'/api/v1/profile',
{
headers: { Authorization: `Bearer ${token}` },
},
);
const userId = profileResponse.data.userId;

const MyReservationItemsResponse =
await axios.get<MyReservationItemsResponse>(
`/api/v1/reservations/user/${userId}`,
{ headers: { Authorization: `Bearer ${token}` } },
);

const now = new Date();

// μ§€λ‚œ μ˜ˆμ•½: endDateκ°€ ν˜„μž¬ λ‚ μ§œλ³΄λ‹€ 이전
const past = MyReservationItemsResponse.data.content.filter(
(reservation) => {
return new Date(reservation.endDate) < now;
},
);

// λ‹€κ°€μ˜€λŠ” μ—¬ν–‰: startDateκ°€ ν˜„μž¬ λ‚ μ§œμ™€ κ°™κ±°λ‚˜ 이후
const upcoming = MyReservationItemsResponse.data.content.filter(
(reservation) => {
return new Date(reservation.startDate) >= now;
},
);

// μ •λ ¬ (startDate κΈ°μ€€ μ΅œμ‹ μˆœ)
setPastMyReservationItems(
past.sort(
(a, b) =>
new Date(b.startDate).getTime() - new Date(a.startDate).getTime(),
),
);
setUpcomingMyReservationItems(
upcoming.sort(
(a, b) =>
new Date(a.startDate).getTime() - new Date(b.startDate).getTime(),
),
);

setLoading(false);
} catch (err) {
console.error(err);
setError('데이터λ₯Ό κ°€μ Έμ˜€λŠ” 데 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.');
setLoading(false);
}
};

void fetchMyReservationItems();
}, []);

if (loading) {
return <p>λ‘œλ”© 쀑...</p>;
}

if (error !== null) {
return <p className="text-red-500">{error}</p>;
}

return (
<div className="flex flex-col items-center min-h-screen bg-gray-100">
<h1 className="text-2xl font-bold my-4">μ˜ˆμ•½ λ‚΄μ—­</h1>

{/* λ‹€κ°€μ˜€λŠ” μ—¬ν–‰ */}
<div className="w-3/4 mb-8">
<h2 className="text-xl font-semibold mb-4">λ‹€κ°€μ˜€λŠ” μ—¬ν–‰</h2>
{upcomingMyReservationItems.length > 0 ? (
<div className="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
{upcomingMyReservationItems.map((reservation) => (
<div
key={reservation.reservationId}
onClick={(e) => {
e.preventDefault();
void navigate(`/reservations/${reservation.reservationId}`);
}}
className="p-4 bg-white rounded-lg shadow-md"
>
<img
src={reservation.imageUrl}
alt={reservation.place}
className="w-full h-40 object-cover rounded-md mb-2"
/>
<h2 className="text-lg font-semibold">{reservation.place}</h2>
<p className="text-sm text-gray-500">
{reservation.startDate} ~ {reservation.endDate}
</p>
</div>
))}
</div>
) : (
<p>λ‹€κ°€μ˜€λŠ” 여행이 μ—†μŠ΅λ‹ˆλ‹€.</p>
)}
</div>

{/* μ§€λ‚œ μ˜ˆμ•½ */}
<div className="w-3/4">
<h2 className="text-xl font-semibold mb-4">μ§€λ‚œ μ˜ˆμ•½</h2>
{pastMyReservationItems.length > 0 ? (
<div className="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
{pastMyReservationItems.map((reservation) => (
<div
key={reservation.reservationId}
className="p-4 bg-white rounded-lg shadow-md"
>
<img
src={reservation.imageUrl}
alt={reservation.place}
className="w-full h-40 object-cover rounded-md mb-2"
/>
<h2 className="text-lg font-semibold">{reservation.place}</h2>
<p className="text-sm text-gray-500">
{reservation.startDate} ~ {reservation.endDate}
</p>
</div>
))}
</div>
) : (
<p>μ§€λ‚œ μ˜ˆμ•½μ΄ μ—†μŠ΅λ‹ˆλ‹€.</p>
)}
</div>
</div>
);
};

export default MyReservationItems;
Loading
Loading