diff --git a/.github/workflows/s3Build.yml b/.github/workflows/s3Build.yml
index cc570d7..7deb911 100644
--- a/.github/workflows/s3Build.yml
+++ b/.github/workflows/s3Build.yml
@@ -19,11 +19,11 @@ jobs:
restore-keys: |
${{ runner.OS }}-build-
${{ runner.OS }}-
-
- - name: Install Dependencies # 의존 파일 설치
+
+ - name: Install Dependencies # 의존 파일 설치
run: npm install --legacy-peer-deps
-
- - name: Build # React Build
+
+ - name: Build # React Build
run: npm run build
- name: Deploy # S3에 배포
@@ -34,5 +34,4 @@ jobs:
aws s3 cp \
--recursive \
--region ap-northeast-2 \
- build s3://roadmakertest
-
+ build s3://http://roadmaker.site.s3-website.ap-northeast-2.amazonaws.com
diff --git a/dbDir/000007.ldb b/dbDir/000007.ldb
deleted file mode 100644
index 0a998f7..0000000
Binary files a/dbDir/000007.ldb and /dev/null differ
diff --git a/dbDir/000010.ldb b/dbDir/000010.ldb
deleted file mode 100644
index 82446ae..0000000
Binary files a/dbDir/000010.ldb and /dev/null differ
diff --git a/dbDir/000011.log b/dbDir/000011.log
deleted file mode 100644
index e69de29..0000000
diff --git a/dbDir/CURRENT b/dbDir/CURRENT
deleted file mode 100644
index 6ba31a3..0000000
--- a/dbDir/CURRENT
+++ /dev/null
@@ -1 +0,0 @@
-MANIFEST-000009
diff --git a/dbDir/LOCK b/dbDir/LOCK
deleted file mode 100644
index e69de29..0000000
diff --git a/dbDir/LOG b/dbDir/LOG
deleted file mode 100644
index e19d979..0000000
--- a/dbDir/LOG
+++ /dev/null
@@ -1,5 +0,0 @@
-2023/07/21-18:39:34.300 8204 Recovering log #8
-2023/07/21-18:39:34.301 8204 Level-0 table #10: started
-2023/07/21-18:39:34.305 8204 Level-0 table #10: 401 bytes OK
-2023/07/21-18:39:34.317 8204 Delete type=0 #8
-2023/07/21-18:39:34.318 8204 Delete type=3 #6
diff --git a/dbDir/LOG.old b/dbDir/LOG.old
deleted file mode 100644
index 3ee6dcb..0000000
--- a/dbDir/LOG.old
+++ /dev/null
@@ -1,5 +0,0 @@
-2023/07/21-14:37:29.523 63b0 Recovering log #5
-2023/07/21-14:37:29.525 63b0 Level-0 table #7: started
-2023/07/21-14:37:29.535 63b0 Level-0 table #7: 260 bytes OK
-2023/07/21-14:37:29.556 63b0 Delete type=0 #5
-2023/07/21-14:37:29.557 63b0 Delete type=3 #4
diff --git a/dbDir/MANIFEST-000009 b/dbDir/MANIFEST-000009
deleted file mode 100644
index a682b65..0000000
Binary files a/dbDir/MANIFEST-000009 and /dev/null differ
diff --git a/hydratable.config.json b/hydratable.config.json
new file mode 100644
index 0000000..dd83409
--- /dev/null
+++ b/hydratable.config.json
@@ -0,0 +1,3 @@
+{
+ "crawlingUrls": ["/", "/copyrights"]
+}
diff --git a/package.json b/package.json
index 7165e53..ae4feaf 100644
--- a/package.json
+++ b/package.json
@@ -2,12 +2,10 @@
"name": "my-app",
"version": "0.1.0",
"private": true,
- "homepage": "http://roadmakertest.s3-website.ap-northeast-2.amazonaws.com",
+ "homepage": "http://roadmaker.site.s3-website.ap-northeast-2.amazonaws.com/",
"dependencies": {
"@dagrejs/dagre": "^1.0.2",
- "@egjs/infinitegrid": "^4.10.1",
- "@fontsource/roboto": "^5.0.8",
- "@fristys/masonry": "^1.1.7",
+ "@emotion/react": "^11.11.1",
"@mantine/carousel": "^6.0.16",
"@mantine/core": "^6.0.16",
"@mantine/dates": "^6.0.16",
@@ -20,14 +18,13 @@
"@mantine/prism": "^6.0.16",
"@mantine/spotlight": "^6.0.16",
"@mantine/tiptap": "^6.0.16",
- "@monaco-editor/react": "^4.5.1",
+ "@reactflow/node-resizer": "^2.1.1",
"@reactflow/node-toolbar": "^1.2.3",
"@syncedstore/core": "^0.5.2",
"@syncedstore/react": "^0.5.2",
"@tabler/icons-react": "^2.25.0",
"@tiptap/core": "^2.0.4",
- "@tiptap/extension-collaboration": "^2.0.4",
- "@tiptap/extension-collaboration-cursor": "^2.0.4",
+ "@tiptap/extension-code-block": "^2.0.4",
"@tiptap/extension-color": "^2.0.4",
"@tiptap/extension-highlight": "^2.0.4",
"@tiptap/extension-history": "^2.0.4",
@@ -44,9 +41,10 @@
"@tiptap/react": "^2.0.4",
"@tiptap/starter-kit": "^2.0.3",
"@tisoap/react-flow-smart-edge": "^3.0.0",
- "@types/masonry-layout": "^4.2.5",
"axios": "^1.4.0",
"classcat": "^5.0.4",
+ "d3-drag": "^3.0.0",
+ "d3-selection": "^3.0.0",
"draft-js": "^0.11.7",
"embla-carousel-react": "^8.0.0-rc11",
"fast-json-patch": "^3.1.1",
@@ -54,29 +52,22 @@
"http-proxy-middleware": "^2.0.6",
"jsonpatch": "^3.1.0",
"lowlight": "^2.9.0",
- "monaco-editor": "^0.40.0",
"nanoid": "^4.0.2",
- "prosemirror-keymap": "^1.2.2",
- "prosemirror-model": "^1.19.3",
- "prosemirror-state": "^1.4.3",
- "prosemirror-view": "^1.31.6",
- "react": "^17.0.0 || ^18.0.0",
- "react-dom": "^17.0.0 || ^18.0.0",
+ "prop-types": "^15.8.1",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
"react-draft-wysiwyg": "^1.15.0",
+ "react-hydratable": "^1.2.1",
"react-infinite-scroller": "^1.2.6",
"react-query": "^3.39.3",
"react-router-dom": "^6.14.1",
"react-scripts": "5.0.1",
"reactflow": "^11.7.4",
- "styled-components": "^6.0.7",
+ "styled-components": "^6.0.4",
"styled-reset": "^4.5.1",
"tiptap": "^1.32.2",
"web-vitals": "^2.1.4",
- "y-leveldb": "^0.1.2",
- "y-monaco": "^0.1.4",
- "y-prosemirror": "^1.2.1",
- "yjs": "^13.6.7",
- "zustand": "^4.3.9"
+ "zustand": "4.3.9"
},
"scripts": {
"start": "react-scripts start",
@@ -85,7 +76,15 @@
"test": "env-cmd -f .env.production react-scripts test",
"eject": "react-scripts eject",
"lint": "yarn lint:ci --fix",
- "lint:ci": "eslint src"
+ "lint:ci": "eslint src",
+ "postbuild": "react-hydratable",
+ "preview": "react-hydratable --preview"
+ },
+ "reactSnap": {
+ "include": [
+ "/",
+ "/roadmap/post"
+ ]
},
"browserslist": {
"production": [
@@ -108,7 +107,7 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
- "@types/node": "^16.18.38",
+ "@types/node": "^20.4.8",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@types/react-draft-wysiwyg": "^1.13.4",
diff --git a/public/img/loadingImg.gif b/public/img/loadingImg.gif
new file mode 100644
index 0000000..da057f1
Binary files /dev/null and b/public/img/loadingImg.gif differ
diff --git a/public/img/logo.png b/public/img/logo.png
index 6b5d811..7be3d57 100644
Binary files a/public/img/logo.png and b/public/img/logo.png differ
diff --git a/public/img/shortLogo.png b/public/img/shortLogo.png
new file mode 100644
index 0000000..7fc1027
Binary files /dev/null and b/public/img/shortLogo.png differ
diff --git a/public/img/warning.gif b/public/img/warning.gif
new file mode 100644
index 0000000..e9f5084
Binary files /dev/null and b/public/img/warning.gif differ
diff --git a/public/index.html b/public/index.html
index 0f3b443..cad2b0c 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,23 +1,19 @@
+ Roadmaker
-
+
-
-
-
+
+
+
+
- Roadmaker
diff --git a/public/manifest.json b/public/manifest.json
deleted file mode 100644
index 080d6c7..0000000
--- a/public/manifest.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "short_name": "React App",
- "name": "Create React App Sample",
- "icons": [
- {
- "src": "favicon.ico",
- "sizes": "64x64 32x32 24x24 16x16",
- "type": "image/x-icon"
- },
- {
- "src": "logo192.png",
- "type": "image/png",
- "sizes": "192x192"
- },
- {
- "src": "logo512.png",
- "type": "image/png",
- "sizes": "512x512"
- }
- ],
- "start_url": ".",
- "display": "standalone",
- "theme_color": "#000000",
- "background_color": "#ffffff"
-}
diff --git a/src/App.tsx b/src/App.tsx
index 963658b..09f50de 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,5 +1,6 @@
// import InteractionFlow from './pages/main/userRoadmap';
import { Notifications } from '@mantine/notifications';
+import RoadMapPostPage from 'pages/roadmap/posts';
import KeywordSearchRoadmaps from 'pages/roadmap/posts/byKeyword';
import { ReactElement } from 'react';
import { QueryClientProvider } from 'react-query';
@@ -12,9 +13,7 @@ import { UserProfile } from './components/user/userProfile';
import ErrorPage from './pages/error';
import LoginPage from './pages/login';
import MainPage from './pages/main';
-import ResetInfoPage from './pages/resetInfo';
import RoadMapEditor from './pages/roadmap/editor';
-import PostedRoadmap from './pages/roadmap/posts/postedRoadmap';
import SignupPage from './pages/signup';
function App(): ReactElement {
@@ -29,13 +28,11 @@ function App(): ReactElement {
element: ,
},
{ path: 'users/signup', element: },
- { path: 'users/reset', element: },
{
path: 'roadmap/editor',
element: ,
},
- // { path: '/roadmap/post/:id', element: }, // origin initialMerge
- { path: '/roadmap/post/:Id', element: },
+ { path: '/roadmap/post/:Id', element: },
{
path: '/roadmap/post/search/:keyword',
element: ,
@@ -50,13 +47,10 @@ function App(): ReactElement {
]);
return (
- {/* */}
- {/* */}
- {/* */}
);
diff --git a/src/assets/Logo.svg b/src/assets/Logo.svg
new file mode 100644
index 0000000..9a2924e
--- /dev/null
+++ b/src/assets/Logo.svg
@@ -0,0 +1,17 @@
+
diff --git a/src/assets/Spinner.svg b/src/assets/Spinner.svg
new file mode 100644
index 0000000..abe4d92
--- /dev/null
+++ b/src/assets/Spinner.svg
@@ -0,0 +1,52 @@
+
+
\ No newline at end of file
diff --git a/src/assets/noImage.svg b/src/assets/noImage.svg
new file mode 100644
index 0000000..8f680bc
--- /dev/null
+++ b/src/assets/noImage.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/auth/useAuth.ts b/src/auth/useAuth.ts
index 1a4cb6b..019bb68 100644
--- a/src/auth/useAuth.ts
+++ b/src/auth/useAuth.ts
@@ -13,8 +13,8 @@ interface UseAuth {
signout: () => void;
isUserModalOpen: boolean;
setIsUserModalOpen: (isUserModalOpen: boolean) => void;
- modalText: string; // 추가: 모달에 표시할 텍스트 상태
- setModalText: (text: string) => void; // 추가: 모달 텍스트를 업데이트하는 함수
+ modalText: string;
+ setModalText: (text: string) => void;
openModal: boolean;
setOpenModal: (openModal: boolean) => void;
success: boolean;
@@ -116,10 +116,8 @@ export function useAuth(): UseAuth {
? errorResponse?.response?.status
: SERVER_ERROR;
status === 403
- ? // eslint-disable-next-line no-alert
- alert('이메일과 비밀번호가 일치하지 않습니다.')
- : // eslint-disable-next-line no-alert
- alert(`status code : ${status}!`);
+ ? alert('이메일과 비밀번호가 일치하지 않습니다.')
+ : alert(`status code : ${status}!`);
}
}
diff --git a/src/components/comments/index.tsx b/src/components/comments/index.tsx
new file mode 100644
index 0000000..633d1bd
--- /dev/null
+++ b/src/components/comments/index.tsx
@@ -0,0 +1,207 @@
+/* eslint-disable no-console */
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import {
+ Avatar,
+ Button,
+ Center,
+ createStyles,
+ Group,
+ Modal,
+ Paper,
+ rem,
+ SimpleGrid,
+ Text,
+ Textarea,
+} from '@mantine/core';
+import { useDisclosure } from '@mantine/hooks';
+import axios from 'axios';
+import { baseUrl } from 'axiosInstance/constants';
+import { useUser } from 'components/user/hooks/useUser';
+import { Footer } from 'layout/mainLayout/footer/Footer';
+import { useCallback, useEffect, useState } from 'react';
+import InfiniteScroll from 'react-infinite-scroller';
+import { useInfiniteQuery } from 'react-query';
+import { useLocation } from 'react-router-dom';
+
+const useStyles = createStyles((theme) => ({
+ body: {
+ paddingLeft: rem(54),
+ paddingTop: theme.spacing.sm,
+ },
+}));
+
+function CommentSection() {
+ const [opened, { open, close }] = useDisclosure(false);
+ const { classes, theme } = useStyles();
+ // const [count, handlers] = useCounter(0, { min: 0, max: 1000 });
+ const [commentPage, setCommentPage] = useState(1);
+ const { pathname } = useLocation();
+ const [title, setTitle] = useState('');
+ const [content, setContent] = useState([]);
+ const [commentInput, setCommentInput] = useState('');
+ const { user } = useUser();
+ const [counts, setCounts] = useState([]);
+
+ const fetchComments = useCallback(() => {
+ axios
+ .get(
+ `${baseUrl}/roadmaps/load-roadmap/${pathname.slice(
+ pathname.lastIndexOf('/') + 1,
+ )}/comments?page=${commentPage}&size=5`,
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ },
+ )
+ .then((v) => {
+ setContent(v?.data);
+ // setCounts(new Array(commentContents.length).fill(0));
+ // setNickname(v?.data?.commentNickname);
+ });
+ // .catch((e) => // console.log(e));
+ }, [commentPage, pathname]);
+
+ const {
+ refetch,
+ data,
+ fetchNextPage,
+ hasNextPage,
+ isLoading,
+ isError,
+ error,
+ } = useInfiniteQuery(
+ 'comments',
+ ({ pageParam = initialUrl }) => fetchUrl(pageParam),
+ {
+ getNextPageParam: (lastPage) => {
+ // // console.log(lastPage.result);
+ if (lastPage.result.length !== 0) {
+ return lastPage.next;
+ }
+ return undefined;
+ },
+ },
+ );
+
+ useEffect(() => {
+ setCommentPage(1);
+ refetch();
+ fetchComments();
+ }, [commentPage, fetchComments, pathname, refetch, user?.accessToken]);
+
+ const initialUrl = `${baseUrl}/roadmaps/load-roadmap/${pathname.slice(
+ pathname.lastIndexOf('/') + 1,
+ )}/comments?page=${commentPage}&size=5`;
+
+ const fetchUrl = async (url) => {
+ const response = await fetch(url);
+ return response.json();
+ };
+
+ if (isLoading) return Loading...
;
+ if (isError) return Error! {error.toString()}
;
+
+ const handleCommentContentChange = (event) => {
+ setCommentInput(event.target.value);
+ };
+
+ function handleSubmit() {
+ axios
+ .post(
+ `${baseUrl}/comments/save-comment`,
+ {
+ content: commentInput,
+ roadmapId: Number(pathname.slice(pathname.lastIndexOf('/') + 1)),
+ },
+ {
+ headers: {
+ Authorization: `Bearer ${user?.accessToken}`,
+ 'Content-Type': 'application/json',
+ },
+ },
+ )
+ .then(() => {
+ fetchComments();
+ refetch();
+ });
+ // .catch((e) => // console.log('err', e));
+ }
+ return (
+ <>
+
+
+ 코멘트 작성
+
+
+
+
+
+
+
+
+
+ {content.length === 0 ? (
+
+ 댓글이 없습니다.
+
+ ) : (
+
+
+ {data.pages.map((pageData) => {
+ return pageData.result.map((comments, index) => (
+
+
+
+ {comments?.nickname.substring(0, 1)}
+
+
+ {comments?.nickname}
+
+ {comments?.createdAt}
+
+
+
+
+ {comments?.content}
+
+
+ ));
+ })}
+
+
+ )}
+
+ >
+ );
+}
+
+export default CommentSection;
diff --git a/src/components/common/typingAnimation/Typer.tsx b/src/components/common/typingAnimation/Typer.tsx
new file mode 100644
index 0000000..cb76421
--- /dev/null
+++ b/src/components/common/typingAnimation/Typer.tsx
@@ -0,0 +1,88 @@
+import { CSSProperties } from 'react';
+import { styled } from 'styled-components';
+
+interface FontBlue extends CSSProperties {
+ color: string;
+}
+
+const blues: Array = [
+ { color: '#216b89' },
+ { color: '#527e90' },
+ { color: '#000000' },
+ { color: '#4191dd' },
+ { color: '#3e67df' },
+ { color: '#939de6' },
+];
+
+export function Typer({ data }) {
+ const text = [...data];
+ const keyword = text.slice(1, text.lastIndexOf('"'));
+ return (
+
+
+ "
+ {keyword.map((v, idx) => (
+
+ {v}
+
+ ))}
+ "에 관한 로드맵을 생성 중...
+
+
+ );
+}
+
+export function NodeTyper({ data }) {
+ return (
+
+ {data.length > 10 ? (
+ {`${data.slice(0, 8)}...`}
+ ) : (
+ {data}
+ )}
+
+ );
+}
+
+const Wrap = styled.div`
+ display: inline-block;
+ font-family: 'arial';
+ font-size: 24px;
+ padding: 1rem;
+
+ & .typed {
+ overflow: hidden;
+ white-space: nowrap;
+ border-right: 2px solid;
+ width: 0;
+
+ animation:
+ typing 1.5s steps(30, end) forwards,
+ blinking 1s infinite;
+ }
+
+ & .letters {
+ display: inline;
+ }
+
+ @keyframes typing {
+ from {
+ width: 0;
+ }
+ to {
+ width: 100%;
+ }
+ }
+
+ @keyframes blinking {
+ 0% {
+ border-right-color: transparent;
+ }
+ 50% {
+ border-right-color: black;
+ }
+ 100% {
+ border-right-color: transparent;
+ }
+ }
+`;
diff --git a/src/components/editor/ColorSelectorNode.jsx b/src/components/editor/ColorSelectorNode.jsx
deleted file mode 100644
index 0ec8e1a..0000000
--- a/src/components/editor/ColorSelectorNode.jsx
+++ /dev/null
@@ -1,34 +0,0 @@
-/* eslint-disable no-console */
-import { memo } from 'react';
-import { Handle, Position } from 'reactflow';
-
-export default memo(({ data, isConnectable }) => {
- return (
- <>
- console.log('handle onConnect', params)}
- isConnectable={isConnectable}
- />
-
- Custom Color Picker Node: {data.color}
-
-
- {/* */}
- >
- );
-});
diff --git a/src/components/editor/CustomNode.jsx b/src/components/editor/CustomNode.jsx
deleted file mode 100644
index 44b139f..0000000
--- a/src/components/editor/CustomNode.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-// import React, { memo } from 'react';
-// import { Handle } from 'reactflow';
-
-// export default memo(({ data, isConnectable }) => {
-// const handleClick = () => {
-// console.log('node click');
-// };
-// return (
-// <>
-// console.log('handle onConnect', params)}
-// isConnectable={isConnectable}
-// />
-//
-// {data.label}
-//
-
-//
-// >
-// );
-// });
diff --git a/src/components/editor/DoneStatusNode.jsx b/src/components/editor/DoneStatusNode.jsx
new file mode 100644
index 0000000..6aac870
--- /dev/null
+++ b/src/components/editor/DoneStatusNode.jsx
@@ -0,0 +1,186 @@
+import { memo } from 'react';
+import { Handle, Position } from 'reactflow';
+import { styled } from 'styled-components';
+
+export function DoneStatusNode({ data }) {
+ return (
+ <>
+ {/* {!done && typeof done === 'boolean' && (
+
+ )}
+ {done && (
+
+ )} */}
+ {/*
+
+
+ {data.done ? (
+
+ {data.label}
+
+ ) : (
+
+ {data.label}
+
+ )}
+
+
+ */}
+ {data.done ? (
+
+
+
+ {data.label}
+
+
+
+ ) : (
+
+
+
+ {data.label}
+
+
+
+ )}
+ {/*
+
+
+ {data.done ? (
+
+ {data.label}
+
+ ) : (
+
+ {data.label}
+
+ )}
+
+
+ */}
+ >
+ );
+}
+export default memo(DoneStatusNode);
+
+const Wrap = styled.div`
+ .node {
+ border-radius: 15px;
+ border: 1px solid #000;
+ box-sizing: border-box;
+ width: 240px;
+ }
+ .react-flow__node.react-flow__node-custom {
+ font-size: 1rem !important;
+ }
+ .react-flow__node.react-flow__node-custom.selectable {
+ padding: 0;
+ }
+`;
diff --git a/src/components/editor/Minimap.tsx b/src/components/editor/Minimap.tsx
index 79c283f..e2ef8db 100644
--- a/src/components/editor/Minimap.tsx
+++ b/src/components/editor/Minimap.tsx
@@ -2,20 +2,9 @@ import { ReactElement } from 'react';
import ReactFlow, { MiniMap } from 'reactflow';
function Minimap(): ReactElement {
- // const nodeColor = (node) => {
- // switch (node.type) {
- // case 'input':
- // return '#6ede87';
- // case 'output':
- // return '#6865A5';
- // default:
- // return '#ff0072';
- // }
- // };
return (
- {/* */}
diff --git a/src/components/editor/PanelItem.tsx b/src/components/editor/PanelItem.tsx
new file mode 100644
index 0000000..345ea2d
--- /dev/null
+++ b/src/components/editor/PanelItem.tsx
@@ -0,0 +1,195 @@
+/* eslint-disable no-nested-ternary */
+import { ActionIcon, Tooltip } from '@mantine/core';
+import {
+ IconBinaryTree,
+ IconFileCheck,
+ IconInfoCircle,
+ IconPlus,
+ IconSchema,
+ IconTrashX,
+} from '@tabler/icons-react';
+
+export default function PanelItem({
+ onLayout,
+ setCurrentFlow,
+ edgeState,
+ nodeState,
+ setConfirmDelete,
+ setSubmitModal,
+ onAddNode,
+ getViewport,
+ setCurrentView,
+ currentView,
+ currentFlow,
+}) {
+ // const theme = useMantineTheme();
+ // const getColor = (color: string) =>
+ // theme.colors[color][theme.colorScheme === 'dark' ? 5 : 7];
+
+ return (
+ <>
+ {/*
+
+
+
+
+
+
+
+
+
+ {
+ onLayout('TB');
+ setCurrentFlow('TB');
+ }}
+ >
+
+ vertical layout
+
+
+
+ {
+ onLayout('LR');
+ setCurrentFlow('LR');
+ }}
+ mr={10}
+ >
+
+ horizontal layout
+
+
+
+ */}
+
+ {currentFlow !== 'TB' && currentFlow !== 'LR' ? (
+ {
+ if (currentFlow !== 'TB') {
+ onLayout('LR');
+ setCurrentFlow('LR');
+ } else if (currentFlow !== 'LR') {
+ onLayout('TB');
+ setCurrentFlow('TB');
+ }
+ }}
+ >
+
+
+ ) : currentFlow === 'TB' ? (
+ {
+ onLayout('LR');
+ setCurrentFlow('LR');
+ }}
+ >
+
+
+ ) : (
+ {
+ onLayout('TB');
+ setCurrentFlow('TB');
+ }}
+ >
+
+
+ )}
+
+
+ {nodeState.length === 0 ? (
+
+
+
+ ) : (
+ setConfirmDelete(true)}>
+
+
+ )}
+
+
+
+ {
+ // const { zoom } = getViewport();
+ setCurrentView({
+ x: currentView.x,
+ y: nodeState.at(-1)?.position?.y,
+ zoom: 0.4,
+ });
+ onAddNode();
+ }}
+ >
+
+
+
+
+
+
+
+
+ v.data.label !== '')
+ ? '로드맵 발행'
+ : '빈노드를 채워주시거나 노드를 이어주세요.'
+ }`}
+ >
+ {nodeState.length === 0 || edgeState.length === 0 ? (
+
+
+
+ ) : (
+ setSubmitModal(true)}>
+
+
+ )}
+
+ >
+ );
+}
diff --git a/src/components/editor/ResizableNodeSelected.jsx b/src/components/editor/ResizableNodeSelected.jsx
index 532a313..896add0 100644
--- a/src/components/editor/ResizableNodeSelected.jsx
+++ b/src/components/editor/ResizableNodeSelected.jsx
@@ -1,11 +1,47 @@
-import { useEffect } from 'react';
-import { Handle, NodeResizer, Position } from 'reactflow';
+/* eslint-disable @typescript-eslint/no-unused-vars */
+/* eslint-disable react-hooks/exhaustive-deps */
+
+import { IconAlertCircle } from '@tabler/icons-react';
+import { NodeTyper } from 'components/common/typingAnimation/Typer';
+import { drag } from 'd3-drag';
+import { select } from 'd3-selection';
+import { useEffect, useMemo, useRef, useState } from 'react';
+import {
+ Handle,
+ NodeResizer,
+ Position,
+ useUpdateNodeInternals,
+} from 'reactflow';
+import { styled } from 'styled-components';
+
+export function ResizableNodeSelected({
+ data,
+ selected,
+ id,
+ sourcePosition = Position.Left,
+ targetPosition = Position.Right,
+}) {
+ const [isLoading, setIsLoading] = useState(true);
+ const rotateControlRef = useRef(null);
+ const updateNodeInternals = useUpdateNodeInternals();
+ // eslint-disable-next-line no-unused-vars
+ const [resizable, setResizable] = useState(true);
+ // eslint-disable-next-line no-unused-vars
+ const [rotatable, setRotatable] = useState(true);
+
+ useEffect(() => {
+ if (data !== '내용을 입력해주세요.') {
+ setIsLoading(false);
+ }
+ }, [isLoading]);
-export function ResizableNodeSelected({ data, selected }) {
- // window.ResizeObserver = undefined;
useEffect(() => {
window.addEventListener('error', (e) => {
- if (e.message === 'ResizeObserver loop limit exceeded') {
+ if (
+ e.message === 'ResizeObserver loop limit exceeded' ||
+ e.message ===
+ 'ResizeObserver loop completed with undelivered notifications.'
+ ) {
const resizeObserverErrDiv = document.getElementById(
'webpack-dev-server-client-overlay-div',
);
@@ -21,60 +57,80 @@ export function ResizableNodeSelected({ data, selected }) {
}
});
}, []);
+
+ useMemo(() => {
+ const selection = select(rotateControlRef.current);
+ const dragHandler = drag().on('drag', () => {
+ updateNodeInternals(id);
+ });
+ if (selected) {
+ selection.call(dragHandler);
+ }
+ }, [id, updateNodeInternals, selected]);
+
return (
- <>
+
-
-
- {data.label}
-
-
- >
+
+
+ {data.label === `` && (
+
+ )}
+ {data.length > 2 ? (
+
+ {data.label === `` ? '내용을 추가해주세요.' : data.label}
+
+ ) : (
+
+ )}
+
+
+
+
);
}
+const Wrap = styled.div`
+ .node {
+ /* width: 100%; */
+ max-width: 240px;
+ font-size: 24px;
+ /* width: 7rem; */
+ height: 100%;
+ border-radius: 15px;
+ border: 1px solid #000;
+ background-color: #fff;
+ padding: 20px;
+ box-sizing: border-box;
+ }
-// export default memo(ResizableNodeSelected);
-// // export default ResizableNodeSelected;
-
-// function ResizableNodeSelected({ data, selected }) {
-// // window.ResizeObserver = undefined;
-// useEffect(() => {
-// window.addEventListener('error', (e) => {
-// if (e.message === 'ResizeObserver loop limit exceeded') {
-// const resizeObserverErrDiv = document.getElementById(
-// 'webpack-dev-server-client-overlay-div',
-// );
-// const resizeObserverErr = document.getElementById(
-// 'webpack-dev-server-client-overlay',
-// );
-// if (resizeObserverErr) {
-// resizeObserverErr.setAttribute('style', 'display: none');
-// }
-// if (resizeObserverErrDiv) {
-// resizeObserverErrDiv.setAttribute('style', 'display: none');
-// }
-// }
-// });
-// }, []);
-// return (
-// <>
-//
-//
-// {data.label}
-//
-// >
-// );
-// }
+ .node :global .react-flow__resize-control.handle {
+ width: 10px;
+ height: 10px;
+ border-radius: 100%;
+ }
-// export default memo(ResizableNodeSelected);
+ .react-flow__node .react-flow__node-custom .nopan .selectable {
+ max-width: 240px;
+ }
+ .react-flow__node .react-flow__node-custom .nopan .selectable {
+ max-width: 240px;
+ }
+`;
diff --git a/src/components/editor/RoadMapEditor copy.jsx b/src/components/editor/RoadMapEditor copy.jsx
deleted file mode 100644
index 563e307..0000000
--- a/src/components/editor/RoadMapEditor copy.jsx
+++ /dev/null
@@ -1,1047 +0,0 @@
-// import 'reactflow/dist/style.css';
-
-// import dagre from '@dagrejs/dagre';
-// import {
-// Button,
-// Center,
-// Image,
-// LoadingOverlay,
-// Modal,
-// MultiSelect,
-// SimpleGrid,
-// Text,
-// Textarea,
-// TextInput,
-// } from '@mantine/core';
-// // import { useRoadmap } from 'components/roadmaps/hooks/useRoadmap'; // origin : initialMerge
-// import { Dropzone, IMAGE_MIME_TYPE } from '@mantine/dropzone';
-// import { useDisclosure } from '@mantine/hooks';
-// import { useRoadmap } from 'components/roadmaps/posts/hooks/useRoadmap';
-// import { useUser } from 'components/user/hooks/useUser';
-// import { useCallback, useEffect, useMemo, useState } from 'react';
-// import { useNavigate, useSearchParams } from 'react-router-dom';
-// import ReactFlow, {
-// addEdge,
-// Background,
-// Controls,
-// MiniMap,
-// Panel,
-// Position,
-// ReactFlowProvider,
-// useEdgesState,
-// useNodesState,
-// useOnSelectionChange,
-// useReactFlow,
-// } from 'reactflow';
-// import { getStoredRoadmap, setStoredRoadmap } from 'storage/roadmap-storage';
-// import { styled } from 'styled-components';
-
-// import { useInput } from '../common/hooks/useInput';
-// import CustomNode from './custom/CustomNode';
-// // eslint-disable-next-line import/no-named-as-default
-// import TooltipNode from './custom/TooltipNode';
-
-// const dagreGraph = new dagre.graphlib.Graph();
-// dagreGraph.setDefaultEdgeLabel(() => ({}));
-
-// const nodeWidth = 172;
-// const nodeHeight = 36;
-
-// const flowKey = 'example-flow';
-
-// const getLayoutedElements = (nodes, edges, direction = 'TB') => {
-// const isHorizontal = direction === 'LR';
-// dagreGraph.setGraph({ rankdir: direction });
-
-// nodes.forEach((node) => {
-// dagreGraph.setNode(node?.id, { width: nodeWidth, height: nodeHeight });
-// });
-
-// edges.forEach((edge) => {
-// dagreGraph.setEdge(edge?.source, edge?.target);
-// });
-
-// dagre.layout(dagreGraph);
-
-// nodes.forEach((node) => {
-// const nodeWithPosition = dagreGraph.node(node?.id);
-// // eslint-disable-next-line no-param-reassign
-// node.targetPosition = isHorizontal ? 'left' : 'top';
-// // eslint-disable-next-line no-param-reassign
-// node.sourcePosition = isHorizontal ? 'right' : 'bottom';
-// // We are shifting the dagre node position (anchor=center center) to the top left
-// // so it matches the React Flow node anchor point (top left).
-// // eslint-disable-next-line no-param-reassign
-// node.position = {
-// x: nodeWithPosition.x - nodeWidth / 2,
-// y: nodeWithPosition.y - nodeHeight / 2,
-// };
-// return node;
-// });
-
-// return { nodes, edges };
-// };
-
-// const position = { x: 0, y: 0 };
-// const edgeType = 'smoothstep';
-// // const nodeTypes = {
-// // ResizableNodeSelected,
-// // custom: CustomNode,
-// // };
-// const nodeTypes = {
-// custom: CustomNode,
-// tooltip: TooltipNode,
-// };
-
-// const initialNodes = [
-// {
-// id: '1',
-// data: {
-// label: 'test',
-// toolbarPosition: Position.Right,
-// // currentFlow,
-// },
-// position: { x: 100, y: 100 },
-// // type: 'default',
-// // type: { nodeTypes },
-// type: 'custom',
-// style: {
-// background: '#fff',
-// border: '1px solid black',
-// borderRadius: 15,
-// fontSize: 12,
-// },
-// },
-// {
-// id: '2',
-// data: {
-// label: 'Node 2',
-// toolbarPosition: Position.Top,
-// },
-// position: { x: 100, y: 200 },
-// // type: 'default',
-// // type: { nodeTypes },
-// // type: 'tooltip',
-// type: 'custom',
-// style: {
-// background: '#fff',
-// border: '1px solid black',
-// borderRadius: 15,
-// fontSize: 12,
-// },
-// },
-// ];
-
-// const initialEdges = [
-// { id: 'e11a', source: '1', target: '1a', type: edgeType, animated: true },
-// ];
-// const defaultViewport = { x: 0, y: 0, zoom: 1.5 };
-
-// function Roadmap({
-// editor,
-// label,
-// roadMapTitle,
-// roadmapImage,
-// roadmapDescription,
-// roadmapTag,
-// onRoadMapTitleChange,
-// setRoadMapTitle,
-// setLabel,
-// toggleEditor,
-// onChangeLabel,
-// id,
-// setState,
-// state,
-// onChangeId,
-// setId,
-// }) {
-// const [currentFlow, setCurrentFlow] = useState('');
-// // const initialNodes = [
-// // {
-// // id: '1',
-// // data: {
-// // label: 'test',
-// // toolbarPosition: Position.Right,
-// // currentFlow,
-// // getLayoutedElements,
-// // },
-// // position: { x: 100, y: 100 },
-// // type: 'default',
-// // // type: { nodeTypes },
-// // // type: 'custom',
-// // style: {
-// // background: '#fff',
-// // border: '1px solid black',
-// // borderRadius: 15,
-// // fontSize: 12,
-// // },
-// // },
-// // {
-// // id: '2',
-// // data: {
-// // label: 'Node 2',
-// // toolbarPosition: Position.Top,
-// // currentFlow,
-// // },
-// // position: { x: 100, y: 200 },
-// // type: 'default',
-// // // type: { nodeTypes },
-// // // type: 'tooltip',
-// // // type: 'custom',
-// // style: {
-// // background: '#fff',
-// // border: '1px solid black',
-// // borderRadius: 15,
-// // fontSize: 12,
-// // },
-// // },
-// // ];
-
-// // const initialEdges = [
-// // { id: 'e11a', source: '1', target: '1a', type: edgeType, animated: true },
-// // ];
-// // const { prompt } = usePromptAnswer();
-
-// const [search] = useSearchParams();
-// // const edgeSet = new Set(); // tsx
-// const edgeSet = new Set();
-// // const edgeSet = new Set(); // tsx
-// const nodeSet = new Set();
-// const [isLoading, setIsLoading] = useState(true);
-// const [nodeState, setNodes, onNodesChange] = useNodesState(initialNodes);
-// const [edgeState, setEdges, onEdgesChange] = useEdgesState(initialEdges);
-// const [title, onChangeTitle, setTitle] = useInput('');
-// const [desc, onChangeDesc, setDesc] = useInput('');
-// const [gptRes, setGptRes] = useState([]);
-// const [confirmDelete, setConfirmDelete] = useState(false);
-
-// const [nodeBg, setNodeBg] = useState('#eee');
-// const [nodeHidden, setNodeHidden] = useState(false);
-// const [rfInstance, setRfInstance] = useState(null);
-// const { setViewport } = useReactFlow();
-// const [useGpt, setUseGpt] = useState(false);
-// const [opened, { open, close }] = useDisclosure(false);
-// const [nodeModal, setNodeModal] = useState(false);
-
-// const [selectedData, setSelectedData] = useState([
-// { value: 'react', label: 'React' },
-// { value: 'ng', label: 'Angular' },
-// ]);
-// const { user } = useUser();
-// // const [files, setFiles] = useState([]); // tsx
-// const [files, setFiles] = useState([]);
-// const navigate = useNavigate();
-// // eslint-disable-next-line consistent-return
-// useEffect(() => {
-// if (!user) {
-// return navigate('/users/signin');
-// }
-// // const getRecentGpt = localStorage.getItem('recent_gpt_search');
-// // console.log(getRecentGpt);
-
-// // axios.post(`${baseUrl}/chat?prompt=${}`,{ headers: {
-// }, []);
-
-// // useEffect(()=>{
-
-// // },[])
-
-// const proOptions = { hideAttribution: true };
-
-// // const { x, y, zoom } = useViewport();
-
-// // useEffect(() => {
-// // console.log(x, y, zoom);
-// // }, [x, y, zoom]);
-// // useEffect(() => {
-// // if (search.size > 0 && gptRes.length === 0) {
-// // axios
-// // .post(`${baseUrl}/chat?prompt=${search.get('title')}`, {
-// // headers: {
-// // 'Content-Type': 'application/json',
-// // Authorization: `Bearer ${user?.accessToken}`,
-// // },
-// // })
-// // .then((res) => {
-// // // setGptRes(res?.data);
-// // // const { data } = prompt;
-// // // const dataCopy = [...data];
-// // // console.log(dataCopy);
-// // // console.log('res.data', res.data);
-
-// // // eslint-disable-next-line array-callback-return
-// // });
-// // }
-// // }, []);
-// // if (getStoredRoadmap()) {
-// // const { edges, nodes, viewport } = getStoredRoadmap();
-// // setNodes(nodes);
-// // setEdges(edges);
-// // setViewport(viewport);
-
-// // return;
-// // }
-// // console.log(search.get('title'));
-
-// // if (prompt && search.size > 0 && prompt.keyword === search.get('title')) {
-// // // if (useGpt && search.size > 0) {
-// // // gpt 자동생성
-
-// // const { data } = prompt;
-// // const dataCopy = [...data];
-// // // console.log(dataCopy);
-
-// // // eslint-disable-next-line array-callback-return
-// // dataCopy.map((v) => {
-// // if (!nodeSet.has(v?.id)) {
-// // initialNodes.push({
-// // id: v?.id,
-// // data: {
-// // label: v?.content,
-// // },
-// // type: 'default',
-// // position,
-// // style: {
-// // background: '#fff',
-// // border: '1px solid black',
-// // borderRadius: 15,
-// // fontSize: 12,
-// // },
-// // });
-// // nodeSet.add(`${v?.id}`);
-// // }
-
-// // // source랑 target 구해서 간선id 만들고 이어주기
-// // // parseInt는 오로지 숫자인 부분만 parse해줬음
-
-// // if (v.id !== `${parseInt(v?.id, 10)}`) {
-// // if (!edgeSet.has(`e${parseInt(v?.id, 10)}${v?.id}`)) {
-// // initialEdges.push({
-// // id: `e${parseInt(v?.id, 10)}${v?.id}`,
-// // source: `${parseInt(v?.id, 10)}`,
-// // target: v.id,
-// // type: edgeType,
-// // animated: true,
-// // });
-// // }
-// // edgeSet.add(`e${parseInt(v?.id, 10)}${v?.id}`);
-// // }
-// // });
-// // setNodes(initialNodes);
-// // setEdges(initialEdges);
-// // // search.size !== 0 ? setNodes(initialNodes) : setNodes([]);
-// // // search.size !== 0 ? setEdges(initialEdges) : setEdges([]);
-// // if (search.size !== 0) {
-// // // onLayout('TB');
-// // onLayout('LR');
-// // }
-// // >>>>>>> initialmerge
-
-// // useEffect(() => {
-// // // console.log(gptRes);
-
-// // }, []);
-// // gptRes.map((v) => {
-// // if (!nodeSet.has(v?.id)) {
-// // initialNodes.push({
-// // id: v?.id,
-// // data: {
-// // label: v?.content,
-// // },
-// // type: 'default',
-// // position,
-// // style: {
-// // background: '#fff',
-// // border: '1px solid black',
-// // borderRadius: 15,
-// // fontSize: 12,
-// // },
-// // });
-// // nodeSet.add(`${v?.id}`);
-// // }
-
-// // // source랑 target 구해서 간선id 만들고 이어주기
-// // // parseInt는 오로지 숫자인 부분만 parse해줬음
-
-// // if (v.id !== `${parseInt(v?.id, 10)}`) {
-// // if (!edgeSet.has(`e${parseInt(v?.id, 10)}${v?.id}`)) {
-// // initialEdges.push({
-// // id: `e${parseInt(v?.id, 10)}${v?.id}`,
-// // source: `${parseInt(v?.id, 10)}`,
-// // target: v.id,
-// // type: edgeType,
-// // animated: true,
-// // });
-// // }
-// // edgeSet.add(`e${parseInt(v?.id, 10)}${v?.id}`);
-// // }
-// // });
-// // setNodes(initialNodes);
-// // setEdges(initialEdges);
-// // if (search.size !== 0) {
-// // // onLayout('TB');
-// // onLayout('LR');
-// // }
-// // }, [gptRes]);
-
-// useMemo(() => {
-// // 노드 내용 수정
-// setNodes((nds) =>
-// nds.map((node) => {
-// // if (node.id === '1') {
-// if (node.id === id) {
-// // it's important that you create a new object here
-// // in order to notify react flow about the change
-// // eslint-disable-next-line no-param-reassign
-// node.data = {
-// ...node.data,
-// label,
-// };
-// }
-
-// return node;
-// }),
-// );
-// }, [label, id]);
-// // }, [label, id]);
-// // useMemo(() => {
-// // setNodes((nds) =>
-// // nds.map((node) => {
-// // // if (node.id === '1') {
-// // if (node.id === id) {
-// // // it's important that you create a new object here
-// // // in order to notify react flow about the change
-// // // eslint-disable-next-line no-param-reassign
-// // node.data = {
-// // ...node.data,
-// // label: nodeName,
-// // };
-// // }
-// // console.log(node);
-
-// // return node;
-// // }),
-// // );
-// // }, [label, nodeName]);
-
-// useMemo(() => {
-// // console.log('roadmapeditor props', editor);
-// setNodes([...nodeState]);
-// // eslint-disable-next-line react-hooks/exhaustive-deps
-// }, [editor]);
-
-// const onConnect = useCallback(
-// (params) => {
-// setEdges((els) => addEdge(params, els));
-// },
-// [setEdges],
-// );
-
-// // const onSave = useCallback(() => {
-// // if (rfInstance) {
-// // const flow = rfInstance.toObject();
-// // localStorage.setItem(flowKey, JSON.stringify(flow));
-// // console.log(flow);
-// // }
-// // }, [rfInstance]);
-
-// // useCallback(() => {
-// useMemo(() => {
-// if (rfInstance) {
-// const flow = rfInstance.toObject();
-// localStorage.setItem(flowKey, JSON.stringify(flow));
-// setStoredRoadmap(flow);
-// }
-// }, [rfInstance]);
-
-// const onRestore = useCallback(() => {
-// const restoreFlow = async () => {
-// const flowStr = localStorage.getItem(flowKey);
-// if (flowStr) {
-// const flow = JSON.parse(flowStr);
-// const {
-// x = 0,
-// y = 0,
-// zoom = 1,
-// nodes: restoredNodes,
-// edges: restoredEdges,
-// } = flow;
-// setNodes(restoredNodes || []);
-// setEdges(restoredEdges || []);
-// setViewport({ x, y, zoom });
-// // console.log(flowStr);
-// }
-// };
-
-// restoreFlow();
-// // roadmap.getRoadmap(title, desc, flowKey);
-// }, [setNodes, setEdges, setViewport]);
-// // const onClickItem = useCallback((e) => {
-// // console.log(e);
-// // }, []);
-
-// // const onLayout = useCallback(
-// // (direction) => {
-// // const { nodes: layoutedNodes, edges: layoutedEdges } =
-// // getLayoutedElements(nodes, edges, direction);
-
-// // setNodes([...layoutedNodes]);
-// // setEdges([...layoutedEdges]);
-// // },
-// // [nodes, edges, setEdges, setNodes],
-// // );
-
-// const onLayout = useCallback(
-// (direction) => {
-// const { nodes: layoutedNodes, edges: layoutedEdges } =
-// getLayoutedElements(nodeState, edgeState, direction);
-
-// setNodes([...layoutedNodes]);
-// setEdges([...layoutedEdges]);
-// },
-// [nodeState, edgeState, setEdges, setNodes],
-// );
-
-// const onAddNode = useCallback(() => {
-// // const nodeCount: number = [...nodeState]?.length; // tsx
-// const nodeCount = [...nodeState]?.length;
-// setNodes([
-// ...nodeState,
-// {
-// // TODO : 노드id 는 '1a' 형식이다. 자식 노드면 '1a'지만 '1'의 형제 노드면 '2'가 된다
-// // label에 들어가는 데이터가 에러를 발생시키는 걸 해결하자.
-// id: (nodeCount + 1).toString(),
-// data: {
-// label: ``,
-// toolbarPosition: Position.Top,
-// currentFlow,
-// },
-// type: 'default',
-// // type: 'ResizableNodeSelected',
-// // type: { nodeTypes },
-// // type: 'tooltip',
-// // type: 'custom',
-// position,
-// // position: { x, y },
-// style: {
-// background: '#fff',
-// border: '1px solid black',
-// borderRadius: 15,
-// fontSize: 12,
-// },
-// },
-// ]);
-// }, [nodeState, setNodes]);
-
-// const { postRoadmap } = useRoadmap();
-
-// const onPublishRoadmap = useCallback(() => {
-// const { edges, nodes, viewport } = getStoredRoadmap();
-// console.log('nodes', nodes);
-// const nodesCopy = [...nodes];
-// const edgesCopy = [...edges];
-// nodesCopy.map((v) => {
-// state.map((item) => {
-// if (v?.id === item?.id) {
-// // console.log('onPublish', item.details);
-// // eslint-disable-next-line no-param-reassign
-// v.detailedContent = item?.details;
-// // eslint-disable-next-line no-param-reassign
-// // v.targetPosition = item.targetPosition;
-// // // eslint-disable-next-line no-param-reassign
-// // v.sourcePosition = item.sourcePosition;
-// }
-// });
-// });
-// edgesCopy.map((v) => {
-// // eslint-disable-next-line no-param-reassign
-// v.animated = true;
-// // eslint-disable-next-line no-param-reassign
-// v.type = edgeType;
-// });
-
-// const data = {
-// roadmap: {
-// title: roadMapTitle,
-// description: desc,
-// thumbnailUrl: '',
-// tag: roadmapTag,
-// },
-// nodes: nodesCopy,
-// edges: edgesCopy,
-// viewport: defaultViewport,
-// };
-// postRoadmap(data);
-// // navigate('/');
-// // eslint-disable-next-line react-hooks/exhaustive-deps
-// }, [nodeState]);
-
-// // const { deleteElements } = useReactFlow();
-// const useRemoveNode = useCallback(() => {
-// setNodes((nds) => nds.filter((node) => node?.id !== label));
-// }, [label]);
-
-// useEffect(() => {
-// setNodes((nds) =>
-// nds.map((node) => {
-// if (node?.id === id) {
-// // eslint-disable-next-line no-param-reassign
-// node.style = { ...node.style, backgroundColor: nodeBg };
-// }
-
-// return node;
-// }),
-// );
-// // };
-// }, [nodeState, nodeBg, id]);
-
-// useMemo(() => {
-// if (edgeState && nodeState) {
-// return;
-// }
-// onLayout('TB');
-
-// // eslint-disable-next-line react-hooks/exhaustive-deps
-// }, [edgeState, nodeState]);
-
-// useEffect(() => {
-// setNodes((nds) =>
-// nds.map((node) => {
-// if (node?.id === id) {
-// // when you update a simple type you can just update the value
-// // eslint-disable-next-line no-param-reassign
-// node.hidden = nodeHidden;
-// }
-// return node;
-// }),
-// );
-// setNodes((nds) =>
-// nds.map((node) => {
-// // if (node.id === '1') {
-// if (node?.id === label) {
-// // when you update a simple type you can just update the value
-// // eslint-disable-next-line no-param-reassign
-// node.data.label = label;
-// // console.log(node.data.label);
-// }
-// return node;
-// }),
-// );
-// }, [nodeState, edgeState]);
-// // }, [nodeState, edgeState, setNodes, id, nodeHidden, label]);
-// // setEdges((eds) =>
-// // eds.map((edge) => {
-// // // if (edge.id === 'e1-2') {
-// // if (edge.id === 'e11a') {
-// // // console.log(edge);
-// // // if (parseInt(edge.id, 10) === label) {
-// // // eslint-disable-next-line no-param-reassign
-// // edge.hidden = nodeHidden;
-// // }
-
-// // return edge;
-// // }),
-// // );
-
-// const previews = files.map((file, index) => {
-// const imageUrl = URL.createObjectURL(file);
-// return (
-// URL.revokeObjectURL(imageUrl) }}
-// />
-// );
-// });
-
-// // search.size >0 && return ();
-// // eslint-disable-next-line consistent-return
-// useMemo(() => {
-// // useEffect(() => {
-// if (search.size > 0) {
-// return ;
-// }
-// if (gptRes.length > 0) {
-// gptRes.map((v) => {
-// if (!nodeSet.has(v?.id)) {
-// initialNodes.push({
-// id: v?.id,
-// data: {
-// label: v?.content,
-// toolbarPosition: Position.Left,
-// },
-// type: 'default',
-// // type: 'ResizableNodeSelected',
-// // type: { nodeTypes },
-// // type: 'tooltip',
-// position,
-// style: {
-// background: '#fff',
-// border: '1px solid black',
-// borderRadius: 15,
-// fontSize: 12,
-// },
-// });
-// nodeSet.add(`${v?.id}`);
-// }
-
-// // source랑 target 구해서 간선id 만들고 이어주기
-// // parseInt는 오로지 숫자인 부분만 parse해줬음
-
-// if (v?.id !== `${parseInt(v?.id, 10)}`) {
-// if (!edgeSet.has(`e${parseInt(v?.id, 10)}${v?.id}`)) {
-// initialEdges.push({
-// id: `e${parseInt(v?.id, 10)}${v?.id}`,
-// source: `${parseInt(v?.id, 10)}`,
-// target: v.id,
-// type: edgeType,
-// animated: true,
-// });
-// }
-// edgeSet.add(`e${parseInt(v?.id, 10)}${v?.id}`);
-// }
-// });
-// setNodes(initialNodes);
-// setEdges(initialEdges);
-// if (search.size !== 0) {
-// // onLayout('TB');
-// onLayout('LR');
-// }
-// }
-// }, [gptRes]);
-
-// useMemo(() => {
-// console.log(currentFlow);
-// }, [currentFlow]);
-// const [selectedNode, setSelectedNode] = useState([]);
-// useOnSelectionChange({
-// onChange: ({ nodes, edges }) => {
-// // console.log('changed selection', nodes);
-// setSelectedNode(nodes);
-// // if (nodes.length > 0) {
-// setNodeModal(true);
-// // }
-// },
-// // console.log('changed selection', nodes, edges),
-// // console.log('changed selection', edges),
-// });
-
-// return (
-//
-//
-//
-// 로드맵 정보
-//
-//
-// {/*
-//
-//
-// */}
-// `+ Create ${query}`}
-// onCreate={(query) => {
-// const item = { value: query, label: query };
-// setSelectedData((current) => [...current, item]);
-// return item;
-// }}
-// />
-//
-// Drop images here
-//
-// 0 ? 'xl' : 0}
-// >
-// {previews}
-//
-//
-//
-//
-//
-//
-// setConfirmDelete(false)}
-// >
-//
-//
-// 정말로 모든 노드를 지우겠습니까?
-// 모두 지우기를 누를 시 작업 내용을 복구할 수 없습니다.
-//
-//
-//
-//
-//
-//
-//
-//
-// setNodeModal(false)} size="xl">
-//
-//
-// nodes
-// {JSON.stringify(selectedNode[0])}
-// {
-// setLabel(evt.target.value);
-// }}
-// />
-//
-//
-
-// {selectedNode[0]?.id && toggleEditor}
-
-//
-//
-//
-//
-//
-// {isLoading ? (
-//
-// ) : (
-// {
-// setLabel(`${n?.data?.label}`);
-// setId(`${n?.id}`);
-// }}
-// attributionPosition="bottom-left"
-// fitView
-// zoomOnDoubleClick
-// elevateNodesOnSelect
-// snapToGrid
-// proOptions={proOptions}
-// onInit={setRfInstance}
-// nodeTypes={nodeTypes}
-// style={{
-// width: '100%',
-// height: '100%',
-// backgroundColor: '#ebf6fc',
-// opacity: '80%',
-// }}
-// >
-// {/*
-//
-//
{
-// setLabel(evt.target.value);
-// }}
-// />
-//
-//
-//
-//
background:
-//
setNodeBg(evt.target.value)}
-// />
-
-//
-//
hidden:
-//
setNodeHidden(evt.target.checked)}
-// />
-//
-//
-// */}
-//
-// {currentFlow === 'TB' ? (
-//
-// ) : (
-//
-// )}
-
-//
-// {nodeState.length === 0 ? (
-//
-// ) : (
-//
-// )}
-//
-//
-//
-//
-//
-//
-//
-// )}
-//
-// );
-// }
-// const Wrap = styled.div`
-// width: 100%;
-// height: 90vh;
-// & .updatenode__controls {
-// position: absolute;
-// right: 10px;
-// top: 10px;
-// z-index: 4;
-// font-size: 12px;
-// }
-
-// & .updatenode__controls label {
-// display: block;
-// }
-
-// & .updatenode__bglabel {
-// margin-top: 10px;
-// }
-
-// & .updatenode__checkboxwrapper {
-// margin-top: 10px;
-// display: flex;
-// align-items: center;
-// }
-
-// & .confirm_btn_wrap {
-// display: inline-flex;
-// width: 100%;
-// }
-// `;
-// export default function RoadMapCanvas({
-// editor,
-// label,
-// roadMapTitle,
-// roadmapImage,
-// toggleEditor,
-// roadmapDescription,
-// roadmapTag,
-// setLabel,
-// onRoadMapTitleChange,
-// setRoadMapTitle,
-// id,
-// onChangeLabel,
-// setState,
-// state,
-// onChangeId,
-// setId,
-// }) {
-// return (
-//
-//
-//
-// );
-// }
diff --git a/src/components/editor/RoadMapEditor.tsx b/src/components/editor/RoadMapEditor.tsx
index 4931fe4..e41d897 100644
--- a/src/components/editor/RoadMapEditor.tsx
+++ b/src/components/editor/RoadMapEditor.tsx
@@ -1,7 +1,6 @@
-/* eslint-disable no-console */
-/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-unused-vars */
-/* eslint-disable array-callback-return */
+/* eslint-disable react-hooks/exhaustive-deps */
+// @ts-nocheck
import 'reactflow/dist/style.css';
import dagre from '@dagrejs/dagre';
@@ -12,27 +11,36 @@ import {
ColorInput,
Image,
Input,
- LoadingOverlay,
Modal,
- MultiSelect,
Popover,
SimpleGrid,
Text,
Textarea,
TextInput,
+ Tooltip,
} from '@mantine/core';
import { Dropzone, FileWithPath, IMAGE_MIME_TYPE } from '@mantine/dropzone';
import { useDisclosure } from '@mantine/hooks';
-import { IconWand } from '@tabler/icons-react';
+import {
+ IconAlertCircle,
+ IconCertificate,
+ IconInfoCircle,
+ IconWand,
+} from '@tabler/icons-react';
import axios from 'axios';
import { baseUrl } from 'axiosInstance/constants';
+import { Typer } from 'components/common/typingAnimation/Typer';
import { useRoadmap } from 'components/roadmaps/posts/hooks/useRoadmap';
import { useUser } from 'components/user/hooks/useUser';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import ReactFlow, {
+ addEdge,
Background,
+ Connection,
+ ConnectionLineType,
Controls,
+ Edge,
MiniMap,
Panel,
ReactFlowProvider,
@@ -44,46 +52,57 @@ import { setStoredRoadmap } from 'storage/roadmap-storage';
import { styled } from 'styled-components';
import { useInput } from '../common/hooks/useInput';
+import PanelItem from './PanelItem';
import { ResizableNodeSelected } from './ResizableNodeSelected';
import { RoadmapEdge, RoadmapNode, RoadmapNodes } from './types';
const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({}));
-const nodeWidth = 172;
-const nodeHeight = 36;
+// const nodeWidth = 172;
+// const nodeHeight = 36;
+const nodeWidth = 200;
+const nodeHeight = 40;
const flowKey = 'example-flow';
-
-const getLayoutedElements = (nodes, edges, direction = 'TB') => {
- const isHorizontal = direction === 'LR';
+// const getLayoutedElements = (nodes: any[], edges: any[], direction = 'LR') => {
+const getLayoutedElements = (nodes, edges, direction = 'LR') => {
+ const isHorizontal = direction === 'TB';
dagreGraph.setGraph({ rankdir: direction });
- nodes.forEach((node) => {
+ nodes.forEach((node: { id: string }) => {
dagreGraph.setNode(node?.id, { width: nodeWidth, height: nodeHeight });
});
- edges.forEach((edge) => {
- dagreGraph.setEdge(edge?.source, edge?.target);
- });
+ edges.forEach(
+ // (edge: { source: dagre.Edge; target: string | { [key: string]: any } }) => {
+ (edge: { source: dagre.Edge; target: string | { [key: string] } }) => {
+ dagreGraph.setEdge(edge?.source, edge?.target);
+ },
+ );
dagre.layout(dagreGraph);
- nodes.forEach((node) => {
- const nodeWithPosition = dagreGraph.node(node?.id);
- // eslint-disable-next-line no-param-reassign
- node.targetPosition = isHorizontal ? 'left' : 'top';
- // eslint-disable-next-line no-param-reassign
- node.sourcePosition = isHorizontal ? 'right' : 'bottom';
- // We are shifting the dagre node position (anchor=center center) to the top left
- // so it matches the React Flow node anchor point (top left).
- // eslint-disable-next-line no-param-reassign
- node.position = {
- x: nodeWithPosition.x - nodeWidth / 2,
- y: nodeWithPosition.y - nodeHeight / 2,
- };
- return node;
- });
+ nodes.forEach(
+ (node: {
+ id?: string | dagre.Label;
+ targetPosition: string;
+ sourcePosition: string;
+ position: { x: number; y: number };
+ }) => {
+ const nodeWithPosition = dagreGraph.node(node?.id);
+ // eslint-disable-next-line no-param-reassign
+ node.targetPosition = isHorizontal ? 'top' : 'left';
+ // eslint-disable-next-line no-param-reassign
+ node.sourcePosition = isHorizontal ? 'bottom' : 'right';
+ // eslint-disable-next-line no-param-reassign
+ node.position = {
+ x: nodeWithPosition.x - nodeWidth / 2,
+ y: nodeWithPosition.y - nodeHeight / 2,
+ };
+ return node;
+ },
+ );
return { nodes, edges };
};
@@ -92,48 +111,98 @@ const position = { x: 0, y: 0 };
const edgeType = 'smoothstep';
const nodeTypes = {
custom: ResizableNodeSelected,
- // ResizableNodeSelected,
- // custom: CustomNode,
};
-// const onInit = (reactFlowInstance) => {
-// reactFlowInstance.fitView();
-// };
const initialNodes = [
{
id: '1',
- data: { label: 'test' },
- position: { x: 100, y: 100, zoom: 1 },
+ data: { label: '내용을 입력해주세요.' },
+ position: { x: 100, y: 100, zoom: 0.45 },
type: 'custom',
- // type: 'default',
- // type: nodeTypes.custom,
- // type: 'ResizableNodeSelected',
style: {
- background: '#fff',
+ background: '#f4b4b4',
border: '1px solid black',
borderRadius: 15,
- fontSize: 12,
+ fontSize: 24,
},
+ blogKeyword: '',
},
{
id: '2',
- data: { label: 'Node 2' },
- position: { x: 100, y: 200, zoom: 1 },
- // type: 'default',
- // type: nodeTypes.custom,
+ data: { label: '내용을 입력해주세요.' },
+ position: { x: 100, y: 200, zoom: 0.45 },
type: 'custom',
style: {
- background: '#fff',
+ background: '#f4e9bc',
border: '1px solid black',
borderRadius: 15,
- fontSize: 12,
+ fontSize: 24,
},
+ blogKeyword: '',
},
];
const initialEdges = [
- { id: 'e11a', source: '1', target: '1a', type: edgeType, animated: true },
+ { id: 'e1e2', source: '1', target: '2', type: edgeType, animated: true },
];
-const defaultViewport = { x: 0, y: 0, zoom: 1.5 };
+const defaultViewport = { x: 0, y: 0, zoom: 0.45 };
+
+const colorPalette = {
+ blues: [
+ '#7296a495',
+ '#9ebecb3a',
+ '#cddee54f',
+ '#e6f0f291F2',
+ '#F3FDFE',
+ '#EFEFEF',
+ ],
+ reds: [
+ '#b2737680',
+ '#d37c7caf7c',
+ '#ecb0b09ab0',
+ '#f4d5d5b9',
+ '#f7d4ccb8',
+ '#f5dad4bcd4',
+ ],
+ yellows: [
+ '#f3e7b7bb',
+ '#f6eac2b3c2',
+ '#f9f2d6',
+ '#fbf6e3',
+ '#fdf9ed',
+ '#f8f0d2',
+ ],
+ greens: [
+ '#90a76ac4',
+ '#c4de96cf',
+ '#a7cc79b8c9',
+ '#deffccb6',
+ '#cbe4b3bf',
+ '#deedcebe',
+ ],
+ oranges: [
+ '#f9ab36c9',
+ '#faa84d',
+ '#fbbc74ab',
+ '#fbd4a4a3',
+ '#fbe6b4d1',
+ '#fbe4c4cd',
+ ],
+};
+
+const selectColor = (clr, cnt) => {
+ switch (parseInt(clr?.id.slice(0, 1), 10 % 5)) {
+ case 0:
+ return colorPalette.reds[cnt % 5];
+ case 1:
+ return colorPalette.reds[cnt % 5];
+ case 2:
+ return colorPalette.oranges[cnt % 5];
+ case 3:
+ return colorPalette.yellows[cnt % 5];
+ default:
+ return colorPalette.blues[cnt % 5];
+ }
+};
function Roadmap({
editor,
@@ -150,6 +219,9 @@ function Roadmap({
onRoadMapTitleChange,
setRoadMapTitle,
setLabel,
+ blogKeyword,
+ onChangeBlogKeyword,
+ setBlogKeyword,
toggleEditor,
onChangeLabel,
id,
@@ -172,9 +244,10 @@ function Roadmap({
const [desc, onChangeDesc, setDesc] = useInput('');
const [gptRes, setGptRes] = useState(true);
const [confirmDelete, setConfirmDelete] = useState(false);
+ const [keywordSubmitState, setKeywordSubmitState] = useState(false);
const [isLoading, setIsLoading] = useState(false);
+ const [isLoadingDetailGpt, setIsLoadingDetailGpt] = useState(true);
const [nodeBg, setNodeBg] = useState('#eee');
- const [nodeHidden, setNodeHidden] = useState(false);
const [rfInstance, setRfInstance] = useState(null);
const { setViewport, getViewport } = useReactFlow();
const [useGpt, setUseGpt] = useState([]);
@@ -183,19 +256,17 @@ function Roadmap({
const [nodeModal, setNodeModal] = useState(false);
const [keyword, setKeyword] = useState('');
const [currentFlow, setCurrentFlow] = useState('');
+ const [isDetailReady, setIsDetailReady] = useState(4);
+ const [details, setDetails] = useState([]);
const [gptDisabled, setGptDisabled] = useState(false);
- const [currentView, setCurrentView] = useState({ x: 0, y: 0, zoom: 1 });
+ const [currentView, setCurrentView] = useState({ x: 0, y: 0, zoom: 0.45 });
const yPos = useRef(currentView.y);
- const [selectedData, setSelectedData] = useState([
- { value: 'react', label: 'React' },
- { value: 'ng', label: 'Angular' },
- ]);
const { user } = useUser();
const [files, setFiles] = useState([]); // 썸네일
const navigate = useNavigate();
const onLayout = useCallback(
- (direction) => {
+ (direction: string) => {
const { nodes: layoutedNodes, edges: layoutedEdges } =
getLayoutedElements(nodeState, edgeState, direction);
@@ -211,30 +282,6 @@ function Roadmap({
if (!user) {
return navigate('/users/signin');
}
- // if (!localStorage.getItem('recent_gpt_search')) {
- // setGptRes(false);
- // }
- // if (localStorage.getItem('recent_gpt_search')) {
- // const localData: NewPrompt = JSON.parse(
- // localStorage.getItem('recent_gpt_search'),
- // );
- // setKeyword(localData?.keyword);
- // axios
- // .post(`${baseUrl}/gpt/roadmap?prompt=${localData.keyword}`, {
- // headers: {
- // 'Content-Type': 'application/json',
- // Authorization: `Bearer ${user?.accessToken}`,
- // },
- // })
- // .then((res) => {
- // res?.data.length > 0 ? setGptRes(false) : setGptRes(true);
- // setUseGpt(res?.data);
- // })
- // .then(() => {
- // setGptRes(false);
- // // onLayout('TB');
- // });
- // }
if (!localStorage.getItem('recent_gpt_search')) {
setGptRes(false);
}
@@ -254,93 +301,98 @@ function Roadmap({
.then((res) => {
res?.data.length > 0 ? setGptRes(false) : setGptRes(true);
setUseGpt(res?.data);
- // onLayout('TB');
})
- // .then(() => {
- // onLayout('TB');
- // })
.then(() => {
setGptRes(false);
+ setGptDisabled(false);
});
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+ useMemo(() => {
+ if (isDetailReady >= 4) {
+ axios
+ .post(`${baseUrl}/gpt/roadmap/detail`, useGpt.slice(0, 10), {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${user?.accessToken}`,
+ },
+ })
+ .then((e) => {
+ if (e.data.length > 0) {
+ setDetails(e.data);
+ }
+ });
+ // .catch((err) => // console.log(err));
+ }
+ }, [isDetailReady]);
+
useEffect(() => {
onLayout('LR');
}, [useGpt.length]);
- // const onSave = useCallback(() => { // 내부적으로 처리
- // if (rfInstance) {
- // const flow = rfInstance.toObject();
- // localStorage.setItem(flowKey, JSON.stringify(flow));
- // console.log(flow);
- // }
- // }, [rfInstance]);
useMemo(() => {
const tmpNode = [];
const tmpEdge = [];
- // console.log(useGpt);
+ let cnt = 0;
+ // // console.log(useGpt);
// eslint-disable-next-line array-callback-return
- useGpt.map((v) => {
- if (!nodeSet.has(v?.id)) {
+ useGpt.map((v, indx) => {
+ if (!nodeSet.has(v?.id) && v?.id.split('.')[0] !== '0') {
tmpNode.push({
id: v?.id,
data: {
label: v?.content,
},
- // type: 'default',
type: 'custom',
position,
+ blogKeyword: '',
style: {
- background: '#fff',
+ background: `${selectColor(v, cnt)}`,
border: '1px solid black',
borderRadius: 15,
- fontSize: 12,
+ fontSize: 24,
},
});
nodeSet.add(`${v?.id}`);
+ cnt += 1;
}
// source랑 target 구해서 간선id 만들고 이어주기
- // parseInt는 오로지 숫자인 부분만 parse해줬음
-
- if (v.id !== `${parseInt(v?.id, 10)}`) {
- if (!edgeSet.has(`e${parseInt(v?.id, 10)}${v?.id}`)) {
+ if (v?.id.split('.').length > 1 && v?.id.split('.')[0] !== '0') {
+ // head인 경우
+ if (!edgeSet.has(`e${v.id.slice(0, v.id.lastIndexOf('.'))}e${v.id}`)) {
tmpEdge.push({
- id: `e${parseInt(v?.id, 10)}${v?.id}`,
- source: `${parseInt(v?.id, 10)}`,
+ id: `e${v.id.slice(0, v.id.lastIndexOf('.'))}e${v.id}`,
+ source: v.id.slice(0, v.id.lastIndexOf('.')),
target: v.id,
type: edgeType,
animated: true,
});
}
- edgeSet.add(`e${parseInt(v?.id, 10)}${v?.id}`);
+ edgeSet.add(`e${v.id.slice(0, v.id.lastIndexOf('.'))}e${v.id}`);
}
});
setNodes(tmpNode);
setEdges(tmpEdge);
}, [useGpt]);
- // useEffect(() => {
- // // 자동 생성 후 formatting
- // if (nodeState && edgeState && useGpt.length > 0) {
- // onLayout('TB');
- // }
- // }, []);
+ useMemo(() => {
+ // console.log(nodeState.length);
+ if (nodeState.length > initialNodes.length) {
+ setIsDetailReady(nodeState.length);
+ setIsLoadingDetailGpt(false);
+ }
+ }, [nodeState.length]);
const proOptions = { hideAttribution: true };
- // const { x, y, zoom } = useViewport();
-
useMemo(() => {
// 노드 내용 수정
setNodes((nds) =>
nds.map((node) => {
// if (node.id === '1') {
if (node.id === id) {
- // it's important that you create a new object here
- // in order to notify react flow about the change
// eslint-disable-next-line no-param-reassign
node.data = {
...node.data,
@@ -364,24 +416,34 @@ function Roadmap({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [editor]);
+ // const onConnect = useCallback(
+ // // 간선 스타일 통일
+ // ({ source, target }) => {
+ // setEdges((els) => {
+ // return [
+ // ...els,
+ // {
+ // // id: `e${source}e${target}`, // 문제 : e122면 12랑 2인지 1이랑 22인지 구분이 안됨..
+ // id: `e${source}${target}`,
+ // source,
+ // target,
+ // type: edgeType,
+ // animated: true,
+ // },
+ // ];
+ // });
+ // },
+ // [setEdges],
+ // );
const onConnect = useCallback(
- // 간선 스타일 통일
- ({ source, target }) => {
- setEdges((els) => {
- return [
- ...els,
- {
- // id: `e${source}e${target}`, // 문제 : e122면 12랑 2인지 1이랑 22인지 구분이 안됨..
- id: `e${source}${target}`,
- source,
- target,
- type: edgeType,
- animated: true,
- },
- ];
- });
- },
- [setEdges],
+ (params: Edge | Connection) =>
+ setEdges((eds) =>
+ addEdge(
+ { ...params, type: ConnectionLineType.SmoothStep, animated: true },
+ eds,
+ ),
+ ),
+ [],
);
// useMemo(() => {
@@ -392,6 +454,14 @@ function Roadmap({
// }
// }, [currentView, nodeState.length]);
+ useMemo(() => {
+ if (useGpt.length > 0 && details.length > 0) {
+ setState(details);
+ // // console.log('details', details);
+ // // console.log('state', state);
+ }
+ }, [useGpt, details]);
+
useMemo(() => {
if (rfInstance) {
const flow = rfInstance.toObject();
@@ -408,13 +478,13 @@ function Roadmap({
const {
x = 0,
y = 0,
- zoom = 1,
+ zoom = 0.45,
nodes: restoredNodes,
edges: restoredEdges,
} = flow;
setNodes(restoredNodes || []);
setEdges(restoredEdges || []);
- setViewport({ x, y, zoom });
+ setViewport({ x, y, zoom: 0.45 });
}
};
@@ -444,18 +514,18 @@ function Roadmap({
background: '#fff',
border: '1px solid black',
borderRadius: 15,
- fontSize: 12,
+ fontSize: 24,
},
},
]);
nodeState.forEach((n) => {
- // console.log(n);
+ // // console.log(n);
// eslint-disable-next-line no-param-reassign
n.sourcePosition = nodeState[0].sourcePosition;
// eslint-disable-next-line no-param-reassign
n.targetPosition = nodeState[0].targetPosition;
});
- // console.log(state); // 노드 추가!
+ // // console.log(state); // 노드 추가!
setState([...state, { id: (nodeCount + 1).toString(), details: '' }]);
setColorsState([
...state,
@@ -463,29 +533,68 @@ function Roadmap({
]);
}, [nodeState, setNodes]);
+ // useMemo(() => {
+ // const copyNodes = [...nodeState];
+ // copyNodes.forEach((n) => {
+ // if (n.id === id) {
+ // // console.log('keyword?', n);
+ // // eslint-disable-next-line no-param-reassign
+ // // n.blogKeyword = blogKeyword;
+ // // !blogKeyword ? (n.blogKeyword = blogKeyword) : (n.blogKeyword = '');
+ // }
+ // });
+ // setNodes(copyNodes);
+ // }, [id, blogKeyword]);
+
const { postRoadmap } = useRoadmap();
const onPublishRoadmap = useCallback(() => {
- // const { edges, nodes, viewport } = getStoredRoadmap();
- // console.log('nodes', nodes);
+ // const onPublishRoadmap = useCallback(() => {
+ if (edgeState.length === 0) {
+ setSubmitModal(false);
+ return;
+ }
const nodesCopy = [...nodeState] as RoadmapNodes;
const edgesCopy = [...edgeState];
+
+ // eslint-disable-next-line consistent-return
+ nodesCopy.forEach((node) => {
+ if (node.data.label === '') {
+ setSubmitModal(false);
+ // eslint-disable-next-line no-alert
+ return alert('노드 내용을 채워주세요.');
+ }
+ });
+
+ if (files[0].length) return;
+
// eslint-disable-next-line array-callback-return
- nodesCopy.map((v) => {
- state.map((item) => {
+ nodesCopy.forEach((v) => {
+ state.forEach((item: { id: string; detailedContent: string }) => {
if (v?.id === item?.id) {
// eslint-disable-next-line no-param-reassign
- v.detailedContent = item?.details;
- // console.log(item?.details);
+ v.detailedContent = item?.detailedContent;
+ // // console.log(item?.details);
// v.details = item?.details;
}
// eslint-disable-next-line no-param-reassign
v.positionAbsolute = v.position;
+ // eslint-disable-next-line no-param-reassign
+ // v.blogKeyword = item.blogKeyword;
});
// eslint-disable-next-line no-param-reassign
+ v.blogKeyword = ''; // ******************************************* 블로그 인증 키워드
+ // eslint-disable-next-line no-param-reassign
v.type = 'custom';
+ // v.type = 'default';
+ // v.sourcePosition = currentFlow === 'LR' ? 'left' : 'top';
+ // eslint-disable-next-line no-param-reassign
+ v.sourcePosition = currentFlow === 'LR' ? 'bottom' : 'right';
+ // eslint-disable-next-line no-param-reassign
+ v.targetPosition = currentFlow === 'LR' ? 'top' : 'left';
});
- edgesCopy.map((v) => {
+
+ edgesCopy.forEach((v) => {
// eslint-disable-next-line no-param-reassign
v.animated = true;
// eslint-disable-next-line no-param-reassign
@@ -502,7 +611,9 @@ function Roadmap({
},
nodes: nodesCopy,
edges: edgesCopy,
- viewport: defaultViewport,
+ // viewport: defaultViewport,
+ // viewport: currentView,
+ viewport: { x: 0, y: 0, zoom: 0.45 },
};
axios
@@ -513,7 +624,7 @@ function Roadmap({
},
})
.then((e) => {
- // console.log('e', e.data);
+ // // console.log('e', e.data);
// const blob = new Blob([JSON.stringify(data)], {
// type: 'multipart/form-data',
// });
@@ -521,6 +632,11 @@ function Roadmap({
const formData = new FormData();
formData.append('file', files[0]);
+
+ // eslint-disable-next-line no-debugger
+ debugger;
+ // console.log(files[0]);
+
axios
.post(`${baseUrl}/roadmaps/${e.data}/thumbnails`, formData, {
headers: {
@@ -529,16 +645,31 @@ function Roadmap({
},
})
.then((v) => {
- // console.log(v);
+ // // console.log(v);
// eslint-disable-next-line no-alert
alert('포스팅 성공!');
- navigate(`/roadmap/post/${e.data}`);
- })
- .catch((err) => console.log(err));
- })
- .catch((err) => console.log(err));
+
+ axios
+ .post(
+ `${baseUrl}/roadmaps/${e.data}/join`,
+ {},
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${user?.accessToken}`,
+ },
+ },
+ )
+ .then(() => {
+ navigate(`/roadmap/post/${e.data}`);
+ });
+ // .catch((err) => // console.log(err));
+ });
+ // .catch((err) => // console.log(err));
+ });
+ // .catch((err) => // console.log(err));
// navigate('/');
- // eslint-disable-next-line react-hooks/exhaustive-deps
+ // }, [nodeState, files[0]]);
}, [nodeState]);
// const { deleteElements } = useReactFlow();
@@ -560,7 +691,7 @@ function Roadmap({
// },
// )
// .then((e) => {
- // // console.log(e);
+ // // // console.log(e);
// // @ts-ignore
// const resDetail: string = e?.content;
// if (resDetail) {
@@ -569,7 +700,7 @@ function Roadmap({
// const temp = [];
// copyState.map((v) => {
// if (v.id === id) {
- // console.log('현재 content', v?.details);
+ // // console.log('현재 content', v?.details);
// resArr.map((k) => {
// temp.push(`${k}
`);
// });
@@ -577,7 +708,7 @@ function Roadmap({
// v.details += temp;
// }
// });
- // console.log('현재 copyState', copyState);
+ // // console.log('현재 copyState', copyState);
// // setState(copyState);
// // setGptDisabled(false);
// }
@@ -586,7 +717,7 @@ function Roadmap({
// // setState(e?.content);
// // 상세 내용 에디터에 내용 넣어주기
// })
- // .catch((err) => console.log(err));
+ // .catch((err) => // console.log(err));
// };
const getGptExampleDetail = useCallback(() => {
setGptDisabled(true);
@@ -603,26 +734,44 @@ function Roadmap({
)
.then((e) => {
// @ts-ignore
- const resDetail: string = e?.data?.content;
+ const resDetail: string = e?.data?.detailedContent;
if (resDetail) {
const copyState = [...state];
- console.log('state', state);
- copyState.map((v) => {
+ copyState.forEach((v) => {
if (v.id === id) {
- console.log('현재 content', v?.details);
+ // // console.log('현재 content', v?.detailedContent);
// eslint-disable-next-line no-param-reassign
- v.details += resDetail;
+ v.detailedContent += resDetail;
}
});
- console.log('현재 copyState', copyState);
+ // // console.log('copyState', copyState);
setState(copyState);
setGptDisabled(false);
}
// 상세 내용 에디터에 내용 넣어주기
- })
- .catch((err) => console.log(err));
+ });
+ // .catch((err) => // console.log(err));
}, [id, state]);
+ useEffect(() => {
+ setNodes((nds) =>
+ nds.map((node) => {
+ if (node?.id === id) {
+ // node.style = { ...node.style, backgroundColor: nodeBg };
+ // eslint-disable-next-line no-param-reassign
+ node.style = {
+ ...node.style,
+ // backgroundColor: color,
+ background: color,
+ };
+ }
+
+ return node;
+ }),
+ );
+ // };
+ }, [nodeState, blogKeyword, id]);
+
useEffect(() => {
setNodes((nds) =>
nds.map((node) => {
@@ -651,22 +800,14 @@ function Roadmap({
}, [edgeState, nodeState]);
useEffect(() => {
- // setNodes((nds) =>
- // nds.map((node) => {
- // if (node?.id === id) {
- // // when you update a simple type you can just update the value
- // // eslint-disable-next-line no-param-reassign
- // node.hidden = nodeHidden;
- // }
- // return node;
- // }),
- // );
setNodes((nds) =>
nds.map((node) => {
// if (node.id === '1') {
if (node?.id === label) {
// when you update a simple type you can just update the value
// eslint-disable-next-line no-param-reassign
+ // node.blogKeyword = blogKeyword; // 노드 키워드
+ // eslint-disable-next-line no-param-reassign
node.style.backgroundColor = color; // 노드 색 변경
// eslint-disable-next-line no-param-reassign
node.data.label = label; // 노드 내용 변경
@@ -676,6 +817,7 @@ function Roadmap({
);
}, [nodeState, edgeState]);
+ // const previews = files.map((file, index) => {
const previews = files.map((file, index) => {
const imageUrl = URL.createObjectURL(file);
return (
@@ -687,21 +829,43 @@ function Roadmap({
);
});
- // const [selectedNode, setSelectedNode] = useState([]);
- // useOnSelectionChange({
- // onChange: ({ nodes, edges }) => {
- // // setSelectedNode(nodes);
- // console.log('selectedNode', selectedNode);
- // // setNodeModal(true);
+ // // @ts-ignore
+ const imgFileDropHandler = (e) => {
+ setFiles(e);
+ };
+ // const imgFileDropHandler = useMemo(
+ // (e) => {
+ // return setFiles(e);
// },
- // });
+ // [e],
+ // );
return (
-
- {/* */}
+ {gptRes && (
+
+
+
+
+
+
+

+
+
+
+
+
+ )}
setSubmitModal(false)}
size="40rem"
>
@@ -714,31 +878,19 @@ function Roadmap({
label="로드맵 이름"
value={title}
onChange={onChangeTitle}
+ rightSection={
+ !title && (
+
+
+
+
+
+ )
+ }
/>
- `+ Create ${query}`}
- onCreate={(query) => {
- const item = { value: query, label: query };
- setSelectedData((current) => [...current, item]);
- return item;
- }}
- />
-
- Drop images here
-
- 0 ? 'xl' : 0}
- >
- {previews}
-
- setConfirmDelete(false)}
+ centered
>
-
-
- 정말로 모든 노드를 지우겠습니까?
+
+
+
+
+ 정말로 모든 노드를 지우겠습니까?
+
+
+
+
모두 지우기를 누를 시 작업 내용을 복구할 수 없습니다.
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
setNodeModal(false)} size="xl">
@@ -807,7 +1035,6 @@ function Roadmap({
shadow="md"
opened={opened}
>
- {/* */}
{
setLabel(label);
+ // // console.log(label);
getGptExampleDetail();
}}
loading={gptDisabled}
@@ -826,7 +1054,13 @@ function Roadmap({
-
+
ChatGpt로 자동 생성하기
@@ -836,12 +1070,13 @@ function Roadmap({
value={label}
mt={10}
mb={10}
+ // onChange={onChangeLabel}
onChange={(evt) => {
setLabel(evt?.target?.value);
}}
+ onBlur={(evt) => setLabel(label)}
placeholder="내용을 입력해주세요."
/>
- {/* ; */}
-
+
}
- // value={label}
+ icon={}
+ value={blogKeyword}
+ // onChange={onChangeBlogKeyword}
+ onChange={(evt) => {
+ setBlogKeyword(evt?.target?.value);
+ }}
mt={10}
mb={10}
+ disabled={keywordSubmitState}
+ rightSection={
+
+
+
+ }
// onChange={(evt) => {
// setLabel(evt?.target?.value);
// }}
- placeholder="블로그 키워드를 입력해주세요."
+ placeholder="css"
/>
{/*
- */}
+
{/* {
@@ -884,21 +1133,9 @@ function Roadmap({
selectedNode[0].data.label = evt.target.value;
}}
/> */}
+ 로드맵 상세 내용
{toggleEditor}
-
- {/* {selectedNode[0]?.id === id && toggleEditor} */}
-
-
-
-
{
setLabel(`${n?.data?.label}`);
setId(n?.id);
setColor(n?.style?.background);
-
- // setSelectedNode(n);
- console.log('n', n);
- console.log('e', e);
+ // // console.log('n', n);
+ // // console.log('e', e);
+ // setBlogKeyword(blogKeyword);
+ // if (details.length > 0 && !isLoadingDetailGpt) {
+ // setState(details);
+ // }
setNodeModal(true);
- // console.log('selectedNode', selectedNode);
+ // // console.log('selectedNode', selectedNode);
}}
attributionPosition="bottom-left"
fitView
zoomOnDoubleClick
elevateNodesOnSelect
snapToGrid
+ connectionLineType={ConnectionLineType.SmoothStep}
proOptions={proOptions}
onInit={setRfInstance}
nodeTypes={nodeTypes}
@@ -938,81 +1178,19 @@ function Roadmap({
}}
>
- {currentFlow === 'LR' ? (
-
- ) : (
-
- )}
-
- {nodeState.length === 0 ? (
-
- ) : (
-
- )}
- {/* */}
-
+
@@ -1021,9 +1199,25 @@ function Roadmap({
);
}
+const CustomLabel = styled.div`
+ display: inline-block;
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: #212529;
+ word-break: break-word;
+ cursor: default;
+`;
const Wrap = styled.div`
width: 100%;
- height: 93.2vh;
+ height: 91vh;
+
+ /* .react-flow__node {
+ min-width: fit-content;
+ } */
+
+ & .gptModal {
+ z-index: 1000 !important;
+ }
& .updatenode__controls {
position: absolute;
right: 10px;
@@ -1036,12 +1230,16 @@ const Wrap = styled.div`
display: block;
}
- & .updatenode__bglabel {
+ /* & .updatenode__bglabel {
margin-top: 10px;
+ } */
+
+ & .react-flow__panel {
+ display: inline-flex;
}
& .updatenode__checkboxwrapper {
- margin-top: 10px;
+ /* margin-top: 10px; */
display: flex;
align-items: center;
}
@@ -1060,6 +1258,9 @@ export default function RoadMapCanvas({
roadmapDescription,
roadmapTag,
setLabel,
+ blogKeyword,
+ onChangeBlogKeyword,
+ setBlogKeyword,
onRoadMapTitleChange,
setRoadMapTitle,
id,
@@ -1097,6 +1298,9 @@ export default function RoadMapCanvas({
roadmapTag={roadmapTag}
setRoadMapTitle={setRoadMapTitle}
setLabel={setLabel}
+ blogKeyword={blogKeyword}
+ onChangeBlogKeyword={onChangeBlogKeyword}
+ setBlogKeyword={setBlogKeyword}
state={state}
onChangeId={onChangeId}
onChangeLabel={onChangeLabel}
diff --git a/src/components/editor/custom/CustomNode.jsx b/src/components/editor/custom/CustomNode.jsx
deleted file mode 100644
index a950aac..0000000
--- a/src/components/editor/custom/CustomNode.jsx
+++ /dev/null
@@ -1,135 +0,0 @@
-// /* eslint-disable no-console */
-// /* eslint-disable @typescript-eslint/no-unused-vars */
-// /* eslint-disable no-unused-vars */
-// import { ActionIcon } from '@mantine/core';
-// import { IconPencil } from '@tabler/icons-react';
-// import { Highlight } from '@tiptap/extension-highlight';
-// import { Link } from '@tiptap/extension-link';
-// import { Placeholder } from '@tiptap/extension-placeholder';
-// import { Subscript } from '@tiptap/extension-subscript';
-// import { Superscript } from '@tiptap/extension-superscript';
-// import { TextAlign } from '@tiptap/extension-text-align';
-// import { Underline } from '@tiptap/extension-underline';
-// import { useEditor } from '@tiptap/react';
-// import StarterKit from '@tiptap/starter-kit';
-// import { useInput } from 'components/common/hooks/useInput';
-// import { memo, useMemo, useState } from 'react';
-// import { Handle, NodeToolbar } from 'reactflow';
-
-// function CustomNode({
-// data,
-// id,
-// targetPosition,
-// sourcePosition,
-// toggleEditor,
-// }) {
-// const [state, setState] = useState([
-// { id: '1', details: '' },
-// { id: '2', details: '' },
-// ]);
-// const [toggle, onChangeToggle, setToggle] = useInput('');
-// const editor = useEditor({
-// extensions: [
-// StarterKit, // history handled by yjs if set to true
-// Placeholder.configure({
-// placeholder: '로드맵 상세 내용을 입력해주세요.',
-// }),
-// Underline,
-// Link,
-// Superscript,
-// Subscript,
-// Highlight,
-// TextAlign.configure({ types: ['heading', 'paragraph'] }),
-// ],
-// // content:'',
-// content: state.filter((v) => v?.id === id)[0]?.details || '',
-
-// onUpdate(e) {
-// // console.log('ydoc', ydoc);
-// // console.log('ytext', ytext);
-// // console.log(e.editor?.getHTML());
-// setToggle(e.editor?.getHTML());
-// // console.log('e.editor', e.editor);
-// // eslint-disable-next-line array-callback-return
-// state.map((item, idx) => {
-// if (item?.id !== id) return;
-// // console.log('state.map, item ,label', label);
-
-// const copyState = [...state];
-// // copyState.splice(idx, 1, {
-// copyState.splice(idx, 1, {
-// id: item?.id,
-// details: e.editor?.getHTML(),
-// });
-// setState(copyState);
-// });
-// },
-// });
-// useMemo(() => {
-// const filt = state.filter((v) => v?.id === id);
-// // console.log(filt);
-// setToggle(filt);
-// if (editor) {
-// // mount 시 에러
-// editor.commands.setContent(filt[0]?.details, false, {
-// preserveWhitespace: 'full', // 빈칸 인식 X 에러 해결
-// });
-// }
-
-// if (id !== '' && filt.length === 0) {
-// setState([...state, { id, details: '' }]);
-// }
-// // console.log('state', state);
-// // eslint-disable-next-line react-hooks/exhaustive-deps
-// }, [state, id, setToggle, id, editor]);
-
-// return (
-// <>
-// {id && toggleEditor}
-// {
-// console.log('custom targetPosition', targetPosition);
-// console.log('custom sourcePosition', sourcePosition);
-// }}
-// >
-//
-//
-//
-//
-// {data.label}
-// {/* */}
-// {/* */}
-//
-//
-// >
-// );
-// }
-
-// export default memo(CustomNode);
diff --git a/src/components/editor/custom/MultiSelectionToolbar.jsx b/src/components/editor/custom/MultiSelectionToolbar.jsx
deleted file mode 100644
index 0352c74..0000000
--- a/src/components/editor/custom/MultiSelectionToolbar.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { NodeToolbar, useStore } from 'reactflow';
-
-const selectedNodesSelector = (state) =>
- Array.from(state.nodeInternals.values())
- .filter((node) => node.selected)
- .map((node) => node.id);
-
-export default function MultiSelectionToolbar() {
- const selectedNodeIds = useStore(selectedNodesSelector);
- const isVisible = selectedNodeIds.length > 1;
-
- return (
-
-
-
- );
-}
diff --git a/src/components/editor/custom/TooltipNode.jsx b/src/components/editor/custom/TooltipNode.jsx
deleted file mode 100644
index 08547e0..0000000
--- a/src/components/editor/custom/TooltipNode.jsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { ActionIcon } from '@mantine/core';
-import { IconPencil } from '@tabler/icons-react';
-import { memo, useState } from 'react';
-import { Handle, NodeToolbar, Position } from 'reactflow';
-
-function TooltipNode({ data }) {
- const [isVisible, setVisible] = useState(false);
-
- return (
- setVisible(true)}
- onMouseLeave={() => setVisible(false)}
- >
-
-
-
-
-
-
{data.label}
-
-
-
- );
-}
-
-export default memo(TooltipNode);
diff --git a/src/components/editor/editorCopy.jsx b/src/components/editor/editorCopy.jsx
deleted file mode 100644
index ead0913..0000000
--- a/src/components/editor/editorCopy.jsx
+++ /dev/null
@@ -1,1015 +0,0 @@
-// /* eslint-disable no-console */
-// /* eslint-disable react-hooks/exhaustive-deps */
-// /* eslint-disable @typescript-eslint/no-unused-vars */
-// /* eslint-disable array-callback-return */
-// import 'reactflow/dist/style.css';
-
-// import dagre from '@dagrejs/dagre';
-// import {
-// ActionIcon,
-// Button,
-// Center,
-// ColorInput,
-// Image,
-// Input,
-// LoadingOverlay,
-// Modal,
-// MultiSelect,
-// Popover,
-// SimpleGrid,
-// Text,
-// Textarea,
-// TextInput,
-// } from '@mantine/core';
-// import { Dropzone, FileWithPath, IMAGE_MIME_TYPE } from '@mantine/dropzone';
-// import { useDisclosure } from '@mantine/hooks';
-// import { IconWand } from '@tabler/icons-react';
-// import axios from 'axios';
-// import { baseUrl } from 'axiosInstance/constants';
-// import { useRoadmap } from 'components/roadmaps/posts/hooks/useRoadmap';
-// import { useUser } from 'components/user/hooks/useUser';
-// import { useCallback, useEffect, useMemo, useState } from 'react';
-// import { useNavigate } from 'react-router-dom';
-// import ReactFlow, {
-// addEdge,
-// Background,
-// Controls,
-// MiniMap,
-// Panel,
-// ReactFlowProvider,
-// useEdgesState,
-// useNodesState,
-// useOnSelectionChange,
-// useReactFlow,
-// } from 'reactflow';
-// import { setStoredRoadmap } from 'storage/roadmap-storage';
-// import { styled } from 'styled-components';
-// import { NewPrompt } from 'types/types';
-
-// import { useInput } from '../common/hooks/useInput';
-// import { RoadmapEdge, RoadmapNode, RoadmapNodes } from './types';
-
-// const dagreGraph = new dagre.graphlib.Graph();
-// dagreGraph.setDefaultEdgeLabel(() => ({}));
-
-// const nodeWidth = 172;
-// const nodeHeight = 36;
-
-// const flowKey = 'example-flow';
-
-// const getLayoutedElements = (nodes, edges, direction = 'TB') => {
-// const isHorizontal = direction === 'LR';
-// dagreGraph.setGraph({ rankdir: direction });
-
-// nodes.forEach((node) => {
-// dagreGraph.setNode(node?.id, { width: nodeWidth, height: nodeHeight });
-// });
-
-// edges.forEach((edge) => {
-// dagreGraph.setEdge(edge?.source, edge?.target);
-// });
-
-// dagre.layout(dagreGraph);
-
-// nodes.forEach((node) => {
-// const nodeWithPosition = dagreGraph.node(node?.id);
-// // eslint-disable-next-line no-param-reassign
-// node.targetPosition = isHorizontal ? 'left' : 'top';
-// // eslint-disable-next-line no-param-reassign
-// node.sourcePosition = isHorizontal ? 'right' : 'bottom';
-// // We are shifting the dagre node position (anchor=center center) to the top left
-// // so it matches the React Flow node anchor point (top left).
-// // eslint-disable-next-line no-param-reassign
-// node.position = {
-// x: nodeWithPosition.x - nodeWidth / 2,
-// y: nodeWithPosition.y - nodeHeight / 2,
-// };
-// return node;
-// });
-
-// return { nodes, edges };
-// };
-
-// const position = { x: 0, y: 0 };
-// const edgeType = 'smoothstep';
-// // const nodeTypes = {
-// // ResizableNodeSelected,
-// // custom: CustomNode,
-// // };
-
-// const initialNodes = [
-// {
-// id: '1',
-// data: { label: 'test' },
-// position: { x: 100, y: 100 },
-// type: 'default',
-// style: {
-// background: '#fff',
-// border: '1px solid black',
-// borderRadius: 15,
-// fontSize: 12,
-// },
-// },
-// {
-// id: '2',
-// data: { label: 'Node 2' },
-// position: { x: 100, y: 200 },
-// type: 'default',
-// style: {
-// background: '#fff',
-// border: '1px solid black',
-// borderRadius: 15,
-// fontSize: 12,
-// },
-// },
-// ];
-
-// const initialEdges = [
-// { id: 'e11a', source: '1', target: '1a', type: edgeType, animated: true },
-// ];
-// const defaultViewport = { x: 0, y: 0, zoom: 1.5 };
-
-// function Roadmap({
-// editor,
-// label,
-// color,
-// onChangeColor,
-// setColor,
-// roadMapTitle,
-// roadmapImage,
-// roadmapDescription,
-// roadmapTag,
-// onRoadMapTitleChange,
-// setRoadMapTitle,
-// setLabel,
-// toggleEditor,
-// onChangeLabel,
-// id,
-// setState,
-// state,
-// onChangeId,
-// setId,
-// // selectedNode,
-// // setSelectedNode,
-// }) {
-// // const { prompt } = usePromptAnswer();
-
-// const edgeSet = new Set();
-// const nodeSet = new Set();
-// // const [nodeState, setNodes, onNodesChange] = useNodesState(initialNodes);
-// const [nodeState, setNodes, onNodesChange] = useNodesState(initialNodes);
-// const [edgeState, setEdges, onEdgesChange] = useEdgesState(initialEdges);
-// const [title, onChangeTitle, setTitle] = useInput(''); // 로드맵 제목
-// // const [thumbnail, onChangeThumbnail, setThumbnail] = useInput(''); // 썸네일
-// const [desc, onChangeDesc, setDesc] = useInput('');
-// const [gptRes, setGptRes] = useState(true);
-// const [confirmDelete, setConfirmDelete] = useState(false);
-// const [isLoading, setIsLoading] = useState(false);
-// const [nodeBg, setNodeBg] = useState('#eee');
-// const [nodeHidden, setNodeHidden] = useState(false);
-// const [rfInstance, setRfInstance] = useState(null);
-// const { setViewport } = useReactFlow();
-// const [useGpt, setUseGpt] = useState([]);
-// const [opened, { open, close }] = useDisclosure(false);
-// const [nodeModal, setNodeModal] = useState(false);
-// const [keyword, setKeyword] = useState('');
-// const [currentFlow, setCurrentFlow] = useState('');
-// const [gptDisabled, setGptDisabled] = useState(false);
-
-// const [selectedData, setSelectedData] = useState([
-// { value: 'react', label: 'React' },
-// { value: 'ng', label: 'Angular' },
-// ]);
-// const { user } = useUser();
-// const [files, setFiles] = useState([]); // 썸네일
-// const navigate = useNavigate();
-
-// const getAutoCreatedGpt = () => {
-// axios
-// .post(`${baseUrl}/gpt/roadmap?prompt=${keyword}`, {
-// headers: {
-// 'Content-Type': 'application/json',
-// Authorization: `Bearer ${user?.accessToken}`,
-// },
-// })
-// .then((res) => {
-// res?.data.length > 0 ? setGptRes(false) : setGptRes(true);
-// setUseGpt(res?.data);
-// })
-// .then(() => {
-// setGptRes(false);
-// // onLayout('TB');
-// })
-// .catch((err) => console.log(err));
-// };
-
-// const onLayout = useCallback(
-// (direction) => {
-// const { nodes: layoutedNodes, edges: layoutedEdges } =
-// getLayoutedElements(nodeState, edgeState, direction);
-
-// setNodes([...layoutedNodes]);
-// setEdges([...layoutedEdges]);
-// },
-// [nodeState, edgeState, setEdges, setNodes],
-// );
-
-// // eslint-disable-next-line consistent-return
-// useEffect(() => {
-// setGptRes(true);
-// if (!user) {
-// return navigate('/users/signin');
-// }
-// if (!localStorage.getItem('recent_gpt_search')) {
-// return setGptRes(false);
-// }
-// if (gptRes) {
-// console.log(localStorage.getItem('recent_gpt_search'));
-// const localData: NewPrompt = JSON.parse(
-// localStorage.getItem('recent_gpt_search'),
-// );
-// setKeyword(localData?.keyword);
-// }
-// }, []);
-
-// useEffect(() => {
-// // useMemo(() => {
-// if (gptRes && keyword) {
-// getAutoCreatedGpt();
-// onLayout('TB');
-// }
-// }, [keyword]);
-
-// const onSave = useCallback(() => {
-// // 내부적으로 처리
-// if (rfInstance) {
-// const flow = rfInstance.toObject();
-// localStorage.setItem(flowKey, JSON.stringify(flow));
-// console.log(flow);
-// }
-// }, [rfInstance]);
-
-// useMemo(() => {
-// const tmpNode = [];
-// const tmpEdge = [];
-// // console.log(useGpt);
-// // eslint-disable-next-line array-callback-return
-// // useGpt.map((v) => {
-// useGpt.forEach((v) => {
-// if (!nodeSet.has(v?.id)) {
-// tmpNode.push({
-// id: v?.id,
-// data: {
-// label: v?.content,
-// },
-// type: 'default',
-// position,
-// style: {
-// background: '#fff',
-// border: '1px solid black',
-// borderRadius: 15,
-// fontSize: 12,
-// },
-// });
-// nodeSet.add(`${v?.id}`);
-// }
-
-// // source랑 target 구해서 간선id 만들고 이어주기
-// // parseInt는 오로지 숫자인 부분만 parse해줬음
-
-// if (v.id !== `${parseInt(v?.id, 10)}`) {
-// if (!edgeSet.has(`e${parseInt(v?.id, 10)}${v?.id}`)) {
-// tmpEdge.push({
-// id: `e${parseInt(v?.id, 10)}${v?.id}`,
-// source: `${parseInt(v?.id, 10)}`,
-// target: v.id,
-// type: edgeType,
-// animated: true,
-// });
-// }
-// edgeSet.add(`e${parseInt(v?.id, 10)}${v?.id}`);
-// }
-// });
-// setNodes(tmpNode);
-// setEdges(tmpEdge);
-// onLayout('LR');
-// }, [useGpt]);
-
-// useEffect(() => {
-// // 자동 생성 후 formatting
-// if (!gptRes && edgeState) {
-// onLayout('LR');
-// }
-// // }, [gptRes, edgeState]);
-// }, [gptRes]);
-
-// const proOptions = { hideAttribution: true };
-
-// // const { x, y, zoom } = useViewport();
-
-// useMemo(() => {
-// // 노드 내용 수정
-// setNodes((nds) =>
-// nds.map((node) => {
-// // if (node.id === '1') {
-// if (node.id === id) {
-// // it's important that you create a new object here
-// // in order to notify react flow about the change
-// // eslint-disable-next-line no-param-reassign
-// node.data = {
-// ...node.data,
-// label,
-// };
-// }
-// return node;
-// }),
-// );
-// }, [label, id]);
-
-// useCallback(() => {
-// if (nodeState.length > 0 && edgeState.length > 0) {
-// onLayout('TB');
-// }
-// // eslint-disable-next-line react-hooks/exhaustive-deps
-// }, [nodeState, edgeState]);
-
-// useMemo(() => {
-// setNodes([...nodeState]);
-// // eslint-disable-next-line react-hooks/exhaustive-deps
-// }, [editor]);
-
-// const onConnect = useCallback(
-// (params) => {
-// setEdges((els) => addEdge(params, els));
-// },
-// [setEdges],
-// );
-
-// useMemo(() => {
-// if (rfInstance) {
-// const flow = rfInstance.toObject();
-// localStorage.setItem(flowKey, JSON.stringify(flow));
-// setStoredRoadmap(flow);
-// }
-// }, [rfInstance]);
-
-// const onRestore = useCallback(() => {
-// const restoreFlow = async () => {
-// const flowStr = localStorage.getItem(flowKey);
-// if (flowStr) {
-// const flow = JSON.parse(flowStr);
-// const {
-// x = 0,
-// y = 0,
-// zoom = 1,
-// nodes: restoredNodes,
-// edges: restoredEdges,
-// } = flow;
-// setNodes(restoredNodes || []);
-// setEdges(restoredEdges || []);
-// setViewport({ x, y, zoom });
-// }
-// };
-
-// restoreFlow();
-// }, [setNodes, setEdges, setViewport]);
-
-// const onAddNode = useCallback(() => {
-// const nodeCount: number = [...nodeState]?.length;
-// setNodes([
-// ...nodeState,
-// {
-// // TODO : 노드id 는 '1a' 형식이다. 자식 노드면 '1a'지만 '1'의 형제 노드면 '2'가 된다
-// // label에 들어가는 데이터가 에러를 발생시키는 걸 해결하자.
-// id: (nodeCount + 1).toString(),
-// data: {
-// label: ``,
-// },
-// type: 'default',
-// position,
-// // position: { x, y },
-// style: {
-// background: '#fff',
-// border: '1px solid black',
-// borderRadius: 15,
-// fontSize: 12,
-// },
-// },
-// ]);
-// // console.log(state); // 노드 추가!
-// setState([...state, { id: (nodeCount + 1).toString(), details: '' }]);
-// }, [nodeState, setNodes]);
-
-// const { postRoadmap } = useRoadmap();
-
-// const onPublishRoadmap = useCallback(() => {
-// // const { edges, nodes, viewport } = getStoredRoadmap();
-// // console.log('nodes', nodes);
-// const nodesCopy = [...nodeState] as RoadmapNodes;
-// const edgesCopy = [...edgeState];
-// // eslint-disable-next-line array-callback-return
-// nodesCopy.map((v) => {
-// // eslint-disable-next-line array-callback-return
-// state.map((item) => {
-// if (v?.id === item?.id) {
-// // eslint-disable-next-line no-param-reassign
-// v.detailedContent = item?.details;
-// // console.log(item?.details);
-// // v.details = item?.details;
-// }
-// // eslint-disable-next-line no-param-reassign
-// v.positionAbsolute = v.position;
-// });
-// });
-// edgesCopy.map((v) => {
-// // eslint-disable-next-line no-param-reassign
-// v.animated = true;
-// // eslint-disable-next-line no-param-reassign
-// v.type = edgeType;
-// });
-
-// const roadmapData = {
-// roadmap: {
-// title,
-// description: desc,
-// // thumbnailUrl: files,
-// // thumbnailUrl: '',
-// // tag: roadmapTag,
-// },
-// nodes: nodesCopy,
-// edges: edgesCopy,
-// viewport: defaultViewport,
-// };
-
-// axios
-// .post(`${baseUrl}/roadmaps`, roadmapData, {
-// headers: {
-// 'Content-Type': 'application/json',
-// Authorization: `Bearer ${user?.accessToken}`,
-// },
-// })
-// .then((e) => {
-// // console.log('e', e.data);
-// // const blob = new Blob([JSON.stringify(data)], {
-// // type: 'multipart/form-data',
-// // });
-
-// const formData = new FormData();
-
-// formData.append('file', files[0]);
-// axios
-// .post(`${baseUrl}/roadmaps/${e.data}/thumbnails`, formData, {
-// headers: {
-// 'Content-Type': 'multipart/form-data',
-// Authorization: `Bearer ${user?.accessToken}`,
-// },
-// })
-// .then((v) => {
-// // console.log(v);
-// // eslint-disable-next-line no-alert
-// alert('포스팅 성공!');
-// navigate(`/roadmap/post/${e.data}`);
-// })
-// .catch((err) => console.log(err));
-// })
-// .catch((err) => console.log(err));
-// // navigate('/');
-// // eslint-disable-next-line react-hooks/exhaustive-deps
-// }, [nodeState]);
-
-// // const { deleteElements } = useReactFlow();
-// // const useRemoveNode = useCallback(() => {
-// // setNodes((nds) => nds.filter((node) => node?.id !== label));
-// // }, [label]);
-
-// // const getGptExampleDetail = () => {
-// // setGptDisabled(true);
-// // axios
-// // .post(
-// // `${baseUrl}/gpt/detail?course=${label}`,
-// // {},
-// // {
-// // headers: {
-// // 'Content-Type': 'application/json',
-// // Authorization: `Bearer ${user?.accessToken}`,
-// // },
-// // },
-// // )
-// // .then((e) => {
-// // // console.log(e);
-// // // @ts-ignore
-// // const resDetail: string = e?.content;
-// // if (resDetail) {
-// // const resArr: Array = resDetail.split('.');
-// // const copyState = [...state];
-// // const temp = [];
-// // copyState.map((v) => {
-// // if (v.id === id) {
-// // console.log('현재 content', v?.details);
-// // resArr.map((k) => {
-// // temp.push(`${k}
`);
-// // });
-// // // eslint-disable-next-line no-param-reassign
-// // v.details += temp;
-// // }
-// // });
-// // console.log('현재 copyState', copyState);
-// // // setState(copyState);
-// // // setGptDisabled(false);
-// // }
-// // setGptDisabled(false);
-
-// // // setState(e?.content);
-// // // 상세 내용 에디터에 내용 넣어주기
-// // })
-// // .catch((err) => console.log(err));
-// // };
-// const getGptExampleDetail = useCallback(() => {
-// setGptDisabled(true);
-// axios
-// .post(
-// `${baseUrl}/gpt/detail?course=${label}`,
-// {},
-// {
-// headers: {
-// 'Content-Type': 'application/json',
-// Authorization: `Bearer ${user?.accessToken}`,
-// },
-// },
-// )
-// .then((e) => {
-// // @ts-ignore
-// const resDetail: string = e?.data?.content;
-// if (resDetail) {
-// const resArr: Array = resDetail.split('.\n');
-// const copyState = [...state];
-// console.log('state', state);
-// const temp = [];
-// copyState.map((v) => {
-// if (v.id === id) {
-// console.log('현재 content', v?.details);
-// resArr.map((k) => {
-// temp.push(`${k}
`);
-// });
-// // eslint-disable-next-line no-param-reassign
-// v.details += temp;
-// }
-// });
-// console.log('현재 copyState', copyState);
-// setState(copyState);
-// setGptDisabled(false);
-// }
-// // setGptDisabled(false);
-
-// // setState(e?.content);
-// // 상세 내용 에디터에 내용 넣어주기
-// })
-// .catch((err) => console.log(err));
-// }, [id, state]);
-
-// useEffect(() => {
-// setNodes((nds) =>
-// nds.map((node) => {
-// if (node?.id === id) {
-// // node.style = { ...node.style, backgroundColor: nodeBg };
-// // eslint-disable-next-line no-param-reassign
-// node.style = {
-// ...node.style,
-// // backgroundColor: color,
-// background: color,
-// };
-// }
-
-// return node;
-// }),
-// );
-// }, [nodeState, color, id]);
-
-// // useMemo(() => {
-// // if (edgeState && nodeState) {
-// // return;
-// // }
-// // onLayout('TB');
-// // // eslint-disable-next-line react-hooks/exhaustive-deps
-// // }, [edgeState, nodeState]);
-
-// useEffect(() => {
-// // setNodes((nds) =>
-// // nds.map((node) => {
-// // if (node?.id === id) {
-// // // when you update a simple type you can just update the value
-// // // eslint-disable-next-line no-param-reassign
-// // node.hidden = nodeHidden;
-// // }
-// // return node;
-// // }),
-// // );
-// setNodes((nds) =>
-// nds.map((node) => {
-// // if (node.id === '1') {
-// if (node?.id === label) {
-// // when you update a simple type you can just update the value
-// // eslint-disable-next-line no-param-reassign
-// node.style.backgroundColor = color; // 노드 색 변경
-// // eslint-disable-next-line no-param-reassign
-// node.data.label = label; // 노드 내용 변경
-// }
-// return node;
-// }),
-// );
-// }, [nodeState, edgeState]);
-
-// const previews = files.map((file, index) => {
-// const imageUrl = URL.createObjectURL(file);
-// return (
-// URL.revokeObjectURL(imageUrl) }}
-// />
-// );
-// });
-
-// // const [selectedNode, setSelectedNode] = useState([]);
-// useOnSelectionChange({
-// onChange: ({ nodes, edges }) => {
-// // setSelectedNode(nodes);
-// console.log('nodes', nodes);
-// // console.log('selectedNode', selectedNode);
-// // setNodeModal(true);
-// },
-// });
-
-// return (
-//
-//
-//
-//
-// 로드맵 정보
-//
-//
-// `+ Create ${query}`}
-// onCreate={(query) => {
-// const item = { value: query, label: query };
-// setSelectedData((current) => [...current, item]);
-// return item;
-// }}
-// />
-//
-// Drop images here
-//
-// 0 ? 'xl' : 0}
-// >
-// {previews}
-//
-//
-//
-//
-//
-//
-// setConfirmDelete(false)}
-// >
-//
-//
-// 정말로 모든 노드를 지우겠습니까?
-// 모두 지우기를 누를 시 작업 내용을 복구할 수 없습니다.
-//
-//
-//
-//
-//
-//
-//
-//
-// setNodeModal(false)} size="xl">
-//
-//
-// 상세내용
-//
-
-//
-// {/* */}
-//
-//
-// {
-// getGptExampleDetail();
-// }}
-// loading={gptDisabled}
-// >
-//
-//
-//
-//
-//
-// ChatGpt로 자동 생성하기
-//
-//
-
-//
}
-// value={label}
-// mt={10}
-// mb={10}
-// onChange={(evt) => {
-// setLabel(evt?.target?.value);
-// }}
-// placeholder="내용을 입력해주세요."
-// />
-// {/*
; */}
-//
{
-// setColor(evt);
-// console.log('color', evt);
-// }}
-// placeholder="Pick color"
-// label="노드의 배경색을 골라주세요."
-// />
-// {/* {
-// // eslint-disable-next-line no-param-reassign
-// // selectedNode[0].style.background = evt.target.value;
-// }}
-// />
-// */}
-// {/* {
-// selectedNode[0].data.label = evt.target.value;
-// }}
-// />
-// {
-// selectedNode[0].data.label = evt.target.value;
-// }}
-// /> */}
-// {toggleEditor}
-
-// {/* {selectedNode[0]?.id === id && toggleEditor} */}
-
-//
-//
-//
-//
-//
-//
-// {
-// setLabel(`${n?.data?.label}`);
-// setId(n?.id);
-// setColor(n?.style?.backgroundColor);
-// // setSelectedNode(n);
-// console.log('n', n);
-// setNodeModal(true);
-// // console.log('selectedNode', selectedNode);
-// }}
-// attributionPosition="bottom-left"
-// fitView
-// zoomOnDoubleClick
-// elevateNodesOnSelect
-// snapToGrid
-// proOptions={proOptions}
-// onInit={setRfInstance}
-// // nodeTypes={nodeTypes}
-// style={{
-// width: '100%',
-// height: '100%',
-// backgroundColor: '#ebf6fc',
-// opacity: '80%',
-// }}
-// >
-//
-// {currentFlow === 'LR' ? (
-//
-// ) : (
-//
-// )}
-//
-// {nodeState.length === 0 ? (
-//
-// ) : (
-//
-// )}
-// {/* */}
-//
-//
-//
-//
-//
-//
-//
-// );
-// }
-// const Wrap = styled.div`
-// width: 100%;
-// height: 93.2vh;
-// & .updatenode__controls {
-// position: absolute;
-// right: 10px;
-// top: 10px;
-// z-index: 4;
-// font-size: 12px;
-// }
-
-// & .updatenode__controls label {
-// display: block;
-// }
-
-// & .updatenode__bglabel {
-// margin-top: 10px;
-// }
-
-// & .updatenode__checkboxwrapper {
-// margin-top: 10px;
-// display: flex;
-// align-items: center;
-// }
-
-// & .confirm_btn_wrap {
-// display: inline-flex;
-// width: 100%;
-// margin: 1rem 0;
-// }
-// `;
-// export default function RoadMapCanvas({
-// editor,
-// label,
-// roadMapTitle,
-// roadmapImage,
-// toggleEditor,
-// roadmapDescription,
-// roadmapTag,
-// setLabel,
-// onRoadMapTitleChange,
-// setRoadMapTitle,
-// id,
-// onChangeLabel,
-// setState,
-// state,
-// onChangeId,
-// setId,
-// color,
-// onChangeColor,
-// setColor,
-// // selectedNode,
-// // setSelectedNode,
-// }) {
-// return (
-//
-//
-//
-// );
-// }
diff --git a/src/components/editor/stash/RoadMapEditor.tsx b/src/components/editor/stash/RoadMapEditor.tsx
deleted file mode 100644
index 53bec4c..0000000
--- a/src/components/editor/stash/RoadMapEditor.tsx
+++ /dev/null
@@ -1,193 +0,0 @@
-// /* eslint-disable @typescript-eslint/no-unused-vars */
-// /* eslint-disable no-console */
-// import { RichTextEditor } from '@mantine/tiptap';
-// import { Highlight } from '@tiptap/extension-highlight';
-// import { Link } from '@tiptap/extension-link';
-// import { Placeholder } from '@tiptap/extension-placeholder';
-// import { Subscript } from '@tiptap/extension-subscript';
-// import { Superscript } from '@tiptap/extension-superscript';
-// import { TextAlign } from '@tiptap/extension-text-align';
-// import { Underline } from '@tiptap/extension-underline';
-// import { useEditor } from '@tiptap/react';
-// import StarterKit from '@tiptap/starter-kit';
-// import MainLayout from 'layout/mainLayout';
-// import { ReactElement, useMemo, useState } from 'react';
-// import { useSearchParams } from 'react-router-dom';
-// import { ReactFlowProvider } from 'reactflow';
-// import { styled } from 'styled-components';
-
-// import { useInput } from '../../../components/common/hooks/useInput';
-// import RoadMapCanvas from '../../../components/editor/RoadMapEditor';
-
-// export default function RoadMapEditor(): ReactElement {
-// const [label, onChangeLabel, setLabel] = useInput('');
-// const [id, onChangeId, setId] = useInput('');
-// const [toggle, onChangeToggle, setToggle] = useInput('');
-// const [search] = useSearchParams();
-// const [state, setState] = useState([
-// { id: '1', details: '' },
-// { id: '2', details: '' },
-// ]);
-// const [roadmapDescription, setRoadmapDescription] = useState('');
-// const [roadmapImage, setRoadmapImage] = useState('');
-// const [roadmapTag, setRoadmapTag] = useState('');
-// const [selectedNode, setSelectedNode] = useState([]);
-// // eslint-disable-next-line @typescript-eslint/no-unused-vars
-// const [roadMapTitle, onRoadMapTitleChange, setRoadMapTitle] = useInput('');
-
-// const editor = useEditor({
-// extensions: [
-// StarterKit, // history handled by yjs if set to true
-// Placeholder.configure({
-// placeholder: '로드맵 상세 내용을 입력해주세요.',
-// }),
-// Underline,
-// Link,
-// Superscript,
-// Subscript,
-// Highlight,
-// TextAlign.configure({ types: ['heading', 'paragraph'] }),
-// ],
-// // content: state.filter((v) => v?.id === id)[0]?.details || '',
-// // content: state.filter((v) => v?.id === id)[0]?.details || '',
-// content: '',
-
-// onUpdate(e) {
-// console.log(e.editor?.getHTML());
-// setToggle(e.editor?.getHTML());
-// console.log('e.editor', e.editor);
-// // eslint-disable-next-line array-callback-return
-// state.map((item, idx) => {
-// if (item?.id !== id) return;
-// const copyState = [...state];
-// copyState.splice(idx, 1, {
-// id: item?.id,
-// details: e.editor?.getHTML(),
-// });
-// setState(copyState);
-// });
-// },
-// });
-
-// useMemo(() => {
-// const filt = state.filter((v) => v?.id === id);
-// console.log('filt', filt);
-// setToggle(filt);
-// if (editor) {
-// // mount 시 에러
-// editor.commands.setContent(filt[0]?.details, true, {
-// preserveWhitespace: 'full', // 빈칸 인식 X 에러 해결
-// });
-// }
-
-// if (label !== '' && filt.length === 0) {
-// setState([...state, { id, details: '' }]);
-// }
-// }, [state, id, label, editor, setToggle]);
-
-// const toggleEditor = useMemo(() => {
-// // const toggleEditor = useCallback(() => {
-// return (
-// id === toggle[0]?.id && (
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-
-//
-//
-//
-//
-//
-//
-
-//
-//
-//
-//
-//
-//
-//
-//
-
-//
-//
-//
-//
-
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-// )
-// );
-// }, [id, toggle, editor]);
-
-// return (
-//
-//
-//
-//
-//
-//
-//
-//
-//
-// );
-// }
-
-// const EditorWrap = styled.div`
-// display: inline-flex;
-// width: 100vw;
-// /* height: 100vh; */
-// & .editor {
-// /* & > .content {
-// width: 100%;
-// overflow-y: scroll;
-// } */
-// }
-
-// & .roadMapWrap {
-// /* height: 100%; */
-// width: 100%;
-// }
-// `;
-// too many rerender issue
-export default {};
diff --git a/src/components/editor/style.module.css b/src/components/editor/style.module.css
new file mode 100644
index 0000000..4331768
--- /dev/null
+++ b/src/components/editor/style.module.css
@@ -0,0 +1,38 @@
+.node {
+ width: 100%;
+ height: 100%;
+ border-radius: 15px;
+ border: 1px solid #000;
+ background-color: #fff;
+ padding: 20px;
+ box-sizing: border-box;
+}
+
+.node :global .react-flow__resize-control.handle {
+ width: 10px;
+ height: 10px;
+ border-radius: 100%;
+}
+
+.rotateHandle {
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ background: #3367d9;
+ left: 50%;
+ top: -30px;
+ border-radius: 100%;
+ transform: translate(-50%, -50%);
+ cursor: alias;
+}
+
+.rotateHandle:after {
+ content: '';
+ display: block;
+ position: absolute;
+ width: 1px;
+ height: 30px;
+ background: #3367d9;
+ left: 4px;
+ top: 5px;
+}
diff --git a/src/components/editor/types.ts b/src/components/editor/types.ts
index 843827e..5b56d9b 100644
--- a/src/components/editor/types.ts
+++ b/src/components/editor/types.ts
@@ -25,6 +25,7 @@ export interface AddedNode {
sourcePosition?: string;
// type?: string | any;
type?: string;
+ blogKeyword?: string;
toolbarPosition?: string;
data?: { label: string };
@@ -50,7 +51,9 @@ export interface RoadmapNode {
position: XYPosition;
data: { label: string };
style?: nodeStyle;
+ done?: boolean;
content?: string;
+ blogKeyword?: string;
targetPosition?: string;
sourcePosition?: string;
selected?: boolean;
diff --git a/src/components/modal/modal.tsx b/src/components/modal/modal.tsx
deleted file mode 100644
index d2377be..0000000
--- a/src/components/modal/modal.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Button, Group, Modal } from '@mantine/core';
-import { useDisclosure } from '@mantine/hooks';
-
-function UserModal() {
- const [opened, { open, close }] = useDisclosure(false);
-
- return (
- <>
-
- {/* Modal content */}
-
-
-
-
-
- >
- );
-}
-
-export default UserModal;
diff --git a/src/components/prompts/hooks/usePrompt.ts b/src/components/prompts/hooks/usePrompt.ts
index 54323da..137404f 100644
--- a/src/components/prompts/hooks/usePrompt.ts
+++ b/src/components/prompts/hooks/usePrompt.ts
@@ -13,7 +13,7 @@ interface UsePrompt {
type PromptResponse = { prompt: Array };
export function usePrompt(): UsePrompt {
- const SERVER_ERROR = 'error contacting server';
+ // const SERVER_ERROR = 'error contacting server';
const navigate = useNavigate();
const { user } = useUser();
@@ -43,7 +43,7 @@ export function usePrompt(): UsePrompt {
navigate(`/roadmap/editor`);
}
} catch (errorResponse) {
- console.log(`${SERVER_ERROR}!: ${errorResponse}`);
+ // // console.log(`${SERVER_ERROR}!: ${errorResponse}`);
}
}
diff --git a/src/components/roadmaps/editor/hooks/useProgressRoadmap.ts b/src/components/roadmaps/editor/hooks/useProgressRoadmap.ts
index 82c01ca..c507e76 100644
--- a/src/components/roadmaps/editor/hooks/useProgressRoadmap.ts
+++ b/src/components/roadmaps/editor/hooks/useProgressRoadmap.ts
@@ -38,12 +38,12 @@
// 'roadmaps',
// JSON.stringify({ data }), // 검색어에 대한 data 저장하도록
// );
-// console.log(data);
-// // console.log('roadmaps', data);
+// // console.log(data);
+// // // console.log('roadmaps', data);
// // navigate(`/roadmaps`);
// }
// } catch (errorResponse) {
-// console.log(`${SERVER_ERROR}!: ${errorResponse}`);
+// // console.log(`${SERVER_ERROR}!: ${errorResponse}`);
// }
// }
// async function roadmapPostSeverCall(
@@ -63,10 +63,10 @@
// },
// });
// if (status === 200) {
-// console.log(data);
+// // console.log(data);
// }
// } catch (errorResponse) {
-// console.log(`${SERVER_ERROR}!: ${errorResponse}`);
+// // console.log(`${SERVER_ERROR}!: ${errorResponse}`);
// }
// }
// async function getRoadmap(id: number): Promise {
diff --git a/src/components/roadmaps/editor/hooks/useRoadmap.ts b/src/components/roadmaps/editor/hooks/useRoadmap.ts
index 9639875..08cba2c 100644
--- a/src/components/roadmaps/editor/hooks/useRoadmap.ts
+++ b/src/components/roadmaps/editor/hooks/useRoadmap.ts
@@ -39,10 +39,10 @@ export function useRoadmap(): UseRoadmap {
!id
? localStorage.setItem('roadmaps', JSON.stringify({ data })) // 모든 로드맵 가져오기
: localStorage.setItem('roadmapById', JSON.stringify({ data })); // 특정 id의 로드맵 가져오기
- // console.log('useRoadmap', data);
+ // // console.log('useRoadmap', data);
}
} catch (errorResponse) {
- console.log(`${SERVER_ERROR}!: ${errorResponse}`);
+ // // console.log(`${SERVER_ERROR}!: ${errorResponse}`);
}
}
async function roadmapPostSeverCall(
@@ -61,12 +61,12 @@ export function useRoadmap(): UseRoadmap {
},
});
if (status === 200) {
- console.log('roadmapPostSeverCall', data);
+ // // console.log('roadmapPostSeverCall', data);
// eslint-disable-next-line no-alert
- alert(data);
+ // alert(data);
}
} catch (errorResponse) {
- console.log(`${SERVER_ERROR}!: ${errorResponse}`);
+ // // console.log(`${SERVER_ERROR}!: ${errorResponse}`);
}
}
async function getRoadmapById(id: number): Promise {
diff --git a/src/components/roadmaps/posts/RoadmapInfo.jsx b/src/components/roadmaps/posts/RoadmapInfo.jsx
new file mode 100644
index 0000000..21e97a9
--- /dev/null
+++ b/src/components/roadmaps/posts/RoadmapInfo.jsx
@@ -0,0 +1,807 @@
+/* eslint-disable no-console */
+/* eslint-disable react-hooks/exhaustive-deps */
+/* eslint-disable @typescript-eslint/no-unused-vars */
+/* eslint-disable no-unused-vars */
+import {
+ ActionIcon,
+ Avatar,
+ Button,
+ Card,
+ Center,
+ createStyles,
+ Drawer,
+ Group,
+ Input,
+ Modal,
+ Popover,
+ rem,
+ ScrollArea,
+ Text,
+ Title,
+ Tooltip,
+ UnstyledButton,
+} from '@mantine/core';
+import { useFocusTrap } from '@mantine/hooks';
+import {
+ IconBook2,
+ IconCalendarStats,
+ IconCertificate,
+ IconCircleArrowRightFilled,
+ IconCircleCheckFilled,
+ IconHeart,
+ IconHeartFilled,
+ IconUser,
+} from '@tabler/icons-react';
+import { Highlight } from '@tiptap/extension-highlight';
+import { Link } from '@tiptap/extension-link';
+import { Subscript } from '@tiptap/extension-subscript';
+import { Superscript } from '@tiptap/extension-superscript';
+import { TextAlign } from '@tiptap/extension-text-align';
+import { Underline } from '@tiptap/extension-underline';
+import Youtube from '@tiptap/extension-youtube';
+import { EditorContent, useEditor } from '@tiptap/react';
+import StarterKit from '@tiptap/starter-kit';
+import axios from 'axios';
+import { baseUrl } from 'axiosInstance/constants';
+import { useInput } from 'components/common/hooks/useInput';
+import { useUser } from 'components/user/hooks/useUser';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { useLocation, useNavigate } from 'react-router-dom';
+import {
+ ReactFlow,
+ ReactFlowProvider,
+ useEdgesState,
+ useNodesState,
+} from 'reactflow';
+import { styled } from 'styled-components';
+
+import { DoneStatusNode } from '../../editor/DoneStatusNode';
+import { useRoadmapData } from './hooks/useRoadMapResponse';
+
+const useStyles = createStyles((theme) => ({
+ title: {
+ fontSize: rem(34),
+ fontWeight: 900,
+
+ [theme.fn.smallerThan('sm')]: {
+ fontSize: rem(24),
+ },
+ },
+
+ description: {
+ maxWidth: 600,
+
+ '&::after': {
+ content: '""',
+ display: 'block',
+ backgroundColor: theme.fn.primaryColor(),
+ width: rem(45),
+ height: rem(2),
+ marginTop: theme.spacing.sm,
+ marginRight: 'auto',
+ },
+ },
+
+ card: {
+ border: `${rem(1)} solid ${
+ theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1]
+ }`,
+ },
+
+ cardTitle: {
+ '&::after': {
+ content: '""',
+ display: 'block',
+ backgroundColor: theme.fn.primaryColor(),
+ width: rem(45),
+ height: rem(2),
+ marginTop: theme.spacing.sm,
+ },
+ },
+}));
+const nodeTypes = {
+ custom: DoneStatusNode,
+};
+export default function RoadMapInfo() {
+ const { classes, theme } = useStyles();
+ const navigate = useNavigate();
+ const { pathname } = useLocation();
+ const [participation, setParticipation] = useState(false);
+ const [currentPage, setCurrentPage] = useState(
+ pathname.slice(pathname.lastIndexOf('/') + 1),
+ );
+ const [isLoading, setLoading] = useState(true);
+ const { roadmapById } = useRoadmapData(
+ pathname.slice(pathname.lastIndexOf('/') + 1),
+ );
+ // @ts-ignore
+ const [currentRoadmap, setCurrentRoadmap] = useState(roadmapById?.data || []);
+ const [label, onChangeLabel, setLabel] = useInput('');
+ const [blogKeyword, onChangeBlogKeyword, setBlogKeyword] = useInput('');
+ const [blogUrl, onChangeBlogUrl, setBlogUrl] = useInput('');
+ const [id, onChangeId, setId] = useInput('');
+ const [toggle, onChangeToggle, setToggle] = useInput('');
+ const { user } = useUser();
+ const [state, setState] = useState([]);
+
+ useEffect(() => {
+ axios
+ .get(`${baseUrl}/roadmaps/${currentPage}`, {
+ headers: {
+ Authorization: `Bearer ${user?.accessToken}`,
+ },
+ })
+ // .catch((e) => {
+ // eslint-disable-next-line no-console
+ // // console.log(e);
+ // })
+ // .then((value: AxiosResponse) => {
+ .then((value) => {
+ const { data } = value;
+ setNodes(data.nodes);
+ setCurrentRoadmap({
+ id: data?.id,
+ title: data?.title,
+ description: data?.description,
+ createdAt: data?.createdAt,
+ updatedAt: data?.updatedAt,
+ ownerAvatarUrl: data?.member?.ownerAvatarUrl,
+ ownerNickname: data?.member?.nickname,
+ isJoined: data?.isJoined,
+ joinCount: data?.joinCount,
+ isLiked: data?.isLiked,
+ likeCount: data?.likeCount,
+ thumbnailUrl: data?.member?.thumbnailUrl,
+ viewport: data?.viewport?.viewport,
+ });
+ setLoading(false);
+ setParticipation(data?.isJoined);
+ const detailState = [];
+ data.nodes.forEach((j) =>
+ detailState.push({ id: j.id, details: j.detailedContent }),
+ );
+ setState(detailState);
+ const edgeSet = new Set();
+ const tempEdges = [];
+ // eslint-disable-next-line array-callback-return
+ data?.edges.forEach((j) => {
+ if (!edgeSet.has(j?.id)) {
+ tempEdges.push(j);
+ }
+ edgeSet.add(j?.id);
+ });
+ setEdges(tempEdges);
+ });
+ }, []);
+
+ const editor = useEditor({
+ extensions: [
+ StarterKit,
+ Youtube.configure({
+ inline: false,
+ ccLanguage: 'ko',
+ interfaceLanguage: 'ko',
+ }),
+ Underline,
+ Link,
+ Superscript,
+ Subscript,
+ Highlight,
+ TextAlign.configure({ types: ['heading', 'paragraph'] }),
+ ],
+ editable: false,
+ content: state.filter((v) => v.id === id)[0]?.details || '',
+ onUpdate(e) {
+ setToggle(e.editor?.getHTML());
+ // eslint-disable-next-line array-callback-return
+ state.forEach((item, idx) => {
+ if (item.id !== id) return;
+ const copyState = [...state];
+ copyState.splice(idx, 1, {
+ id: item.id,
+ details: e.editor?.getHTML(),
+ });
+ setState(copyState);
+ });
+ },
+ });
+
+ useMemo(() => {
+ const filt = state.filter((v) => v.id === id);
+ setToggle(filt);
+ if (editor) {
+ editor.commands.setContent(filt[0]?.details || '내용이 없습니다.');
+ }
+ }, [state, id, setToggle, label, editor]);
+
+ const widthRef = useRef(null);
+ const heightRef = useRef(null);
+
+ useEffect(() => {
+ if (widthRef.current && heightRef.current) {
+ widthRef.current.value = 640;
+ heightRef.current.value = 480;
+ }
+ }, [widthRef.current, heightRef.current]);
+
+ const addYoutubeVideo = () => {
+ // eslint-disable-next-line no-alert
+ const url = prompt('Enter YouTube URL');
+
+ if (url) {
+ editor.commands.setYoutubeVideo({
+ src: url,
+ // width: Math.max(320, parseInt(widthRef.current.value, 10)) || 640,
+ // height: Math.max(180, parseInt(heightRef.current.value, 10)) || 480,
+ });
+ }
+ };
+
+ const onClickLikes = () => {
+ axios
+ .post(
+ `${baseUrl}/likes/like-roadmap/${currentRoadmap.id}`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${user?.accessToken}`,
+ },
+ },
+ )
+ .then((v) => {
+ setCurrentRoadmap({
+ ...currentRoadmap,
+ isLiked: v?.data.isLiked,
+ likeCount: v?.data.likeCount,
+ });
+ });
+ // .catch((err) => // console.log(err));
+ };
+ const submitBlogUrl = useCallback(() => {
+ // // console.log(`id : ${id}, ${currentRoadmap}`);
+ // // console.log('currentRoadmap', currentRoadmap);
+ // // console.log('nodeState', nodeState);
+ // // console.log('id', id);
+ const current = nodeState.filter((v) => v.id === id);
+ // // console.log('current', current[0].blogKeyword);
+ const blogKeywordId = current[0]?.blogKeyword?.id;
+ // // console.log(blogKeywordId);
+ // const blogKeyword = current[0]?.blogKeyword?.keyword;
+ axios.post(
+ `${baseUrl}/certified-blogs/submitUrl`,
+ {
+ // memberId: user?.nickname,
+ // roadmapNodeId: id,
+ // inProgressNodeId: blogKeywordId,
+ blogKeywordId,
+ // inProgressNodeId: 6168,
+ submitUrl: blogUrl,
+ // submitUrl: 'https://techpedia.tistory.com/18',
+ // submitUrl: 'https://dbwp031.tistory.com/41',
+ // submitUrl:
+ // 'https://velog.io/@jiynn_12/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A1%9C-%ED%98%91%EC%97%85%ED%95%98%EA%B8%B0-husky',
+ },
+ {
+ headers: {
+ Authorization: `Bearer ${user?.accessToken}`,
+ },
+ },
+ );
+ // .then((v) => {
+ // // console.log(v);
+ // setCurrentRoadmap({
+ // ...currentRoadmap,
+ // isLiked: v?.data.isLiked,
+ // likeCount: v?.data.likeCount,
+ // });
+ // })
+ // .catch((err) => // console.log(err));
+ }, [blogUrl, id]);
+
+ const [nodeState, setNodes, onNodesChange] = useNodesState([]);
+ const [edgeState, setEdges, onEdgesChange] = useEdgesState([]);
+ const [isSelectable] = useState(true);
+ const [isDraggable] = useState(false);
+ const [isConnectable] = useState(false);
+ const [zoomOnScroll] = useState(false); // zoom in zoom out
+ const [panOnScroll] = useState(false); // 위아래 스크롤
+ const [zoomOnDoubleClick] = useState(false);
+ const [panOnDrag] = useState(false); // 마우스로 이동
+ const [isOpen, setIsOpen] = useState(false);
+ const [modal, setModal] = useState(false);
+
+ const proOptions = { hideAttribution: true };
+
+ const focusTrapRef = useFocusTrap();
+
+ // const observerCallback: ResizeObserverCallback = (entries: ResizeObserverEntry[]) => {
+ // window.requestAnimationFrame((): void | undefined => {
+ // if (!Array.isArray(entries) || !entries.length) {
+ // return;
+ // }
+ // // yourResizeHandler();
+ // });
+ // };
+ // const resizeObserver = new ResizeObserver(observerCallback);
+
+ // eslint-disable-next-line consistent-return
+ const updateRoadmapProgress = () => {
+ if (!user) {
+ // eslint-disable-next-line no-alert
+ alert('로그인 후 이용 가능합니다!');
+ return navigate('/users/signin');
+ }
+ // if (!currentRoadmap.isJoined) {
+ if (!participation) {
+ // eslint-disable-next-line no-alert
+ alert('참여하기 버튼 클릭 후 이용 가능합니다.');
+ return setIsOpen(false);
+ }
+ // eslint-disable-next-line no-alert
+ const copyState = [...nodeState];
+ const current = nodeState.filter((v) => v.id === id);
+ const nodeId = current[0]?.blogKeyword?.id;
+ // eslint-disable-next-line array-callback-return
+ copyState.forEach((v) => {
+ if (v.id === id && participation) {
+ // eslint-disable-next-line no-param-reassign
+ v.done = true;
+ }
+ });
+ nodeState.forEach((v) => {
+ if (v.id === id && participation) {
+ // eslint-disable-next-line no-param-reassign
+ v.done = true;
+ // eslint-disable-next-line no-param-reassign
+ v.data.done = true;
+ // eslint-disable-next-line no-param-reassign
+ v.style.background = '#a8a6a6be';
+ }
+ });
+ setState(copyState);
+ setIsOpen(false);
+ // eslint-disable-next-line no-alert
+ return alert('진행 완료!');
+ // axios
+ // .patch(
+ // `${baseUrl}/roadmaps/in-progress-nodes/${nodeId}/done`,
+ // {
+ // inProgressNodeId: nodeId,
+ // },
+ // {
+ // headers: {
+ // 'Content-Type': 'application/json',
+ // Authorization: `Bearer ${user?.accessToken}`,
+ // },
+ // },
+ // )
+ // // eslint-disable-next-line consistent-return
+ // .then((v) => {
+ // // console.log(v);
+ // if (v.status === 200) {
+ // // console.log(currentRoadmap);
+ // // console.log('copyState', copyState);
+ // copyState.forEach((m) => {
+ // if (m.id === id && (participation || currentRoadmap.isJoined)) {
+ // // eslint-disable-next-line no-param-reassign
+ // m.done = true;
+ // // m.done = v.done;
+ // }
+ // });
+ // nodeState.forEach((m) => {
+ // if (m.id === id && participation) {
+ // // eslint-disable-next-line no-param-reassign
+ // m.data.done = true;
+ // // eslint-disable-next-line no-param-reassign
+ // m.done = true;
+ // // eslint-disable-next-line no-param-reassign
+ // m.style.background = '#a8a6a6be';
+ // }
+ // });
+ // setState(copyState);
+ // setNodes(nodeState);
+ // setIsOpen(false);
+ // // eslint-disable-next-line no-alert
+ // return alert('진행 완료!');
+ // }
+ // // console.log('is state updated?', nodeState);
+ // // eslint-disable-next-line no-alert
+ // // setParticipation(true);
+ // // setCurrentRoadmap({
+ // // ...currentRoadmap,
+ // // joinCount: currentRoadmap.joinCount + 1,
+ // // });
+ // })
+ // .catch((e) => {
+ // if (e.status === 403) {
+ // // eslint-disable-next-line no-alert
+ // return alert('이미 진행을 완료했습니다.');
+ // }
+ // return // console.log(e);
+ // });
+ };
+
+ const joinRoadmap = () => {
+ axios
+ .post(
+ `${baseUrl}/roadmaps/${parseInt(currentPage, 10)}/join`,
+ {},
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${user?.accessToken}`,
+ },
+ },
+ )
+ .then((v) => {
+ setParticipation(true);
+ setCurrentRoadmap({
+ ...currentRoadmap,
+ joinCount: currentRoadmap.joinCount + 1,
+ });
+ });
+ // .catch((e) => // console.log(e));
+ };
+ return (
+ <>
+
+
+
+ {currentRoadmap?.title}
+
+ {currentRoadmap?.ownerAvatarUrl || ''}
+
+ {currentRoadmap?.ownerNickname}
+
+
+
+ {currentRoadmap?.likeCount > 1000000
+ ? new Intl.NumberFormat('en-GB', {
+ notation: 'compact',
+ compactDisplay: 'short',
+ }).format(currentRoadmap?.likeCount)
+ : currentRoadmap?.likeCount}
+
+ {currentRoadmap?.isLiked ? (
+
+ onClickLikes()}
+ size="2rem"
+ color={theme.colors.red[6]}
+ stroke={1.5}
+ />
+
+ ) : (
+
+ onClickLikes()}
+ size="2rem"
+ color={theme.colors.red[6]}
+ stroke={1.5}
+ />
+
+ )}
+
+
+
+
+
+ {' '}
+ 만든 날짜 : {currentRoadmap?.createdAt}
+
+
+ {' '}
+ {currentRoadmap?.description || ''}
+
+
+
+ {' '}
+ 참여인원: {currentRoadmap?.joinCount}명
+
+
+
+
+
+ {
+ setModal(false);
+ }}
+ >
+ {!user && (
+
+
로그인 후 이용 가능합니다.
+
+
+ )}
+
+
+
+
+
+ {/* */}
+
+ {
+ setLabel(`${n?.data?.label}`);
+ setId(`${n?.id}`);
+ // // console.log(id);
+ // setIsOpen(!isOpen);
+ setIsOpen(true);
+ }}
+ style={{ overflow: 'visible' }}
+ // FitBoundsOptions={{ padding: '10px' }}
+ // FitViewOptions={{ padding: '10px' }}
+ // fitViewOptions={p}
+ />
+
+ {/* */}
+ setIsOpen(false)}
+ position="right"
+ keepMounted
+ closeOnEscape
+ lockScroll={false}
+ >
+
+
+
+
+
+
+
+
+
+ updateRoadmapProgress()}>
+
+
+ 진행 완료
+
+
+
+
+
+
+
+ 진행 중
+
+
+
+
+
+
+
+ }
+ value={blogUrl}
+ placeholder="https://myblogUrl.io"
+ // onChange={onChangeBlogUrl}
+ onChange={(e) => {
+ setBlogUrl(e.target.value);
+ }}
+ mt={10}
+ mb={10}
+ // disabled={keywordSubmitState}
+ rightSection={
+
+ {
+ submitBlogUrl();
+ }}
+ >
+
+
+
+ }
+ // onChange={(evt) => {
+ // setLabel(evt?.target?.value);
+ // }}
+ />
+
+
+
+
+
+ {/* */}
+
+
+
+
+
+
+
+ >
+ );
+}
+const Wrap = styled.div`
+ width: 100%;
+ overflow: visible;
+ height: 100em;
+
+ & .react-flow__pane.react-flow__viewport.react-flow__container {
+ height: 'fit-content';
+ width: 'fit-content';
+ transform: scale(0.45) !important;
+ }
+
+ & .mantine-ScrollArea-root {
+ height: 100vh;
+ }
+ & .mantine-Drawer-body {
+ height: 100vh;
+ }
+
+ & .react-flow__node {
+ cursor: pointer;
+ /* margin-top: 10px; */
+ display: block;
+ padding: 10px;
+ z-index: 4;
+ font-size: 1rem;
+ }
+ & .react-flow__node.react-flow__node-custom.selectable:hover {
+ opacity: 30%;
+ transform: scale(120%);
+ }
+
+ & .react-flow__pane {
+ cursor: auto;
+ }
+ & .react-flow {
+ zoom: 100% !important;
+ /* overflow: visible !important; */
+ overflow: scroll !important;
+ overflow-x: hidden !important;
+ /* zoom: 60% !important; */
+ scrollbar-width: thin !important;
+ scrollbar-color: #6969dd #e0e0e0 !important;
+ & ::-webkit-scrollbar {
+ width: 10px;
+ }
+ & ::-webkit-scrollbar-track {
+ background-color: darkgrey !important;
+ }
+ & ::-webkit-scrollbar-thumb {
+ box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
+ }
+ }
+`;
+const EditorWrap = styled.div`
+ & .editor {
+ & > .content {
+ width: 100%;
+ }
+ }
+
+ /* & .roadMapWrap {
+ overflow-x: hidden;
+ } */
+`;
diff --git a/src/components/roadmaps/posts/hooks/useRoadmap.ts b/src/components/roadmaps/posts/hooks/useRoadmap.ts
index be08af6..be374bb 100644
--- a/src/components/roadmaps/posts/hooks/useRoadmap.ts
+++ b/src/components/roadmaps/posts/hooks/useRoadmap.ts
@@ -53,7 +53,7 @@ export function useRoadmap(): UseRoadmap {
);
}
} catch (errorResponse) {
- console.log(`${SERVER_ERROR}!: ${errorResponse}`);
+ // // console.log(`${SERVER_ERROR}!: ${errorResponse}`);
}
}
async function roadmapAuthServerCall(
@@ -83,7 +83,7 @@ export function useRoadmap(): UseRoadmap {
);
}
} catch (errorResponse) {
- console.log(`${SERVER_ERROR}!: ${errorResponse}`);
+ // // console.log(`${SERVER_ERROR}!: ${errorResponse}`);
}
}
async function roadmapPostSeverCall(
@@ -102,10 +102,10 @@ export function useRoadmap(): UseRoadmap {
},
});
if (status === 200) {
- console.log(data);
+ // // console.log(data);
}
} catch (errorResponse) {
- console.log(`${SERVER_ERROR}!: ${errorResponse}`);
+ // // console.log(`${SERVER_ERROR}!: ${errorResponse}`);
}
}
async function roadmapJoinSeverCall(
@@ -125,7 +125,7 @@ export function useRoadmap(): UseRoadmap {
});
if (status === 201) {
alert('참여 성공');
- console.log('data', data);
+ // // console.log('data', data);
}
if (status === 404) {
alert('로드맵을 찾지 못했습니다.');
@@ -134,9 +134,9 @@ export function useRoadmap(): UseRoadmap {
if (errorResponse === 409) {
alert('이미 참여중인 로드맵입니다.');
}
- console.log(`${SERVER_ERROR}!: ${errorResponse}`);
- // console.log(`${SERVER_ERROR}!: ${errorResponse}`);
- // console.log('user', user);
+ // // console.log(`${SERVER_ERROR}!: ${errorResponse}`);
+ // // console.log(`${SERVER_ERROR}!: ${errorResponse}`);
+ // // console.log('user', user);
}
}
async function roadmapProgressSeverCall(
@@ -153,13 +153,13 @@ export function useRoadmap(): UseRoadmap {
},
});
if (status === 200) {
- console.log('성공적으로 업뎃');
+ // // console.log('성공적으로 업뎃');
}
if (status === 403) {
- console.log('권한이 없습니다.');
+ // // console.log('권한이 없습니다.');
}
} catch (errorResponse) {
- console.log(`${SERVER_ERROR}!: ${errorResponse}`);
+ // // console.log(`${SERVER_ERROR}!: ${errorResponse}`);
}
}
async function getRoadmapById(id: number): Promise {
diff --git a/src/components/roadmaps/posts/main/RoadMapMainItems.tsx b/src/components/roadmaps/posts/main/RoadMapMainItems.tsx
new file mode 100644
index 0000000..6284dc5
--- /dev/null
+++ b/src/components/roadmaps/posts/main/RoadMapMainItems.tsx
@@ -0,0 +1,268 @@
+/* eslint-disable no-console */
+import {
+ Avatar,
+ Card,
+ createStyles,
+ Group,
+ Image,
+ rem,
+ SimpleGrid,
+ Text,
+} from '@mantine/core';
+import axios from 'axios';
+import { baseUrl } from 'axiosInstance/constants';
+import { useCallback, useEffect, useState } from 'react';
+import InfiniteScroll from 'react-infinite-scroller';
+import { useInfiniteQuery } from 'react-query';
+import { useNavigate } from 'react-router-dom';
+import { styled } from 'styled-components';
+
+import { ReactComponent as NoImage } from '../../../../assets/noImage.svg';
+import { ReactComponent as Spinner } from '../../../../assets/Spinner.svg';
+
+const useStyles = createStyles((theme) => ({
+ card: {
+ backgroundColor:
+ theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.white,
+ transition: 'transform 150ms ease, box-shadow 150ms ease',
+
+ '&:hover': {
+ transform: 'scale(1.01)',
+ boxShadow: theme.shadows.md,
+ },
+ borderRadius: theme.radius.md,
+ boxShadow: theme.shadows.lg,
+ width: '96%',
+ margin: '3rem auto 1rem',
+ },
+
+ title: {
+ fontFamily: `Greycliff CF, ${theme.fontFamily}`,
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ marginTop: '1.5rem',
+ borderTop: '1px',
+ fontSize: '1.3rem',
+ },
+
+ desc: {
+ display: '-webkit-box',
+ overflow: 'hidden',
+ WebkitLineClamp: 3,
+ WebkitBoxOrient: 'vertical',
+ fontFamily: `Greycliff CF, ${theme.fontFamily}`,
+ maxHeight: '4.5em',
+ lineHeight: '1.3em',
+ marginTop: '0.725rem',
+ },
+
+ like: {
+ color: theme.colors.red[6],
+ },
+
+ item: {
+ width: '100%',
+ },
+
+ section: {
+ height: '24rem',
+ cursor: 'pointer',
+ },
+
+ footer: {
+ padding: `${theme.spacing.xs} ${theme.spacing.lg}`,
+ marginTop: theme.spacing.md,
+ borderTop: `${rem(1)} solid ${
+ theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[2]
+ }`,
+ },
+}));
+
+export default function RoadmapRecommendation() {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [allRoadmapData, setAllRoadmapData] = useState([]);
+ const { classes } = useStyles();
+ const navigate = useNavigate();
+ const [currentPage, setCurrentPage] = useState('');
+ const [roadmapPage, setRoadmapPage] = useState(1);
+
+ const fetchRoadmaps = useCallback(() => {
+ axios
+ .get(`${baseUrl}/roadmaps?page=${roadmapPage}&order-type=recent`)
+ .then((v) => {
+ setAllRoadmapData(v?.data);
+ })
+ .catch((e) => {
+ console.log(e);
+ });
+ }, [roadmapPage]);
+
+ const initialUrl = `${baseUrl}/roadmaps?page=${roadmapPage}&order-type=recent`;
+ const fetchUrl = async (url) => {
+ const response = await fetch(url);
+ return response.json();
+ };
+
+ const {
+ refetch,
+ data,
+ fetchNextPage,
+ hasNextPage,
+ isLoading,
+ isError,
+ error,
+ } = useInfiniteQuery(
+ 'roadmaps',
+ ({ pageParam = initialUrl }) => fetchUrl(pageParam),
+ {
+ getNextPageParam: (lastPage) => {
+ if (lastPage.result.length !== 0) {
+ return lastPage.next;
+ }
+ return undefined;
+ },
+ enabled: roadmapPage === 1,
+ },
+ );
+
+ useEffect(() => {
+ setRoadmapPage(1);
+ refetch();
+ fetchRoadmaps();
+ }, [fetchRoadmaps, refetch]);
+
+ if (isLoading)
+ return (
+
+
+
로드맵 가져오는 중
+
+ );
+ if (isError) return Error! {error.toString()}
;
+
+ return (
+
+
+ {data.pages &&
+ data.pages.map((pageData) => {
+ return pageData.result.map((article, index) => {
+ return (
+
+ {
+ setCurrentPage(article.id);
+ }}
+ onClick={() => {
+ currentPage && navigate(`/roadmap/post/${currentPage}`);
+ }}
+ >
+
+
+
+ {article.thumbnailUrl ? (
+
+ ) : (
+
+
+
+ )}
+
+
+
+
+ {article.title}
+
+
+ {article.description.length < 30
+ ? article.description
+ : // eslint-disable-next-line prefer-template
+ article.description.slice(0, 29) + '...'}
+
+
+
+ {article.createdAt}
+
+
+
+
+ {article.member.nickname.substring(0, 1)}
+
+
+
+ {article.member.nickname}
+
+
+
+
+ );
+ });
+ })}
+
+ {/* */}
+
+ );
+}
+const BlurredImg = styled.div`
+ background-repeat: no-repeat;
+ background-size: cover;
+ .before {
+ filter: blur(10px);
+ }
+ .before ::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ opacity: 0;
+ animation: pulse 2.5s infinite;
+ background-color: var(--text-color);
+ }
+
+ @keyframes pulse {
+ 0% {
+ opacit: 0;
+ }
+ 50% {
+ opacity: 0.1;
+ }
+ 100% {
+ opacity: 0;
+ }
+ }
+ .loaded::before {
+ animation: none;
+ content: none;
+ }
+ & > img {
+ opacity: 0;
+ transition: opacity 250ms ease-in-out;
+ }
+
+ & .loaded > img {
+ opacity: 1;
+ }
+`;
diff --git a/src/components/user/editUserProfile.tsx b/src/components/user/editUserProfile.tsx
index c056313..9c3201b 100644
--- a/src/components/user/editUserProfile.tsx
+++ b/src/components/user/editUserProfile.tsx
@@ -49,14 +49,15 @@ function EditUserProfile() {
{ headers },
)
.then((response) => {
- console.log(response);
+ // // console.log(response);
})
.catch((error) => {
- console.log(error);
+ // // console.log(error);
});
};
return (
+ //
프로필 수정
@@ -90,7 +91,7 @@ function EditUserProfile() {
+ //
);
}
diff --git a/src/components/user/forms/LoginForm.tsx b/src/components/user/forms/LoginForm.tsx
index 7ff7a10..f69e58c 100644
--- a/src/components/user/forms/LoginForm.tsx
+++ b/src/components/user/forms/LoginForm.tsx
@@ -45,13 +45,13 @@ function LoginForm(props: PaperProps): ReactElement {
});
return (
-
-
+
+
({
fontFamily: `Greycliff CF, ${theme.fontFamily}`,
- fontWeight: 900,
+ fontWeight: 400,
})}
>
로그인
@@ -59,7 +59,7 @@ function LoginForm(props: PaperProps): ReactElement {
아직 계정이 없으신가요?
auth.signin(email, password)}
>
diff --git a/src/components/user/forms/ResetInfoForm.tsx b/src/components/user/forms/ResetInfoForm.tsx
deleted file mode 100644
index 21df6ce..0000000
--- a/src/components/user/forms/ResetInfoForm.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { ReactElement } from 'react';
-
-function ResetInfoForm(): ReactElement {
- return (
- <>
- 비밀번호 찾기
- 이메일
-
- 인증하기
-
- 인증번호 입력
-
- 인증하기
- >
- );
-}
-
-export default ResetInfoForm;
diff --git a/src/components/user/forms/SignupForm.tsx b/src/components/user/forms/SignupForm.tsx
index 1b03b4b..7c6a2f6 100644
--- a/src/components/user/forms/SignupForm.tsx
+++ b/src/components/user/forms/SignupForm.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable no-console */
import {
Anchor,
Box,
@@ -23,29 +22,6 @@ import { useNavigate } from 'react-router-dom';
import { useAuth } from '../../../auth/useAuth';
import { useInput } from '../../common/hooks/useInput';
-// const useStyles = createStyles((theme) => ({
-// container: {
-// height: rem(300),
-// width: '100%',
-// backgroundColor: '#52EB9A',
-
-// // // Media query with value from theme
-// // [`@media (max-width: ${em(getBreakpointValue(theme.breakpoints.xl) - 1)})`]:
-// // {
-// // backgroundColor: theme.colors.pink[6],
-// // },
-
-// // // Simplify media query writing with theme functions
-// // [theme.fn.smallerThan('lg')]: {
-// // backgroundColor: theme.colors.yellow[6],
-// // },
-
-// // // Static media query
-// // [`@media (max-width: ${em(800)})`]: {
-// // backgroundColor: theme.colors.orange[6],
-// // },
-// },
-// }));
function SignUpForm(props: PaperProps): ReactElement {
const [email, onChangeEmail, setEmail] = useInput('');
const [nickname, onChangeNickname, setNickname] = useInput('');
@@ -54,7 +30,6 @@ function SignUpForm(props: PaperProps): ReactElement {
useInput('');
const auth = useAuth();
const navigate = useNavigate();
- // const { classes } = useStyles();
const form = useForm({
initialValues: {
@@ -89,7 +64,7 @@ function SignUpForm(props: PaperProps): ReactElement {
});
return (
-
+
{auth.isUserModalOpen && (
{auth.success ? (
<>
-
+
-
+
회원가입 성공
@@ -115,7 +90,7 @@ function SignUpForm(props: PaperProps): ReactElement {
type="submit"
mt={50}
variant="light"
- color="teal.4"
+ color="#ebf6fc"
h={50}
onClick={() => {
navigate('/users/signin');
@@ -126,7 +101,7 @@ function SignUpForm(props: PaperProps): ReactElement {
>
) : (
<>
-
+
)}
-
- ({
- fontFamily: `Greycliff CF, ${theme.fontFamily}`,
- fontWeight: 900,
- })}
- >
+
+
회원가입
-
+
이미 계정이 있으신가요?
{
@@ -192,6 +162,8 @@ function SignUpForm(props: PaperProps): ReactElement {
>
{
auth.setModalText('');
@@ -238,14 +218,6 @@ function SignUpForm(props: PaperProps): ReactElement {
>
회원가입
- {/* {
- auth.setIsUserModalOpen(true);
- auth.setSuccess(false);
- }}
- >
- Test
- */}
diff --git a/src/components/user/hooks/useComment.ts b/src/components/user/hooks/useComment.ts
index b664688..3b3f182 100644
--- a/src/components/user/hooks/useComment.ts
+++ b/src/components/user/hooks/useComment.ts
@@ -39,9 +39,9 @@
// },
// });
// if (status === 201 || status === 200) {
-// // console.log('useAuth ServiceCall', data);
+// // // console.log('useAuth ServiceCall', data);
// // getStoredUser();
-// console.log('conmment', data);
+// // console.log('conmment', data);
// }
// // navigate('/');
// } catch (errorResponse) {
@@ -60,7 +60,7 @@
// comment: string,
// nickname: string,
// ): Promise {
-// // console.log('members', member);
+// // // console.log('members', member);
// writeCall(`/comments/save-comment`, title, comment, nickname);
// }
// return {
diff --git a/src/components/user/hooks/useProfile.ts b/src/components/user/hooks/useProfile.ts
index d0661ae..1b64a1a 100644
--- a/src/components/user/hooks/useProfile.ts
+++ b/src/components/user/hooks/useProfile.ts
@@ -1,7 +1,6 @@
/* eslint-disable no-console */
/* eslint-disable no-alert */
import axios, { AxiosResponse } from 'axios';
-import { getStoredUser } from 'storage/user-storage';
import { axiosInstance } from '../../../axiosInstance';
import type { MemberInfo, NewUser } from '../../../types/types';
@@ -34,7 +33,7 @@ export function UseUserInfo(): useUserInfo {
},
});
if (status === 201 || status === 200) {
- // console.log('useAuth ServiceCall', data);
+ // // console.log('useAuth ServiceCall', data);
// getStoredUser();
if ('member' in data) {
const updateMember: NewUser = data.member;
@@ -76,7 +75,7 @@ export function UseUserInfo(): useUserInfo {
},
});
if (status === 201) {
- console.log('updateUser', data);
+ // console.log('updateUser', data);
if ('member' in data) {
const updateMember: NewUser = data.member;
updateUser({
@@ -102,11 +101,11 @@ export function UseUserInfo(): useUserInfo {
}
}
async function myInfo(member: NewUser): Promise {
- // console.log('members', member);
+ // // console.log('members', member);
infoCall(`/members/${member?.nickname}`, member);
}
async function updateInfo(member: NewUser): Promise {
- console.log('updatemembers', member);
+ // console.log('updatemembers', member);
await infoEditCall(`/members/save-profile`, member);
}
return {
diff --git a/src/components/user/userProfile.tsx b/src/components/user/userProfile.tsx
index ccc6b2d..8f4de24 100644
--- a/src/components/user/userProfile.tsx
+++ b/src/components/user/userProfile.tsx
@@ -1,12 +1,11 @@
/* eslint-disable no-console */
-import { ActionIcon, Avatar, Group, Paper, Text, Title } from '@mantine/core';
-import { IconSettings } from '@tabler/icons-react';
+import { Avatar, Box, Button, Group, Text } from '@mantine/core';
import axios from 'axios';
+import MainLayout from 'layout/mainLayout';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { baseUrl } from '../../axiosInstance/constants';
-import { HeaderMegaMenu } from '../../layout/mainLayout/header/header';
import { useUser } from './hooks/useUser';
import UserRoadmap from './userRoadmap';
@@ -23,50 +22,48 @@ export function UserProfile() {
return;
}
- axios
- .get(`${baseUrl}/members/${user.id}`)
- .then((response) => {
- setNickname(response.data.nickname);
- setBio(response.data.bio);
- setBaekjoonId(response.data.baekjoonId);
- })
- .catch((e) => console.log(e));
+ axios.get(`${baseUrl}/members/${user.id}`).then((response) => {
+ setNickname(response.data.nickname);
+ setBio(response.data.bio);
+ setBaekjoonId(response.data.baekjoonId);
+ });
+ // .catch((e) => // console.log(e));
}, [user.id, user.nickname]);
const myinfo = (
-
-
-
+
+
+
{nickname.substring(0, 1)}
-
- {nickname}
- {
- navigate('edit');
- }}
- >
-
-
-
-
+
+ {nickname}
+
+
{bio}
-
+
{baekjoonId}
-
+ {
+ navigate('edit');
+ }}
+ mt={20}
+ radius="lg"
+ variant="light"
+ color="#ebf6fc"
+ >
+ 프로필 수정
+
+
);
return (
- <>
-
-
- 마이페이지
-
+
{myinfo}
- >
+
);
}
diff --git a/src/components/user/userRoadmap.tsx b/src/components/user/userRoadmap.tsx
index 8571339..03f5483 100644
--- a/src/components/user/userRoadmap.tsx
+++ b/src/components/user/userRoadmap.tsx
@@ -1,18 +1,15 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
-/* eslint-disable no-console */
-import { Carousel } from '@mantine/carousel';
import {
+ Avatar,
Card,
createStyles,
Group,
Image,
- Paper,
- PaperProps,
rem,
+ SimpleGrid,
+ Tabs,
Text,
- useMantineTheme,
} from '@mantine/core';
-import { useMediaQuery } from '@mantine/hooks';
+import { IconPencil, IconPhoto } from '@tabler/icons-react';
import axios from 'axios';
import { baseUrl } from 'axiosInstance/constants';
import { useEffect, useState } from 'react';
@@ -24,23 +21,62 @@ const useStyles = createStyles((theme) => ({
card: {
backgroundColor:
theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.white,
+ transition: 'transform 150ms ease, box-shadow 150ms ease',
+
+ '&:hover': {
+ transform: 'scale(1.01)',
+ boxShadow: theme.shadows.md,
+ },
+ borderRadius: theme.radius.md,
+ boxShadow: theme.shadows.lg,
+ width: '98%',
+ margin: '2rem auto 1rem',
},
title: {
fontFamily: `Greycliff CF, ${theme.fontFamily}`,
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ marginTop: '1.5rem',
+ borderTop: '1px',
+ fontSize: '1rem',
+ },
+
+ desc: {
+ display: '-webkit-box',
+ overflow: 'hidden',
+ WebkitLineClamp: 3,
+ WebkitBoxOrient: 'vertical',
+ fontFamily: `Greycliff CF, ${theme.fontFamily}`,
+ maxHeight: '4.5em',
+ lineHeight: '1.3em',
+ marginTop: '0.5rem',
+ },
+
+ like: {
+ color: theme.colors.red[6],
+ },
+
+ item: {
+ width: '100%',
},
- author: {
+ section: {
+ height: '18rem',
+ cursor: 'pointer',
+ },
+
+ footer: {
padding: `${theme.spacing.xs} ${theme.spacing.lg}`,
marginTop: theme.spacing.md,
borderTop: `${rem(1)} solid ${
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[2]
}`,
- fontSize: '13px',
},
}));
-export default function UserRoadmap(props: PaperProps) {
+export default function UserRoadmap() {
const [joinedRoadmap, setJoinedRoadmap] = useState([]);
const [savedRoadmap, setSavedRoadmap] = useState([]);
const { user } = useUser();
@@ -60,9 +96,7 @@ export default function UserRoadmap(props: PaperProps) {
.then((v) => {
setJoinedRoadmap(v?.data);
})
- .catch((e) => {
- console.log(e);
- });
+ .catch();
}, [user?.accessToken, user.nickname]);
useEffect(() => {
@@ -77,193 +111,153 @@ export default function UserRoadmap(props: PaperProps) {
.then((v) => {
setSavedRoadmap(v?.data);
})
- .catch((e) => {
- console.log(e);
- });
+ .catch();
}, [user?.accessToken, user.nickname]);
- // const theme = useMantineTheme();
- // const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
-
return (
- <>
-
- 진행 중인 로드맵
-
-
-
- {!joinedRoadmap
- ? '아직 진행 중인 로드맵이 없습니다.'
- : joinedRoadmap.map((article) => (
-
-
-
- {article.thumbnailUrl ? (
- {
- setCurrentPage(article.id);
- }}
- onClick={(e) => {
- e.stopPropagation();
- currentPage &&
- navigate(`/roadmap/post/${currentPage}`);
- }}
- />
- ) : (
- {
- setCurrentPage(article.id);
- }}
- onClick={(e) => {
- e.stopPropagation();
- currentPage &&
- navigate(`/roadmap/post/${currentPage}`);
- }}
- />
- )}
-
- {
- setCurrentPage(article.id);
- }}
- onClick={(e) => {
- e.stopPropagation();
- currentPage && navigate(`/roadmap/post/${currentPage}`);
- }}
- style={{ cursor: 'pointer' }}
- >
- {article?.title}
-
-
- {article?.ownerNickname}
-
-
-
- ))}
-
-
+
+
+ }>
+ 진행 중인 로드맵
+
+ }>
+ 내가 만든 로드맵
+
+
+
+
+
+ {joinedRoadmap.length === 0 ? (
+
+ 진행 중인 로드맵이 없습니다. 로드맵 참여를 눌러 다른 로드맵에
+ 참여해주세요!
+
+ ) : (
+ joinedRoadmap.map((article) => (
+
+ {
+ setCurrentPage(article.id);
+ }}
+ onClick={() => {
+ currentPage && navigate(`/roadmap/post/${currentPage}`);
+ }}
+ >
+ {
+ setCurrentPage(article.id);
+ }}
+ onClick={(e) => {
+ e.stopPropagation();
+ currentPage && navigate(`/roadmap/post/${currentPage}`);
+ }}
+ />
+
+ {article?.title}
+
+
+ {article.description}
+
+
+
+ {article.createdAt}
+
+
+
+
+ {article?.member?.nickname.substring(0, 1)}
+
-
- 내가 만든 로드맵
-
-
-
- {savedRoadmap.length === 0
- ? '아직 만든 로드맵이 없습니다. 로드맵을 생성해보세요!'
- : savedRoadmap.map((article) => (
-
-
-
- {article.thumbnailUrl ? (
- {
- setCurrentPage(article.id);
- }}
- onClick={(e) => {
- e.stopPropagation();
- currentPage &&
- navigate(`/roadmap/post/${currentPage}`);
- }}
- />
- ) : (
- {
- setCurrentPage(article.id);
- }}
- onClick={(e) => {
- e.stopPropagation();
- currentPage &&
- navigate(`/roadmap/post/${currentPage}`);
- }}
- />
- )}
-
- {
- setCurrentPage(article.id);
- }}
- onClick={(e) => {
- e.stopPropagation();
- currentPage && navigate(`/roadmap/post/${currentPage}`);
- }}
- style={{ cursor: 'pointer' }}
- >
- {article?.title}
+
+ {article?.member?.nickname}
-
- {article?.ownerNickname}
+
+
+
+ ))
+ )}
+
+
+
+
+
+ {savedRoadmap.length === 0 ? (
+
+ 만든 로드맵이 없습니다. 로드맵 생성 버튼을 누르고 로드맵을
+ 생성해보세요!
+
+ ) : (
+ savedRoadmap.map((article) => (
+
+ {
+ setCurrentPage(article.id);
+ }}
+ onClick={() => {
+ currentPage && navigate(`/roadmap/post/${currentPage}`);
+ }}
+ >
+ {
+ setCurrentPage(article.id);
+ }}
+ onClick={(e) => {
+ e.stopPropagation();
+ currentPage && navigate(`/roadmap/post/${currentPage}`);
+ }}
+ />
+
+ {article?.title}
+
+
+ {article.description}
+
+
+
+ {article.createdAt}
+
+
+
+
+ {article?.member?.nickname.substring(0, 1)}
+
+
+
+ {article?.member?.nickname}
-
-
- ))}
-
-
- >
+
+
+
+ ))
+ )}
+
+
+
);
}
diff --git a/src/index.tsx b/src/index.tsx
index ebc3d66..19b72cb 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -2,16 +2,12 @@ import './index.css';
import { Button, MantineProvider, Text } from '@mantine/core';
import { ContextModalProps, ModalsProvider } from '@mantine/modals';
-import { StyledEngineProvider } from '@mui/material/styles';
-import React from 'react';
-import ReactDOM from 'react-dom/client';
+import { createRoot } from 'react-dom/client';
import App from './App';
import reportWebVitals from './reportWebVitals';
-const root = ReactDOM.createRoot(
- document.getElementById('root') as HTMLElement,
-);
+const root = createRoot(document.getElementById('root') as HTMLElement);
function TestModal({
context,
@@ -37,43 +33,30 @@ declare module '@mantine/modals' {
}
}
root.render(
-
-
-
-
-
-
-
-
- ,
+ },
+ }}
+ >
+
+
+
+ ,
);
// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
+// to log results (for example: reportWebVitals(// console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
diff --git a/src/layout/mainLayout/footer/Footer.tsx b/src/layout/mainLayout/footer/Footer.tsx
new file mode 100644
index 0000000..b45dc75
--- /dev/null
+++ b/src/layout/mainLayout/footer/Footer.tsx
@@ -0,0 +1,181 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import {
+ ActionIcon,
+ Container,
+ createStyles,
+ Group,
+ rem,
+ Text,
+} from '@mantine/core';
+import { IconBrandGithub } from '@tabler/icons-react';
+import { useState } from 'react';
+import { useLocation, useNavigate } from 'react-router-dom';
+
+const useStyles = createStyles((theme) => ({
+ footer: {
+ marginTop: rem(120),
+ paddingTop: `calc(${theme.spacing.xl} * 2)`,
+ paddingBottom: `calc(${theme.spacing.xl} * 2)`,
+ backgroundColor:
+ theme.colorScheme === 'dark'
+ ? theme.colors.dark[6]
+ : theme.colors.gray[0],
+ borderTop: `${rem(1)} solid ${
+ theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[2]
+ }`,
+ },
+
+ logo: {
+ maxWidth: rem(200),
+
+ [theme.fn.smallerThan('sm')]: {
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ },
+ },
+
+ description: {
+ marginTop: rem(5),
+
+ [theme.fn.smallerThan('sm')]: {
+ marginTop: theme.spacing.xs,
+ textAlign: 'center',
+ },
+ },
+
+ inner: {
+ display: 'flex',
+ justifyContent: 'space-between',
+
+ [theme.fn.smallerThan('sm')]: {
+ flexDirection: 'column',
+ alignItems: 'center',
+ },
+ },
+
+ groups: {
+ display: 'flex',
+ flexWrap: 'wrap',
+
+ [theme.fn.smallerThan('sm')]: {
+ display: 'none',
+ },
+ },
+
+ wrapper: {
+ width: rem(160),
+ },
+
+ link: {
+ display: 'block',
+ color:
+ theme.colorScheme === 'dark'
+ ? theme.colors.dark[1]
+ : theme.colors.gray[6],
+ fontSize: theme.fontSizes.sm,
+ paddingTop: rem(3),
+ paddingBottom: rem(3),
+
+ '&:hover': {
+ textDecoration: 'underline',
+ },
+ },
+
+ title: {
+ fontSize: theme.fontSizes.lg,
+ fontWeight: 700,
+ fontFamily: `Greycliff CF, ${theme.fontFamily}`,
+ marginBottom: `calc(${theme.spacing.xs} / 2)`,
+ color: theme.colorScheme === 'dark' ? theme.white : theme.black,
+ },
+
+ afterFooter: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginTop: theme.spacing.xl,
+ paddingTop: theme.spacing.xl,
+ paddingBottom: theme.spacing.xl,
+ borderTop: `${rem(1)} solid ${
+ theme.colorScheme === 'dark' ? theme.colors.dark[4] : theme.colors.gray[2]
+ }`,
+
+ [theme.fn.smallerThan('sm')]: {
+ flexDirection: 'column',
+ },
+ },
+
+ social: {
+ [theme.fn.smallerThan('sm')]: {
+ marginTop: theme.spacing.xs,
+ },
+ },
+}));
+
+interface FooterLinksProps {
+ data: {
+ title: string;
+ links: { label: string; link: string }[];
+ }[];
+}
+
+export function Footer({ data }: FooterLinksProps) {
+ const { classes } = useStyles();
+ const navigate = useNavigate();
+ const { pathname } = useLocation();
+ const [isEditorPage, setIsEditorPage] = useState(false);
+ const groups = data.map((group) => {
+ const links = group.links.map((link, index) => (
+
+ key={index}
+ className={classes.link}
+ component="a"
+ href={link.link}
+ onClick={(event) => event.preventDefault()}
+ >
+ {link.label}
+
+ ));
+
+ return (
+
+ {group.title}
+ {links}
+
+ );
+ });
+
+ // function setLeaveEditorAction(arg0: string) {
+ // if(pathname === '/roadmap/editor'){
+ // return setIsEditorPage(true);
+ // }
+ // navigate('/');
+ // }
+
+ return (
+
+ );
+}
diff --git a/src/layout/mainLayout/header/GptModal.tsx b/src/layout/mainLayout/header/GptModal.tsx
new file mode 100644
index 0000000..dc6dfe8
--- /dev/null
+++ b/src/layout/mainLayout/header/GptModal.tsx
@@ -0,0 +1,86 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import {
+ ActionIcon,
+ LoadingOverlay,
+ TextInput,
+ TextInputProps,
+ useMantineTheme,
+} from '@mantine/core';
+import { IconArrowLeft, IconArrowRight, IconSearch } from '@tabler/icons-react';
+import { AxiosResponse } from 'axios';
+import { useInput } from 'components/common/hooks/useInput';
+import { usePromptAnswer } from 'components/prompts/hooks/usePromptResponse';
+import { useUser } from 'components/user/hooks/useUser';
+import { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+
+export function InputWithButton(props: TextInputProps) {
+ const theme = useMantineTheme();
+ const [prompt, onPromptChange] = useInput('');
+ // const { getprompt } = usePrompt();
+ const navigate = useNavigate();
+ const { user } = useUser();
+ // const { pathname } = useLocation();
+ const [isLoading, setIsLoading] = useState(false);
+ const { clearGptAnswer, updateGptAnswer } = usePromptAnswer();
+ // const [promptResponse, setPromptResponse] = useState();
+ const [promptResponse, setPromptResponse] =
+ useState(null);
+
+ // const onRequestPrompt = useCallback(() => {
+ const onRequestPrompt = () => {
+ updateGptAnswer({ keyword: prompt });
+ setPromptResponse(prompt);
+ };
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ onRequestPrompt();
+ }
+ };
+ useEffect(() => {
+ // @Pyotato : 페이지 안넘어가던 문제 해결~
+ if (promptResponse) {
+ navigate(`/roadmap/editor`);
+ }
+ }, [promptResponse, navigate]);
+ return (
+ <>
+
+ }
+ radius="md"
+ w="600px"
+ rightSection={
+ {
+ if (!user?.accessToken) {
+ // eslint-disable-next-line no-alert
+ alert('로그인 후 이용가능합니다.');
+ navigate('/users/signin');
+ }
+ // onRequestPrompt();
+ onRequestPrompt();
+ }}
+ radius="xl"
+ color={theme.primaryColor}
+ variant="filled"
+ >
+ {theme.dir === 'ltr' ? (
+
+ ) : (
+
+ )}
+
+ }
+ rightSectionWidth={42}
+ placeholder="키워드를 입력하세요"
+ {...props}
+ />
+ >
+ );
+}
diff --git a/src/layout/mainLayout/header/Header.tsx b/src/layout/mainLayout/header/Header.tsx
new file mode 100644
index 0000000..aee36a0
--- /dev/null
+++ b/src/layout/mainLayout/header/Header.tsx
@@ -0,0 +1,396 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import {
+ ActionIcon,
+ Avatar,
+ Box,
+ Burger,
+ Button,
+ Center,
+ createStyles,
+ Drawer,
+ Group,
+ Header,
+ Image,
+ Modal,
+ NavLink,
+ rem,
+ Text,
+ TextInput,
+} from '@mantine/core';
+import { useDisclosure } from '@mantine/hooks';
+import {
+ IconHome2,
+ IconLogin,
+ IconLogout,
+ IconSearch,
+ IconUserCircle,
+} from '@tabler/icons-react';
+import axios from 'axios';
+import { baseUrl } from 'axiosInstance/constants';
+import { useInput } from 'components/common/hooks/useInput';
+import { useCallback, useState } from 'react';
+import { useLocation, useNavigate } from 'react-router-dom';
+import { clearStoredGpt } from 'storage/gpt-storage';
+import { clearStoredRoadmap } from 'storage/roadmap-storage';
+import { styled } from 'styled-components';
+
+import { useAuth } from '../../../auth/useAuth';
+import { useUser } from '../../../components/user/hooks/useUser';
+import { InputWithButton } from './GptModal';
+
+const useStyles = createStyles((theme) => ({
+ link: {
+ display: 'flex',
+ flexWrap: 'nowrap',
+ alignItems: 'center',
+ height: '100%',
+ paddingLeft: theme.spacing.md,
+ paddingRight: theme.spacing.md,
+ textDecoration: 'none',
+ color: theme.colorScheme === 'dark' ? theme.white : theme.black,
+ fontWeight: 500,
+ fontSize: theme.fontSizes.sm,
+
+ [theme.fn.smallerThan('sm')]: {
+ height: rem(42),
+ display: 'flex',
+ alignItems: 'center',
+ width: '100%',
+ },
+
+ ...theme.fn.hover({
+ backgroundColor:
+ theme.colorScheme === 'dark'
+ ? theme.colors.dark[6]
+ : theme.colors.gray[0],
+ }),
+ },
+
+ subLink: {
+ width: '100%',
+ padding: `${theme.spacing.xs} ${theme.spacing.md}`,
+ borderRadius: theme.radius.md,
+
+ ...theme.fn.hover({
+ backgroundColor:
+ theme.colorScheme === 'dark'
+ ? theme.colors.dark[7]
+ : theme.colors.gray[0],
+ }),
+
+ '&:active': theme.activeStyles,
+ },
+
+ dropdownFooter: {
+ backgroundColor:
+ theme.colorScheme === 'dark'
+ ? theme.colors.dark[7]
+ : theme.colors.gray[0],
+ margin: `calc(${theme.spacing.md} * -1)`,
+ marginTop: theme.spacing.sm,
+ padding: `${theme.spacing.md} calc(${theme.spacing.md} * 2)`,
+ paddingBottom: theme.spacing.xl,
+ borderTop: `${rem(1)} solid ${
+ theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1]
+ }`,
+ },
+
+ hiddenMobile: {
+ [theme.fn.smallerThan('sm')]: {
+ display: 'none',
+ },
+ },
+
+ hiddenDesktop: {
+ [theme.fn.largerThan('sm')]: {
+ display: 'none',
+ },
+ },
+}));
+
+export function HeaderItem() {
+ const [drawerOpened, { toggle: toggleDrawer, close: closeDrawer }] =
+ useDisclosure(false);
+ const [linksOpened, { toggle: toggleLinks }] = useDisclosure(false);
+ const { classes, theme } = useStyles();
+ const navigate = useNavigate();
+ const { user } = useUser();
+ const { signout } = useAuth();
+ const { pathname } = useLocation();
+ const [opened, { open, close }] = useDisclosure(false);
+ const [isEditorPage, setIsEditorPage] = useState(false);
+ const [leaveEditorAction, setLeaveEditorAction] = useState('');
+ const [search, onChangeSearch, setSearch] = useInput('');
+ const [isLoading, setIsLoading] = useState(false);
+
+ const searchByKeyword = useCallback(() => {
+ axios
+ .get(`${baseUrl}/roadmaps/search/${search}?page=${1}&size=5`)
+ .then((v) => {
+ localStorage.setItem('roadmap_search_keyword', search);
+ navigate(`/roadmap/post/search/${search}`);
+ })
+ .catch();
+ }, [navigate, search]);
+
+ const handleKeyDown = (e) => {
+ if (e.key === 'Enter') {
+ searchByKeyword();
+ }
+ };
+ return (
+
+
+
+
+ {
+ setLeaveEditorAction('home');
+ pathname === '/roadmap/editor'
+ ? setIsEditorPage(true)
+ : navigate('/');
+ }}
+ className="hoverItem"
+ />
+ {pathname !== '/roadmap/editor' && (
+
+ searchByKeyword()}
+ />
+
+ }
+ />
+ )}
+
+ {pathname !== '/roadmap/editor' && (
+
+ 로드맵 생성
+
+ )}
+ {user && 'accessToken' in user ? (
+ <>
+ {
+ setLeaveEditorAction('mypage');
+ pathname === '/roadmap/editor'
+ ? setIsEditorPage(true)
+ : navigate('/users/mypage');
+ }}
+ >
+ {
+ setLeaveEditorAction('mypage');
+ pathname === '/roadmap/editor'
+ ? setIsEditorPage(true)
+ : navigate('/users/mypage');
+ }}
+ >
+ {user.nickname.slice(0, 1)}
+
+
+ {
+ setLeaveEditorAction('signout');
+ pathname === '/roadmap/editor'
+ ? setIsEditorPage(true)
+ : signout();
+ }}
+ >
+ Sign out
+
+ >
+ ) : (
+ {
+ pathname === '/roadmap/editor'
+ ? setIsEditorPage(true)
+ : navigate('/users/signin');
+ }}
+ >
+ Sign in
+
+ )}
+
+
+
+ {
+ navigate('/');
+ toggleDrawer();
+ }}
+ icon={}
+ />
+ {user && 'accessToken' in user ? (
+
+ {
+ navigate('/users/mypage');
+ toggleDrawer();
+ }}
+ icon={}
+ />
+ {
+ signout();
+ toggleDrawer();
+ }}
+ icon={}
+ />
+
+ ) : (
+ {
+ navigate('/users/signin');
+ toggleDrawer();
+ }}
+ icon={}
+ />
+ )}
+
+
+
+
+
+ setIsEditorPage(false)}
+ centered
+ >
+
+
+
+
+
+
+ 로드맵 작성을 중단하시겠습니까?
+
+ 변경사항이 저장되지 않을 수 있습니다.
+ {
+ if (leaveEditorAction === 'mypage') {
+ navigate('/users/mypage');
+ }
+ if (leaveEditorAction === 'home') {
+ navigate('/');
+ }
+ if (leaveEditorAction === 'signout') {
+ signout();
+ }
+ }}
+ >
+ 나가기
+
+
+
+
+
+
+
+ 새로운 로드맵 생성하기
+
+
+
+
+
+
+
+
+ 오늘은 그냥 템플릿 없이 빈 로드맵 만들게요.
+ {
+ clearStoredRoadmap();
+ clearStoredGpt();
+ if (!user || !('accessToken' in user)) {
+ // eslint-disable-next-line no-alert
+ alert('로그인 후 이용가능합니다.');
+ navigate('/users/signin');
+ }
+ // if(localStorage.getItem(''))
+ navigate('/roadmap/editor');
+ }}
+ >
+ 빈 로드맵 만들기
+
+
+
+
+
+ );
+}
+const HeaderWrap = styled.nav`
+ & .hoverItem:hover {
+ cursor: pointer;
+ }
+ & .confirm_btn_wrap {
+ display: inline-flex;
+ }
+`;
diff --git a/src/layout/mainLayout/header/header.tsx b/src/layout/mainLayout/header/HeaderItem.tsx
similarity index 78%
rename from src/layout/mainLayout/header/header.tsx
rename to src/layout/mainLayout/header/HeaderItem.tsx
index 7adba11..8626f6a 100644
--- a/src/layout/mainLayout/header/header.tsx
+++ b/src/layout/mainLayout/header/HeaderItem.tsx
@@ -1,8 +1,8 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-alert */
-/* eslint-disable no-console */
+/* eslint-disable @typescript-eslint/no-unused-vars */
import {
ActionIcon,
+ Avatar,
Box,
Button,
Center,
@@ -13,7 +13,6 @@ import {
LoadingOverlay,
Modal,
rem,
- Text,
TextInput,
TextInputProps,
useMantineTheme,
@@ -23,7 +22,6 @@ import { IconArrowLeft, IconArrowRight, IconSearch } from '@tabler/icons-react';
import axios, { AxiosResponse } from 'axios';
import { baseUrl } from 'axiosInstance/constants';
import { useInput } from 'components/common/hooks/useInput';
-// import { usePrompt } from 'components/prompts/hooks/usePrompt';
import { usePromptAnswer } from 'components/prompts/hooks/usePromptResponse';
import { useCallback, useMemo, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
@@ -84,7 +82,6 @@ const useStyles = createStyles((theme) => ({
margin: `calc(${theme.spacing.md} * -1)`,
marginTop: theme.spacing.sm,
padding: `${theme.spacing.md} calc(${theme.spacing.md} * 2)`,
- // paddingBottom: theme.spacing.xl,
borderTop: `${rem(1)} solid ${
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1]
}`,
@@ -122,15 +119,19 @@ export function HeaderMegaMenu() {
localStorage.setItem('roadmap_search_keyword', search);
navigate(`/roadmap/post/search/${search}`);
})
- // eslint-disable-next-line no-console
- .catch((e) => console.log(e));
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [search]);
+ .catch();
+ }, [navigate, search]);
+
+ const handleKeyDown = (e) => {
+ if (e.key === 'Enter') {
+ searchByKeyword();
+ }
+ };
return (
-
+
{
setLeaveEditorAction('home');
@@ -150,22 +151,21 @@ export function HeaderMegaMenu() {
}}
className="hoverItem"
/>
- {/* */}
{pathname !== '/roadmap/editor' && (
:
-
+
setIsEditorPage(false)}
>
-
+
로드맵을 아직 출간하지 않았습니다.
변경사항이 저장되지 않을 수 있습니다.
+
{
if (leaveEditorAction === 'mypage') {
navigate('/users/mypage');
@@ -212,6 +221,8 @@ export function HeaderMegaMenu() {
setIsEditorPage(false)}
>
@@ -220,11 +231,11 @@ export function HeaderMegaMenu() {
-
+
새로운 로드맵 생성하기
-
+
-
+
오늘은 그냥 템플릿 없이 빈 로드맵 만들게요.
{
clearStoredRoadmap();
clearStoredGpt();
@@ -247,7 +259,6 @@ export function HeaderMegaMenu() {
alert('로그인 후 이용가능합니다.');
navigate('/users/signin');
}
- // if(localStorage.getItem(''))
navigate('/roadmap/editor');
}}
>
@@ -258,33 +269,21 @@ export function HeaderMegaMenu() {
{pathname !== '/roadmap/editor' && (
- 로드맵 생성하기
+ 로드맵 생성
)}
{user && 'accessToken' in user ? (
<>
- {
- setLeaveEditorAction('mypage');
- pathname === '/roadmap/editor'
- ? setIsEditorPage(true)
- : navigate('/users/mypage');
- }}
- >
- {user.nickname}님
-
{
setLeaveEditorAction('signout');
pathname === '/roadmap/editor'
@@ -294,10 +293,26 @@ export function HeaderMegaMenu() {
>
Sign out
+ {
+ setLeaveEditorAction('mypage');
+ pathname === '/roadmap/editor'
+ ? setIsEditorPage(true)
+ : navigate('/users/mypage');
+ }}
+ >
+ {user.nickname.slice(0, 1)}
+
>
) : (
{
pathname === '/roadmap/editor'
? setIsEditorPage(true)
@@ -327,76 +342,29 @@ const HeaderWrap = styled.nav`
export function InputWithButton(props: TextInputProps) {
const theme = useMantineTheme();
const [prompt, onPromptChange, setPrompt] = useInput('');
- // const { getprompt } = usePrompt();
const navigate = useNavigate();
const { user } = useUser();
- // const { pathname } = useLocation();
const [isLoading, setIsLoading] = useState(false);
const { clearGptAnswer, updateGptAnswer } = usePromptAnswer();
- // const [promptResponse, setPromptResponse] = useState();
const [promptResponse, setPromptResponse] =
useState(null);
- // const onRequestPrompt = useCallback(() => {
const onRequestPrompt = () => {
updateGptAnswer({ keyword: prompt });
setPromptResponse(prompt);
};
- // const onRequestPrompt = () => {
- // updateGptAnswer({ keyword: prompt });
- // setPromptResponse(prompt);
- // // setIsLoading(true);
- // // axios
- // // .post(`${baseUrl}/chat?prompt=${prompt}`, {
- // // headers: {
- // // 'Content-Type': 'application/json',
- // // Authorization: `Bearer ${user?.accessToken}`,
- // // },
- // // })
- // // .then((result) => {
- // // console.log(result);
- // // setPromptResponse(result);
- // // // navigate(`/roadmap/editor`);
- // // })
- // // .then(() => {
- // // setIsLoading(false);
- // // })
- // // .catch((err) => {
- // // console.log(err);
- // // });
-
- // // queryClient.prefetchQuery(['prompts', prompt], () => {
- // // getprompt(prompt);
- // // updateGptAnswer(promptResponse as unknown as Prompt);
- // // });
- // };
-
- // const onRequestPrompt = () => {
-
- // axios
- // .post(`${baseUrl}/chat?prompt=${prompt}`, {
- // headers: {
- // 'Content-Type': 'application/json',
- // Authorization: `Bearer ${user?.accessToken}`,
- // },
- // })
- // .then((result) => {
- // console.log(result);
- // navigate(`/roadmap/editor`);
- // })
- // .catch((err) => {
- // console.log(err);
- // });
- // };
-
- // const onRequestPrompt = useMemo(() => {}, []);
useMemo(() => {
- // @Pyotato : 페이지 안넘어가던 문제 해결~
if (promptResponse) {
navigate(`/roadmap/editor`);
}
}, [promptResponse, navigate]);
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ onRequestPrompt();
+ }
+ };
return (
<>
@@ -406,6 +374,7 @@ export function InputWithButton(props: TextInputProps) {
icon={}
radius="md"
w="600px"
+ onKeyDown={handleKeyDown}
rightSection={
-
- {children}
+ {/* */}
+
+
+ {pathname !== '/roadmap/editor' ? {children} : children}
+ {pathname !== '/roadmap/editor' && }
>
);
}
export default MainLayout;
+const Wrap = styled.section`
+ width: 80vw;
+ /* height: fit-content; */
+ margin: 0 auto;
+`;
diff --git a/src/pages/error/index.tsx b/src/pages/error/index.tsx
index ab8f880..b34c0e4 100644
--- a/src/pages/error/index.tsx
+++ b/src/pages/error/index.tsx
@@ -72,6 +72,7 @@ export default function ErrorPage() {
variant="outline"
size="md"
mt="xl"
+ color="#ebf6fc"
className={classes.control}
onClick={() => navigate('/')}
>
diff --git a/src/pages/main/commentPage.tsx b/src/pages/main/commentPage.tsx
index 6d0c59c..c417598 100644
--- a/src/pages/main/commentPage.tsx
+++ b/src/pages/main/commentPage.tsx
@@ -1,5 +1,3 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
-/* eslint-disable no-console */
import {
Button,
Center,
@@ -12,16 +10,15 @@ import {
SimpleGrid,
Text,
Textarea,
- TextInput,
} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import axios from 'axios';
-import { baseUrl } from 'axiosInstance/constants';
import { useCallback, useEffect, useState } from 'react';
import InfiniteScroll from 'react-infinite-scroller';
import { useInfiniteQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
+import { baseUrl } from '../../axiosInstance/constants';
import { useUser } from '../../components/user/hooks/useUser';
const useStyles = createStyles((theme) => ({
@@ -33,15 +30,13 @@ const useStyles = createStyles((theme) => ({
function CommentPage() {
const [opened, { open, close }] = useDisclosure(false);
- const { classes, theme } = useStyles();
+ const { classes } = useStyles();
// const [count, handlers] = useCounter(0, { min: 0, max: 1000 });
const [commentPage, setCommentPage] = useState(1);
const { pathname } = useLocation();
- const [title, setTitle] = useState('');
const [content, setContent] = useState([]);
const [commentInput, setCommentInput] = useState('');
const { user } = useUser();
- const [counts, setCounts] = useState([]);
const fetchComments = useCallback(() => {
axios
@@ -60,7 +55,7 @@ function CommentPage() {
// setCounts(new Array(commentContents.length).fill(0));
// setNickname(v?.data?.commentNickname);
})
- .catch((e) => console.log(e));
+ .catch();
}, [commentPage, pathname]);
const {
@@ -103,10 +98,6 @@ function CommentPage() {
if (isLoading) return Loading...
;
if (isError) return Error! {error.toString()}
;
- const handleCommentTitleChange = (event) => {
- setTitle(event.target.value);
- };
-
const handleCommentContentChange = (event) => {
setCommentInput(event.target.value);
};
@@ -131,7 +122,7 @@ function CommentPage() {
fetchComments();
refetch();
})
- .catch((e) => console.log('err', e));
+ .catch();
}
return (
<>
@@ -140,25 +131,6 @@ function CommentPage() {
코멘트 작성
- {/* */}
-
{
handleSubmit();
@@ -181,7 +154,6 @@ function CommentPage() {
- {/* */}
- 코멘트 작성하기
+
+ 코멘트 작성하기
+
{content.length === 0 ? (
@@ -209,7 +183,7 @@ function CommentPage() {
>
{data.pages.map((pageData) => {
return pageData.result.map((comments, index) => (
-
+
{/*
{user.nickname.substring(0, 1)}
diff --git a/src/pages/main/index.tsx b/src/pages/main/index.tsx
index ce22d51..8e58e54 100644
--- a/src/pages/main/index.tsx
+++ b/src/pages/main/index.tsx
@@ -1,13 +1,12 @@
-import { HeaderMegaMenu } from 'layout/mainLayout/header/header';
+import MainLayout from 'layout/mainLayout';
-import RoadmapRecommendation from './roadmapRecommendation';
+import RoadmapRecommendation from '../../components/roadmaps/posts/main/RoadMapMainItems';
function MainPage() {
return (
- <>
-
+
- >
+
);
}
export default MainPage;
diff --git a/src/pages/main/roadmapRecommendation.tsx b/src/pages/main/roadmapRecommendation.tsx
deleted file mode 100644
index f0033ef..0000000
--- a/src/pages/main/roadmapRecommendation.tsx
+++ /dev/null
@@ -1,249 +0,0 @@
-/* eslint-disable no-console */
-import {
- ActionIcon,
- Card,
- Container,
- createStyles,
- Group,
- Image,
- rem,
- SimpleGrid,
- Text,
-} from '@mantine/core';
-import { IconHeart } from '@tabler/icons-react';
-import axios from 'axios';
-import { baseUrl } from 'axiosInstance/constants';
-import { useCallback, useEffect, useState } from 'react';
-import InfiniteScroll from 'react-infinite-scroller';
-import { useInfiniteQuery } from 'react-query';
-import { useNavigate } from 'react-router-dom';
-
-const useStyles = createStyles((theme) => ({
- card: {
- backgroundColor:
- theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.white,
- columnWidth: '350px',
- columnGap: '15px',
- },
-
- title: {
- fontFamily: `Greycliff CF, ${theme.fontFamily}`,
- padding: '10px',
- marginTop: '11px',
- borderTop: '1px',
- },
-
- author: {
- padding: `${theme.spacing.xs} ${theme.spacing.lg}`,
- marginTop: theme.spacing.md,
- borderTop: `${rem(1)} solid ${
- theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[2]
- }`,
- fontSize: '13px',
- },
-
- like: {
- color: theme.colors.red[6],
- },
-
- label: {
- textTransform: 'uppercase',
- fontSize: theme.fontSizes.xs,
- fontWeight: 700,
- },
-
- list: {
- display: 'flex',
- flexDirection: 'column',
- margin: 0,
- marginBottom: '15px',
- padding: '10px',
- alignItems: 'center',
- gap: '10px',
- },
-
- item: {
- width: '100%',
- },
-
- // list: {
- // display: flex, // 1
- // flex-direction: column, // 2
- // flex-wrap: wrap, // 3
- // align-content: start, // 4
- // height: 1000px, // 5
- // },
-
- // item: {
- // width: 25%; // 6
- // }
-}));
-
-export default function RoadmapRecommendation() {
- const [allRoadmapData, setAllRoadmapData] = useState([]);
- const { classes } = useStyles();
- const navigate = useNavigate();
- const [currentPage, setCurrentPage] = useState('');
- const [roadmapPage, setRoadmapPage] = useState(1);
-
- // const date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));
- // options = {
- // year: 'numeric',
- // month: 'numeric',
- // day: 'numeric',
- // hour: 'numeric',
- // minute: 'numeric',
- // second: 'numeric',
- // hour12: false,
- // timeZone: '',
- // };
- // console.log(new Intl.DateTimeFormat(undefined, options).format(date));
- // const themes = useMantineTheme();
- // const mobile = useMediaQuery(`(max-width: ${themes.breakpoints.sm})`);
- // const { getRoadmapById, getAllRoadmap, getRoadmapByIdAuth } = useRoadmap();
- // const { roadmaps } = useRoadmapData();
-
- const fetchRoadmaps = useCallback(() => {
- axios
- .get(`${baseUrl}/roadmaps?page=${roadmapPage}&order-type=recent`)
- .then((v) => {
- console.log('data', v?.data);
- setAllRoadmapData(v?.data);
- })
- .catch((e) => {
- console.log(e);
- });
- }, [roadmapPage]);
-
- const initialUrl = `${baseUrl}/roadmaps?page=${roadmapPage}&order-type=recent`;
- const fetchUrl = async (url) => {
- console.log('url', url);
- const response = await fetch(url);
- return response.json();
- };
-
- const {
- refetch,
- data,
- fetchNextPage,
- hasNextPage,
- isLoading,
- isError,
- error,
- } = useInfiniteQuery(
- 'roadmaps',
- ({ pageParam = initialUrl }) => fetchUrl(pageParam),
- {
- getNextPageParam: (lastPage) => {
- if (lastPage.result.length !== 0) {
- return lastPage.next;
- }
- return undefined;
- },
- enabled: roadmapPage === 1,
- },
- );
-
- useEffect(() => {
- setRoadmapPage(1);
- refetch();
- fetchRoadmaps();
- }, [fetchRoadmaps, refetch]);
-
- if (isLoading) return Loading...
;
- if (isError) return Error! {error.toString()}
;
-
- return (
- <>
-
- {/* 추천 로드맵
*/}
-
- {!allRoadmapData ? (
- 만들어진 로드맵이 없습니다.
- ) : (
-
-
- {data.pages &&
- data.pages.map((pageData) => {
- return pageData.result.map((article, index) => (
- <>
-
- {article.thumbnailUrl ? (
- {
- setCurrentPage(article.id);
- }}
- onClick={() => {
- currentPage &&
- navigate(`/roadmap/post/${currentPage}`);
- }}
- />
- ) : (
- {
- setCurrentPage(article.id);
- }}
- onClick={() => {
- currentPage &&
- navigate(`/roadmap/post/${currentPage}`);
- }}
- />
- )}
-
- {/*
-
- {article?.ownerNickname}
-
-
-
-
-
- */
- /* {
- setCurrentPage(article.id);
- }}
- onClick={() => {
- currentPage &&
- navigate(`/roadmap/post/${currentPage}`);
- }}
- style={{
- cursor: 'pointer',
- margin: '50px',
- zIndex: '10',
- }}
- >
- {/* {new Intl.DateTimeFormat('ko', {
- dateStyle: 'full',
- }).format(article?.createdAt)}
- {article?.title}
- {article?.createdAt}
- */}
- >
- ));
- })}
-
-
- )}
- >
- );
-}
diff --git a/src/pages/main/searchByKeyword/InfiniteRoadmaps.jsx b/src/pages/main/searchByKeyword/InfiniteRoadmaps.jsx
index 4a15183..427dade 100644
--- a/src/pages/main/searchByKeyword/InfiniteRoadmaps.jsx
+++ b/src/pages/main/searchByKeyword/InfiniteRoadmaps.jsx
@@ -1,138 +1,217 @@
-// import axios from 'axios';
-// import { baseUrl } from 'axiosInstance/constants';
-// import { useState } from 'react';
-// import InfiniteScroll from 'react-infinite-scroller';
-// import { useInfiniteQuery } from 'react-query';
-
-// import { Roadmap } from './Roadmap';
-
-// export function InfiniteSearchRoadmap() {
-// const [pages, setPages] = useState(0);
-// const fetchUrl = async (param) => {
-// axios
-// // .get(`${baseUrl}/roadmaps/search/${search}?page=${url}&size=5`)
-// .get(`${baseUrl}/roadmaps/search/자바?page=${param}&size=5`)
-// // .get(url)
-// .then((v) => {
-// console.log(v);
-// // setPages(pages + 1);
-// })
-// .catch((e) => console.log(e));
-// };
-
-// const {
-// data,
-// fetchNextPage,
-// hasNextPage,
-// isLoading,
-// isFetching,
-// isError,
-// error,
-// } = useInfiniteQuery(
-// 'search-roadmaps', // query key
-// ({ pageParam = pages }) => fetchUrl(pageParam), // default value will be the initial Url
-// // { getNextPageParam: (lastPage) => lastPage.next || undefined }, // undefined ===> hasNextPage false
-// { getNextPageParam: () => pages || undefined }, // undefined ===> hasNextPage false
-// ); // destructure
-// if (isLoading) {
-// return loading...
;
-// }
-// if (isError) {
-// return Error!{error.toString()}
;
-// }
-// return (
-// <>
-// {isFetching && loading...
}
-//
-// {data.pages.map((allRoadmapData) => {
-// console.log(data);
-// console.log(allRoadmapData);
-// return allRoadmapData?.map((r) => {
-// // actual data
-// return (
-//
-// );
-// });
-// })}
-//
-// >
-// );
-// }
+/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-console */
+import {
+ Avatar,
+ Card,
+ createStyles,
+ Group,
+ Image,
+ rem,
+ SimpleGrid,
+ Text,
+} from '@mantine/core';
+import axios from 'axios';
import { baseUrl } from 'axiosInstance/constants';
+import { useCallback, useEffect, useState } from 'react';
import InfiniteScroll from 'react-infinite-scroller';
import { useInfiniteQuery } from 'react-query';
+import { useNavigate } from 'react-router-dom';
-import { Roadmap } from './Roadmap';
-
-// const initialUrl = 'https://swapi.dev/api/people/';
-const keyword = localStorage.getItem('roadmap_search_keyword');
-// const initialUrl = `${baseUrl}/roadmaps/search/타입스크립트?&size=5`;
-const initialUrl = `${baseUrl}/roadmaps/search/${keyword}/`;
-const fetchUrl = async (url) => {
- const response = await fetch(url);
- console.log('response', response.status);
- // return response.json();
- // const response = await axios.get(url, {}).then((v) => {
- // return v;
- // });
- return response;
-};
+import { ReactComponent as NoImage } from '../../../assets/noImage.svg';
+import { ReactComponent as Spinner } from '../../../assets/Spinner.svg';
+
+const useStyles = createStyles((theme) => ({
+ card: {
+ backgroundColor:
+ theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.white,
+ transition: 'transform 150ms ease, box-shadow 150ms ease',
+
+ '&:hover': {
+ transform: 'scale(1.01)',
+ boxShadow: theme.shadows.md,
+ },
+ borderRadius: theme.radius.md,
+ boxShadow: theme.shadows.lg,
+ width: '96%',
+ margin: '3rem auto 1rem',
+ },
+
+ title: {
+ fontFamily: `Greycliff CF, ${theme.fontFamily}`,
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ marginTop: '1.5rem',
+ borderTop: '1px',
+ fontSize: '1.3rem',
+ },
+
+ desc: {
+ display: '-webkit-box',
+ overflow: 'hidden',
+ WebkitLineClamp: 3,
+ WebkitBoxOrient: 'vertical',
+ fontFamily: `Greycliff CF, ${theme.fontFamily}`,
+ maxHeight: '4.5em',
+ lineHeight: '1.3em',
+ marginTop: '0.725rem',
+ },
+
+ like: {
+ color: theme.colors.red[6],
+ },
+
+ item: {
+ width: '100%',
+ },
+
+ section: {
+ height: '24rem',
+ cursor: 'pointer',
+ },
+
+ footer: {
+ padding: `${theme.spacing.xs} ${theme.spacing.lg}`,
+ marginTop: theme.spacing.md,
+ borderTop: `${rem(1)} solid ${
+ theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[2]
+ }`,
+ },
+}));
export function InfiniteRoadmapByKeyword() {
+ const [searchPage, setSearchPage] = useState(1);
+ // eslint-disable-next-line no-unused-vars
+ const [allRoadmapData, setAllRoadmapData] = useState([]);
+ const { classes } = useStyles();
+ const [currentPage, setCurrentPage] = useState('');
+ const navigate = useNavigate();
+ const keyword = localStorage.getItem('roadmap_search_keyword');
+
+ const fetchRoadmaps = useCallback(() => {
+ axios
+ .get(`${baseUrl}/roadmaps/search/${keyword}?page=${searchPage}&size=5`)
+ .then((v) => {
+ setAllRoadmapData(v?.data);
+ // console.log(v?.data);
+ })
+ .catch((e) => {
+ // console.log(e);
+ });
+ }, [keyword, searchPage]);
+
+ const initialUrl = `${baseUrl}/roadmaps/search/${keyword}?page=${searchPage}&size=5`;
+ const fetchUrl = async (url) => {
+ // console.log('fetchUrl:', url);
+ const response = await fetch(url);
+ // console.log('response', response);
+ return response.json();
+ };
+
const {
data,
+ refetch,
fetchNextPage,
hasNextPage,
isLoading,
- isFetching,
isError,
error,
- } =
- // const { data, fetchNextPage, hasNextPage, isFetching, isError, error } =
- useInfiniteQuery(
- 'search-keyword', // query key
- ({ pageParam = initialUrl }) => fetchUrl(pageParam), // default value will be the initial Url
- {
- getNextPageParam: (lastPage) => lastPage?.next || undefined,
- }, // undefined ===> hasNextPage false
- ); // destructure
- if (isLoading) {
- // if (isFetching) {
- // yikes! its scrolling to the top !(yes, we are getting new data, but we are scrolling to the top whenever it is refetching)
- // that's cos we are early returning every time we are fetching new data
- return loading...
;
- }
- if (isError) {
- return Error!{error.toString()}
;
- }
+ } = useInfiniteQuery(
+ 'search-keyword',
+ ({ pageParam = initialUrl }) => fetchUrl(pageParam),
+ {
+ getNextPageParam: (lastPage) => {
+ if (lastPage.result.length !== 0) {
+ return lastPage.next;
+ }
+ return undefined;
+ },
+ },
+ );
+
+ useEffect(() => {
+ setSearchPage(1);
+ refetch();
+ fetchRoadmaps();
+ }, [fetchRoadmaps, refetch]);
+ // console.log('data', data);
+
+ if (isLoading)
+ return (
+
+
+
로드맵을 검색하고 있어요
+
+ );
+ if (isError) return Error! {error.toString()}
;
return (
- <>
- {isFetching && loading...
}
-
- {data &&
- data.pages &&
- data?.pages?.map((pageData) => {
- // return pageData?.results.map((person) => {
- return pageData?.result.map((person) => {
- // actual data
+
+
+ {!data.pages && 아직 로드맵이 없습니다.
}
+ {data?.pages &&
+ data?.pages.map((pageData) => {
+ return pageData?.result?.map((article, index) => {
return (
-
+
+ {
+ setCurrentPage(article?.id);
+ }}
+ onClick={() => {
+ currentPage && navigate(`/roadmap/post/${currentPage}`);
+ }}
+ >
+
+
+ {article?.thumbnailUrl ? (
+
+ ) : (
+
+ )}
+
+
+
+ {article.title}
+
+
+ {article.description}
+
+
+
+ {article.createdAt}
+
+
+
+
+ {article.member.nickname.substring(0, 1)}
+
+
+
+ {article.member.nickname}
+
+
+
+
);
});
})}
-
- >
+
+
);
}
diff --git a/src/pages/main/searchByKeyword/people/InfinitePeople.jsx b/src/pages/main/searchByKeyword/people/InfinitePeople.jsx
deleted file mode 100644
index 2aa5daa..0000000
--- a/src/pages/main/searchByKeyword/people/InfinitePeople.jsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import InfiniteScroll from 'react-infinite-scroller';
-import { useInfiniteQuery } from 'react-query';
-
-import { Person } from './Person';
-
-const initialUrl = 'https://swapi.dev/api/people/';
-const fetchUrl = async (url) => {
- const response = await fetch(url);
- return response.json();
-};
-
-export function InfinitePeople() {
- const {
- data,
- fetchNextPage,
- hasNextPage,
- isLoading,
- isFetching,
- isError,
- error,
- } =
- // const { data, fetchNextPage, hasNextPage, isFetching, isError, error } =
- useInfiniteQuery(
- 'sw-people', // query key
- ({ pageParam = initialUrl }) => fetchUrl(pageParam), // default value will be the initial Url
- { getNextPageParam: (lastPage) => lastPage.next || undefined }, // undefined ===> hasNextPage false
- ); // destructure
- if (isLoading) {
- // if (isFetching) {
- // yikes! its scrolling to the top !(yes, we are getting new data, but we are scrolling to the top whenever it is refetching)
- // that's cos we are early returning every time we are fetching new data
- return loading...
;
- }
- if (isError) {
- return Error!{error.toString()}
;
- }
- return (
- <>
- {isFetching && loading...
}
-
- {data.pages.map((pageData) => {
- return pageData.results.map((person) => {
- // actual data
- return (
-
- );
- });
- })}
-
- >
- );
-}
diff --git a/src/pages/main/searchByKeyword/people/Person.jsx b/src/pages/main/searchByKeyword/people/Person.jsx
deleted file mode 100644
index d55b2e5..0000000
--- a/src/pages/main/searchByKeyword/people/Person.jsx
+++ /dev/null
@@ -1,11 +0,0 @@
-export function Person({ name, hairColor, eyeColor }) {
- return (
-
- {name}
-
- - hair: {hairColor}
- - eyes: {eyeColor}
-
-
- );
-}
diff --git a/src/pages/resetInfo/index.tsx b/src/pages/resetInfo/index.tsx
deleted file mode 100644
index efbddfe..0000000
--- a/src/pages/resetInfo/index.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { HeaderMegaMenu } from 'layout/mainLayout/header/header';
-
-import ResetInfoForm from '../../components/user/forms/ResetInfoForm';
-
-function ResetInfoPage() {
- return (
- <>
-
-
- >
- );
-}
-
-export default ResetInfoPage;
diff --git a/src/pages/roadmap/editor/index.tsx b/src/pages/roadmap/editor/index.tsx
index 8ec56ad..2d866ac 100644
--- a/src/pages/roadmap/editor/index.tsx
+++ b/src/pages/roadmap/editor/index.tsx
@@ -22,6 +22,7 @@ import RoadMapCanvas from '../../../components/editor/RoadMapEditor';
export default function RoadMapEditor(): ReactElement {
const [label, onChangeLabel, setLabel] = useInput(''); // 노드 상세 내용
+ const [blogKeyword, onChangeBlogKeyword, setBlogKeyword] = useInput(''); // 노드 상세 내용
const [color, onChangeColor, setColor] = useInput('#fff'); // 노드 색
// const [gptDetails, onChangeGptDetails, setGptDetails] = useInput(''); // 노드 색
const [id, onChangeId, setId] = useInput('');
@@ -29,8 +30,8 @@ export default function RoadMapEditor(): ReactElement {
const [search] = useSearchParams();
const [state, setState] = useState([
// tiptap 에디터 내용
- { id: '1', details: '' },
- { id: '2', details: '' },
+ { id: '1', detailedContent: '' },
+ { id: '2', detailedContent: '' },
]);
const [colorsState, setColorsState] = useState([
// tiptap 에디터 내용
@@ -60,7 +61,7 @@ export default function RoadMapEditor(): ReactElement {
Highlight,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
],
- content: state.filter((v) => v?.id === id)[0]?.details || '',
+ content: state.filter((v) => v?.id === id)[0]?.detailedContent || '',
autofocus: false,
onUpdate(e) {
@@ -68,17 +69,17 @@ export default function RoadMapEditor(): ReactElement {
// // @ts-ignore
// e?.transaction?.curSelection?.$anchor?.pos,
// );
- // console.log(e.editor?.getHTML());
+ // // console.log(e.editor?.getHTML());
setToggle(e.editor?.getHTML());
- // console.log('e.editor', e.editor);
- // console.log('e.editor isFocused', e.editor.getHTML());
+ // // console.log('e.editor', e.editor);
+ // // console.log('e.editor isFocused', e.editor.getHTML());
// eslint-disable-next-line array-callback-return
state.map((item, idx) => {
if (item?.id !== id) return;
const copyState = [...state];
copyState.splice(idx, 1, {
id: item?.id,
- details: e.editor?.getHTML(),
+ detailedContent: e.editor?.getHTML(),
});
setState(copyState);
});
@@ -94,7 +95,7 @@ export default function RoadMapEditor(): ReactElement {
// );
// @ts-ignore
setCursorPosition(e?.transaction?.curSelection?.$anchor?.pos);
- // console.log(
+ // // console.log(
// 'ontransaction',
// // @ts-ignore
// e?.transaction?.curSelection?.$anchor?.pos,
@@ -103,24 +104,24 @@ export default function RoadMapEditor(): ReactElement {
},
// onTransaction: ({ transaction }) => {
// // @ts-ignore
- // console.log('ontransaction', transaction?.curSelection?.$anchor?.pos);
+ // // console.log('ontransaction', transaction?.curSelection?.$anchor?.pos);
// e.editor?.chain().focus().setTextSelection(10).run();
// },
});
useMemo(() => {
const filt = state.filter((v) => v?.id === id);
- // console.log('filt', filt);
+ // // console.log('filt', filt);
setToggle(filt);
if (editor) {
// editor.value?.chain().focus().setContent(JSON.parse(content)).run()
// mount 시 에러
- // editor.commands.setContent(filt[0]?.details, true, {
- editor.commands.setContent(filt[0]?.details, false, {
+ // editor.commands.setContent(filt[0]?.detailedContent, true, {
+ editor.commands.setContent(filt[0]?.detailedContent, false, {
preserveWhitespace: 'full', // 빈칸 인식 X 에러 해결
// preserveWhitespace: true, // 빈칸 인식 X 에러 해결
});
- console.log('editor', editor);
+ // console.log('editor', editor);
// editor.commands.focus(
// // @ts-ignore
// editor?.transaction?.curSelection?.$anchor?.pos,
@@ -129,7 +130,7 @@ export default function RoadMapEditor(): ReactElement {
editor.chain().focus().setTextSelection(cursorPosition).run();
}
if (label !== '' && filt.length === 0) {
- setState([...state, { id, details: '' }]);
+ setState([...state, { id, detailedContent: '' }]);
}
}, [state, setToggle, editor, label, id]);
@@ -196,6 +197,9 @@ export default function RoadMapEditor(): ReactElement {
state={state}
editor={editor}
id={id}
+ blogKeyword={blogKeyword}
+ onChangeBlogKeyword={onChangeBlogKeyword}
+ setBlogKeyword={setBlogKeyword}
toggleEditor={toggleEditor}
roadMapTitle={roadMapTitle}
onChangeId={onChangeId}
diff --git a/src/pages/roadmap/posts/byKeyword/index.tsx b/src/pages/roadmap/posts/byKeyword/index.tsx
index 7e31598..e04260e 100644
--- a/src/pages/roadmap/posts/byKeyword/index.tsx
+++ b/src/pages/roadmap/posts/byKeyword/index.tsx
@@ -1,11 +1,10 @@
-import { HeaderMegaMenu } from 'layout/mainLayout/header/header';
+import MainLayout from 'layout/mainLayout';
import { InfiniteRoadmapByKeyword } from 'pages/main/searchByKeyword/InfiniteRoadmaps';
export default function KeywordSearchRoadmaps() {
return (
- <>
-
+
- >
+
);
}
diff --git a/src/pages/roadmap/posts/completeRoadmap.tsx b/src/pages/roadmap/posts/completeRoadmap.tsx
deleted file mode 100644
index 68307c3..0000000
--- a/src/pages/roadmap/posts/completeRoadmap.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-// /* eslint-disable @typescript-eslint/no-unused-vars */
-// /* eslint-disable no-console */
-// import { Highlight } from '@tiptap/extension-highlight';
-// import { Link } from '@tiptap/extension-link';
-// import { Subscript } from '@tiptap/extension-subscript';
-// import { Superscript } from '@tiptap/extension-superscript';
-// import { TextAlign } from '@tiptap/extension-text-align';
-// import { Underline } from '@tiptap/extension-underline';
-// import { useEditor } from '@tiptap/react';
-// import StarterKit from '@tiptap/starter-kit';
-// import { ReactElement, useEffect, useMemo, useState } from 'react';
-// import { useSearchParams } from 'react-router-dom';
-// import { ReactFlowProvider } from 'reactflow';
-// import { styled } from 'styled-components';
-
-// import { useInput } from '../../../components/common/hooks/useInput';
-// import InteractionFlow from './userRoadmap';
-
-// export default function CompleteRoadmap(currentRoadmap): ReactElement {
-// const [label, onChangeLabel, setLabel] = useInput('');
-// const [id, onChangeId, setId] = useInput('');
-// const [toggle, onChangeToggle, setToggle] = useInput('');
-// const [search] = useSearchParams();
-// // const [state, setState] = useState([
-// // { id: '1', details: `자바스크립트
` },
-// // { id: '2', details: `'함수 개념과 활용법'
` },
-// // { id: '2b', details: `'자바스크립트 상세'
` },
-// // { id: '2c', details: `'조건문과 반복문 상세'
` },
-// // ]);
-// const [state, setState] = useState([]);
-
-// const [details, setDetails] = useState([]);
-
-// useEffect(() => {
-// console.log('currentRoadmap @ completeRoadmap', currentRoadmap);
-// }, []);
-
-// // eslint-disable-next-line @typescript-eslint/no-unused-vars
-// const [roadMapTitle, onRoadMapTitleChange, setRoadMapTitle] = useInput(
-// search.get('title') || '',
-// );
-
-// const editor = useEditor({
-// extensions: [
-// StarterKit,
-// Underline,
-// Link,
-// Superscript,
-// Subscript,
-// Highlight,
-// TextAlign.configure({ types: ['heading', 'paragraph'] }),
-// ],
-// content: state.filter((v) => v.id === id)[0]?.details || '',
-// onUpdate(e) {
-// setToggle(e.editor?.getHTML());
-// // eslint-disable-next-line array-callback-return
-// state.map((item, idx) => {
-// if (item.id !== id) return;
-
-// const copyState = [...state];
-// copyState.splice(idx, 1, {
-// id: item.id,
-// details: e.editor?.getHTML(),
-// });
-// setState(copyState);
-// });
-// },
-// });
-
-// useEffect(() => {
-// console.log('currentRoadmap', currentRoadmap);
-// }, []);
-
-// useMemo(() => {
-// const filt = state.filter((v) => v.id === id);
-// setToggle(filt);
-// if (editor) {
-// editor.commands.setContent(filt[0]?.details || '');
-// }
-// console.log('state', state);
-// }, [state, id, setToggle, label, editor]);
-
-// return (
-//
-//
-//
-//
-//
-//
-//
-// );
-// }
-
-// const EditorWrap = styled.div`
-// & .editor {
-// & > .content {
-// width: 100%;
-// }
-// }
-
-// & .roadMapWrap {
-// overflow-x: hidden;
-// }
-// `;
-export {};
diff --git a/src/pages/roadmap/posts/index.tsx b/src/pages/roadmap/posts/index.tsx
new file mode 100644
index 0000000..5e6da81
--- /dev/null
+++ b/src/pages/roadmap/posts/index.tsx
@@ -0,0 +1,15 @@
+import CommentSection from 'components/comments';
+import RoadMapInfo from 'components/roadmaps/posts/RoadmapInfo';
+import MainLayout from 'layout/mainLayout';
+
+export default function RoadMapPostPage() {
+ return (
+
+ {/* */}
+
+
+ {/* */}
+ {/* */}
+
+ );
+}
diff --git a/src/pages/roadmap/posts/postedRoadmap.jsx b/src/pages/roadmap/posts/postedRoadmap.jsx
deleted file mode 100644
index fd305d8..0000000
--- a/src/pages/roadmap/posts/postedRoadmap.jsx
+++ /dev/null
@@ -1,596 +0,0 @@
-/* eslint-disable no-console */
-/* eslint-disable react-hooks/exhaustive-deps */
-/* eslint-disable @typescript-eslint/no-unused-vars */
-/* eslint-disable no-unused-vars */
-/* eslint-disable no-nested-ternary */
-import {
- ActionIcon,
- Avatar,
- Button,
- Card,
- Center,
- Container,
- createStyles,
- Drawer,
- Group,
- Modal,
- rem,
- SimpleGrid,
- Text,
- Title,
-} from '@mantine/core';
-import { IconHeart, IconHeartFilled, IconUser } from '@tabler/icons-react';
-import { Highlight } from '@tiptap/extension-highlight';
-import { Link } from '@tiptap/extension-link';
-import { Subscript } from '@tiptap/extension-subscript';
-import { Superscript } from '@tiptap/extension-superscript';
-import { TextAlign } from '@tiptap/extension-text-align';
-import { Underline } from '@tiptap/extension-underline';
-import { EditorContent, useEditor } from '@tiptap/react';
-import StarterKit from '@tiptap/starter-kit';
-import axios from 'axios';
-import { baseUrl } from 'axiosInstance/constants';
-import { useInput } from 'components/common/hooks/useInput';
-import { useRoadmapData } from 'components/roadmaps/posts/hooks/useRoadMapResponse';
-import { useUser } from 'components/user/hooks/useUser';
-import MainLayout from 'layout/mainLayout';
-import { useEffect, useMemo, useState } from 'react';
-import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
-import {
- Background,
- Controls,
- MiniMap,
- ReactFlow,
- ReactFlowProvider,
- useEdgesState,
- useNodesState,
-} from 'reactflow';
-import { styled } from 'styled-components';
-
-import CommentPage from '../../main/commentPage';
-
-const useStyles = createStyles((theme) => ({
- title: {
- fontSize: rem(34),
- fontWeight: 900,
-
- [theme.fn.smallerThan('sm')]: {
- fontSize: rem(24),
- },
- },
-
- description: {
- maxWidth: 600,
-
- '&::after': {
- content: '""',
- display: 'block',
- backgroundColor: theme.fn.primaryColor(),
- width: rem(45),
- height: rem(2),
- marginTop: theme.spacing.sm,
- marginRight: 'auto',
- },
- },
-
- card: {
- border: `${rem(1)} solid ${
- theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1]
- }`,
- },
-
- cardTitle: {
- '&::after': {
- content: '""',
- display: 'block',
- backgroundColor: theme.fn.primaryColor(),
- width: rem(45),
- height: rem(2),
- marginTop: theme.spacing.sm,
- },
- },
-}));
-
-function PostedRoadmap() {
- const navigate = useNavigate();
- const { classes, theme } = useStyles();
- const { pathname } = useLocation();
- const [participation, setParticipation] = useState();
- const [currentPage, setCurrentPage] = useState(
- pathname.slice(pathname.lastIndexOf('/') + 1),
- );
- const [isLoading, setLoading] = useState(true);
- const { roadmapById } = useRoadmapData(
- pathname.slice(pathname.lastIndexOf('/') + 1),
- );
- // const { joinRoadmap } = useRoadmap();
- const [currentRoadmap, setCurrentRoadmap] = useState(roadmapById?.data || []);
- const [label, onChangeLabel, setLabel] = useInput('');
- const [id, onChangeId, setId] = useInput('');
- const [toggle, onChangeToggle, setToggle] = useInput('');
- const [search] = useSearchParams();
- const { user } = useUser();
- // const [state, setState] = useState([
- // { id: '1', details: `자바스크립트
` },
- // { id: '2', details: `'함수 개념과 활용법'
` },
- // { id: '2b', details: `'자바스크립트 상세'
` },
- // { id: '2c', details: `'조건문과 반복문 상세'
` },
- // ]);
- const [state, setState] = useState([]);
-
- const [details, setDetails] = useState([]);
- useEffect(() => {
- // const url = user
- // ? `${baseUrl}/roadmaps/${currentPage}`
- // : `${baseUrl}/roadmaps/load-roadmap/${currentPage}`;
- // if (!user) {
- // axios
- // .get(url)
- // .catch((e) => {
- // // eslint-disable-next-line no-console
- // console.log(e);
- // })
- // .then((v) => {
- // setNodes(v?.data.nodes);
- // setCurrentRoadmap({
- // title: v?.data?.roadmap?.title,
- // description: v?.data?.roadmap?.description,
- // ownerAvatarUrl: v?.data?.roadmap?.ownerAvatarUrl,
- // ownerNickname: v?.data?.roadmap?.ownerNickname,
- // thumbnailUrl: v?.data?.roadmap?.thumbnailUrl,
- // isJoined: v?.data?.isJoined,
- // });
- // setParticipation(v?.data.isJoined);
- // const detailState = [];
- // v?.data.nodes.map((j) => {
- // detailState.push({ id: j.id, details: j.detailedContent });
- // });
- // setState(detailState);
- // const edgeSet = new Set();
- // const tempEdges = [];
- // // eslint-disable-next-line array-callback-return
- // v?.data?.edges.map((j) => {
- // if (!edgeSet.has(j?.id)) {
- // tempEdges.push(j);
- // }
- // edgeSet.add(j?.id);
- // });
- // setEdges(tempEdges);
- // });
- // }
-
- axios
- .get(`${baseUrl}/roadmaps/${currentPage}`, {
- headers: {
- Authorization: `Bearer ${user?.accessToken}`,
- },
- })
- .catch((e) => {
- // eslint-disable-next-line no-console
- console.log(e);
- })
- .then((v) => {
- setNodes(v?.data.nodes);
- setCurrentRoadmap({
- id: v?.data?.id,
- title: v?.data?.title,
- description: v?.data?.description,
- createdAt: v?.data?.createdAt,
- updatedAt: v?.data?.updatedAt,
- ownerAvatarUrl: v?.data?.member?.ownerAvatarUrl,
- ownerNickname: v?.data?.member?.nickname,
- isJoined: v?.data?.isJoined,
- joinCount: v?.data?.joinCount,
- isLiked: v?.data?.isLiked,
- likeCount: v?.data?.likeCount,
- thumbnailUrl: v?.data?.member?.thumbnailUrl,
- });
- setLoading(false);
- setParticipation(v?.data?.isJoined);
- const detailState = [];
- v?.data.nodes.map((j) =>
- detailState.push({ id: j.id, details: j.detailedContent }),
- );
- setState(detailState);
- const edgeSet = new Set();
- const tempEdges = [];
- // eslint-disable-next-line array-callback-return
- v?.data?.edges.map((j) => {
- if (!edgeSet.has(j?.id)) {
- tempEdges.push(j);
- }
- edgeSet.add(j?.id);
- });
- setEdges(tempEdges);
- });
-
- // if (currentPage !== roadmapById?.data?.roadmap?.id) {
- // setCurrentRoadmap(
- // JSON.parse(localStorage.getItem('roadmapById'))?.data?.roadmap,
- // );
- // }
- }, []);
-
- const editor = useEditor({
- extensions: [
- StarterKit,
- Underline,
- Link,
- Superscript,
- Subscript,
- Highlight,
- TextAlign.configure({ types: ['heading', 'paragraph'] }),
- ],
- editable: false,
- content: state.filter((v) => v.id === id)[0]?.details || '',
- onUpdate(e) {
- setToggle(e.editor?.getHTML());
- // eslint-disable-next-line array-callback-return
- state.map((item, idx) => {
- if (item.id !== id) return;
-
- const copyState = [...state];
- copyState.splice(idx, 1, {
- id: item.id,
- details: e.editor?.getHTML(),
- });
- setState(copyState);
- });
- },
- });
-
- useMemo(() => {
- const filt = state.filter((v) => v.id === id);
- setToggle(filt);
- if (editor) {
- editor.commands.setContent(filt[0]?.details || '');
- }
- }, [state, id, setToggle, label, editor]);
-
- const onClickLikes = () => {
- axios
- .post(
- `${baseUrl}/likes/like-roadmap/${currentRoadmap.id}`,
- {},
- {
- headers: {
- Authorization: `Bearer ${user?.accessToken}`,
- },
- },
- )
- .then((v) => {
- setCurrentRoadmap({
- ...currentRoadmap,
- isLiked: v?.data.isLiked,
- likeCount: v?.data.likeCount,
- });
- })
- .catch((err) => console.log(err));
- };
-
- const [nodeState, setNodes, onNodesChange] = useNodesState([]);
- const [edgeState, setEdges, onEdgesChange] = useEdgesState([]);
- const [isSelectable] = useState(true);
- const [isDraggable] = useState(false);
- const [isConnectable] = useState(false);
- const [zoomOnScroll] = useState(true); // zoom in zoom out
- const [panOnScroll] = useState(false); // 위아래 스크롤
- const [zoomOnDoubleClick] = useState(false);
- const [panOnDrag] = useState(true); // 마우스로 이동
- const [isOpen, setIsOpen] = useState(false);
- const [modal, setModal] = useState(false);
-
- const proOptions = { hideAttribution: true };
-
- const joinRoadmap = () => {
- axios
- .post(
- `${baseUrl}/roadmaps/${parseInt(currentPage, 10)}/join`,
- {},
- {
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${user?.accessToken}`,
- },
- },
- )
- .then((v) => {
- setParticipation(true);
- })
- .catch((e) => console.log(e));
- };
-
- return (
-
-
-
-
- {currentRoadmap?.title}
-
-
- {currentRoadmap?.likeCount > 1000000
- ? new Intl.NumberFormat('en-GB', {
- notation: 'compact',
- compactDisplay: 'short',
- }).format(currentRoadmap?.likeCount)
- : currentRoadmap?.likeCount}
-
- {currentRoadmap?.isLiked ? (
-
- onClickLikes()}
- size="2rem"
- color={theme.colors.red[6]}
- stroke={1.5}
- />
-
- ) : (
-
- onClickLikes()}
- size="2rem"
- color={theme.colors.red[6]}
- stroke={1.5}
- />
-
- )}
-
-
-
-
- {currentRoadmap?.ownerNickname}
-
- {currentRoadmap?.ownerAvatarUrl || ''}
-
-
-
- {
- setModal(false);
- }}
- >
- {!user && (
-
-
로그인 후 이용 가능합니다.
- navigate('/users/signin')}>
- 로그인하기
-
-
- )}
-
- {
- if (!user?.accessToken) {
- setModal(true);
- }
- joinRoadmap();
- }}
- >
- {isLoading && ' 로딩 중'}
- {!isLoading && participation && user?.accessToken
- ? '참여 중'
- : !isLoading && (!participation || !user?.accessToken)
- ? '참여하기'
- : ''}
-
-
- {currentRoadmap?.description || ''}
-
-
-
-
-
- 참여인원: {currentRoadmap?.joinCount}명
-
-
- {/*
-
-
- 완료인원: {}명
-
- */}
- {/*
-
- */}
- {/*
-
-
- 난이도: {currentRoadmap?.recommendedExecutionTimeUnit}
-
- */}
-
-
-
-
-
- {
- setLabel(`${n?.data?.label}`);
- setId(`${n?.id}`);
- setIsOpen(!isOpen);
- }}
- fitView
- style={{
- backgroundColor: '#ebf6fc',
- }}
- >
-
-
-
-
- setIsOpen(!isOpen)}
- overlayProps={{ opacity: 0.5, blur: 4 }}
- position="right"
- size="35%"
- >
-
-
-
-
- setIsOpen(!isOpen)}
- variant="light"
- >
- 닫기
-
-
-
-
- {/* setIsOpen(!isOpen)}>
-
-
-
-
- setIsOpen(!isOpen)}
- variant="light"
- >
- 닫기
-
-
- */}
-
-
-
-
-
-
-
- );
-}
-const Wrap = styled.div`
- width: 100%;
- height: 60vh;
- & .updatenode__controls {
- position: absolute;
- right: 10px;
- top: 10px;
- z-index: 4;
- font-size: 12px;
- }
-
- & .updatenode__controls label {
- display: block;
- }
-
- & .updatenode__bglabel {
- margin-top: 10px;
- }
-
- & .updatenode__checkboxwrapper {
- margin-top: 10px;
- display: flex;
- align-items: center;
- }
-`;
-export default PostedRoadmap;
-const EditorWrap = styled.div`
- & .editor {
- & > .content {
- width: 100%;
- }
- }
-
- & .roadMapWrap {
- overflow-x: hidden;
- }
-`;
diff --git a/src/pages/roadmap/posts/userRoadmap.tsx b/src/pages/roadmap/posts/userRoadmap.tsx
index e4efd49..8ad1571 100644
--- a/src/pages/roadmap/posts/userRoadmap.tsx
+++ b/src/pages/roadmap/posts/userRoadmap.tsx
@@ -5,7 +5,6 @@
// import { modals } from '@mantine/modals';
import { Button, Center, Modal } from '@mantine/core';
import { EditorContent } from '@tiptap/react';
-import { ResizableNodeSelected } from 'components/editor/ResizableNodeSelected';
import { useEffect, useState } from 'react';
import ReactFlow, {
Background,
@@ -17,6 +16,7 @@ import ReactFlow, {
} from 'reactflow';
import 'reactflow/dist/style.css';
import { styled } from 'styled-components';
+import { ResizableNodeSelected } from '../../../components/editor/ResizableNodeSelected';
const edgeType = 'smoothstep';
const initialNodes = [
@@ -91,13 +91,13 @@ function Roadmap({
setNodes(currentRoadmap.currentRoadmap.nodes);
setEdges(currentRoadmap.currentRoadmap.edges);
}
- // console.log('currentRoadmap', currentRoadmap.currentRoadmap);
+ // // console.log('currentRoadmap', currentRoadmap.currentRoadmap);
}, []);
const proOptions = { hideAttribution: true };
return (
-
+
- setIsOpen(!isOpen)} variant="light">
+ setIsOpen(!isOpen)}
+ variant="light"
+ >
닫기
diff --git a/store.ts b/store.ts
index 9558245..a5d8f7a 100644
--- a/store.ts
+++ b/store.ts
@@ -8,9 +8,7 @@ import {
OnEdgesChange,
OnNodesChange,
} from 'reactflow';
-import { shallow } from 'zustand/shallow';
-// import { create } from 'zustand';
-import { createWithEqualityFn } from 'zustand/traditional';
+import { create } from 'zustand';
export type RFState = {
nodes: Node[];
@@ -19,8 +17,8 @@ export type RFState = {
onEdgesChange: OnEdgesChange;
};
-const useStore = createWithEqualityFn(
- // const useStore = create(
+// const useStore = createWithEqualityFn(
+const useStore = create(
(set, get) => ({
// const useStore = create((set, get) => ({
nodes: [
@@ -44,7 +42,7 @@ const useStore = createWithEqualityFn(
},
}),
// Object.is,
- shallow,
+ // shallow,
);
export default useStore;
diff --git a/tsconfig.json b/tsconfig.json
index b48f0d6..f3bfab5 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -35,12 +35,12 @@
"src/**/*.tsx",
"node_modules/@types",
"src/setProxy.js",
- "src/pages/main/roadmapRecommendation.jsx",
"src/components/editor/ResizableNodeSelected.jsx",
"src/pages/roadmap/posts/postedRoadmap.jsx",
"src/components/user/userProfile.jsx",
"src/components/editor/RoadMapEditor.jsx",
"src/pages/main/commentPage.tsx",
- "src/pages/main/searchByKeyword/Roadmap.jsx"
+ "src/pages/main/searchByKeyword/Roadmap.jsx",
+ "src/components/roadmaps/posts/RoadmapInfo.jsx"
]
}