diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..9a771d1d --- /dev/null +++ b/.babelrc @@ -0,0 +1,8 @@ +{ + "presets": ["next/babel"], + "env": { + "production": { + "plugins": ["transform-remove-console"] + } + } +} diff --git a/package-lock.json b/package-lock.json index d1660303..f15fed29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.13.0", "@next/third-parties": "^14.2.5", "axios": "^1.7.7", + "babel-plugin-transform-remove-console": "^6.9.4", "browser-image-compression": "^2.0.2", "cookie": "^1.0.0", "eslint-config-airbnb": "^19.0.4", @@ -1622,6 +1623,11 @@ "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==", "license": "MIT" }, + "node_modules/babel-plugin-transform-remove-console": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz", + "integrity": "sha512-88blrUrMX3SPiGkT1GnvVY8E/7A+k6oj3MNvUtTIxJflFzXTw1bHkuJ/y039ouhFMp2prRn5cQGzokViYi1dsg==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", diff --git a/package.json b/package.json index 7a18413c..821f0960 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@emotion/styled": "^11.13.0", "@next/third-parties": "^14.2.5", "axios": "^1.7.7", + "babel-plugin-transform-remove-console": "^6.9.4", "browser-image-compression": "^2.0.2", "cookie": "^1.0.0", "eslint-config-airbnb": "^19.0.4", diff --git a/public/assets/icons/ic_hamburger.svg b/public/assets/icons/ic_hamburger.svg new file mode 100644 index 00000000..3adfaada --- /dev/null +++ b/public/assets/icons/ic_hamburger.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/icons/ic_hamburger_menu.svg b/public/assets/icons/ic_hamburger_gray.svg similarity index 100% rename from public/assets/icons/ic_hamburger_menu.svg rename to public/assets/icons/ic_hamburger_gray.svg diff --git a/public/assets/icons/ic_kebab.svg b/public/assets/icons/ic_kebab.svg new file mode 100644 index 00000000..a4971b75 --- /dev/null +++ b/public/assets/icons/ic_kebab.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/icons/ic_pin.svg b/public/assets/icons/ic_pin.svg new file mode 100644 index 00000000..5d69d159 --- /dev/null +++ b/public/assets/icons/ic_pin.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/assets/icons/ic_trash.svg b/public/assets/icons/ic_trash.svg new file mode 100644 index 00000000..b7f9f723 --- /dev/null +++ b/public/assets/icons/ic_trash.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b01eda78..21169a73 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,26 +1,27 @@ -import Providers from "./providers"; -import { GoogleAnalytics, GoogleTagManager } from "@next/third-parties/google"; +import GoogleAnalytics from '@/lib/GoogleAnalytics'; +import Providers from './providers'; +import { GoogleTagManager } from '@next/third-parties/google'; export const metadata = { - title: "레터링 | 온라인 편지 아카이빙 플랫폼", - titleTemplate: "%s - 레터링", + title: '레터링 | 온라인 편지 아카이빙 플랫폼', + titleTemplate: '%s - 레터링', description: - "다양한 우주 행성 그리고 별빛이 담긴 편지지로 마음을 형상화한 편지를 보관해보세요.", + '다양한 우주 행성 그리고 별빛이 담긴 편지지로 마음을 형상화한 편지를 보관해보세요.', icons: { - icon: "/favicon_16.png", + icon: '/favicon_16.png' }, openGraph: { - site_name: "레터링", - title: "레터링 | 온라인 편지 아카이빙 플랫폼", + site_name: '레터링', + title: '레터링 | 온라인 편지 아카이빙 플랫폼', description: - "다양한 우주 행성 그리고 별빛이 담긴 편지지로 마음을 형상화한 편지를 보관해보세요.", - url: "https://www.lettering.world", - type: "website", - }, + '다양한 우주 행성 그리고 별빛이 담긴 편지지로 마음을 형상화한 편지를 보관해보세요.', + url: 'https://www.lettering.world', + type: 'website' + } }; export default function RootLayout({ - children, + children }: Readonly<{ children: React.ReactNode; }>) { @@ -60,14 +61,14 @@ export default function RootLayout({ a.getElementsByTagName('head')[0].appendChild(s); m.mazeUniversalSnippetApiKey = e; })(window, document, 'https://snippet.maze.co/maze-universal-loader.js', '697c563b-a019-4f27-8185-5f33599d9c4d'); - `, + ` }} /> {children} - - + + ); diff --git a/src/app/planet/manage/page.tsx b/src/app/planet/manage/page.tsx index a0554be3..3d24ab6e 100644 --- a/src/app/planet/manage/page.tsx +++ b/src/app/planet/manage/page.tsx @@ -1,37 +1,41 @@ -"use client"; - -import React, { useEffect, useState } from "react"; -import styled, { css } from "styled-components"; -import { theme } from "@/styles/theme"; -import NavigatorBar from "@/components/common/NavigatorBar"; -import Button from "@/components/common/Button"; -import PlanetList from "@/components/planet/PlanetList"; -import Image from "next/image"; -import ConfirmModal from "@/components/common/ConfirmModal"; -import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; +'use client'; + +import React, { useEffect, useState } from 'react'; +import styled, { css } from 'styled-components'; +import { theme } from '@/styles/theme'; +import NavigatorBar from '@/components/common/NavigatorBar'; +import PlanetList from '@/components/planet/PlanetList'; +import Image from 'next/image'; +import ConfirmModal from '@/components/common/ConfirmModal'; +import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; import { deleteSpaces, getSpaceList, - putSpacesOrder, -} from "@/api/planet/space/space"; -import { useToast } from "@/hooks/useToast"; -import { spaceState } from "@/recoil/spaceStore"; -import { useSetRecoilState } from "recoil"; -import { useRouter } from "next/navigation"; -import Loader, { LoaderContainer } from "@/components/common/Loader"; -import { Planet } from "@/types/planet"; + putSpacesOrder +} from '@/api/planet/space/space'; +import { useToast } from '@/hooks/useToast'; +import { spaceState } from '@/recoil/spaceStore'; +import { useSetRecoilState } from 'recoil'; +import { useRouter } from 'next/navigation'; +import Loader, { LoaderContainer } from '@/components/common/Loader'; +import { Planet } from '@/types/planet'; +import BottomSheet from '@/components/common/BottomSheet'; const PlanetManagePage = () => { const router = useRouter(); const { showToast } = useToast(); const [count, setCount] = useState(null); - const [deleteMode, setDeleteMode] = useState(false); - const [checkedPlanets, setCheckedPlanets] = useState([]); + const [dragMode, setDragMode] = useState(false); + const [selectedId, setSelectedId] = useState(); const [confirmDeleteModal, setConfirmDeleteModal] = useState(false); - const [changedOrder, setChangedOrder] = useState([]); + const [changedOrder, setChangedOrder] = useState< + { spaceId: string; index: number }[] + >([]); const [isLoading, setIsLoading] = useState(false); const [planets, setPlanets] = useState(); + const [showBottom, setShowBottom] = useState(false); + const [isBottomUp, setIsBottomUp] = useState(false); const setViewSpaceId = useSetRecoilState(spaceState); @@ -39,11 +43,11 @@ const PlanetManagePage = () => { try { setIsLoading(true); const response = await getSpaceList(); - console.log("전체 스페이스 목록 조회 성공:", response.data); + console.log('전체 스페이스 목록 조회 성공:', response.data); setPlanets(response.data.spaces); setCount(response.data.spaces.length); } catch (error) { - console.error("전체 스페이스 목록 조회 실패:", error); + console.error('전체 스페이스 목록 조회 실패:', error); } finally { setIsLoading(false); } @@ -53,70 +57,104 @@ const PlanetManagePage = () => { fetchSpaceList(); }, []); - const handleClickDeleteMode = () => { - setDeleteMode(!deleteMode); + const handleClickDragMode = () => { + setDragMode(!dragMode); }; - const handleClickCheckAll = () => { - if (planets && planets.length > 0) { - if (checkedPlanets.length === planets.length) { - setCheckedPlanets([]); - } else { - setCheckedPlanets(planets.map((planet) => planet.spaceId)); - } + const handleOrderEditComplete = async () => { + /* 스페이스 순서 변경 API 호출 */ + try { + const response = await putSpacesOrder({ orders: changedOrder }); + console.log('결과', changedOrder); + console.log('스페이스 순서 변경 성공:', response); + } catch (error) { + console.error('스페이스 순서 변경 실패:', error); } + setDragMode(false); }; const handleChangeChecked = (id: string) => { - if (deleteMode) { - if (checkedPlanets.includes(id)) { - setCheckedPlanets(checkedPlanets.filter((planetId) => planetId !== id)); - } else { - setCheckedPlanets([...checkedPlanets, id]); - } - } else { + if (!dragMode) { /* 선택 행성 조회 모드 */ if (id) { setViewSpaceId(id); - router.push("/planet"); + router.push('/planet'); } } }; + /* BottomSheet 관련 함수 */ + useEffect(() => { + if (isBottomUp) { + setShowBottom(true); + } else { + setTimeout(() => { + setShowBottom(false); + }, 490); + } + }, [isBottomUp]); + + const handleShowBottom = (id: string) => { + setSelectedId(id); + setIsBottomUp(true); + }; + + const handleBottomUpChange = (state: boolean) => { + setIsBottomUp(state); + }; + + /* 홈(메인) 행성 고정 */ + const handleFixMainPlanet = () => { + const selectedPlanet = planets?.filter((planet) => + selectedId.includes(planet.spaceId) + ); + + /* TODO: 선택 행성 (selectedId) 메인 행성으로 변경 API 연동 */ + + setIsBottomUp(false); + showToast(`${selectedPlanet[0]?.spaceName} 행성을 홈으로 고정했어요`, { + icon: false, + close: false, + bottom: '65px' + }); + }; + + /* 행성 삭제 관련 함수 */ const handleDeletePlanet = () => { + setIsBottomUp(false); setConfirmDeleteModal(true); }; const handleConfirmDeletePlanet = async () => { - if (checkedPlanets.length > 0) { - const smallestIndexPlanet = planets - ?.filter((planet) => checkedPlanets.includes(planet.spaceId)) - ?.reduce((prev, curr) => - planets.indexOf(prev) < planets.indexOf(curr) ? prev : curr - ); + if (selectedId) { + const selectedPlanet = planets?.filter((planet) => + selectedId.includes(planet.spaceId) + ); /* 행성 삭제하기 */ try { - const response = await deleteSpaces({ spaceIds: checkedPlanets }); - console.log("행성 삭제 성공:", response.data); + const response = await deleteSpaces({ spaceIds: [selectedId] }); + console.log('행성 삭제 성공:', response.data); setPlanets( - planets?.filter((planet) => !checkedPlanets.includes(planet.spaceId)) + (prevPlanets) => + prevPlanets?.filter( + (planet) => !selectedId.includes(planet.spaceId) + ) || [] ); await fetchSpaceList(); + setIsBottomUp(false); } catch (error) { - console.error("행성 삭제 실패:", error); + console.error('행성 삭제 실패:', error); } setConfirmDeleteModal(false); - setDeleteMode(false); + setDragMode(false); showToast( - `${smallestIndexPlanet?.spaceName} ${ - checkedPlanets.length > 1 ? `외 ${checkedPlanets.length - 1}개` : "" - } 행성과 등록된 편지들이 함께 삭제 되었어요`, + `${selectedPlanet[0]?.spaceName} 행성과 등록된 편지들이 함께 삭제되었어요`, { icon: false, close: false, - bottom: "65px", + bottom: '65px' } ); } @@ -126,14 +164,14 @@ const PlanetManagePage = () => { setConfirmDeleteModal(false); }; - const onDragEnd = async ({ + const onDragEnd = ({ source, - destination, + destination }: { source: any; destination: any; }) => { - console.log("dragEnd"); + console.log('dragEnd'); if (!destination) return; // destination이 없다면 return console.log(source, destination); @@ -148,21 +186,11 @@ const PlanetManagePage = () => { const newOrder: { spaceId: string; index: number }[] = items.map( (item: Planet, index: number) => ({ spaceId: item.spaceId, - index: index, + index: index }) ); - setChangedOrder(newOrder.map((item) => item.spaceId)); - + setChangedOrder(newOrder); console.log(newOrder); - - /* 스페이스 순서 변경 API 호출 */ - try { - const response = await putSpacesOrder({ orders: newOrder }); - console.log("결과", newOrder); - console.log("스페이스 순서 변경 성공:", response); - } catch (error) { - console.error("스페이스 순서 변경 실패:", error); - } }; return ( @@ -170,20 +198,14 @@ const PlanetManagePage = () => { - - {deleteMode ? ( - - check - 전체 선택 + + {dragMode ? ( + + 편집 완료 ) : ( - - 삭제 + + 순서 편집 )} @@ -199,11 +221,10 @@ const PlanetManagePage = () => { {planets?.map((planet, index) => ( { id={planet.spaceId} planetName={planet.spaceName} count={planet.letterCount} - checked={checkedPlanets} - deleteMode={deleteMode} + dragMode={dragMode} onClick={() => { handleChangeChecked(planet.spaceId); }} - isMain={index === 0} + onShowBottom={() => handleShowBottom(planet.spaceId)} + isMain={planet.isMainSpace} innerRef={provided.innerRef} dragHandleProps={provided.dragHandleProps} draggableProps={provided.draggableProps} - modify={true} /> )} @@ -234,22 +254,33 @@ const PlanetManagePage = () => { )} - {deleteMode && ( - -