Skip to content

Commit

Permalink
Merge branch 'master' into 조혜진
Browse files Browse the repository at this point in the history
  • Loading branch information
MEGUMMY1 authored Jul 13, 2024
2 parents 88cf0bc + 629307c commit 0847d40
Show file tree
Hide file tree
Showing 23 changed files with 590 additions and 445 deletions.
2 changes: 1 addition & 1 deletion components/Layout/NavigationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function NavigationBar() {
const dropdownRef = useClickOutside<HTMLDivElement>(closeProfileDropdown);

return (
<div className="flex h-[70px] justify-between pl-[369px] pr-[351px] t:px-[24px] m:px-[24px] border-b border-var-gray3 border-solid ">
<div className="sticky top-0 flex h-[70px] z-30 justify-between pl-[369px] pr-[351px] t:px-[24px] m:px-[24px] border-b bg-white border-var-gray3 border-solid ">
<div className="flex items-center">
<Link href="/">
<Image src={Logo} alt="로고 아이콘" />
Expand Down
109 changes: 94 additions & 15 deletions components/MyPageInput/MyPageInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import { FieldValues, useForm } from 'react-hook-form';
import { PrimaryButton } from '../Button/Button';
import InputBox from '../InputBox/InputBox';
import { validation } from './validation';
import { useQuery } from '@tanstack/react-query';
import { useEffect } from 'react';
import { apiMyInfo } from '@/pages/api/users/apiUsers';
import hamburgerIcon from '@/public/icon/hamburger_icon.svg';
import { useEffect, useState } from 'react';
import useEditMyInfo from '@/hooks/useEditMyInfo';
import { useUserData } from '@/hooks/useUserData';
import Image from 'next/image';
import useUploadProfile from '@/hooks/useUploadProfile';
import { ProfileImageResponse } from '@/pages/api/users/apiUser.types';
import profileThumbnail from '@/public/image/profile-circle-icon-512x512-zxne30hp.png';
import editProfileIcon from '@/public/image/btn.png';
import { useSideNavigation } from '@/hooks/useSideNavigation';

export default function MyPageInput() {
const {
Expand All @@ -17,9 +22,14 @@ export default function MyPageInput() {
formState: { errors },
} = useForm<FieldValues>({ mode: 'onChange' });

const userData = useUserData();
const { openSideNavigation } = useSideNavigation();

const userData = useUserData();
const { postProfileImgMutation } = useUploadProfile();
const { postMyInfoMutation } = useEditMyInfo();
const [profileImageUrl, setProfileImageUrl] = useState<string | null>(
userData?.profileImageUrl
);

const onSubmit = (data: FieldValues) => {
const { nickname, password, passwordCheck } = data;
Expand All @@ -29,6 +39,32 @@ export default function MyPageInput() {
}
};

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
const formData = new FormData();
formData.append('image', file);

postProfileImgMutation.mutate(formData, {
onSuccess: (response: ProfileImageResponse) => {
setProfileImageUrl(response.profileImageUrl);
postMyInfoMutation.mutate({
profileImageUrl: response.profileImageUrl,
});
},
onError: (error) => {
console.error('데이터를 불러오는데 실패했습니다.', error);
},
});
}
};

useEffect(() => {
if (userData?.profileImageUrl) {
setProfileImageUrl(userData.profileImageUrl);
}
}, [userData?.profileImageUrl]);

useEffect(() => {
if (userData) {
setValue('nickname', userData.nickname);
Expand All @@ -49,20 +85,53 @@ export default function MyPageInput() {
};

return (
<div className="flex flex-col w-[792px] h-[564px] t:w-[429px] t:h-[556px] m:w-full m:h-[492px] m:pb-[210px]">
<div className="flex flex-col w-[792px] h-[564px] t:w-[429px] t:h-[556px] m:w-full m:h-full m:pb-[150px] m:px-[16px] ">
<form className="flex flex-col gap-[24px] t:gap-[16px]">
<div className="flex justify-between">
<div className="flex m:gap-[15px] p:justify-between t:justify-between">
<Image
src={hamburgerIcon}
alt="햄버거 메뉴 아이콘"
className="p:hidden t:hidden"
onClick={() => openSideNavigation()}
/>
<p className="font-bold text-[32px]">내 정보</p>
<PrimaryButton
style={isAllFieldsValid() ? 'dark' : 'disabled'}
size="small"
onClick={handleSubmit(onSubmit)}
disabled={!isAllFieldsValid()}
>
저장하기
</PrimaryButton>
<div className="flex items-center m:hidden">
<PrimaryButton
style={isAllFieldsValid() ? 'dark' : 'disabled'}
size="small"
onClick={handleSubmit(onSubmit)}
disabled={!isAllFieldsValid()}
>
저장하기
</PrimaryButton>
</div>
</div>
<div className="w-fill flex justify-center p:hidden t:hidden">
<label htmlFor="upload-image" className="cursor-pointer">
<div className="w-[160px] relative">
<Image
src={profileImageUrl ? profileImageUrl : profileThumbnail}
width={160}
height={160}
className="rounded-full w-[160px] h-[160px] "
alt="유저 프로필 사진"
/>
<Image
src={editProfileIcon}
className="w-[44px] h-[44px] absolute bottom-[-5px] right-[10px] "
alt="유저 프로필 사진 수정"
/>
<input
id="upload-image"
type="file"
accept="image/*"
className="hidden"
onChange={handleFileChange}
/>
</div>
</label>
</div>
<div className="flex flex-col gap-[32px]">
<div className="flex flex-col gap-[25px]">
<div>
<InputBox
label="닉네임"
Expand Down Expand Up @@ -108,6 +177,16 @@ export default function MyPageInput() {
errors={errors}
/>
</div>
<div className="mt-[30px] flex items-center p:hidden t:hidden">
<PrimaryButton
style={isAllFieldsValid() ? 'dark' : 'disabled'}
size="large"
onClick={handleSubmit(onSubmit)}
disabled={!isAllFieldsValid()}
>
저장하기
</PrimaryButton>
</div>
</div>
</form>
</div>
Expand Down
2 changes: 1 addition & 1 deletion components/NavigationDropdown/NotificationDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default function NotificationDropdown({
{notifications.map((notification) => (
<div
key={notification.id}
className="flex-col items-center px-[12px] py-[16px] justify-between rounded-[5px] border-b w-[328px] h-[120px] w-[335px] h-[105px] bg-white border-gray-200 "
className="flex-col items-center px-[12px] py-[16px] justify-between rounded-[5px] border-b w-[328px] h-[120px] m:w-[335px] m:h-[105px] bg-white border-gray-200 "
>
<div className="flex justify-between">
승인 아이콘
Expand Down
2 changes: 1 addition & 1 deletion components/ReservationFilter/ReservationFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Image from 'next/image';
import filterIcon from '@/public/icon/filter_arrowdown.svg';
import React, { useState } from 'react';
import useClickOutside from '@/hooks/useClickOutside';
import { ReservationFilterProps, statusType } from './ReservationFilter.types';
import { ReservationFilterProps, statusType } from './myReservationTypes.types';
import {
Status,
statusTitles,
Expand Down
6 changes: 0 additions & 6 deletions components/ReservationFilter/ReservationFilter.types.ts

This file was deleted.

35 changes: 35 additions & 0 deletions components/ReservationFilter/myReservationTypes.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export type statusType =
| 'pending'
| 'confirmed'
| 'declined'
| 'canceled'
| 'completed';

export interface ReservationFilterProps {
setFilterOption: React.Dispatch<React.SetStateAction<statusType | undefined>>;
filterOption: statusType | undefined;
}
export interface ReservationCardProps {
reservationData: MyReservationProps;
}

export interface MyReservationProps {
id: number;
teamId: string;
userId: number;
activity: {
bannerImageUrl: string;
title: string;
id: number;
};
scheduleId: number;
status: 'pending' | 'canceled' | 'declined' | 'completed' | 'confirmed';
reviewSubmitted: boolean;
totalPrice: number;
headCount: number;
date: string;
startTime: string;
endTime: string;
createdAt: string;
updatedAt: string;
}
90 changes: 67 additions & 23 deletions components/ReservationListCard/ReservationListCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,90 @@ import { statusType } from '../ReservationFilter/ReservationFilter.types';
import { useModal } from '@/hooks/useModal';
import Review from '../Review/Review';
import { ReservationCardProps } from './ReservationListCard.types';
import { ReservationCardProps } from '../ReservationFilter/myReservationTypes.types';
import { usePopup } from '@/hooks/usePopup';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { apiEditMyReservation } from '@/pages/api/myReservations/apiMyReservations';
import { useUserData } from '@/hooks/useUserData';
import { formatCurrency } from '@/utils/formatCurrency';
import Link from 'next/link';

const ReservatioListCard = ({ mockData }: ReservationCardProps) => {
const ReservatioListCard = ({ reservationData }: ReservationCardProps) => {
const { openModal, closeModal } = useModal();

const handleCancelReservation = () => {};
const handleOpenReviewModal = () => {
openModal({
title: '후기 작성',
hasButton: false,
content: <Review reservation={mockData} closeModal={closeModal} />,
content: <Review reservation={reservationData} closeModal={closeModal} />,
});
};

const isPendingOrAccepted = (status: statusType) => {
return status === 'pending' || status === 'accept';
const ReservationListCard = ({ reservationData }: ReservationCardProps) => {
const { openPopup } = usePopup();
const userData = useUserData();
const queryClient = useQueryClient();

const EditMyReservationMutation = useMutation({
mutationFn: apiEditMyReservation,
onSuccess: () =>
queryClient.invalidateQueries({
queryKey: ['myReservationList', userData.id],
}),
onError: (error) => {
console.error('Error editing reservation:', error);
},
});

const handleCancelReservation = () => {
openPopup({
popupType: 'select',
content: '예약을 취소하시겠어요?',
btnName: ['아니오', '취소하기'],
callBackFnc: () =>
EditMyReservationMutation.mutate({ reservationId: reservationData.id }),
});
};

return (
<div className="h-[212px] relative flex rounded-3xl shadow-card">
<Image
src="/image/TestImage.jpg"
alt="액티비티 사진"
objectFit="cover"
width={204}
height={204}
/>
<div className="h-[212px] relative flex rounded-3xl shadow-card overflow-hidden">
<div className="min-w-[204px] h-[204px] relative">
<Link
href={`/activity-details/${reservationData.activity.id}`}
className="text-[20px] font-bold mt-[8px] hover:underline"
>
<Image
src={reservationData.activity.bannerImageUrl}
alt="액티비티 사진"
layout="fill"
objectFit="cover"
className="hover:scale-110"
/>
</Link>
</div>
<div className="w-full p-[24px]">
<p className={`text-[16px] font-bold ${statusStyle[mockData.status]}`}>
{statusTitles[mockData.status]}
</p>
<p className="text-[20px] font-bold mt-[8px]">
{mockData.activity.title}
<p
className={`text-[16px] font-bold ${statusStyle[reservationData.status]}`}
>
{statusTitles[reservationData.status]}
</p>
<Link
href={`/activity-details/${reservationData.activity.id}`}
className="text-[20px] font-bold mt-[8px] hover:underline"
>
{reservationData.activity.title}
</Link>
<p className="mt-[12px] text-[18px]">
{mockData.date}&nbsp;&nbsp;·&nbsp;&nbsp;{mockData.startTime}~
{mockData.endTime}&nbsp;&nbsp;·&nbsp;&nbsp;{mockData.headCount}
{reservationData.date}&nbsp;&nbsp;·&nbsp;&nbsp;
{reservationData.startTime}~{reservationData.endTime}
&nbsp;&nbsp;·&nbsp;&nbsp;{reservationData.headCount}
</p>
<div className="w-full flex justify-between mt-[16px] items-center">
<p className="font-medium text-[24px]">{mockData.totalPrice}</p>
{isPendingOrAccepted(mockData.status) && (
<p className="font-medium text-[24px]">
{formatCurrency(reservationData.totalPrice)}
</p>
{reservationData.status === 'pending' && (
<PrimaryButton
size="medium"
style="bright"
Expand All @@ -53,7 +97,7 @@ const ReservatioListCard = ({ mockData }: ReservationCardProps) => {
예약 취소
</PrimaryButton>
)}
{mockData.reviewSubmitted && (
{reservationData.status === 'completed' && (
<PrimaryButton
size="medium"
style="dark"
Expand All @@ -68,4 +112,4 @@ const ReservatioListCard = ({ mockData }: ReservationCardProps) => {
);
};

export default ReservatioListCard;
export default ReservationListCard;
Loading

0 comments on commit 0847d40

Please sign in to comment.