diff --git a/package-lock.json b/package-lock.json index c6c78690..d258f13a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "date-fns": "^3.6.0", "framer-motion": "^11.3.28", "lottie-react": "^2.4.0", - "next": "14.2.3", + "next": "14.2.10", "node-html-parser": "^6.1.13", "quill-image-drop-and-paste": "^2.0.1", "rc-slider": "^11.1.5", @@ -723,9 +723,9 @@ } }, "node_modules/@next/env": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz", - "integrity": "sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==" + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.10.tgz", + "integrity": "sha512-dZIu93Bf5LUtluBXIv4woQw2cZVZ2DJTjax5/5DOs3lzEOeKLy7GxRSr4caK9/SCPdaW6bCgpye6+n4Dh9oJPw==" }, "node_modules/@next/eslint-plugin-next": { "version": "14.2.3", @@ -737,9 +737,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.3.tgz", - "integrity": "sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.10.tgz", + "integrity": "sha512-V3z10NV+cvMAfxQUMhKgfQnPbjw+Ew3cnr64b0lr8MDiBJs3eLnM6RpGC46nhfMZsiXgQngCJKWGTC/yDcgrDQ==", "cpu": [ "arm64" ], @@ -752,9 +752,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz", - "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.10.tgz", + "integrity": "sha512-Y0TC+FXbFUQ2MQgimJ/7Ina2mXIKhE7F+GUe1SgnzRmwFY3hX2z8nyVCxE82I2RicspdkZnSWMn4oTjIKz4uzA==", "cpu": [ "x64" ], @@ -767,9 +767,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz", - "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.10.tgz", + "integrity": "sha512-ZfQ7yOy5zyskSj9rFpa0Yd7gkrBnJTkYVSya95hX3zeBG9E55Z6OTNPn1j2BTFWvOVVj65C3T+qsjOyVI9DQpA==", "cpu": [ "arm64" ], @@ -782,9 +782,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz", - "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.10.tgz", + "integrity": "sha512-n2i5o3y2jpBfXFRxDREr342BGIQCJbdAUi/K4q6Env3aSx8erM9VuKXHw5KNROK9ejFSPf0LhoSkU/ZiNdacpQ==", "cpu": [ "arm64" ], @@ -797,9 +797,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz", - "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.10.tgz", + "integrity": "sha512-GXvajAWh2woTT0GKEDlkVhFNxhJS/XdDmrVHrPOA83pLzlGPQnixqxD8u3bBB9oATBKB//5e4vpACnx5Vaxdqg==", "cpu": [ "x64" ], @@ -812,9 +812,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", - "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.10.tgz", + "integrity": "sha512-opFFN5B0SnO+HTz4Wq4HaylXGFV+iHrVxd3YvREUX9K+xfc4ePbRrxqOuPOFjtSuiVouwe6uLeDtabjEIbkmDA==", "cpu": [ "x64" ], @@ -827,9 +827,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", - "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.10.tgz", + "integrity": "sha512-9NUzZuR8WiXTvv+EiU/MXdcQ1XUvFixbLIMNQiVHuzs7ZIFrJDLJDaOF1KaqttoTujpcxljM/RNAOmw1GhPPQQ==", "cpu": [ "arm64" ], @@ -842,9 +842,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", - "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.10.tgz", + "integrity": "sha512-fr3aEbSd1GeW3YUMBkWAu4hcdjZ6g4NBl1uku4gAn661tcxd1bHs1THWYzdsbTRLcCKLjrDZlNp6j2HTfrw+Bg==", "cpu": [ "ia32" ], @@ -857,9 +857,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", - "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.10.tgz", + "integrity": "sha512-UjeVoRGKNL2zfbcQ6fscmgjBAS/inHBh63mjIlfPg/NG8Yn2ztqylXt5qilYb6hoHIwaU2ogHknHWWmahJjgZQ==", "cpu": [ "x64" ], @@ -3937,11 +3937,11 @@ "dev": true }, "node_modules/next": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.3.tgz", - "integrity": "sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==", + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.10.tgz", + "integrity": "sha512-sDDExXnh33cY3RkS9JuFEKaS4HmlWmDKP1VJioucCG6z5KuA008DPsDZOzi8UfqEk3Ii+2NCQSJrfbEWtZZfww==", "dependencies": { - "@next/env": "14.2.3", + "@next/env": "14.2.10", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -3956,15 +3956,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.3", - "@next/swc-darwin-x64": "14.2.3", - "@next/swc-linux-arm64-gnu": "14.2.3", - "@next/swc-linux-arm64-musl": "14.2.3", - "@next/swc-linux-x64-gnu": "14.2.3", - "@next/swc-linux-x64-musl": "14.2.3", - "@next/swc-win32-arm64-msvc": "14.2.3", - "@next/swc-win32-ia32-msvc": "14.2.3", - "@next/swc-win32-x64-msvc": "14.2.3" + "@next/swc-darwin-arm64": "14.2.10", + "@next/swc-darwin-x64": "14.2.10", + "@next/swc-linux-arm64-gnu": "14.2.10", + "@next/swc-linux-arm64-musl": "14.2.10", + "@next/swc-linux-x64-gnu": "14.2.10", + "@next/swc-linux-x64-musl": "14.2.10", + "@next/swc-win32-arm64-msvc": "14.2.10", + "@next/swc-win32-ia32-msvc": "14.2.10", + "@next/swc-win32-x64-msvc": "14.2.10" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", diff --git a/package.json b/package.json index b1b7d11f..775be27a 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "date-fns": "^3.6.0", "framer-motion": "^11.3.28", "lottie-react": "^2.4.0", - "next": "14.2.3", + "next": "14.2.10", "node-html-parser": "^6.1.13", "quill-image-drop-and-paste": "^2.0.1", "rc-slider": "^11.1.5", diff --git a/src/app/api/auth/user-image/route.ts b/src/app/api/auth/user-image/route.ts new file mode 100644 index 00000000..84f5691c --- /dev/null +++ b/src/app/api/auth/user-image/route.ts @@ -0,0 +1,30 @@ +import { NextRequest, NextResponse } from "next/server"; + +// 사용자 이미지 삭제 API +export async function DELETE(request: NextRequest) { + const access_cookie = request.cookies.get("access_token"); + if (!access_cookie) { + const refresh_cookie = request.cookies.get("refresh_token"); + if (!refresh_cookie) { + // 리프레시 토큰이 없으므로 요청 중단 + return new NextResponse("Refresh token not found", { status: 403 }); + } + // 리프레시 토큰으로 재발급 받아 재요청 보내기 위한 응답 + return new NextResponse("Refresh token not found", { status: 401 }); + } + + const response = await fetch(`${process.env.BACKEND_URL}/api/user-image`, { + method: "DELETE", + headers: { + Cookie: `${access_cookie?.name}=${access_cookie?.value}`, + "Content-Type": "application/json", + }, + cache: "no-store", + }); + + if (!response.ok) { + return new NextResponse(`${response.statusText}`, { status: response.status }); + } + + return await response.json(); +} diff --git a/src/app/api/gathering/[id]/route.ts b/src/app/api/gathering/[id]/route.ts index 5c5d718a..83108c4b 100644 --- a/src/app/api/gathering/[id]/route.ts +++ b/src/app/api/gathering/[id]/route.ts @@ -8,7 +8,6 @@ export async function GET( try { const cookie = request.cookies.get("access_token"); - // Back-end API 호출 const response = await fetch( `${process.env.BACKEND_URL}/api/gatherings/${params.id}`, { @@ -43,7 +42,6 @@ export async function PUT( const cookie = request.cookies.get("access_token"); const bodyData = await request.json(); - // Back-end API 호출 const response = await fetch( `${process.env.BACKEND_URL}/api/gatherings/${params.id}`, { @@ -60,11 +58,9 @@ export async function PUT( if (!response.ok) { throw new Error(response.statusText); } - await revalidatePath(`/gathering/${params.id}`); - // return NextResponse.redirect( - // new URL(`/getGathering/${params.id}`), - // ); - + revalidatePath(`/gathering/${params.id}`); + revalidatePath("/mypage"); + revalidatePath("/gathering"); return response; } catch (e) { return new Response(JSON.stringify({ error: "Failed to update data." }), { diff --git a/src/app/api/gathering/route.ts b/src/app/api/gathering/route.ts index 9307829f..9b507cb5 100644 --- a/src/app/api/gathering/route.ts +++ b/src/app/api/gathering/route.ts @@ -31,6 +31,8 @@ export async function POST(request: NextRequest) { throw new Error(`Error: ${errorData.error || "Unknown error occurred"}`); } const resultData = await response.json(); + // 생성시 홈 화면에 new모임 최신화 + revalidateTag("getNewGatheringList"); return NextResponse.json( { data: resultData, message: "데이터가 성공적으로 처리되었습니다." }, { status: 200 }, @@ -90,6 +92,8 @@ export async function DELETE(request: NextRequest) { return NextResponse.json({ error: "서버 오류" }, { status: 500 }); } + revalidateTag("getNewGatheringList"); revalidatePath("/gathering", "layout"); + revalidatePath("/mypage"); return response; } diff --git a/src/components/auth/AddUserInformationForm.tsx b/src/components/auth/AddUserInformationForm.tsx new file mode 100644 index 00000000..92c93540 --- /dev/null +++ b/src/components/auth/AddUserInformationForm.tsx @@ -0,0 +1,126 @@ +"use client" + +import { useRef, useState } from "react"; + +interface IAddUserInformationForm { + +} +const AddUserInformationForm = (props: IAddUserInformationForm) => { + const [sex, setSex] = useState(""); + const [date, setDate] = useState({ + year: "", + month: "", + day: "", + }); + + const monthRef = useRef(null); + const dayRef = useRef(null); + + // 숫자만 입력되게 필터링 + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + if (!/^\d*$/.test(value)) return; + + setDate((prev) => ({ + ...prev, + [name]: value, + })); + + if (name === "year" && value.length === 4) { + monthRef.current?.focus(); + } + + if (name === "month" && value.length === 2) { + dayRef.current?.focus(); + } + }; + + return ( +
+

+ 안녕하세요 솔리투어입니다 +

+

+ 신뢰할 수 있는 이용 환경을 위해 필요한 정보를 입력해 주세요 +

+
+
+

성별

+
+ + +
+
+
+

생년월일

+
+ + . + + . + +
+
+
+

이름

+ +
+
+

+ 정보를 입력하지 않아도 서비스를 이용할 수 있으나 일부 서비스 이용이 + 제한될 수 있습니다 +

+ +
+ ); +}; +export default AddUserInformationForm \ No newline at end of file diff --git a/src/components/common/FloatingButton.tsx b/src/components/common/FloatingButton.tsx index c42d534c..fc535cba 100644 --- a/src/components/common/FloatingButton.tsx +++ b/src/components/common/FloatingButton.tsx @@ -9,10 +9,11 @@ interface Props { animationFlag: boolean; onClick: () => void; onScrollToTop: () => void; + createGatheringClick: (e: React.MouseEvent) => void; } const FloatingButton = forwardRef( - ({ visible, animationFlag, onClick, onScrollToTop }, ref) => { + ({ visible, animationFlag, onClick, onScrollToTop, createGatheringClick }, ref) => { return (
{visible && ( @@ -31,7 +32,7 @@ const FloatingButton = forwardRef( createGatheringClick(e)} >

모임

등록하기

diff --git a/src/components/common/GatheringItem.tsx b/src/components/common/GatheringItem.tsx index ec7acf3e..984d7ab6 100644 --- a/src/components/common/GatheringItem.tsx +++ b/src/components/common/GatheringItem.tsx @@ -9,6 +9,7 @@ import Link from "next/link"; interface IGatheringItem { data: Gathering; + isAccessGathering: boolean; } const SEX: { [key: string]: string } = { @@ -35,11 +36,16 @@ const status: { [key: string]: string } = { }; // todo -const GatheringItem = ({ data }: IGatheringItem) => { +const GatheringItem = ({ data, isAccessGathering }: IGatheringItem) => { return ( { + if (!isAccessGathering) { + e.preventDefault(); + } + }} >
{/* 상태와 북마크 */} diff --git a/src/components/common/Header.tsx b/src/components/common/Header.tsx index 0bf59cf3..8a1ea5ea 100644 --- a/src/components/common/Header.tsx +++ b/src/components/common/Header.tsx @@ -39,7 +39,7 @@ const Header = ({ } >
-
+
{ }, [toastifyStore]); return ( -
+
); diff --git a/src/components/common/TopList.tsx b/src/components/common/TopList.tsx index a45fa830..9274cd78 100644 --- a/src/components/common/TopList.tsx +++ b/src/components/common/TopList.tsx @@ -31,7 +31,7 @@ async function getTopGatheringList() { `${process.env.BACKEND_URL}/api/gatherings/ranks`, { method: "GET", - next: { revalidate: 3600, tags: ["getTopGatheringList"] }, + next: { revalidate: 60, tags: ["getTopGatheringList"] }, }, ); diff --git a/src/components/common/modal/Modal.tsx b/src/components/common/modal/Modal.tsx index 08f19e21..7b15bd06 100644 --- a/src/components/common/modal/Modal.tsx +++ b/src/components/common/modal/Modal.tsx @@ -79,7 +79,9 @@ export const Modal = ({ }} > {isHeaderBar && ( -
+
)}
diff --git a/src/components/gathering/read/detail/GatheringViewer.tsx b/src/components/gathering/read/detail/GatheringViewer.tsx index 806acf04..8826bed1 100644 --- a/src/components/gathering/read/detail/GatheringViewer.tsx +++ b/src/components/gathering/read/detail/GatheringViewer.tsx @@ -45,6 +45,11 @@ const GatheringViewer = ({ gatheringStatus={data.gatheringStatus} isFinish={data.isFinish} openChattingUrl={data.openChattingUrl} + allowedGender={data.allowedSex} + allowedAgeRange={{ + startAge: data.startAge, + endAge: data.endAge, + }} />
{/* 프로필 이미지, 닉네임, 좋아요, 조회수 */} diff --git a/src/components/mypage/MyPageGatheringList.tsx b/src/components/mypage/MyPageGatheringList.tsx index 29ca60ae..be4893c2 100644 --- a/src/components/mypage/MyPageGatheringList.tsx +++ b/src/components/mypage/MyPageGatheringList.tsx @@ -4,15 +4,22 @@ import GatheringItemSkeleton from "../skeleton/common/GatheringItemSkeleton"; interface IMyPageGatheringList { elements: Gathering[]; isLoading: boolean; + checkAccessGathering: (e: React.MouseEvent) => void; + isAccessGathering: boolean; } const MyPageGatheringList = ({ elements, isLoading, + checkAccessGathering, + isAccessGathering, }: IMyPageGatheringList) => { return ( -
+
checkAccessGathering(e)} + className="my-6 grid w-full justify-items-center gap-x-3 gap-y-3 min-[744px]:grid-cols-2" + > {isLoading ? Array.from({ length: 6 }).map((_, index) => ( @@ -21,6 +28,7 @@ const MyPageGatheringList = ({ ))}
diff --git a/src/components/mypage/MyPageUserImage.tsx b/src/components/mypage/MyPageUserImage.tsx index bf80044a..7216ea81 100644 --- a/src/components/mypage/MyPageUserImage.tsx +++ b/src/components/mypage/MyPageUserImage.tsx @@ -12,16 +12,17 @@ interface IMyPageUserImage { isModalOpen: boolean; closeCropModal: () => void; onChangeImageUrl: (_: string) => void; + deleteImage: () => void; } const MyPageUserImage = (props: IMyPageUserImage) => { return (
-
+
- props.dragAndDrop.onDropOrInputEvent(e)} - /> +
{ >
@@ -53,8 +53,9 @@ const MyProfile = (props: IMyProfileProps) => {

3. 필요한 정보는 회원탈퇴하기전에 따로 보관해주시기 바랍니다.

-
- {"'회원탈퇴를 하겠습니다.' 라고 입력해주세요."} +
+ '회원탈퇴를 하겠습니다.' + 라고 입력해주세요.
{ disabled={props.userDeleteText !== "회원탈퇴를 하겠습니다."} onClick={props.userDeleteHandler} className={ - "h-[3rem] w-full rounded-full bg-main text-white disabled:bg-gray2" + "h-[3rem] flex-shrink-0 w-full rounded-full bg-main text-white disabled:bg-gray2" } > 회원탈퇴
-
+
@@ -136,19 +137,19 @@ const MyProfile = (props: IMyProfileProps) => { />
-
+ {/*
성별
-
+
*/}
{ 연동된 계정
- 카카오톡 + {props.userInfo.provider}
{props.userInfo.userImage.createdDate}
- {"kakao-icon-image"} + { + props.userInfo.provider == "kakao" && + {"kakao-icon-image"} + } + { + props.userInfo.provider == "google" && + {"google-icon-image"} + }
-
- +
+
); diff --git a/src/containers/common/FloatingButtonContainer.tsx b/src/containers/common/FloatingButtonContainer.tsx index 2cfe1de6..4b4248ca 100644 --- a/src/containers/common/FloatingButtonContainer.tsx +++ b/src/containers/common/FloatingButtonContainer.tsx @@ -1,13 +1,21 @@ "use client"; +import AddUserInformationForm from "@/components/auth/AddUserInformationForm"; import FloatingButton from "@/components/common/FloatingButton"; +import { Modal } from "@/components/common/modal/Modal"; +import useModalState from "@/hooks/useModalState"; import useOutsideClick from "@/hooks/useOutsideClick"; +import useAuthStore from "@/store/authStore"; +import { useRouter } from "next/navigation"; import { useRef, useState } from "react"; const FloatingButtonContainer = () => { const [visible, setVisible] = useState(false); const [animationFlag, setAnimationFlag] = useState(false); const ref = useRef(null); + const authStore = useAuthStore(); + const modalState = useModalState(); + const router = useRouter(); const onClick = async () => { if (visible) { @@ -19,6 +27,20 @@ const FloatingButtonContainer = () => { setVisible(!visible); }; + const createGatheringClick = async ( + e: React.MouseEvent, + ) => { + onClick(); + if (authStore.id > 0 && (!authStore.sex || !authStore.age)) { + e.preventDefault(); + modalState.openModal(); + } + if (authStore.id < 1) { + e.preventDefault(); + router.push("/auth/signin"); + } + }; + const onScrollToTop = () => { window.scrollTo({ top: 0, behavior: "smooth" }); }; @@ -28,13 +50,19 @@ const FloatingButtonContainer = () => { }); return ( - + <> + + + + + ); }; diff --git a/src/containers/diary/detail/DiaryViewerContainer.tsx b/src/containers/diary/detail/DiaryViewerContainer.tsx index e09e7335..c72956fc 100644 --- a/src/containers/diary/detail/DiaryViewerContainer.tsx +++ b/src/containers/diary/detail/DiaryViewerContainer.tsx @@ -17,12 +17,35 @@ const DiaryViewerContainer = ({ data }: Props) => { useModalBackHandler(modalVisible, () => setModalVisible(false)); useEffect(() => { + // Intersection Observer 설정 + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + // 요소가 화면에 보이면 fadeIn 애니메이션 적용 + (entry.target as HTMLElement).style.opacity = "1"; + entry.target.classList.add("animate-fadeIn"); + } + }); + }, + { threshold: 0.5 }, // 요소가 50% 이상 보일 때 트리거 + ); + setTimeout(() => { + Array.from( + document.querySelector(".diaryViewerContent")?.children!, + ).forEach((element) => { + (element as HTMLElement).style.opacity = "0"; + observer.observe(element); // 요소 관찰 시작 + }); + document .querySelector(".diaryViewerContent") ?.querySelectorAll("img") .forEach((img) => { img.style.borderRadius = "1rem"; + img.style.boxShadow = + "0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)"; }); }, 100); }, []); diff --git a/src/containers/gathering/createUpdate/editor/GatheringEditorHashTagContainer.tsx b/src/containers/gathering/createUpdate/editor/GatheringEditorHashTagContainer.tsx index f6e6d4dc..a40e560b 100644 --- a/src/containers/gathering/createUpdate/editor/GatheringEditorHashTagContainer.tsx +++ b/src/containers/gathering/createUpdate/editor/GatheringEditorHashTagContainer.tsx @@ -24,6 +24,10 @@ const onChangeHashTagHandler = ( .replace(/,$/, "") .trim(); if (tempTag === "") return; + if (tempTag.length < 2) { + (inputTagRef.current as HTMLInputElement).value = ""; + return; + } setTags((prev) => Array.from(new Set([...prev, tempTag]))); (inputTagRef.current as HTMLInputElement).value = ""; formContext.setValue("hashtags", Array.from(new Set([...tags, tempTag]))); @@ -36,7 +40,11 @@ const onChangeHashTagHandler = ( const tempTag = (inputTagRef.current as HTMLInputElement).value .replace(/,$/, "") .trim(); - if (tempTag === "") return; + if (tempTag === "") return; + if (tempTag.length < 2) { + (inputTagRef.current as HTMLInputElement).value = ""; + return; + } setTags((prev) => Array.from(new Set([...prev, tempTag]))); (inputTagRef.current as HTMLInputElement).value = ""; formContext.setValue("hashtags", Array.from(new Set([...tags, tempTag]))); diff --git a/src/containers/gathering/read/GatheringCardListContainer.tsx b/src/containers/gathering/read/GatheringCardListContainer.tsx index 5985e877..392720a6 100644 --- a/src/containers/gathering/read/GatheringCardListContainer.tsx +++ b/src/containers/gathering/read/GatheringCardListContainer.tsx @@ -1,8 +1,12 @@ "use client"; +import AddUserInformationForm from "@/components/auth/AddUserInformationForm"; +import { Modal } from "@/components/common/modal/Modal"; import Pagination from "@/components/common/Pagination"; import GatheringCardList from "@/components/gathering/read/GatheringCardList"; import GatheringItemSkeleton from "@/components/skeleton/common/GatheringItemSkeleton"; +import useModalState from "@/hooks/useModalState"; +import useAuthStore from "@/store/authStore"; import { Gathering } from "@/types/GatheringDto"; import { useRouter, useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; @@ -22,7 +26,9 @@ const GatheringCardListContainer = () => { const [totalElements, setTotalElements] = useState(1); const [elements, setElements] = useState([]); const [loading, setLoading] = useState(true); + const authStore = useAuthStore(); const router = useRouter(); + const modalState = useModalState(); const [currentPage, setCurrentPage] = useState( searchParams.get("page") ? Number(searchParams.get("page")) : 1, ); @@ -35,6 +41,19 @@ const GatheringCardListContainer = () => { window.history.pushState({}, "", url.toString()); }; + const checkAccessGathering = async ( + e: React.MouseEvent, + ) => { + if (authStore.id > 0 && (!authStore.sex || !authStore.age)) { + e.preventDefault(); + modalState.openModal(); + } + if (authStore.id < 1) { + e.preventDefault(); + router.push("/auth/signin"); + } + }; + useEffect(() => { const temp = async () => { try { @@ -78,8 +97,17 @@ const GatheringCardListContainer = () => { {loading ? ( ) : ( - <> - + <> + + + + 0 + } + /> {elements.length != 0 && ( { const searchParams = useSearchParams(); + const toastifyStore = useToastifyStore(); const [searchValue, setSearchValue] = useState( searchParams.get("search") || searchParams.get("tagName") || "", ); @@ -17,7 +19,6 @@ const GatheringSearchContainer = (props: IGatheringSearchContainer) => { const url = new URL(window.location.href); const params = new URLSearchParams(url.search); if (dropdownValue == "태그") { - // 태그 검색일 경우 params.set("tagName", searchValue); params.delete("page"); url.search = params.toString(); @@ -36,6 +37,7 @@ const GatheringSearchContainer = (props: IGatheringSearchContainer) => { const dropDownHandler = (value: string) => { setDropdownValue(value); + if (value == dropdownValue) return; const url = new URL(window.location.href); const params = new URLSearchParams(url.search); params.delete("page"); @@ -47,8 +49,10 @@ const GatheringSearchContainer = (props: IGatheringSearchContainer) => { } params.delete("search"); // 태그 검색인 경우 글자 수 제한이 15글자이므로 글자를 제거해주는 작업 - setSearchValue(searchValue.trim().slice(0, 15)); - params.set("tagName", searchValue.trim().slice(0, 15) || ""); + let _text = searchValue.trim().slice(0, 15) || ""; + if (_text.length < 2) _text = ""; + setSearchValue(_text); + params.set("tagName", _text); url.search = params.toString(); window.history.pushState({}, "", url.toString()); }; diff --git a/src/containers/gathering/read/detail/GatheringSupportManagementContainer.tsx b/src/containers/gathering/read/detail/GatheringSupportManagementContainer.tsx index 496625db..ae009ea2 100644 --- a/src/containers/gathering/read/detail/GatheringSupportManagementContainer.tsx +++ b/src/containers/gathering/read/detail/GatheringSupportManagementContainer.tsx @@ -12,6 +12,11 @@ interface IGatheringSupportManagementContainer { gatheringStatus: string; isFinish: boolean; openChattingUrl: string; + allowedGender: string; + allowedAgeRange: { + startAge: number, + endAge: number, + } } const GatheringSupportManagementContainer = ( props: IGatheringSupportManagementContainer, @@ -54,9 +59,11 @@ const GatheringSupportManagementContainer = ( }); if (res.ok) { setGatheringStatus(null); - gatheringStore.setGathering({ - currentParticipants: gatheringStore.currentParticipants - 1, - }); + if(gatheringStatus == "CONSENT") { + gatheringStore.setGathering({ + currentParticipants: gatheringStore.currentParticipants - 1, + }); + } toastifyStore.setToastify({ type: "warning", message: "모임을 취소했습니다.", @@ -123,6 +130,8 @@ const GatheringSupportManagementContainer = ( isFullParticipants={ gatheringStore.personCount == gatheringStore.currentParticipants } + isAllowedGender={props.allowedGender == "ALL" || authStore.sex.toUpperCase() == props.allowedGender} + isAllowedAgeRange={authStore.age <= props.allowedAgeRange.startAge && authStore.age >= props.allowedAgeRange.endAge} /> ); }; diff --git a/src/containers/mypage/MyPageGatheringContainer.tsx b/src/containers/mypage/MyPageGatheringContainer.tsx index afaf8ef2..8341f14b 100644 --- a/src/containers/mypage/MyPageGatheringContainer.tsx +++ b/src/containers/mypage/MyPageGatheringContainer.tsx @@ -1,11 +1,15 @@ "use client" +import AddUserInformationForm from "@/components/auth/AddUserInformationForm"; import CategoryList from "@/components/common/CategoryList"; +import { Modal } from "@/components/common/modal/Modal"; import Pagination from "@/components/common/Pagination"; import MyPageGatheringList from "@/components/mypage/MyPageGatheringList"; +import useModalState from "@/hooks/useModalState"; +import useAuthStore from "@/store/authStore"; import { Gathering } from "@/types/GatheringDto"; import { fetchWithAuth } from "@/utils/fetchWithAuth"; -import { useSearchParams } from "next/navigation"; +import { useRouter, useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; interface IMyPageGatheringContainer { @@ -32,8 +36,11 @@ const MyPageGatheringContainer = (props: IMyPageGatheringContainer) => { const [activeCategory, setActiveCategory] = useState(""); const [currentPage, setCurrentPage] = useState( Number(searchParams.get("page")) || 1, - ); - const [elements, setElements] = useState([]); + ); + const router = useRouter(); + const modalState = useModalState(); + const [elements, setElements] = useState([]); + const authStore = useAuthStore(); const [totalElements, setTotalElements] = useState(0); const [isLoading, setIsLoading] = useState(false); const pageHandler = (page: number) => { @@ -55,6 +62,20 @@ const MyPageGatheringContainer = (props: IMyPageGatheringContainer) => { setCurrentPage(1); } + const checkAccessGathering = async ( + e: React.MouseEvent, + ) => { + if (authStore.id > 0 && (!authStore.sex || !authStore.age)) { + e.preventDefault(); + modalState.openModal(); + } + if (authStore.id < 1) { + e.preventDefault(); + router.push("/auth/signin"); + } + }; + + useEffect(() => { setIsLoading(true); const url = new URL(window.location.href); @@ -96,15 +117,25 @@ const MyPageGatheringContainer = (props: IMyPageGatheringContainer) => { return (
+ + + - + 0 + } + />
diff --git a/src/containers/mypage/MyPageUserImageContainer.tsx b/src/containers/mypage/MyPageUserImageContainer.tsx index a538f92b..ff3fabc3 100644 --- a/src/containers/mypage/MyPageUserImageContainer.tsx +++ b/src/containers/mypage/MyPageUserImageContainer.tsx @@ -1,6 +1,7 @@ import MyPageUserImage from "@/components/mypage/MyPageUserImage"; import { useDragAndDrop } from "@/hooks/useDragAndDrop"; import useModalState from "@/hooks/useModalState"; +import { fetchWithAuth } from "@/utils/fetchWithAuth"; import { useState } from "react"; interface IMyPageUserImageContainer { @@ -17,6 +18,16 @@ const MyPageUserImageContainer = (props: IMyPageUserImageContainer) => { setImageBase64Data(imageDataUrl); modalState.openModal(); // 이미지 편집을 위한 모달창 } + + const deleteImage = () => { + alert("개발중"); + + const response = fetchWithAuth("/api/auth/user-image", { + method: "DELETE", + "Content-Type": "application/json", + }); + + } const closeCropModal = () => { setImageBase64Data(""); @@ -51,6 +62,7 @@ const MyPageUserImageContainer = (props: IMyPageUserImageContainer) => { imageBase64Data={imageBase64Data} closeCropModal={closeCropModal} onChangeImageUrl={onChangeImageUrl} + deleteImage={deleteImage} />
); diff --git a/src/containers/mypage/MyProfileContainer.tsx b/src/containers/mypage/MyProfileContainer.tsx index aa5b3e58..4f932036 100644 --- a/src/containers/mypage/MyProfileContainer.tsx +++ b/src/containers/mypage/MyProfileContainer.tsx @@ -2,6 +2,7 @@ import MyProfile from "@/components/mypage/MyProfile"; import useModalState from "@/hooks/useModalState"; +import useAuthStore from "@/store/authStore"; import useToastifyStore from "@/store/toastifyStore"; import { userResponseDto } from "@/types/UserDto"; import { fetchWithAuth } from "@/utils/fetchWithAuth"; @@ -20,6 +21,7 @@ const MyProfileContainer = ({userInfo}: IMyProfileContainer) => { const [userDeleteText, setUserDeleteText] = useState(""); const router = useRouter(); const toastifyStore = useToastifyStore(); + const authStore = useAuthStore(); const submitChangeNicknameHandler = async () => { if (nickname == "" && nickname == defaultNickname) return; @@ -50,28 +52,33 @@ const MyProfileContainer = ({userInfo}: IMyProfileContainer) => { } const userDeleteHandler = async () => { - - const response = await fetchWithAuth("/api/auth/user", { + const response = await fetchWithAuth(`/api/auth/user?type=${userInfo.provider}`, { method: "DELETE", "Content-Type": "application/json", }); if (response.ok) { - toastifyStore.setToastify({ + modalState.closeModal(); + await toastifyStore.setToastify({ type: "success", - message: "회원탈퇴에 성공했습니다." - }) - router.push("/"); + message: "회원탈퇴에 성공했습니다.", + }); + authStore.initialize(); + setTimeout(() => { + router.replace("/"); + }, 300); } - + else { toastifyStore.setToastify({ type: "error", message: "회원탈퇴에 실패했습니다.", }); + } } return ( + <> { changeUserDeleteText={changeUserDeleteText} userDeleteText={userDeleteText} userDeleteHandler={userDeleteHandler} - /> + /> + ); }; export default MyProfileContainer;