diff --git a/src/apis/canary.ts b/src/apis/canary.ts index ca16dea..c022ea3 100644 --- a/src/apis/canary.ts +++ b/src/apis/canary.ts @@ -10,7 +10,7 @@ export type CanaryApplyForm = { gender: boolean; address: string; detailAddress: string; - zip: string; + zip: string | number; certificateFilePath: string; latitude: number; longitude: number; diff --git a/src/assets/images/no-challenger-medal.png b/src/assets/images/no-challenger-medal.png new file mode 100644 index 0000000..632d85c Binary files /dev/null and b/src/assets/images/no-challenger-medal.png differ diff --git a/src/components/Challenge/BestChallenger.tsx b/src/components/Challenge/BestChallenger.tsx index 33c1dc1..4583ca2 100644 --- a/src/components/Challenge/BestChallenger.tsx +++ b/src/components/Challenge/BestChallenger.tsx @@ -5,6 +5,7 @@ import Challenger from "./Challenger"; import { getTopParticipants } from "../../apis/challenge"; import { useQuery } from "@tanstack/react-query"; import { BestChallengerProps } from "../../@types/challenge"; +import NoChallenger from "../Common/NoChallenger"; export const scroll = keyframes` from { @@ -29,25 +30,31 @@ const BestChallenger = () => { title="베스트 챌린저" desc="금주의 베스트 챌린저! 베스트 챌린저는 어떤 챌린지에 참여했을까요?" /> -
- {topChallengersData.data.map( - (challenger: BestChallengerProps, index: number) => ( - - ) - )} -
+ {topChallengersData.length > 0 ? ( +
+ {topChallengersData.data.map( + (challenger: BestChallengerProps, index: number) => ( + + ) + )} +
+ ) : ( + + )} ); }; diff --git a/src/components/ChallengeDetail/Challengers.tsx b/src/components/ChallengeDetail/Challengers.tsx index 47b1f6c..9a5952f 100644 --- a/src/components/ChallengeDetail/Challengers.tsx +++ b/src/components/ChallengeDetail/Challengers.tsx @@ -7,6 +7,7 @@ import { scroll } from "../Challenge/BestChallenger"; import { useQuery } from "@tanstack/react-query"; import { getChallengeParticipants } from "../../apis/challenge"; import { BestChallengerProps } from "../../@types/challenge"; +import NoChallenger from "../Common/NoChallenger"; type ChallengerProps = { challengeId?: string; @@ -60,28 +61,34 @@ const Challengers = ({ /> )} -
- {challengerData.data.map( - (challenger: BestChallengerProps, index: number) => ( - - ) - )} -
+ {challengerData.length > 0 ? ( +
+ {challengerData.data.map( + (challenger: BestChallengerProps, index: number) => ( + + ) + )} +
+ ) : ( + + )} ); }; diff --git a/src/components/ChallengeDetail/Header.tsx b/src/components/ChallengeDetail/Header.tsx index 0c166ff..1436ce6 100644 --- a/src/components/ChallengeDetail/Header.tsx +++ b/src/components/ChallengeDetail/Header.tsx @@ -25,12 +25,14 @@ const Header = ({ return ( - {categories?.map((category) => ( -
- {challengeType.find((type) => type.id === category) - ?.label || category} -
- ))} +
+ {categories?.map((category) => ( +
+ {challengeType.find((type) => type.id === category) + ?.label || category} +
+ ))} +

{title}

{desc}

@@ -80,15 +82,20 @@ const ChallengeInfo = styled.div` ${tw` w-[1280px] flex flex-col items-start `} + .categories { + ${tw` + flex gap-[8px] mb-[20px] + `} + } .category { ${tw` text-medium-20 font-medium text-white - bg-mainColor rounded-[37px] p-[1px 13px] + bg-mainColor rounded-[37px] p-[3px 13px] pb-[6px] `} } .title { ${tw` - text-bold-64 font-bold mt-[6.5px] text-fontColor1 + text-bold-64 font-bold mt-[-20px] text-fontColor1 `} } .desc { diff --git a/src/components/Common/NoChallenger.tsx b/src/components/Common/NoChallenger.tsx new file mode 100644 index 0000000..723e696 --- /dev/null +++ b/src/components/Common/NoChallenger.tsx @@ -0,0 +1,34 @@ +import styled from "styled-components"; +import tw from "twin.macro"; +import medal from "../../assets/images/no-challenger-medal.png"; + +type NoChallengerProps = { + text: string; +}; + +const NoChallenger = ({ text }: NoChallengerProps) => { + return ( + +
아직 {text} 챌린저가 없어요!
+ no-challenger-medal +
+ ); +}; + +export default NoChallenger; + +const Container = styled.div` + ${tw` + w-full h-[415px] flex flex-col gap-[4px] items-center justify-center + `} + div { + ${tw` + text-medium-20 font-medium text-fontColor2 + `} + } + img { + ${tw` + opacity-[63%] + `} + } +`; diff --git a/src/components/Common/PageHeader.tsx b/src/components/Common/PageHeader.tsx index df29bb9..9e05c48 100644 --- a/src/components/Common/PageHeader.tsx +++ b/src/components/Common/PageHeader.tsx @@ -2,44 +2,50 @@ import React from "react"; import styled from "styled-components"; import tw from "twin.macro"; interface PageHeaderProps { - title: string; - desc: string; - imgSrc: string; + title: string; + desc: string; + desc2?: string; + imgSrc: string; } -function PageHeader({ title, desc, imgSrc }: PageHeaderProps) { - return ( - <> - {/* 헤더 */} -
-
- {title} - {desc} -
-
- 헤더이미지 -
-
- - ); +function PageHeader({ title, desc, desc2, imgSrc }: PageHeaderProps) { + return ( + <> + {/* 헤더 */} +
+
+ {title} + {desc} + {desc2 && {desc2}} +
+
+ 헤더이미지 +
+
+ + ); } export default PageHeader; const Header = styled.div` - ${tw`flex flex-row mt-20 h-[528px] w-[1280px] m-auto justify-between`} + ${tw`flex flex-row mt-20 h-[528px] w-[1280px] m-auto justify-between`} - .text-wrapper { - ${tw`flex flex-col w-1/2 mt-36`} - .title-text { - ${tw`text-bold-48 font-sans text-fontColor1 font-bold mb-5`} + .text-wrapper { + ${tw`flex flex-col w-1/2 mt-36`} + .title-text { + ${tw`text-bold-48 font-sans text-fontColor1 font-bold mb-5`} + } + .description-text { + ${tw`text-medium-20 text-fontColor3 font-sans w-[498px] font-medium `} + } } - .description-text { - ${tw`text-medium-20 text-fontColor3 font-sans w-[498px] font-medium `} + .image-background { + width: 50%; + height: 528px; + background: radial-gradient( + 50% 50% at 50% 50%, + #ffd7c0 0%, + #faf8f5 100% + ); } - } - .image-background { - width: 50%; - height: 528px; - background: radial-gradient(50% 50% at 50% 50%, #ffd7c0 0%, #faf8f5 100%); - } `; diff --git a/src/components/MyPage/CanaryModal.tsx b/src/components/MyPage/CanaryModal.tsx index 5aae49d..4b5d57e 100644 --- a/src/components/MyPage/CanaryModal.tsx +++ b/src/components/MyPage/CanaryModal.tsx @@ -9,7 +9,18 @@ import useAuthStore from "../../storage/useAuthStore"; import { getPresignedUrl, uploadImageToS3 } from "../../apis/file-upload"; import { postCanaryApply } from "../../apis/canary"; -const CanaryModal = ({ onClick }: { onClick: () => void }) => { +interface postCode { + address: string; + zonecode: number | string; + detailAddress?: string; +} + +// prop 타입 정의 +interface CanaryModalProps { + onClick: () => void; +} + +const CanaryModal = ({ onClick }: CanaryModalProps) => { const { userData } = useAuthStore.getState(); const [success, setSuccess] = useState(false); const [image, setImage] = useState(null); @@ -28,34 +39,40 @@ const CanaryModal = ({ onClick }: { onClick: () => void }) => { gender: true, address: "", detailAddress: "", - zip: "", + zip: "" as number | string, certificateFilePath: "", latitude: 0, longitude: 0, }); - const handleApplyData = useCallback( - (data: any, key: string) => { - setApplyData({ ...applyData, [key]: data }); - }, - [applyData] - ); + const handleApplyData = (value: any, field: any) => { + let formattedValue = value; + + // 필드에 따라 포맷팅 처리 + if (field === "phone") { + formattedValue = formatPhoneNumber(value); + } + + setApplyData({ + ...applyData, + [field]: formattedValue, + }); + }; + const handleBirthData = useCallback( (data: any, key: string) => { setBirthData({ ...birthData, [key]: data }); }, [birthData] ); - const handleAddressChange = useCallback( - (data: any) => { - setApplyData({ - ...applyData, - address: data.address, - detailAddress: data.detailAddress, - zip: data.zonecode, - }); - }, - [applyData] - ); + + const handleAddressChange = (data: postCode) => { + setApplyData({ + ...applyData, + address: data.address, + detailAddress: data.detailAddress!, + zip: data.zonecode, + }); + }; const handleImageChange = useCallback( (e: React.ChangeEvent) => { @@ -67,6 +84,26 @@ const CanaryModal = ({ onClick }: { onClick: () => void }) => { [] ); + const formatPhoneNumber = (value: any) => { + // 숫자만 남기기 + const numbersOnly = value.replace(/\D/g, ""); + + // 3자리, 4자리, 4자리로 나누어 하이픈 추가 + let formattedNumber = numbersOnly; + if (numbersOnly.length > 3 && numbersOnly.length <= 7) { + formattedNumber = `${numbersOnly.slice(0, 3)}-${numbersOnly.slice( + 3 + )}`; + } else if (numbersOnly.length > 7) { + formattedNumber = `${numbersOnly.slice(0, 3)}-${numbersOnly.slice( + 3, + 7 + )}-${numbersOnly.slice(7, 11)}`; + } + + return formattedNumber; + }; + const convertToISO = (year: number, month: number, day: number): string => { const date = new Date(year, month - 1, day); return date.toISOString(); @@ -97,6 +134,9 @@ const CanaryModal = ({ onClick }: { onClick: () => void }) => { } } }, [accesstoken, applyData, image, birthData]); + + console.log(applyData); + return ( { @@ -252,8 +292,9 @@ const ImageInput = styled.div` `} span { ${tw` - absolute left-0 top-0 w-full h-full flex items-center ml-[21px] - `} + absolute left-0 top-0 w-[500px] h-full flex items-center ml-[21px] + overflow-hidden whitespace-nowrap + `} } } `; diff --git a/src/components/MyPage/Header.tsx b/src/components/MyPage/Header.tsx index 3a86b71..2806121 100644 --- a/src/components/MyPage/Header.tsx +++ b/src/components/MyPage/Header.tsx @@ -107,7 +107,7 @@ const Profile = styled.div` .change-name { ${tw` - text-medium-20 font-medium text-fontColor2 underline underline-offset-4 decoration-1 cursor-pointer + text-medium-20 font-medium text-fontColor2 underline underline-offset-4 decoration-1 cursor-pointer mt-[10px] `} } `; diff --git a/src/components/MyPage/PostalCode.tsx b/src/components/MyPage/PostalCode.tsx index f3f434a..4d8d131 100644 --- a/src/components/MyPage/PostalCode.tsx +++ b/src/components/MyPage/PostalCode.tsx @@ -6,92 +6,100 @@ import { useState, useEffect } from "react"; import DaumPost from "../Common/DaumPost"; interface postCode { - address: string; - zonecode: number | string; - detailAddress?: string; + address: string; + zonecode: number | string; + detailAddress?: string; } interface PostalCodeProps { - initialAddress?: postCode; - onChange?: (data: postCode) => void; + initialAddress?: postCode; + onChange?: (data: postCode) => void; } const PostalCode = ({ initialAddress, onChange }: PostalCodeProps) => { - const [popup, setPopup] = useState(false); + const [popup, setPopup] = useState(false); - const [form, setForm] = useState({ - address: "", - zonecode: "", - detailAddress: "", - }); + const [form, setForm] = useState({ + address: "", + zonecode: "", + detailAddress: "", + }); - const handlePopup = () => { - setPopup(!popup); - }; + const handlePopup = () => { + setPopup(!popup); + }; - useEffect(() => { - if (initialAddress) { - setForm(initialAddress); - } - }, [initialAddress]); + useEffect(() => { + if (initialAddress) { + setForm(initialAddress); + } + }, [initialAddress]); - useEffect(() => { - if (onChange && form.address !== initialAddress?.address) { - onChange(form); - } - }, [form, initialAddress]); + useEffect(() => { + if (onChange && form.address !== initialAddress?.address) { + onChange(form); + } + }, [form, initialAddress?.address]); - const handleDetailAddressChange = ( - e: React.ChangeEvent - ) => { - setForm((prevForm) => ({ ...prevForm, detailAddress: e.target.value })); - }; + useEffect(() => { + if (onChange && form.detailAddress !== initialAddress?.detailAddress) { + onChange(form); + } + }, [form.detailAddress, initialAddress?.detailAddress]); - return ( - <> - - - - -