From 48e82731522df9966f2097929a9fa6d315f9c511 Mon Sep 17 00:00:00 2001
From: rlarlduf20
Date: Sat, 13 Jan 2024 17:12:56 +0900
Subject: [PATCH 1/9] =?UTF-8?q?Feat:=20=EC=B9=9C=EA=B5=AC=20=EC=B6=94?=
=?UTF-8?q?=EA=B0=80=20=EB=B2=84=ED=8A=BC=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C?=
=?UTF-8?q?=20=EB=AA=A8=EB=8B=AC=20=EC=83=9D=EC=84=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
새로운 조각보 만들기와 동일하게 parallel routes 생성 후 intercept 하는 방식
modal route 이동에 따른 로고 텍스트 수정
---
.../{@modal => @addAlbumModal}/(.)addAlbum/page.tsx | 0
.../(main)/{@modal => @addAlbumModal}/default.tsx | 0
src/app/(main)/@addMateModal/(.)addMate/page.tsx | 12 ++++++++++++
src/app/(main)/@addMateModal/default.tsx | 5 +++++
src/app/(main)/addMate/page.tsx | 11 +++++++++++
src/app/(main)/layout.tsx | 12 +++++++++---
src/components/Modal.tsx | 2 +-
src/components/header/LogoText.tsx | 2 +-
src/templates/AddMateSection/index.tsx | 12 ++++++++++++
src/templates/MainSection/UserProfile.tsx | 4 ++++
10 files changed, 55 insertions(+), 5 deletions(-)
rename src/app/(main)/{@modal => @addAlbumModal}/(.)addAlbum/page.tsx (100%)
rename src/app/(main)/{@modal => @addAlbumModal}/default.tsx (100%)
create mode 100644 src/app/(main)/@addMateModal/(.)addMate/page.tsx
create mode 100644 src/app/(main)/@addMateModal/default.tsx
create mode 100644 src/app/(main)/addMate/page.tsx
create mode 100644 src/templates/AddMateSection/index.tsx
diff --git a/src/app/(main)/@modal/(.)addAlbum/page.tsx b/src/app/(main)/@addAlbumModal/(.)addAlbum/page.tsx
similarity index 100%
rename from src/app/(main)/@modal/(.)addAlbum/page.tsx
rename to src/app/(main)/@addAlbumModal/(.)addAlbum/page.tsx
diff --git a/src/app/(main)/@modal/default.tsx b/src/app/(main)/@addAlbumModal/default.tsx
similarity index 100%
rename from src/app/(main)/@modal/default.tsx
rename to src/app/(main)/@addAlbumModal/default.tsx
diff --git a/src/app/(main)/@addMateModal/(.)addMate/page.tsx b/src/app/(main)/@addMateModal/(.)addMate/page.tsx
new file mode 100644
index 0000000..2eff91e
--- /dev/null
+++ b/src/app/(main)/@addMateModal/(.)addMate/page.tsx
@@ -0,0 +1,12 @@
+import Modal from "@/components/Modal";
+import AddMateSection from "@/templates/AddMateSection";
+
+const AddMateModalPage = () => {
+ return (
+
+
+
+ );
+};
+
+export default AddMateModalPage;
diff --git a/src/app/(main)/@addMateModal/default.tsx b/src/app/(main)/@addMateModal/default.tsx
new file mode 100644
index 0000000..395785b
--- /dev/null
+++ b/src/app/(main)/@addMateModal/default.tsx
@@ -0,0 +1,5 @@
+const Default = () => {
+ return null;
+};
+
+export default Default;
diff --git a/src/app/(main)/addMate/page.tsx b/src/app/(main)/addMate/page.tsx
new file mode 100644
index 0000000..86a8565
--- /dev/null
+++ b/src/app/(main)/addMate/page.tsx
@@ -0,0 +1,11 @@
+import AddMateSection from "@/templates/AddMateSection";
+
+const AddMatePage = () => {
+ return (
+
+ );
+};
+
+export default AddMatePage;
diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx
index ba67da2..b103f18 100644
--- a/src/app/(main)/layout.tsx
+++ b/src/app/(main)/layout.tsx
@@ -12,10 +12,15 @@ export const metadata: Metadata = {
interface RootLayoutProps {
children: React.ReactNode;
- modal: React.ReactNode;
+ addAlbumModal: React.ReactNode;
+ addMateModal: React.ReactNode;
}
-const RootLayout = async ({ children, modal }: RootLayoutProps) => {
+const RootLayout = async ({
+ children,
+ addAlbumModal,
+ addMateModal,
+}: RootLayoutProps) => {
return (
@@ -23,7 +28,8 @@ const RootLayout = async ({ children, modal }: RootLayoutProps) => {
{children}
- {modal}
+ {addAlbumModal}
+ {addMateModal}
diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx
index 0b6c9c6..12c0722 100644
--- a/src/components/Modal.tsx
+++ b/src/components/Modal.tsx
@@ -11,7 +11,7 @@ const Modal = ({ children }: ModalProps) => {
const router = useRouter();
const pathname = usePathname();
- if (!pathname.includes("addAlbum")) {
+ if (!pathname.includes("addAlbum") && !pathname.includes("addMate")) {
return null;
}
const handleOnOpenChange = (open: boolean) => {
diff --git a/src/components/header/LogoText.tsx b/src/components/header/LogoText.tsx
index 7f68028..b0d0c54 100644
--- a/src/components/header/LogoText.tsx
+++ b/src/components/header/LogoText.tsx
@@ -7,7 +7,7 @@ const LogoText = () => {
return (
- {pathname === "/" ? "내 조각보" : "둘러보기"}
+ {pathname === "/browse" ? "둘러보기" : "조각보"}
);
};
diff --git a/src/templates/AddMateSection/index.tsx b/src/templates/AddMateSection/index.tsx
new file mode 100644
index 0000000..9b076b2
--- /dev/null
+++ b/src/templates/AddMateSection/index.tsx
@@ -0,0 +1,12 @@
+const AddMateSection = () => {
+ return (
+
+
+
+ );
+};
+
+export default AddMateSection;
diff --git a/src/templates/MainSection/UserProfile.tsx b/src/templates/MainSection/UserProfile.tsx
index 88080ec..e53ea2a 100644
--- a/src/templates/MainSection/UserProfile.tsx
+++ b/src/templates/MainSection/UserProfile.tsx
@@ -1,3 +1,4 @@
+import Link from "next/link";
import Trapezoid from "@/components/Trapezoid";
import SignOutButton from "@/components/SignOutButton";
import type { UserType } from "@/types";
@@ -40,6 +41,9 @@ const UserProfile = ({ user }: UserProfileProps) => {
회원정보 수정
+
+ 친구 만들기
+
);
From be651a8e61222be904c490844178995034f85e61 Mon Sep 17 00:00:00 2001
From: rlarlduf20
Date: Sat, 13 Jan 2024 19:36:03 +0900
Subject: [PATCH 2/9] =?UTF-8?q?Feat:=20=EC=B9=9C=EA=B5=AC=EC=B6=94?=
=?UTF-8?q?=EA=B0=80=20Input=20UI=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/header/LogoText.tsx | 2 +-
src/components/header/NavLink.tsx | 6 +++---
src/templates/AddMateSection/index.tsx | 11 +++++++++--
3 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/src/components/header/LogoText.tsx b/src/components/header/LogoText.tsx
index b0d0c54..15b72c6 100644
--- a/src/components/header/LogoText.tsx
+++ b/src/components/header/LogoText.tsx
@@ -7,7 +7,7 @@ const LogoText = () => {
return (
- {pathname === "/browse" ? "둘러보기" : "조각보"}
+ {pathname === "/browse" ? "둘러보기" : "내 조각보"}
);
};
diff --git a/src/components/header/NavLink.tsx b/src/components/header/NavLink.tsx
index 199799a..cbdf5eb 100644
--- a/src/components/header/NavLink.tsx
+++ b/src/components/header/NavLink.tsx
@@ -8,10 +8,10 @@ const NavLink = () => {
return (
<>
- {pathname === "/" ? (
- 둘러보기
- ) : (
+ {pathname === "/browse" ? (
내 조각보
+ ) : (
+ 둘러보기
)}
>
);
diff --git a/src/templates/AddMateSection/index.tsx b/src/templates/AddMateSection/index.tsx
index 9b076b2..5c2e20c 100644
--- a/src/templates/AddMateSection/index.tsx
+++ b/src/templates/AddMateSection/index.tsx
@@ -1,10 +1,17 @@
const AddMateSection = () => {
return (
);
};
From f474f6a89f26a97b69bd558dd49eca70953cb752 Mon Sep 17 00:00:00 2001
From: rlarlduf20
Date: Sat, 13 Jan 2024 20:23:11 +0900
Subject: [PATCH 3/9] =?UTF-8?q?Feat:=20useDebounce=20hook=20=EC=83=9D?=
=?UTF-8?q?=EC=84=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
친구 추가 시 닉네임을 입력할 때 잦은 요청 방지를 위한 useDebounce hook 생성
---
src/hooks/useDebounce.tsx | 17 ++++++++++++
src/templates/AddMateSection/SearchBox.tsx | 31 ++++++++++++++++++++++
src/templates/AddMateSection/index.tsx | 12 +++------
3 files changed, 52 insertions(+), 8 deletions(-)
create mode 100644 src/hooks/useDebounce.tsx
create mode 100644 src/templates/AddMateSection/SearchBox.tsx
diff --git a/src/hooks/useDebounce.tsx b/src/hooks/useDebounce.tsx
new file mode 100644
index 0000000..431ef68
--- /dev/null
+++ b/src/hooks/useDebounce.tsx
@@ -0,0 +1,17 @@
+import { useState, useEffect } from "react";
+
+const useDebounce = (value: string, delay: number) => {
+ const [debouncedValue, setDebouncedValue] = useState(value);
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ setDebouncedValue(value);
+ }, delay);
+
+ return () => clearTimeout(timer);
+ }, [value, delay]);
+
+ return debouncedValue;
+};
+
+export default useDebounce;
diff --git a/src/templates/AddMateSection/SearchBox.tsx b/src/templates/AddMateSection/SearchBox.tsx
new file mode 100644
index 0000000..899327e
--- /dev/null
+++ b/src/templates/AddMateSection/SearchBox.tsx
@@ -0,0 +1,31 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import useDebounce from "@/hooks/useDebounce";
+
+const SearchBox = () => {
+ const [searchText, setSearchText] = useState("");
+ const debouncedSearchText = useDebounce(searchText, 500);
+ const handleSearch = (e: React.ChangeEvent) => {
+ setSearchText(e.target.value);
+ };
+
+ useEffect(() => {
+ console.log("debounced", debouncedSearchText);
+ }, [debouncedSearchText]);
+
+ return (
+
+
+
+ );
+};
+
+export default SearchBox;
diff --git a/src/templates/AddMateSection/index.tsx b/src/templates/AddMateSection/index.tsx
index 5c2e20c..b5cd7dd 100644
--- a/src/templates/AddMateSection/index.tsx
+++ b/src/templates/AddMateSection/index.tsx
@@ -1,17 +1,13 @@
+import SearchBox from "./SearchBox";
+
const AddMateSection = () => {
return (
);
};
From 711eacba37b98a87fa7bcccfcc6e1b82860e6976 Mon Sep 17 00:00:00 2001
From: rlarlduf20
Date: Tue, 16 Jan 2024 15:03:43 +0900
Subject: [PATCH 4/9] =?UTF-8?q?Feat:=20=EC=9C=A0=EC=A0=80=20=EC=9D=B4?=
=?UTF-8?q?=EB=A6=84=20=EA=B2=80=EC=83=89=20=EC=8B=9C=20api=20response?=
=?UTF-8?q?=EA=B0=92=EC=9C=BC=EB=A1=9C=20=ED=95=B4=EB=8B=B9=20=EC=9C=A0?=
=?UTF-8?q?=EC=A0=80=20=EB=B0=9B=EA=B8=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/app/api/searchUser/route.ts | 20 +++++++++++
src/templates/AddMateSection/SearchBox.tsx | 40 ++++++++++++++++++++--
src/types/index.ts | 6 ++++
3 files changed, 64 insertions(+), 2 deletions(-)
create mode 100644 src/app/api/searchUser/route.ts
diff --git a/src/app/api/searchUser/route.ts b/src/app/api/searchUser/route.ts
new file mode 100644
index 0000000..17e31c8
--- /dev/null
+++ b/src/app/api/searchUser/route.ts
@@ -0,0 +1,20 @@
+import { getServerSession } from "next-auth";
+import { authOptions } from "../auth/[...nextauth]/route";
+
+export async function POST(request: Request) {
+ const { jogakTokens } = await getServerSession(authOptions);
+
+ const { debouncedSearchText } = await request.json();
+ console.log(debouncedSearchText);
+ const res = await fetch(
+ `${process.env.SERVER_URL}/user/search?nickname=${debouncedSearchText}`,
+ {
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${jogakTokens.accessToken}`,
+ },
+ }
+ );
+
+ return res;
+}
diff --git a/src/templates/AddMateSection/SearchBox.tsx b/src/templates/AddMateSection/SearchBox.tsx
index 899327e..98f39bd 100644
--- a/src/templates/AddMateSection/SearchBox.tsx
+++ b/src/templates/AddMateSection/SearchBox.tsx
@@ -2,16 +2,29 @@
import { useEffect, useState } from "react";
import useDebounce from "@/hooks/useDebounce";
+import type { SearchUsersType } from "@/types";
+import Trapezoid from "@/components/Trapezoid";
const SearchBox = () => {
const [searchText, setSearchText] = useState("");
+ const [searchedUser, setSearchedUser] = useState([]);
const debouncedSearchText = useDebounce(searchText, 500);
const handleSearch = (e: React.ChangeEvent) => {
setSearchText(e.target.value);
};
useEffect(() => {
- console.log("debounced", debouncedSearchText);
+ const getSearchedUserList = async () => {
+ const res = await fetch("api/searchUser", {
+ method: "POST",
+ body: JSON.stringify({
+ debouncedSearchText,
+ }),
+ });
+ const data = await res.json();
+ setSearchedUser(data);
+ };
+ getSearchedUserList();
}, [debouncedSearchText]);
return (
@@ -21,9 +34,32 @@ const SearchBox = () => {
value={searchText}
onChange={handleSearch}
className="block pl-[6px] pb-[6px] bg-main_black w-[200px]
- border-b-[1px] border-white text-[18px]
+ border-b-[1px] border-white text-[18px] mb-[50px]
outline-none"
/>
+ {!searchText ? (
+ 친구를 추가하고 함께 조각을 완성해봐요.
+ ) : searchedUser.length === 0 ? (
+ 일치하는 유저가 없습니다.
+ ) : (
+
+ {searchedUser.map((item, index) => (
+
+
+
{item.nickname}
+
초대
+
+ ))}
+
+ )}
);
};
diff --git a/src/types/index.ts b/src/types/index.ts
index deb4f91..54f200c 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -30,3 +30,9 @@ export interface AlbumsType {
images: ImageType[];
albumEditors: any;
}
+
+export interface SearchUsersType {
+ nickname: string;
+ socialId: string;
+ profileImageUrl: string | null;
+}
From 1a9acadfba693afd73dc7be107e0bb508f7670db Mon Sep 17 00:00:00 2001
From: rlarlduf20
Date: Tue, 16 Jan 2024 15:16:12 +0900
Subject: [PATCH 5/9] =?UTF-8?q?Feat:=20=EA=B3=A0=EC=9C=A0ID=20=EC=B6=94?=
=?UTF-8?q?=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/templates/AddMateSection/SearchBox.tsx | 7 ++++++-
src/types/index.ts | 2 +-
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/templates/AddMateSection/SearchBox.tsx b/src/templates/AddMateSection/SearchBox.tsx
index 98f39bd..b0c8744 100644
--- a/src/templates/AddMateSection/SearchBox.tsx
+++ b/src/templates/AddMateSection/SearchBox.tsx
@@ -54,7 +54,12 @@ const SearchBox = () => {
bgColor: "white",
}}
/>
- {item.nickname}
+
+
{item.nickname}
+
+ #{item.socialID.slice(0, 6)}
+
+
초대
))}
diff --git a/src/types/index.ts b/src/types/index.ts
index 54f200c..0c859fd 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -33,6 +33,6 @@ export interface AlbumsType {
export interface SearchUsersType {
nickname: string;
- socialId: string;
+ socialID: string;
profileImageUrl: string | null;
}
From 96574297d130a33811c4181b55f1f9acfa66b0ce Mon Sep 17 00:00:00 2001
From: rlarlduf20
Date: Tue, 16 Jan 2024 16:15:06 +0900
Subject: [PATCH 6/9] =?UTF-8?q?Feat:=20=EC=95=8C=EB=A6=BC=EC=B0=BD=20?=
=?UTF-8?q?=EB=93=9C=EB=A1=AD=EB=8B=A4=EC=9A=B4=20UI=20=EC=83=9D=EC=84=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
알림창을 한번 더 클릭 혹은 외부 영역 클릭 시 드롭다운 닫힘
---
src/app/api/searchUser/route.ts | 1 -
src/components/Notification.tsx | 25 +++++++++++++++++++++++
src/hooks/useMouseDownOutside.tsx | 22 ++++++++++++++++++++
src/templates/MainSection/UserProfile.tsx | 2 ++
4 files changed, 49 insertions(+), 1 deletion(-)
create mode 100644 src/components/Notification.tsx
create mode 100644 src/hooks/useMouseDownOutside.tsx
diff --git a/src/app/api/searchUser/route.ts b/src/app/api/searchUser/route.ts
index 17e31c8..4e3a3ae 100644
--- a/src/app/api/searchUser/route.ts
+++ b/src/app/api/searchUser/route.ts
@@ -5,7 +5,6 @@ export async function POST(request: Request) {
const { jogakTokens } = await getServerSession(authOptions);
const { debouncedSearchText } = await request.json();
- console.log(debouncedSearchText);
const res = await fetch(
`${process.env.SERVER_URL}/user/search?nickname=${debouncedSearchText}`,
{
diff --git a/src/components/Notification.tsx b/src/components/Notification.tsx
new file mode 100644
index 0000000..9acacb1
--- /dev/null
+++ b/src/components/Notification.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import useMouseDownOutside from "@/hooks/useMouseDownOutside";
+import { useRef } from "react";
+
+const Notification = () => {
+ const notificationRef = useRef();
+ const { isOpen, setIsOpen } = useMouseDownOutside(notificationRef);
+
+ return (
+
+ setIsOpen((prev) => !prev)}
+ >
+ 알림
+
+ {isOpen && (
+
+ )}
+
+ );
+};
+
+export default Notification;
diff --git a/src/hooks/useMouseDownOutside.tsx b/src/hooks/useMouseDownOutside.tsx
new file mode 100644
index 0000000..c44ca5f
--- /dev/null
+++ b/src/hooks/useMouseDownOutside.tsx
@@ -0,0 +1,22 @@
+import { useState, useEffect } from "react";
+
+const useMouseDownOutside = (ref: any) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ useEffect(() => {
+ const outsideMouseDown = (e: any) => {
+ if (isOpen && ref.current && !ref.current.contains(e.target)) {
+ setIsOpen(false);
+ }
+ };
+ document.addEventListener("mousedown", outsideMouseDown);
+ console.log(isOpen);
+ return () => {
+ document.removeEventListener("mousedown", outsideMouseDown);
+ };
+ }, [isOpen, ref]);
+
+ return { isOpen, setIsOpen };
+};
+
+export default useMouseDownOutside;
diff --git a/src/templates/MainSection/UserProfile.tsx b/src/templates/MainSection/UserProfile.tsx
index e53ea2a..61430e8 100644
--- a/src/templates/MainSection/UserProfile.tsx
+++ b/src/templates/MainSection/UserProfile.tsx
@@ -1,6 +1,7 @@
import Link from "next/link";
import Trapezoid from "@/components/Trapezoid";
import SignOutButton from "@/components/SignOutButton";
+import Notification from "@/components/Notification";
import type { UserType } from "@/types";
interface UserProfileProps {
@@ -44,6 +45,7 @@ const UserProfile = ({ user }: UserProfileProps) => {
친구 만들기
+
);
From cbff9f6aa86aaf7b37b9bbff394166c6eef3406e Mon Sep 17 00:00:00 2001
From: rlarlduf20
Date: Tue, 16 Jan 2024 16:52:03 +0900
Subject: [PATCH 7/9] =?UTF-8?q?Feat:=20=EC=B4=88=EB=8C=80=20=EB=B2=84?=
=?UTF-8?q?=ED=8A=BC=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20api=20=EC=9A=94?=
=?UTF-8?q?=EC=B2=AD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/app/api/inviteUser/route.ts | 21 +++++++++++++++++++++
src/templates/AddMateSection/SearchBox.tsx | 18 +++++++++++++++++-
2 files changed, 38 insertions(+), 1 deletion(-)
create mode 100644 src/app/api/inviteUser/route.ts
diff --git a/src/app/api/inviteUser/route.ts b/src/app/api/inviteUser/route.ts
new file mode 100644
index 0000000..3c8f5db
--- /dev/null
+++ b/src/app/api/inviteUser/route.ts
@@ -0,0 +1,21 @@
+import { getServerSession } from "next-auth";
+import { authOptions } from "../auth/[...nextauth]/route";
+
+export async function POST(request: Request) {
+ const { jogakTokens } = await getServerSession(authOptions);
+
+ const { userID } = await request.json();
+ console.log(userID);
+ const res = await fetch(
+ `${process.env.SERVER_URL}/user/friend?socialID=${userID}`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${jogakTokens.accessToken}`,
+ },
+ }
+ );
+ console.log(res);
+ return res;
+}
diff --git a/src/templates/AddMateSection/SearchBox.tsx b/src/templates/AddMateSection/SearchBox.tsx
index b0c8744..6fc9abf 100644
--- a/src/templates/AddMateSection/SearchBox.tsx
+++ b/src/templates/AddMateSection/SearchBox.tsx
@@ -12,6 +12,20 @@ const SearchBox = () => {
const handleSearch = (e: React.ChangeEvent) => {
setSearchText(e.target.value);
};
+ const handleInvite = async (userID: string) => {
+ const res = await fetch("api/inviteUser", {
+ method: "POST",
+ body: JSON.stringify({
+ userID,
+ }),
+ });
+ if (!res.ok) {
+ alert("요청에 실패했습니다. 다시 시도해주세요.");
+ return;
+ }
+
+ alert("초대 메시지가 발송됐습니다.");
+ };
useEffect(() => {
const getSearchedUserList = async () => {
@@ -60,7 +74,9 @@ const SearchBox = () => {
#{item.socialID.slice(0, 6)}
- 초대
+ handleInvite(item.socialID)}>
+
초대
+
))}
From 627f26ee47c45a8e2afb771b46d495e97f549a8a Mon Sep 17 00:00:00 2001
From: rlarlduf20
Date: Tue, 16 Jan 2024 17:34:19 +0900
Subject: [PATCH 8/9] =?UTF-8?q?Chore:=20sse=20=EC=84=A4=EC=A0=95=EC=9D=84?=
=?UTF-8?q?=20=EC=9C=84=ED=95=9C=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC?=
=?UTF-8?q?=EB=A6=AC=20=EC=84=A4=EC=B9=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package-lock.json | 6 ++++++
package.json | 1 +
src/components/Notification.tsx | 9 +++++++--
src/templates/MainSection/UserProfile.tsx | 2 +-
4 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 427ea41..e1929af 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,7 @@
"autoprefixer": "10.4.16",
"eslint": "8.49.0",
"eslint-config-next": "13.5.2",
+ "event-source-polyfill": "^1.0.31",
"jsonwebtoken": "^9.0.2",
"konva": "^9.2.3",
"next": "^13.5.4",
@@ -2090,6 +2091,11 @@
"node": ">=0.10.0"
}
},
+ "node_modules/event-source-polyfill": {
+ "version": "1.0.31",
+ "resolved": "https://registry.npmjs.org/event-source-polyfill/-/event-source-polyfill-1.0.31.tgz",
+ "integrity": "sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA=="
+ },
"node_modules/eventsource": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz",
diff --git a/package.json b/package.json
index 962405b..de98cd3 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"autoprefixer": "10.4.16",
"eslint": "8.49.0",
"eslint-config-next": "13.5.2",
+ "event-source-polyfill": "^1.0.31",
"jsonwebtoken": "^9.0.2",
"konva": "^9.2.3",
"next": "^13.5.4",
diff --git a/src/components/Notification.tsx b/src/components/Notification.tsx
index 9acacb1..4565584 100644
--- a/src/components/Notification.tsx
+++ b/src/components/Notification.tsx
@@ -1,12 +1,17 @@
"use client";
import useMouseDownOutside from "@/hooks/useMouseDownOutside";
-import { useRef } from "react";
+import { useEffect, useRef } from "react";
const Notification = () => {
const notificationRef = useRef();
const { isOpen, setIsOpen } = useMouseDownOutside(notificationRef);
-
+ useEffect(() => {
+ console.log("connect");
+ return () => {
+ console.log("decon");
+ };
+ }, []);
return (
{
회원정보 수정
-
+
친구 만들기
From 3818384b1426fd9996a1d73663257fcc98a68798 Mon Sep 17 00:00:00 2001
From: rlarlduf20
Date: Wed, 17 Jan 2024 22:57:27 +0900
Subject: [PATCH 9/9] =?UTF-8?q?Feat:=20=EC=B4=88=EB=8C=80=20=EC=8B=9C=20?=
=?UTF-8?q?=ED=91=B8=EC=89=AC=20=EC=95=8C=EB=A6=BC=20=EA=B8=B0=EB=8A=A5=20?=
=?UTF-8?q?=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
server side event를 활용해 받은 메시지 popup에 띄우고 알림창에 추가
인터렉션이 많은 부분이기 때문에 client side fetching으로 변경
---
package-lock.json | 7 ++
package.json | 1 +
src/app/api/inviteUser/route.ts | 5 +-
src/app/api/replyMate/route.ts | 21 ++++
src/components/Notification.tsx | 133 ++++++++++++++++++++--
src/components/header/MainNav.tsx | 2 +
src/hooks/useMouseDownOutside.tsx | 1 -
src/hooks/usePushNotification.tsx | 47 ++++++++
src/templates/MainSection/UserProfile.tsx | 2 -
src/types/index.ts | 4 +-
10 files changed, 207 insertions(+), 16 deletions(-)
create mode 100644 src/app/api/replyMate/route.ts
create mode 100644 src/hooks/usePushNotification.tsx
diff --git a/package-lock.json b/package-lock.json
index e1929af..a785449 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -33,6 +33,7 @@
"ws": "^8.14.2"
},
"devDependencies": {
+ "@types/event-source-polyfill": "^1.0.5",
"@types/jsonwebtoken": "^9.0.3",
"@types/sockjs-client": "^1.5.4",
"@types/ws": "^8.5.10"
@@ -722,6 +723,12 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@types/event-source-polyfill": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/event-source-polyfill/-/event-source-polyfill-1.0.5.tgz",
+ "integrity": "sha512-iaiDuDI2aIFft7XkcwMzDWLqo7LVDixd2sR6B4wxJut9xcp/Ev9bO4EFg4rm6S9QxATLBj5OPxdeocgmhjwKaw==",
+ "dev": true
+ },
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
diff --git a/package.json b/package.json
index de98cd3..9b6583d 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
"ws": "^8.14.2"
},
"devDependencies": {
+ "@types/event-source-polyfill": "^1.0.5",
"@types/jsonwebtoken": "^9.0.3",
"@types/sockjs-client": "^1.5.4",
"@types/ws": "^8.5.10"
diff --git a/src/app/api/inviteUser/route.ts b/src/app/api/inviteUser/route.ts
index 3c8f5db..adeff01 100644
--- a/src/app/api/inviteUser/route.ts
+++ b/src/app/api/inviteUser/route.ts
@@ -3,9 +3,8 @@ import { authOptions } from "../auth/[...nextauth]/route";
export async function POST(request: Request) {
const { jogakTokens } = await getServerSession(authOptions);
-
const { userID } = await request.json();
- console.log(userID);
+
const res = await fetch(
`${process.env.SERVER_URL}/user/friend?socialID=${userID}`,
{
@@ -16,6 +15,6 @@ export async function POST(request: Request) {
},
}
);
- console.log(res);
+
return res;
}
diff --git a/src/app/api/replyMate/route.ts b/src/app/api/replyMate/route.ts
new file mode 100644
index 0000000..d3ce5e5
--- /dev/null
+++ b/src/app/api/replyMate/route.ts
@@ -0,0 +1,21 @@
+import { getServerSession } from "next-auth";
+import { authOptions } from "../auth/[...nextauth]/route";
+
+export async function POST(request: Request) {
+ const { jogakTokens } = await getServerSession(authOptions);
+
+ const { responseType, userID } = await request.json();
+
+ const res = await fetch(
+ `${process.env.SERVER_URL}/user/friend-reply?socialID=${userID}&reply=${responseType}`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${jogakTokens.accessToken}`,
+ },
+ }
+ );
+
+ return res;
+}
diff --git a/src/components/Notification.tsx b/src/components/Notification.tsx
index 4565584..0563e05 100644
--- a/src/components/Notification.tsx
+++ b/src/components/Notification.tsx
@@ -1,27 +1,142 @@
"use client";
import useMouseDownOutside from "@/hooks/useMouseDownOutside";
-import { useEffect, useRef } from "react";
+import usePushNotification from "@/hooks/usePushNotification";
+import { useEffect, useRef, useState } from "react";
+import type { FriendsType } from "@/types";
+import { useSession } from "next-auth/react";
+
+interface PushNotiPropsType {
+ info: FriendsType | any;
+ handleResponse: (r: string, u: string, n: string) => void;
+ setIsAppear?: any;
+ handleFilterPushMsg: (u: string) => void;
+}
+
+const PushNoti = ({
+ info,
+ handleResponse,
+ setIsAppear,
+ handleFilterPushMsg,
+}: PushNotiPropsType) => {
+ return (
+
+
{info?.nickname}님이 친구 요청을 보냈습니다.
+
+
+
+
+
+ );
+};
const Notification = () => {
- const notificationRef = useRef();
+ const notificationRef = useRef(null);
const { isOpen, setIsOpen } = useMouseDownOutside(notificationRef);
+ const { pushMsg, isAppear, setIsAppear } = usePushNotification();
+ const [receivedReq, setReceivedReq] = useState([]);
+ const { data: session } = useSession();
+
+ const handleResponse = async (
+ responseType: string,
+ userID: string,
+ nickname: string
+ ) => {
+ const res = await fetch("api/replyMate", {
+ method: "POST",
+ body: JSON.stringify({
+ userID,
+ responseType,
+ }),
+ });
+ if (res.ok) {
+ if (responseType === "reject") alert("거절하셨습니다.");
+ if (responseType === "accept")
+ alert(`${nickname}님과 친구가 되었습니다.`);
+ }
+ };
+ const handleFilterPushMsg = (userID: string) => {
+ const filteredReceivedReq = receivedReq.filter((item) => {
+ return item.socialID !== userID;
+ });
+ setReceivedReq(filteredReceivedReq);
+ };
+
useEffect(() => {
- console.log("connect");
- return () => {
- console.log("decon");
+ const getReceivedReq = async () => {
+ const res = await fetch(
+ `${process.env.NEXT_PUBLIC_SERVER_URL}/user/profile`,
+ {
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${session?.jogakTokens.accessToken}`,
+ },
+ }
+ );
+ const data = await res.json();
+ setReceivedReq(data.receivedFriendRequest);
};
- }, []);
+ if (session?.jogakTokens.accessToken) {
+ getReceivedReq();
+ }
+ if (pushMsg) {
+ setReceivedReq((prev: any) => {
+ return [...prev, pushMsg];
+ });
+ }
+ }, [session?.jogakTokens.accessToken, pushMsg]);
+
return (
setIsOpen((prev) => !prev)}
>
- 알림
+
알림
+
+ {receivedReq.length}
+
{isOpen && (
-
+
+ {receivedReq.map((item, index) => (
+
+ ))}
+
+ )}
+ {isAppear && (
+
)}
);
diff --git a/src/components/header/MainNav.tsx b/src/components/header/MainNav.tsx
index a2385ee..2dee9a7 100644
--- a/src/components/header/MainNav.tsx
+++ b/src/components/header/MainNav.tsx
@@ -3,12 +3,14 @@ import LogoText from "./LogoText";
import NavLink from "./NavLink";
import HomeLogoIcon from "../../../public/images/svg/home-logo.svg";
import RouteTrapezoidIcon from "../../../public/images/svg/route-trapezoid.svg";
+import Notification from "../Notification";
const MainNav = () => {
return (