diff --git a/package-lock.json b/package-lock.json
index 427ea41..a785449 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",
@@ -32,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"
@@ -721,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",
@@ -2090,6 +2098,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..9b6583d 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",
@@ -33,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/(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/app/api/inviteUser/route.ts b/src/app/api/inviteUser/route.ts
new file mode 100644
index 0000000..adeff01
--- /dev/null
+++ b/src/app/api/inviteUser/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 { userID } = await request.json();
+
+ const res = await fetch(
+ `${process.env.SERVER_URL}/user/friend?socialID=${userID}`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${jogakTokens.accessToken}`,
+ },
+ }
+ );
+
+ 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/app/api/searchUser/route.ts b/src/app/api/searchUser/route.ts
new file mode 100644
index 0000000..4e3a3ae
--- /dev/null
+++ b/src/app/api/searchUser/route.ts
@@ -0,0 +1,19 @@
+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();
+ 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/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/Notification.tsx b/src/components/Notification.tsx
new file mode 100644
index 0000000..0563e05
--- /dev/null
+++ b/src/components/Notification.tsx
@@ -0,0 +1,145 @@
+"use client";
+
+import useMouseDownOutside from "@/hooks/useMouseDownOutside";
+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(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(() => {
+ 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 && (
+
+ )}
+
+ );
+};
+
+export default Notification;
diff --git a/src/components/header/LogoText.tsx b/src/components/header/LogoText.tsx
index 7f68028..15b72c6 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/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 (