개발/프로젝트 16

새로고침/뒤로가기 시도 시 모달 띄우기

일기 작성 페이지에서 새로고침, 뒤로가기 등을 시도할 때 이동을 막고 경고 모달을 띄우려고 한다. const router = useRouter();const pathname = usePathname(); // 현재 경로const lastPathname = useRef(pathname); // 이전 경로 저장useEffect(() => { if (pathname !== lastPathname.current) { // 페이지 이동 시도 감지 setShowSaveRestrictionModal(true); // 모달 띄우기 router.replace(lastPathname.current); // 현재 페이지로 강제 이동 → 이동 막기 }}, [pathname, router]);pathnam..

개발/프로젝트 2025.05.27

Tiptap 라이브러리로 텍스트 에디터 만들기

텍스트 에디터를 만들기 위해 Tiptap 라이브러리를 사용해 봤다. Tiptap 라이브러리ProseMirror를 기반으로 한 모던하고 확장 가능한 Rich Text Editor!Next.js, React, Vue, Svelte 등 다양한 프레임워크와 잘 통합되고 마크다운처럼 텍스트를 구조적으로 관리할 수 있는 기능을 제공한다. Tiptap 주요 특징1. ProseMirror 기반- 마크다운처럼 텍스트 구조를 명확하게 파악할 수 있는 트리 기반 편집기2. 확장성- Extension을 통해 기능을 자유롭게 추가하거나 수정 가능3. Headless 구조- 원하는 UI 직접 구성 가능!4. React/Vue 지원- @tiptap/react, @tiptap/vue-3 패키지 사용 Tiptap 구성 요소1. use..

개발/프로젝트 2025.05.27

스크롤 기반 싱글 페이지 라우팅 구현하기

포트폴리오 페이지를 만들려는데, 스크롤 기반으로 동작되도록 하고 싶었다.일단 필요한 기능은1. 스크롤 시 경로 변경2. 메뉴 클릭 시 해당 섹션으로 이동3. 특정 페이지(메인, 마지막)에선 메뉴 숨김const mainRef = useRef(null); // 타입 명시const aboutMeRef = useRef(null);const projectRef = useRef(null);const experienceRef = useRef(null);const contactRef = useRef(null);useRef로 각 세션의 DOM을 저장한다.이걸 이용해서 scrollntoView()로 해당 섹션으로 스크롤 하거나, IntersectionObserver로 감시할 수 있다.useRef리액트 컴포넌트에서 DOM ..

개발/프로젝트 2025.04.04

[리팩토링] 새로고침 해도 검색 결과가 남아있도록 하기

// 검색 버튼 클릭 시 실행const searchHandler = () => { if (!selectedProvider) globalSearch("전체", selectedTag, selectedTerm); else globalSearch(selectedProvider, selectedTag, selectedTerm); // 통합 검색의 경우 통합 검색 페이지로 이동 if (type === "header") { router.push(`/global-search`); }};기존에는 배급사와 입력한 태그, 입력한 검색어를 바탕으로 globalSearch를 호출해서 검색 API를 호출했었다.이렇게 호출해서 스토어에 검색어들도 저장해놓고, 이걸 다시 불러와서 검색창에 적용해놨..

개발/프로젝트 2025.02.28

Zustand로 API 호출 및 상태 관리하기

Zustand를 사용해서 웹툰 관련 상태와 API 호출 함수들을 관리하는 스토어인 WebtoonStore.ts를 작성해 보았다.Zustand는 React 애플리케이션에서 전역 상태 관리를 쉽게 할 수 있도록 도와주는 경량 라이브러리이다. import { create } from "zustand"; interface WebtoonState { webtoons: WebtoonData[]; // 웹툰 목록 tagList: Tag[]; // 태그 목록 selectedProvider: string; // 배급사 selectedTag: string; // 검색 태그 selectedTerm: string; // 검색어 selectedTagIds: number[]; // 선택한 태그 selectedTags..

개발/프로젝트 2025.02.28

Tailwind CSS + SCSS로 반응형 구현하기

기존 Tailwind CSS를 사용하고 있었고, 모바일뷰, 태블릿뷰, 데스크탑뷰 이렇게 3개의 반응형을 구현해야 했다.멘토님께서 BP가 많아서 복잡해질 가능성이 있다고, 아예 뷰마다 컴포넌트를 분리하라고 하셨는데제대로 된 방법을 몰라서 그런지 코드 중복이 너무 많아졌고, 유지보수에도 문제가 있을 것 같았다.그래서 또 알려주신 방법이, Tailwind CSS와 SCSS를 함께 사용하는 것!return ( {/* 인기 웹툰 */} 인기 웹툰 {breakpoint === "mobile" ? ( {popularDa..

개발/프로젝트 2025.02.25

Material UI를 사용하여 페이지네이션 구현하기

이번에는 MUI의 Pagination 컴포넌트를 사용해서 페이지네이션을 구현해 보았다.ThemeProvider를 통해 스타일도 커스텀해 보았다.  React Pagination component - Material UIThe Pagination component enables the user to select a specific page from a range of pages.mui.com↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ 참고했던 것  // theme.tsimport { createTheme } from "@mui/material/styles";const theme = createTheme({ breakpoints: { values: { xs: 0, // 모바일 sm: 60..

개발/프로젝트 2025.02.13

Swiper.js 라이브러리를 사용해서 슬라이더 구현하기 (Next.js + React)

Swiper.js 라이브러리를 사용해서 반응형 슬라이더를 구현해 보았다.Swiper는 브라우저에서만 동작하는 라이브러리라서, 'use client'를 선언하여 클라이언트 컴포넌트로 지정해야 사용이 가능했다.// Swipers.tsx"use client";import React, { ReactNode } from "react";import { Swiper, SwiperSlide } from "swiper/react";import { Pagination, Navigation, Autoplay } from "swiper/modules";import "swiper/css";import "swiper/css/navigation";import "swiper/css/pagination";interface Swiper..

개발/프로젝트 2025.02.13

자동 로그인 구현하기

현재 로그인 시 액세스 토큰과 리프레시 토큰을 받아 쿠키에 저장 중이다.액세스 토큰은 유효기간이 1시간이며, 리프레시 토큰은 7일이다.그래서 자동 로그인 시, 액세스 토큰이 만료됐을 때 리프레시 토큰으로 재발급을 받아서 다시 쿠키에 넣어주어야 한다.우선 로그인 상태를 확인하는 함수를 만들어 주었다.import { destroyCookie, setCookie } from 'nookies';import { clearUser } from '@/store/userSlice';import { useDispatch } from 'react-redux';import { useAccessTokenRefreshMutation } from '@/api/userApi';// 로그인 상태 확인 함수export const us..

개발/프로젝트 2025.01.12

Next.js에서 RTK Query로 카카오 로그인 구현하기

카카오 소셜 로그인은1. /social/kakao/login 으로 이동하여 인증 진행2. 받아온 code로 /social/kakao/callback에 GET 요청 보내서 토큰 받기3. 토큰은 쿠키에 저장4. 로그인 성공 후 토큰으로 유저 정보 GET 요청으로 받아옴5. 유저 정보 Redux에 저장이런 흐름으로 진행하였다. // sign-inconst handleLogin = (provider: string) => { switch (provider) { case 'kakao': // 서버의 카카오 로그인 엔드포인트로 이동 window.location.href = 'kakao login endpoint'; // 해당 페이지로 리디렉션 break; case 'google..

개발/프로젝트 2025.01.09