diff --git a/aeye/app/(nav)/cams/page.tsx b/aeye/app/(nav)/cams/page.tsx index 38c6723..f1653ae 100644 --- a/aeye/app/(nav)/cams/page.tsx +++ b/aeye/app/(nav)/cams/page.tsx @@ -1,27 +1,16 @@ "use client"; import { useEffect } from "react"; +import fetchWithInterception from "@/app/fetchWrapper"; export default function Cams() { - const fetchVids = async () => { - try { - const res = await fetch("https://api.a-eye.live/video", { - method: "GET", - headers: { - Authorization: `Bearer ${localStorage.getItem("accessToken")}`, - }, - }); - if (res.ok) { - const jsonData = await res.json(); - console.log(jsonData.data); - } - } catch (err) { - console.error(err); - } - }; - useEffect(() => { - fetchVids(); + fetchWithInterception("https://api.a-eye.live/video", { + method: "GET", + }) + .then((response) => response.json()) + .then((jsonData) => console.log(jsonData.data)) + .catch((error) => console.error(error)); }, []); return
; } diff --git a/aeye/app/(nav)/home/SearchResult.tsx b/aeye/app/(nav)/home/SearchResult.tsx index cf8eccd..ce1cc58 100644 --- a/aeye/app/(nav)/home/SearchResult.tsx +++ b/aeye/app/(nav)/home/SearchResult.tsx @@ -4,6 +4,9 @@ import { searchQueryState } from "@/app/recoil-states"; import { useEffect, useState } from "react"; import Vidpane from "@/app/components/Vidpane"; import { Grid, Paper, Typography } from "@mui/material"; +import fetchWithInterception from "@/app/fetchWrapper"; + +const FETCH_TIMEOUT = 200; function Vidgroup({ videos }: { videos: VideoDocument[] }) { return ( @@ -22,39 +25,30 @@ function Vidgroup({ videos }: { videos: VideoDocument[] }) { export default function SearchResult() { const searchQuery = useRecoilValue(searchQueryState); const [results, setResults] = useState(); - const [searchTimeout, setSearchTimeout] = useState( - null - ); - - const fetchResults = async () => { - const res = await fetch( - `https://api.a-eye.live/video/search?keyword=${searchQuery}`, - { - method: "GET", - headers: { - Authorization: `Bearer ${localStorage.getItem("accessToken")}`, - }, - } - ); - if (res.ok) { - const jsonData = await res.json(); - console.log(jsonData.data); - setResults(jsonData.data); - } - }; useEffect(() => { - if (searchTimeout) { - clearTimeout(searchTimeout); - } + let searchTimeout: NodeJS.Timeout; - setSearchTimeout(setTimeout(fetchResults, 200)); - - return () => { - if (searchTimeout) { - clearTimeout(searchTimeout); - } + const fetchResults = () => { + fetchWithInterception( + `https://api.a-eye.live/video/search?keyword=${searchQuery}`, + { method: "GET" } + ) + .then((response) => { + if (!response.ok) { + throw new Error("Network response was not ok"); + } + return response.json(); + }) + .then((jsonData) => setResults(jsonData.data)) + .catch((error) => console.error(error)); }; + + if (searchQuery) { + searchTimeout = setTimeout(fetchResults, FETCH_TIMEOUT); + } + + return () => clearTimeout(searchTimeout); }, [searchQuery]); return ( diff --git a/aeye/app/(nav)/my/page.tsx b/aeye/app/(nav)/my/page.tsx index 718571c..f26310f 100644 --- a/aeye/app/(nav)/my/page.tsx +++ b/aeye/app/(nav)/my/page.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react"; import { styled } from "@mui/material/styles"; import { Box, Grid, Paper, Avatar, Tooltip, Typography } from "@mui/material"; +import fetchWithInterception from "@/app/fetchWrapper"; const StyledPaper = styled(Paper)(({ theme }) => ({ backgroundColor: theme.palette.mode === "dark" ? "#1A2027" : "#fff", @@ -26,23 +27,12 @@ function ProfileAvatar() { const [member, setMember] = useState(); useEffect(() => { - const fetchMember = async () => { - try { - const res = await fetch("https://api.a-eye.live/member/detail", { - method: "GET", - headers: { - Authorization: `Bearer ${localStorage.getItem("accessToken")}`, - }, - }); - if (res.ok) { - const jsonData = await res.json(); - setMember(jsonData.data); - } - } catch (err) { - console.log(err); - } - }; - fetchMember(); + fetchWithInterception("https://api.a-eye.live/member/detail", { + method: "GET", + }) + .then((response) => response.json()) + .then((jsonData) => setMember(jsonData.data)) + .catch((error) => console.error(error)); }, []); return ( diff --git a/aeye/app/fetchWrapper.ts b/aeye/app/fetchWrapper.ts new file mode 100644 index 0000000..dd8f1f7 --- /dev/null +++ b/aeye/app/fetchWrapper.ts @@ -0,0 +1,33 @@ +// Example usage: +// fetchWithInterception(apiUrl, { method: 'GET' }) +// .then(response => response.json()) +// .then(data => console.log(data)) +// .catch(error => console.error(error)); + +const fetchWithInterception = async ( + url: string, + options?: RequestInit +): Promise => { + const modifiedOptions = { ...options }; + modifiedOptions.headers = { + ...modifiedOptions.headers, + Authorization: `Bearer ${localStorage.getItem("accessToken")}`, + }; + return fetch(url, modifiedOptions) + .then(async (response: Response) => { + if (!response.ok) { + if (response.status === 401) { + alert("토근이 만료되었습니다. 다시 로그인해주세요."); + window.location.href = "/"; + } + throw new Error("Request failed"); + } + return response; + }) + .catch((error: Error) => { + console.error("Fetch error:", error); + throw error; + }); +}; + +export default fetchWithInterception;