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

Dev #90

Merged
merged 6 commits into from
Feb 2, 2025
Merged

Dev #90

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
4 changes: 3 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import Redirect from '@/routes/Redirect';

import CompleteProfilePage from './components/home/Topbar/Menu/CompleteProfilePage';
import RegisterPage from './components/home/Topbar/Menu/RegisterPage';
import { ReviewProvider } from './components/roomdetail/ReviewContext';
import { ReviewProvider } from './components/roomdetail/contexts/ReviewContext';
import MyHosting from './routes/MyHosting';
import MyReservations from './routes/MyReservations';
import MyReviews from './routes/MyReviews';
import { MyWishlist } from './routes/MyWishlist';
import OtherUserProfilePage from './routes/OtherUserProfilePage';
import ProfileEdit from './routes/ProfileEdit';
import ProfilePage from './routes/ProfilePage';
Expand Down Expand Up @@ -49,6 +50,7 @@ export const App = () => {
<Route path="/register" element={<RegisterPage />} />
<Route path="/MyReservations" element={<MyReservations />} />
<Route path="/MyReviews" element={<MyReviews />} />
<Route path="/MyWishlist" element={<MyWishlist />} />
<Route path="/MyHosting" element={<MyHosting />} />
<Route path="/EditHosting/:roomId" element={<EditHosting />} />
<Route path="/reviews/:reservationId" element={<Review />} />
Expand Down
1 change: 0 additions & 1 deletion src/components/common/Modal/BaseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ type BaseModalProps = {
title?: string;
children: React.ReactNode;
footer?: React.ReactNode;
width?: string;
};

const BaseModal = ({
Expand Down
57 changes: 57 additions & 0 deletions src/components/common/Modal/CompactModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import CloseIcon from '@mui/icons-material/Close';
import React from 'react';

type CompactModalProps = {
isOpen: boolean;
onClose: () => void;
title?: string;
children: React.ReactNode;
footer?: React.ReactNode;
};

const CompactModal = ({
isOpen,
onClose,
title,
children,
footer,
}: CompactModalProps) => {
if (!isOpen) return null;

return (
<>
{/* Backdrop */}
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40"
onClick={onClose}
/>

{/* Modal */}
<div className="fixed inset-x-0 top-8 mx-auto w-full max-w-xl max-h-[90vh] bg-white rounded-xl z-50 flex flex-col">
{/* Header */}
<div className="flex items-center justify-center py-4 border-b shrink-0">
<button
onClick={onClose}
className="absolute left-4 p-2 rounded-full hover:bg-gray-100"
>
<CloseIcon sx={{ fontSize: 18 }} />
</button>

{title != null && <h2 className="text-lg font-semibold">{title}</h2>}
</div>

{/* Content - Scrollable */}
<div className="flex-1 overflow-y-auto">
<div className="p-6">{children}</div>
</div>

{/* Footer - Fixed */}
{footer != null && (
<div className="border-t p-4 shrink-0">{footer}</div>
)}
</div>
</>
);
};

export default CompactModal;
83 changes: 55 additions & 28 deletions src/components/home/FilterBar/FilterModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import RemoveIcon from '@mui/icons-material/Remove';
import StarIcon from '@mui/icons-material/Star';
import StarBorderIcon from '@mui/icons-material/StarBorder';
import StarHalfIcon from '@mui/icons-material/StarHalf';
import { useState } from 'react';
import { useEffect, useState } from 'react';

import {
ROOM_AMENITIES,
ROOM_FACILITIES,
} from '@/components/common/constants/roomOption';
import BaseModal from '@/components/common/Modal/BaseModal';
import CompactModal from '@/components/common/Modal/CompactModal';
import { useSearch } from '@/components/home/context/SearchContext';
import type { RoomDetails } from '@/types/room';

Expand All @@ -18,9 +18,6 @@ type FilterModalProps = {
onClose: () => void;
};

const initialMinPrice = 1;
const initialMaxPrice = 1000000;

type FacilityCountType = Pick<RoomDetails, 'bedroom' | 'bathroom' | 'bed'>;
type AmenityType = Pick<RoomDetails, 'wifi' | 'selfCheckin' | 'luggage' | 'TV'>;

Expand Down Expand Up @@ -111,8 +108,8 @@ const StarRating = ({ value, onChange, className = '' }: StarRatingProps) => {

export default function FilterModal({ isOpen, onClose }: FilterModalProps) {
const { filter, searchRooms } = useSearch();
const [minPrice, setMinPrice] = useState(filter.minPrice ?? initialMinPrice);
const [maxPrice, setMaxPrice] = useState(filter.maxPrice ?? initialMaxPrice);
const [minPrice, setMinPrice] = useState(filter.minPrice);
const [maxPrice, setMaxPrice] = useState(filter.maxPrice);
const [facilityCount, setFacilityCount] =
useState<FacilityCountType>(initialFacilityCount);
const [amenities, setAmenities] = useState<AmenityType>(initialAmenities);
Expand All @@ -131,7 +128,6 @@ export default function FilterModal({ isOpen, onClose }: FilterModalProps) {
: parseInt(prev[type]);
const newValue = increment ? currentValue + 1 : currentValue - 1;

// 최소값과 최대값 범위 내에서만 변경
if (newValue < facility.min || newValue > facility.max) return prev;

return {
Expand All @@ -149,7 +145,11 @@ export default function FilterModal({ isOpen, onClose }: FilterModalProps) {
};

const handleApply = () => {
if (minPrice > maxPrice) {
if (
minPrice === '' ||
maxPrice === '' ||
Number(minPrice) > Number(maxPrice)
) {
alert('최소 가격이 최대 가격보다 클 수 없습니다.');
return;
}
Expand All @@ -167,23 +167,51 @@ export default function FilterModal({ isOpen, onClose }: FilterModalProps) {
};

const handleReset = () => {
setMinPrice(initialMinPrice);
setMaxPrice(initialMaxPrice);
const resetFilter = {
...filter,
minPrice: '',
maxPrice: '',
roomType: null,
bedroom: '0',
bathroom: '0',
bed: '0',
wifi: false,
selfCheckin: false,
luggage: false,
TV: false,
rating: null,
};

// 로컬 상태도 초기화
setMinPrice('');
setMaxPrice('');
setFacilityCount(initialFacilityCount);
setAmenities(initialAmenities);
void searchRooms({
newFilter: {
...filter,
minPrice: null,
maxPrice: null,
roomType: null,
...initialFacilityCount,
...initialAmenities,
},
});
setRating(null);

void searchRooms({ newFilter: resetFilter });
onClose();
};

useEffect(() => {
if (isOpen) {
setMinPrice(filter.minPrice);
setMaxPrice(filter.maxPrice);
setFacilityCount({
bedroom: filter.bedroom ?? '0',
bathroom: filter.bathroom ?? '0',
bed: filter.bed ?? '0',
});
setAmenities({
wifi: filter.wifi ?? false,
selfCheckin: filter.selfCheckin ?? false,
luggage: filter.luggage ?? false,
TV: filter.TV ?? false,
});
setRating(filter.rating);
}
}, [isOpen, filter]);

const modalFooter = (
<div className="flex justify-between items-center">
<button
Expand All @@ -202,12 +230,11 @@ export default function FilterModal({ isOpen, onClose }: FilterModalProps) {
);

return (
<BaseModal
<CompactModal
isOpen={isOpen}
onClose={onClose}
title="필터"
footer={modalFooter}
width="max-w-2xl"
>
<div className="space-y-8">
{/* 가격 범위 섹션 */}
Expand All @@ -223,9 +250,9 @@ export default function FilterModal({ isOpen, onClose }: FilterModalProps) {
</span>
<input
type="number"
value={minPrice}
value={Number(minPrice)}
onChange={(e) => {
setMinPrice(Number(e.target.value));
setMinPrice(e.target.value);
}}
min={0}
placeholder="최저 요금"
Expand All @@ -241,9 +268,9 @@ export default function FilterModal({ isOpen, onClose }: FilterModalProps) {
</span>
<input
type="number"
value={maxPrice}
value={Number(maxPrice)}
onChange={(e) => {
setMaxPrice(Number(e.target.value));
setMaxPrice(e.target.value);
}}
min={0}
placeholder="최고 요금"
Expand Down Expand Up @@ -330,6 +357,6 @@ export default function FilterModal({ isOpen, onClose }: FilterModalProps) {
)}
</div>
</div>
</BaseModal>
</CompactModal>
);
}
4 changes: 2 additions & 2 deletions src/components/home/FilterBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const FilterBar = () => {
className="px-4 py-2 rounded-xl border border-gray-200 hover:shadow-md transition flex items-center gap-2"
>
<ImportExport className="h-4 w-4" />
<span className="text-sm">정렬</span>
<span className="text-sm hidden lg:inline">정렬</span>
</button>
<button
onClick={() => {
Expand All @@ -80,7 +80,7 @@ const FilterBar = () => {
className="px-4 py-2 rounded-xl border border-gray-200 hover:shadow-md transition flex items-center gap-2"
>
<TuneRounded className="h-4 w-4" />
<span className="text-sm">필터</span>
<span className="text-sm hidden lg:inline">필터</span>
</button>
</div>

Expand Down
29 changes: 29 additions & 0 deletions src/components/home/Listings/ListingItem.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';

import HeartIcon from '@/assets/icons/Heart';
import axiosInstance from '@/axiosInstance';
import { WifiIcon } from '@/components/common/constants/icons';
import LoginModal from '@/components/home/Topbar/Menu/LoginModal';
import type { RoomMain } from '@/types/roomSearch';

type ListingItemProps = {
Expand All @@ -11,13 +13,21 @@ type ListingItemProps = {
};

const ListingItem = ({ listing, onUpdateListing }: ListingItemProps) => {
const navigate = useNavigate();
const [isLiked, setIsLiked] = useState<boolean>(listing.isLiked);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [isLoginModalOpen, setLoginModalOpen] = useState<boolean>(false);

const handleWishList = async (e: React.MouseEvent) => {
e.stopPropagation();

const token = localStorage.getItem('token');
if (token == null) {
handleLoginModalOpen();
return;
}

if (isLoading) return;

setIsLoading(true);
Expand All @@ -41,6 +51,14 @@ const ListingItem = ({ listing, onUpdateListing }: ListingItemProps) => {
}
};

const handleLoginModalOpen = () => {
setLoginModalOpen(true);
};

const handleLoginModalClose = () => {
setLoginModalOpen(false);
};

return (
<div className="group cursor-pointer">
{/* 전체를 감싸는 relative 컨테이너 */}
Expand Down Expand Up @@ -100,6 +118,17 @@ const ListingItem = ({ listing, onUpdateListing }: ListingItemProps) => {
<span className="text-gray-500">/박</span>
</p>
</div>

{isLoginModalOpen && (
<LoginModal
isOpen={isLoginModalOpen}
onClose={handleLoginModalClose}
navigateToSignup={() => {
handleLoginModalClose();
void navigate('/register');
}}
/>
)}
</div>
);
};
Expand Down
15 changes: 7 additions & 8 deletions src/components/home/Listings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ const Listings = () => {
rooms,
isLoading: isNormalLoading,
error,
initRooms,
searchRooms,
pageInfo,
pageRooms,
} = useSearch();

const { mode } = useMode();
Expand All @@ -30,10 +29,10 @@ const Listings = () => {

useEffect(() => {
if (isInitialMount.current) {
void initRooms();
void searchRooms();
isInitialMount.current = false;
}
}, [initRooms]);
}, [searchRooms]);

const handleRoomClick = (id: string) => {
void navigate(`/${id}`);
Expand Down Expand Up @@ -143,12 +142,12 @@ const Listings = () => {
))}
</div>

{/* 페이지네이션 - 일반 모드일 때만 표시 */}
{/* 페이지네이션 */}
{mode === 'normal' && pageInfo.totalPages > 1 && (
<div className="mt-8 flex justify-center gap-2">
<button
onClick={() => {
void pageRooms(pageInfo.pageNumber - 1);
void searchRooms({ pageNumber: pageInfo.pageNumber - 1 });
}}
disabled={pageInfo.pageNumber === 0}
className="rounded-lg border px-4 py-2 disabled:cursor-not-allowed disabled:opacity-50"
Expand All @@ -159,7 +158,7 @@ const Listings = () => {
<button
key={i}
onClick={() => {
void pageRooms(i);
void searchRooms({ pageNumber: i });
}}
className={`rounded-lg px-4 py-2 ${
pageInfo.pageNumber === i
Expand All @@ -172,7 +171,7 @@ const Listings = () => {
))}
<button
onClick={() => {
void pageRooms(pageInfo.pageNumber + 1);
void searchRooms({ pageNumber: pageInfo.pageNumber + 1 });
}}
disabled={pageInfo.pageNumber === pageInfo.totalPages - 1}
className="rounded-lg border px-4 py-2 disabled:cursor-not-allowed disabled:opacity-50"
Expand Down
Loading