diff --git a/.pnp.cjs b/.pnp.cjs
index 4e6ae80..3955f3b 100755
--- a/.pnp.cjs
+++ b/.pnp.cjs
@@ -3987,6 +3987,15 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
+ ["@types/js-cookie", [\
+ ["npm:3.0.6", {\
+ "packageLocation": "./.yarn/cache/@types-js-cookie-npm-3.0.6-c9126e5b48-173afaf5ea.zip/node_modules/@types/js-cookie/",\
+ "packageDependencies": [\
+ ["@types/js-cookie", "npm:3.0.6"]\
+ ],\
+ "linkType": "HARD"\
+ }]\
+ ]],\
["@types/json-schema", [\
["npm:7.0.15", {\
"packageLocation": "./.yarn/cache/@types-json-schema-npm-7.0.15-fd16381786-a996a745e6.zip/node_modules/@types/json-schema/",\
@@ -6190,6 +6199,7 @@ const RAW_RUNTIME_STATE =
["@react-three/drei", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:9.88.13"],\
["@react-three/fiber", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:8.15.10"],\
["@react-three/postprocessing", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:2.15.11"],\
+ ["@types/js-cookie", "npm:3.0.6"],\
["@types/node", "npm:20.9.2"],\
["@types/react", "npm:18.2.37"],\
["@types/react-dom", "npm:18.2.15"],\
@@ -6203,7 +6213,9 @@ const RAW_RUNTIME_STATE =
["eslint", "npm:8.53.0"],\
["eslint-plugin-react-hooks", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:4.6.0"],\
["eslint-plugin-react-refresh", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:0.4.4"],\
+ ["js-cookie", "npm:3.0.5"],\
["leva", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:0.9.35"],\
+ ["lucide-react", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:0.294.0"],\
["react", "npm:18.2.0"],\
["react-dom", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:18.2.0"],\
["react-markdown", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:9.0.1"],\
@@ -9799,6 +9811,15 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
+ ["js-cookie", [\
+ ["npm:3.0.5", {\
+ "packageLocation": "./.yarn/cache/js-cookie-npm-3.0.5-8fc8fcc9b4-04a0e56040.zip/node_modules/js-cookie/",\
+ "packageDependencies": [\
+ ["js-cookie", "npm:3.0.5"]\
+ ],\
+ "linkType": "HARD"\
+ }]\
+ ]],\
["js-tokens", [\
["npm:4.0.0", {\
"packageLocation": "./.yarn/cache/js-tokens-npm-4.0.0-0ac852e9e2-e248708d37.zip/node_modules/js-tokens/",\
@@ -10313,6 +10334,28 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
+ ["lucide-react", [\
+ ["npm:0.294.0", {\
+ "packageLocation": "./.yarn/cache/lucide-react-npm-0.294.0-86f24c8068-3c8d05743c.zip/node_modules/lucide-react/",\
+ "packageDependencies": [\
+ ["lucide-react", "npm:0.294.0"]\
+ ],\
+ "linkType": "SOFT"\
+ }],\
+ ["virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:0.294.0", {\
+ "packageLocation": "./.yarn/__virtual__/lucide-react-virtual-163e38136d/0/cache/lucide-react-npm-0.294.0-86f24c8068-3c8d05743c.zip/node_modules/lucide-react/",\
+ "packageDependencies": [\
+ ["lucide-react", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:0.294.0"],\
+ ["@types/react", "npm:18.2.37"],\
+ ["react", "npm:18.2.0"]\
+ ],\
+ "packagePeers": [\
+ "@types/react",\
+ "react"\
+ ],\
+ "linkType": "HARD"\
+ }]\
+ ]],\
["maath", [\
["npm:0.6.0", {\
"packageLocation": "./.yarn/cache/maath-npm-0.6.0-bf6e867013-a9b716841d.zip/node_modules/maath/",\
@@ -13265,7 +13308,6 @@ const RAW_RUNTIME_STATE =
["eslint", "npm:8.53.0"],\
["eslint-config-prettier", "virtual:2499dbb93d824027565d71b0716c4fb8b548ad61955d0a0286bfb3c5b4058e227894b6691d96808c00f576db14870018375210362c26ee321ea99fd6ed041c74#npm:9.0.0"],\
["eslint-plugin-prettier", "virtual:2499dbb93d824027565d71b0716c4fb8b548ad61955d0a0286bfb3c5b4058e227894b6691d96808c00f576db14870018375210362c26ee321ea99fd6ed041c74#npm:5.0.1"],\
- ["form-data", "npm:4.0.0"],\
["ioredis", "npm:5.3.2"],\
["jest", "virtual:2499dbb93d824027565d71b0716c4fb8b548ad61955d0a0286bfb3c5b4058e227894b6691d96808c00f576db14870018375210362c26ee321ea99fd6ed041c74#npm:29.7.0"],\
["mongoose", "npm:8.0.1"],\
diff --git a/.yarn/cache/@types-js-cookie-npm-3.0.6-c9126e5b48-173afaf5ea.zip b/.yarn/cache/@types-js-cookie-npm-3.0.6-c9126e5b48-173afaf5ea.zip
new file mode 100644
index 0000000..97ddf14
Binary files /dev/null and b/.yarn/cache/@types-js-cookie-npm-3.0.6-c9126e5b48-173afaf5ea.zip differ
diff --git a/.yarn/cache/js-cookie-npm-3.0.5-8fc8fcc9b4-04a0e56040.zip b/.yarn/cache/js-cookie-npm-3.0.5-8fc8fcc9b4-04a0e56040.zip
new file mode 100644
index 0000000..c85ff7a
Binary files /dev/null and b/.yarn/cache/js-cookie-npm-3.0.5-8fc8fcc9b4-04a0e56040.zip differ
diff --git a/.yarn/cache/lucide-react-npm-0.294.0-86f24c8068-3c8d05743c.zip b/.yarn/cache/lucide-react-npm-0.294.0-86f24c8068-3c8d05743c.zip
new file mode 100644
index 0000000..2f67589
Binary files /dev/null and b/.yarn/cache/lucide-react-npm-0.294.0-86f24c8068-3c8d05743c.zip differ
diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz
index c44764b..3b0cfe1 100644
Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ
diff --git a/Dockerfile-was b/Dockerfile-was
index 55dc6ad..f417898 100644
--- a/Dockerfile-was
+++ b/Dockerfile-was
@@ -4,6 +4,7 @@ WORKDIR /app
ADD . /app
+RUN SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm_config_arch=x64 npm_config_platform=linux yarn workspace server add sharp
RUN yarn workspace server build
EXPOSE 3000
diff --git a/docker-compose.yml b/docker-compose.yml
index d8c573c..aecaf48 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -4,6 +4,7 @@ services:
was:
container_name: was
image: qkrwogk/web16-b1g1-be:GITHUB_SHA
+ restart: always
ports:
- 3000:3000
env_file:
@@ -13,6 +14,7 @@ services:
web:
container_name: web
image: qkrwogk/web16-b1g1-fe:GITHUB_SHA
+ restart: always
ports:
- 80:80
- 443:443
diff --git a/packages/client/index.html b/packages/client/index.html
index eea3d72..6c458c2 100644
--- a/packages/client/index.html
+++ b/packages/client/index.html
@@ -4,7 +4,7 @@
-
Vite + React + TS
+ 별 하나에 글 하나🌟
diff --git a/packages/client/package.json b/packages/client/package.json
index b8fcc6f..78d869e 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -16,6 +16,7 @@
"@react-three/fiber": "^8.15.10",
"@types/react-router-dom": "^5.3.3",
"axios": "^1.6.2",
+ "lucide-react": "^0.294.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^9.0.1",
@@ -26,6 +27,7 @@
"devDependencies": {
"@emotion/react": "^11.11.1",
"@react-three/postprocessing": "^2.15.11",
+ "@types/js-cookie": "^3.0.6",
"@types/node": "^20.9.2",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
@@ -37,6 +39,7 @@
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
+ "js-cookie": "^3.0.5",
"leva": "^0.9.35",
"three-stdlib": "^2.28.5",
"typescript": "^5.0.2",
diff --git a/packages/client/src/app/Router.tsx b/packages/client/src/app/Router.tsx
index 24bd6fa..3c901c3 100644
--- a/packages/client/src/app/Router.tsx
+++ b/packages/client/src/app/Router.tsx
@@ -6,12 +6,26 @@ import {
import Home from '../pages/Home';
import Landing from '../pages/Landing';
import { WritingModal } from 'features/writingModal';
+import LoginModal from 'widgets/loginModal';
+import SignUpModal from 'widgets/signupModal/SignUpModal';
+import NickNameSetModal from 'widgets/nickNameSetModal/NickNameSetModal';
+import LogoAndStart from 'widgets/logoAndStart';
+import { PostModal } from 'features/postModal';
export const router = createBrowserRouter(
createRoutesFromElements(
<>
- } />
+ }>
+ } />
+ } />
+ } />
+ } />
+
+
}>
+
+ } />
+
} />
>,
diff --git a/packages/client/src/entities/posts/ui/Post.tsx b/packages/client/src/entities/posts/ui/Post.tsx
index 1172751..dbec242 100644
--- a/packages/client/src/entities/posts/ui/Post.tsx
+++ b/packages/client/src/entities/posts/ui/Post.tsx
@@ -6,11 +6,11 @@ import { Html } from '@react-three/drei';
import styled from '@emotion/styled';
import { useViewStore } from 'shared/store/useViewStore';
import * as THREE from 'three';
-import { PostData } from 'shared/lib/types/post';
-import { usePostStore } from 'shared/store/usePostStore';
+import { StarData } from 'shared/lib/types/star';
+import { useNavigate } from 'react-router-dom';
interface PropsType {
- data: PostData;
+ data: StarData;
onClick: () => void;
isSelected: boolean;
}
@@ -19,7 +19,7 @@ export default function Post({ data, onClick, isSelected }: PropsType) {
const { targetView, setTargetView } = useCameraStore();
const meshRef = useRef(null!);
const { view, setView } = useViewStore();
- const { setData } = usePostStore();
+ const navigate = useNavigate();
const handleMeshClick = (e: ThreeEvent) => {
e.stopPropagation();
@@ -28,16 +28,19 @@ export default function Post({ data, onClick, isSelected }: PropsType) {
if (meshRef.current !== targetView) {
setView('DETAIL');
setTargetView(meshRef.current);
+ navigate(`/home/${data.id}`);
return;
}
- setData(data);
+ navigate(`/home/${data.id}/detail`);
setView('POST');
};
return (
('star');
return (
<>
- {dummyData.map((data, index) => (
- setPost(index)}
- isSelected={post === index}
- />
- ))}
+ {data &&
+ data.map((data, index) => (
+ setPost(index)}
+ isSelected={post === index}
+ />
+ ))}
>
);
}
-
-const dummyData = [
- {
- position: new THREE.Vector3(1000, 1000, 1800),
- size: 100,
- color: 'red',
- title: '별글입니당',
- content: '# 배가고프다',
- images: [
- 'https://github.com/boostcampwm2023/web16-B1G1/assets/35567292/ed205126-f8c5-4f84-98d9-fade19cd6c1d',
- ],
- },
- {
- position: new THREE.Vector3(1300, 500, 1000),
- size: 200,
- color: 'blue',
- title: '별글입니당',
- content: '~stroke~',
- images: [
- 'https://github.com/boostcampwm2023/web16-B1G1/assets/35567292/ed205126-f8c5-4f84-98d9-fade19cd6c1d',
- ],
- },
- {
- position: new THREE.Vector3(3000, 1000, 2500),
- size: 150,
- color: 'yellow',
- title: '별글입니당',
- content: "- I'm hungry",
- images: [
- 'https://github.com/boostcampwm2023/web16-B1G1/assets/35567292/ed205126-f8c5-4f84-98d9-fade19cd6c1d',
- ],
- },
-]; // TODO: 서버로부터 받아온 데이터로 변경 필요
diff --git a/packages/client/src/features/postModal/PostModal.tsx b/packages/client/src/features/postModal/PostModal.tsx
deleted file mode 100644
index 54a6294..0000000
--- a/packages/client/src/features/postModal/PostModal.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { useViewStore } from 'shared/store/useViewStore';
-import { Button, Modal, ModalPortal } from 'shared/ui';
-import { PostData } from 'shared/lib/types/post';
-import ReactMarkdown from 'react-markdown';
-import remarkGfm from 'remark-gfm';
-import styled from '@emotion/styled';
-
-interface PropsType {
- data: PostData;
-}
-
-export default function PostModal({ data }: PropsType) {
- const { setView } = useViewStore();
- return (
-
- {
- setView('DETAIL');
- }}
- >
-
-
-
-
-
- {data.content}
-
-
-
-
- );
-}
-
-const rightButton = (
-
-);
-
-const PostModalLayout = styled(Modal)`
- transform: translate(-10%, -50%);
-`;
-
-const TextContainer = styled.div`
- overflow-y: auto;
- width: 807px;
-
- ${({ theme: { colors } }) => ({
- color: colors.text.secondary,
- })}
-`;
-
-const ImageContainer = styled.div`
- display: flex;
- justify-content: center;
- margin-bottom: 26px;
-`;
-
-const Image = styled.img`
- width: 649px;
-`;
diff --git a/packages/client/src/features/postModal/api/deletePost.ts b/packages/client/src/features/postModal/api/deletePost.ts
new file mode 100644
index 0000000..0516e04
--- /dev/null
+++ b/packages/client/src/features/postModal/api/deletePost.ts
@@ -0,0 +1,11 @@
+import instance from 'shared/apis/AxiosInterceptor';
+import { BASE_URL } from '@constants';
+
+export const deletePost = async (postId: string) => {
+ const { data } = await instance({
+ method: 'DELETE',
+ url: `${BASE_URL}/post/${postId}`,
+ });
+
+ return data;
+};
diff --git a/packages/client/src/features/postModal/index.ts b/packages/client/src/features/postModal/index.ts
new file mode 100644
index 0000000..321a58c
--- /dev/null
+++ b/packages/client/src/features/postModal/index.ts
@@ -0,0 +1 @@
+export { default as PostModal } from './ui/PostModal';
diff --git a/packages/client/src/features/postModal/ui/ImageSlider.tsx b/packages/client/src/features/postModal/ui/ImageSlider.tsx
new file mode 100644
index 0000000..d70b18f
--- /dev/null
+++ b/packages/client/src/features/postModal/ui/ImageSlider.tsx
@@ -0,0 +1,129 @@
+import { useState } from 'react';
+import { ArrowBigLeft, ArrowBigRight, CircleDot, Circle } from 'lucide-react';
+import styled from '@emotion/styled';
+
+interface PropsType {
+ imageUrls: string[];
+}
+
+export default function ImageSlider({ imageUrls }: PropsType) {
+ const [imageIndex, setImageIndex] = useState(0);
+
+ const handlePrev = () => {
+ setImageIndex((index) => {
+ if (index === 0) return imageUrls.length - 1;
+ return index - 1;
+ });
+ };
+
+ const handleNext = () => {
+ setImageIndex((index) => {
+ if (index === imageUrls.length - 1) return 0;
+ return index + 1;
+ });
+ };
+
+ const Dots = () => {
+ return (
+ <>
+ {imageUrls.map((_, index) => (
+ setImageIndex(index)}>
+ {index === imageIndex ? : }
+
+ ))}
+ >
+ );
+ };
+
+ return (
+
+
+ {imageUrls.map((url) => {
+ return ;
+ })}
+
+
+
+
+
+
+
+ );
+}
+
+const Layout = styled.div`
+ position: relative;
+`;
+
+const CurrentImage = styled.div`
+ width: 100%;
+ height: 100%;
+ display: flex;
+ overflow: hidden;
+`;
+
+const Image = styled.img<{ index: number }>`
+ object-fit: cover;
+ width: 100%;
+ height: 100%;
+ display: block;
+ flex-shrink: 0;
+ flex-grow: 0;
+ translate: ${({ index }) => -100 * index}%;
+ transition: translate 300ms ease-in-out;
+`;
+
+const Button = styled.button`
+ all: unset;
+ display: block;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ padding: 16px;
+ cursor: pointer;
+ transition: background-color 100ms ease-in-out;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.2);
+ }
+
+ > * {
+ stroke: white;
+ fill: black;
+ width: 32px;
+ height: 32px;
+ }
+`;
+
+const Pagination = styled.div`
+ position: absolute;
+ bottom: 8px;
+ left: 50%;
+ translate: -50%;
+ display: flex;
+ gap: 4px;
+`;
+
+const Dot = styled.button`
+ all: unset;
+ display: block;
+ cursor: pointer;
+ width: 16px;
+ height: 16px;
+ transition: scale 100ms ease-in-out;
+
+ &:hover {
+ scale: 1.2;
+ }
+
+ > * {
+ stroke: white;
+ fill: black;
+ width: 100%;
+ height: 100%;
+ }
+`;
diff --git a/packages/client/src/features/postModal/ui/PostModal.tsx b/packages/client/src/features/postModal/ui/PostModal.tsx
new file mode 100644
index 0000000..4bb69f6
--- /dev/null
+++ b/packages/client/src/features/postModal/ui/PostModal.tsx
@@ -0,0 +1,122 @@
+import { useViewStore } from 'shared/store/useViewStore';
+import { Button, Modal, ModalPortal } from 'shared/ui';
+import ReactMarkdown from 'react-markdown';
+import remarkGfm from 'remark-gfm';
+import styled from '@emotion/styled';
+import { useState } from 'react';
+import AlertDialog from 'shared/ui/alertDialog/AlertDialog';
+import { useNavigate, useParams } from 'react-router-dom';
+import { useFetch } from 'shared/hooks';
+import { PostData } from 'shared/lib/types/post';
+import { deletePost } from '../api/deletePost';
+import ImageSlider from './ImageSlider';
+
+export default function PostModal() {
+ const { setView } = useViewStore();
+ const [deleteModal, setDeleteModal] = useState(false);
+ const { postId } = useParams();
+ const navigate = useNavigate();
+ const { data } = useFetch(`post/${postId}`);
+
+ const rightButton = (
+
+ );
+
+ const handleDelete = async () => {
+ const res = await deletePost(postId!);
+ setDeleteModal(false);
+ if (res.status === 200) {
+ setView('MAIN');
+ navigate('/home');
+ } else {
+ alert('글 삭제 실패');
+ }
+ };
+
+ return (
+ data && (
+
+ {
+ setView('MAIN');
+ navigate(`/home/${postId}`);
+ }}
+ >
+
+ {data.images.length && (
+
+
+
+ )}
+
+
+ {data.content}
+
+
+
+
+ {deleteModal && (
+ {
+ setDeleteModal(false);
+ }}
+ onClickActionButton={handleDelete}
+ />
+ )}
+
+ )
+ );
+}
+
+const PostModalLayout = styled(Modal)`
+ transform: translate(-10%, -50%);
+`;
+
+const Container = styled.div`
+ height: 50vh;
+ overflow-y: auto;
+
+ &::-webkit-scrollbar {
+ width: 8px;
+ }
+
+ &::-webkit-scrollbar-track {
+ background-color: ${({ theme }) => theme.colors.text.primary};
+ border-radius: 8px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background-color: ${({ theme }) => theme.colors.text.third};
+ border-radius: 4px;
+ }
+`;
+
+const TextContainer = styled.div`
+ width: 40vw;
+ ${({ theme: { colors } }) => ({
+ color: colors.text.secondary,
+ })}
+`;
+
+const ImageContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ margin-bottom: 26px;
+ width: 100%;
+ height: 100%;
+`;
diff --git a/packages/client/src/features/writingModal/api/sendPost.ts b/packages/client/src/features/writingModal/api/sendPost.ts
new file mode 100644
index 0000000..78ab025
--- /dev/null
+++ b/packages/client/src/features/writingModal/api/sendPost.ts
@@ -0,0 +1,34 @@
+import instance from 'shared/apis/AxiosInterceptor';
+import { BASE_URL } from '@constants';
+
+const dummyStarData = {
+ color: 'pink',
+ size: 200,
+ position: {
+ x: 1000,
+ y: 7000,
+ z: -1600,
+ },
+}; // TODO: 별 커스텀 데이터로 변경
+
+export const sendPost = async (text: string, files: FileList | null) => {
+ try {
+ const formData = new FormData();
+ formData.append('title', '제목');
+ formData.append('content', text);
+ formData.append('star', JSON.stringify(dummyStarData));
+ if (files) {
+ for (let i = 0; i < files.length; i++) formData.append('file', files[i]);
+ }
+
+ const response = await instance.post(`${BASE_URL}post`, formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ });
+
+ return response;
+ } catch (err) {
+ console.error(err);
+ }
+};
diff --git a/packages/client/src/features/writingModal/ui/WritingModal.tsx b/packages/client/src/features/writingModal/ui/WritingModal.tsx
index 66bacf1..054e394 100644
--- a/packages/client/src/features/writingModal/ui/WritingModal.tsx
+++ b/packages/client/src/features/writingModal/ui/WritingModal.tsx
@@ -3,23 +3,34 @@ import { Button, Modal } from 'shared/ui';
import TextArea from 'shared/ui/textArea/TextArea';
import { ModalPortal } from 'shared/ui';
import Images from './Images';
-import axios from 'axios';
import { useNavigate } from 'react-router-dom';
+import { sendPost } from '../api/sendPost';
export default function WritingModal() {
const [text, setText] = useState('');
const [files, setFiles] = useState(null);
const navigate = useNavigate();
+ const handleSendPost = async () => {
+ const response = await sendPost(text, files);
+ if (response!.status === 201) {
+ navigate('/home');
+ // TODO: home에서 별 정보 refetching
+ } else {
+ alert('글쓰기 실패');
+ }
+ };
+
return (
sendToServer(text, files)}
+ onClick={handleSendPost}
size="m"
buttonType="CTA-icon"
+ type="submit"
>
다음
@@ -32,27 +43,3 @@ export default function WritingModal() {
);
}
-
-const sendToServer = async (text: string, files: FileList | null) => {
- try {
- const formData = new FormData();
- formData.append('title', 'title');
- formData.append('text', text);
- if (files)
- for (let i = 0; i < files.length; i++) formData.append('image', files[i]);
-
- const config = {
- headers: {
- 'Content-Type': 'multipart/form-data',
- },
- };
- const response = await axios.post(
- `http://www.별글.site/board`,
- formData,
- config,
- );
- console.log(response);
- } catch (error) {
- console.log(error);
- }
-};
diff --git a/packages/client/src/pages/Home/index.tsx b/packages/client/src/pages/Home/index.tsx
index a49d7a7..6f1f5aa 100644
--- a/packages/client/src/pages/Home/index.tsx
+++ b/packages/client/src/pages/Home/index.tsx
@@ -1,24 +1,43 @@
import Screen from 'widgets/screen';
import { useViewStore } from 'shared/store/useViewStore';
-import { usePostStore } from 'shared/store/usePostStore';
-import PostModal from 'features/postModal/PostModal';
-import { Outlet } from 'react-router-dom';
+import { Outlet, useNavigate } from 'react-router-dom';
import UnderBar from 'shared/ui/underBar/UnderBar';
import UpperBar from './ui/UpperBar';
+import Cookies from 'js-cookie';
+import { useEffect } from 'react';
+import { useLoadingStore } from 'shared/store/useLoadingStore';
+import WarpScreen from 'widgets/warpScreen/WarpScreen';
export default function Home() {
const { view } = useViewStore();
- const { data } = usePostStore();
+ const navigate = useNavigate();
+ const { isLoading } = useLoadingStore();
+
+ useEffect(() => {
+ const accessToken = Cookies.get('accessToken');
+ const refreshToken = Cookies.get('refreshToken');
+ if (!accessToken && !refreshToken) {
+ navigate('/');
+ }
+ }, []);
return (
<>
- {view === 'POST' && }
-
-
+ {isLoading && }
+
+ {view === 'MAIN' && (
+ <>
+
+
+ >
+ )}
>
);
}
+
+// TODO: 내 은하에서는 MAIN의 뒤로가기 버튼 안보이게 하기
+// TODO: 다른 사람 은하에서는 뒤로가기 버튼만 보이게 하기 ?
diff --git a/packages/client/src/pages/Landing/index.tsx b/packages/client/src/pages/Landing/index.tsx
index 6d898e3..084f5ed 100644
--- a/packages/client/src/pages/Landing/index.tsx
+++ b/packages/client/src/pages/Landing/index.tsx
@@ -1,30 +1,10 @@
import LandingScreen from 'widgets/landingScreen';
-import LoginModal from 'widgets/loginModal';
-import LogoAndStart from 'widgets/logoAndStart';
-import SignUpModal from 'widgets/signupModal/SignUpModal';
-import { useReducer, useState } from 'react';
-import NickNameSetModal from 'widgets/nickNameSetModal/NickNameSetModal';
+import { useState } from 'react';
import { useToastStore } from 'shared/store/useToastStore';
import { Toast } from 'shared/ui';
-
-const pageReducer = (
- state: number,
- action: { type: 'NEXT' | 'PREV' | 'SET'; pageIndex?: number },
-) => {
- switch (action.type) {
- case 'NEXT':
- return state + 1;
- case 'PREV':
- return state - 1;
- case 'SET':
- return action.pageIndex ?? state;
- default:
- return state;
- }
-};
+import { Outlet } from 'react-router-dom';
export default function Landing() {
- const [page, dispatch] = useReducer(pageReducer, 0);
const [mouse, setMouse] = useState([0.5, 0.5]);
const { text } = useToastStore.getState();
@@ -38,11 +18,7 @@ export default function Landing() {
}}
>
{text && {text}}
-
- {page === 0 && }
- {page === 1 && }
- {page === 2 && }
- {page === 3 && }
+
);
diff --git a/packages/client/src/shared/apis/AxiosInterceptor.ts b/packages/client/src/shared/apis/AxiosInterceptor.ts
index 64dfc31..9a5f1d5 100644
--- a/packages/client/src/shared/apis/AxiosInterceptor.ts
+++ b/packages/client/src/shared/apis/AxiosInterceptor.ts
@@ -3,6 +3,7 @@ import { useEffect } from 'react';
const instance = axios.create({
baseURL: 'https://www.별글.site/api/',
+ withCredentials: true,
});
interface Props {
diff --git a/packages/client/src/shared/apis/index.ts b/packages/client/src/shared/apis/index.ts
index a8cb769..aee9cff 100644
--- a/packages/client/src/shared/apis/index.ts
+++ b/packages/client/src/shared/apis/index.ts
@@ -1,2 +1,3 @@
export * from './AxiosInterceptor';
export * from './signUp';
+export * from './login';
diff --git a/packages/client/src/shared/apis/login.ts b/packages/client/src/shared/apis/login.ts
new file mode 100644
index 0000000..cfc8647
--- /dev/null
+++ b/packages/client/src/shared/apis/login.ts
@@ -0,0 +1,51 @@
+import axios, { AxiosError } from 'axios';
+import { BASE_URL } from '@constants';
+import Cookies from 'js-cookie';
+import { NavigateFunction } from 'react-router-dom';
+import { useLoadingStore } from 'shared/store/useLoadingStore';
+
+axios.defaults.withCredentials = true;
+
+export const postLogin = async (
+ data: {
+ username: string;
+ password: string;
+ },
+ setIdState: React.Dispatch>,
+ setPasswordState: React.Dispatch>,
+ navigate: NavigateFunction,
+) => {
+ try {
+ const res = await axios.post(BASE_URL + 'auth/signin', data, {
+ withCredentials: true,
+ });
+
+ Cookies.set('userId', data.username, { path: '/', expires: 7 });
+ Cookies.set('refreshToken', res.data.refreshToken, {
+ path: '/',
+ secure: true,
+ expires: 1,
+ });
+ Cookies.set('accessToken', res.data.accessToken, {
+ path: '/',
+ secure: true,
+ expires: 1 / 24,
+ });
+ navigate('/home');
+ useLoadingStore.setState({
+ isLoading: true,
+ });
+
+ setTimeout(() => {
+ useLoadingStore.setState({
+ isLoading: false,
+ });
+ }, 5500);
+ } catch (err) {
+ if (err instanceof AxiosError) {
+ if (err.response?.status === 404) setIdState(false);
+ else if (err.response?.status === 401) setPasswordState(false);
+ else alert(err);
+ } else alert(err);
+ }
+};
diff --git a/packages/client/src/shared/hooks/useFetch.ts b/packages/client/src/shared/hooks/useFetch.ts
index b1a4a29..5c81598 100644
--- a/packages/client/src/shared/hooks/useFetch.ts
+++ b/packages/client/src/shared/hooks/useFetch.ts
@@ -1,7 +1,6 @@
import { useState, useEffect } from 'react';
import axios, { AxiosError } from 'axios';
-
-const BASE_URL = 'wow'; // TODO: Add base url
+import { BASE_URL } from '@constants';
export const useFetch = (api: string) => {
const [data, setData] = useState();
@@ -11,7 +10,9 @@ export const useFetch = (api: string) => {
useEffect(() => {
const fetchData = async () => {
try {
- const res = await axios.get(BASE_URL + api);
+ const res = await axios.get(BASE_URL + api, {
+ withCredentials: true,
+ });
setData(res.data);
setLoading(false);
} catch (err) {
diff --git a/packages/client/src/shared/lib/constants/width.ts b/packages/client/src/shared/lib/constants/width.ts
index 175e198..b6a62d3 100644
--- a/packages/client/src/shared/lib/constants/width.ts
+++ b/packages/client/src/shared/lib/constants/width.ts
@@ -1,2 +1,2 @@
export const MAX_WIDTH1 = 1210;
-export const MAX_WIDTH2 = 1030;
+export const MAX_WIDTH2 = 930;
diff --git a/packages/client/src/shared/lib/types/post.ts b/packages/client/src/shared/lib/types/post.ts
index 36c7c8b..d10106a 100644
--- a/packages/client/src/shared/lib/types/post.ts
+++ b/packages/client/src/shared/lib/types/post.ts
@@ -1,8 +1,6 @@
export interface PostData {
+ id: number;
title: string;
- position: THREE.Vector3;
- size: number;
- color: string;
content: string;
images: string[];
}
diff --git a/packages/client/src/shared/lib/types/star.ts b/packages/client/src/shared/lib/types/star.ts
new file mode 100644
index 0000000..76883d4
--- /dev/null
+++ b/packages/client/src/shared/lib/types/star.ts
@@ -0,0 +1,7 @@
+export interface StarData {
+ id: number;
+ title: string;
+ position: THREE.Vector3;
+ size: number;
+ color: string;
+}
diff --git a/packages/client/src/shared/store/index.ts b/packages/client/src/shared/store/index.ts
new file mode 100644
index 0000000..e1d5870
--- /dev/null
+++ b/packages/client/src/shared/store/index.ts
@@ -0,0 +1,4 @@
+export * from './useCameraStore';
+export * from './useSignUpStore';
+export * from './useToastStore';
+export * from './useViewStore';
diff --git a/packages/client/src/shared/store/useLoadingStore.ts b/packages/client/src/shared/store/useLoadingStore.ts
new file mode 100644
index 0000000..e4c9959
--- /dev/null
+++ b/packages/client/src/shared/store/useLoadingStore.ts
@@ -0,0 +1,13 @@
+import { create } from 'zustand';
+
+interface LoadingState {
+ isLoading: boolean;
+ setIsLoading: (id: boolean) => void;
+}
+
+export const useLoadingStore = create((set) => ({
+ isLoading: false,
+ setIsLoading: (value) => {
+ set((state) => ({ ...state, isLoading: value }));
+ },
+}));
diff --git a/packages/client/src/shared/store/usePostStore.ts b/packages/client/src/shared/store/usePostStore.ts
deleted file mode 100644
index df4b148..0000000
--- a/packages/client/src/shared/store/usePostStore.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { PostData } from './../lib/types/post';
-import { create } from 'zustand';
-import * as THREE from 'three';
-
-interface PostState {
- data: PostData;
- setData: (data: PostData) => void;
-}
-
-export const usePostStore = create((set) => ({
- data: {
- title: '',
- content: '',
- position: new THREE.Vector3(0, 0, 0),
- size: 1,
- color: '#000000',
- images: [],
- },
- setData: (data: PostData) => set({ data }),
-}));
diff --git a/packages/client/src/shared/store/useViewStore.ts b/packages/client/src/shared/store/useViewStore.ts
index 798ff72..8e4b06d 100644
--- a/packages/client/src/shared/store/useViewStore.ts
+++ b/packages/client/src/shared/store/useViewStore.ts
@@ -1,6 +1,6 @@
import { create } from 'zustand';
-type view = 'MAIN' | 'DETAIL' | 'WRITING' | 'POST';
+type view = 'MAIN' | 'DETAIL' | 'POST';
interface ViewState {
view: view;
diff --git a/packages/client/src/shared/store/userLoginStore.ts b/packages/client/src/shared/store/userLoginStore.ts
deleted file mode 100644
index 8868b57..0000000
--- a/packages/client/src/shared/store/userLoginStore.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { create } from 'zustand';
-
-interface LoginState {
- id: string;
- setId: (id: string) => void;
- password: string;
- setPassword: (password: string) => void;
-}
-
-export const useLoginStore = create()((set) => ({
- id: '',
- setId: (id: string) => set({ id }),
- password: '',
- setPassword: (password: string) => set({ password }),
-}));
diff --git a/packages/client/src/shared/ui/alertDialog/AlertDialog.tsx b/packages/client/src/shared/ui/alertDialog/AlertDialog.tsx
new file mode 100644
index 0000000..9a8a096
--- /dev/null
+++ b/packages/client/src/shared/ui/alertDialog/AlertDialog.tsx
@@ -0,0 +1,101 @@
+import styled from '@emotion/styled';
+import { Body02ME, Title01 } from '../styles';
+import { css } from '@emotion/react';
+import { Button } from '..';
+
+interface PropsTypes extends React.HTMLAttributes {
+ title: string;
+ cancelButtonText: string;
+ actionButtonText: string;
+ onClickCancelButton: () => void;
+ onClickActionButton: () => void;
+ description?: string;
+}
+
+export default function AlertDialog({
+ title,
+ description,
+ cancelButtonText,
+ actionButtonText,
+ onClickCancelButton,
+ onClickActionButton,
+ ...args
+}: PropsTypes) {
+ return (
+
+
+ {title}
+ {description && {description}}
+
+
+
+
+
+
+
+ );
+}
+
+const Overlay = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 998;
+ background-color: rgba(0, 0, 0, 0.5);
+`;
+
+const Layout = styled.div`
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 999;
+
+ display: flex;
+ flex-direction: column;
+ width: 516px;
+ border-radius: 16px;
+ padding: 32px;
+ margin: 12px 0 0 0;
+
+ ${({ theme: { colors } }) => css`
+ background-color: ${colors.background.bdp01_80};
+ border: 2px solid ${colors.stroke.focus_80};
+ `};
+`;
+
+const Title = styled.h1`
+ display: flex;
+ justify-content: flex-start;
+ margin: 0 0 8px 0;
+ color: ${({ theme: { colors } }) => colors.text.primary};
+ ${Title01}
+`;
+
+const Description = styled.p`
+ color: ${({ theme: { colors } }) => colors.text.third};
+ ${Body02ME}
+`;
+
+const ButtonsContainer = styled.div`
+ display: flex;
+ justify-content: flex-end;
+ margin: 32px 0 0 0;
+ gap: 8px;
+`;
diff --git a/packages/client/src/shared/ui/buttons/Button.tsx b/packages/client/src/shared/ui/buttons/Button.tsx
index 26b2e08..d9fd508 100644
--- a/packages/client/src/shared/ui/buttons/Button.tsx
+++ b/packages/client/src/shared/ui/buttons/Button.tsx
@@ -61,7 +61,7 @@ const CustomButton = styled.button`
}
&:disabled {
- border-color: none;
+ border-color: transparent;
background: ${colors.primary.disabled};
color: ${colors.text.disabled};
}
@@ -80,7 +80,7 @@ const CustomButton = styled.button`
case 'warning-border':
return css`
border: 1px solid ${colors.warning.filled};
- background: none;
+ background: transparent;
color: ${colors.warning.filled};
${size === 'm' ? Body02ME : Body03ME}
diff --git a/packages/client/src/shared/ui/index.ts b/packages/client/src/shared/ui/index.ts
index 47333b7..c2e3056 100644
--- a/packages/client/src/shared/ui/index.ts
+++ b/packages/client/src/shared/ui/index.ts
@@ -1,6 +1,6 @@
export * from './buttons';
export * from './inputBar';
-export * from './modals';
+export * from './modal';
export * from './search';
export * from './textArea';
export * from './toast';
diff --git a/packages/client/src/shared/ui/modals/Modal.tsx b/packages/client/src/shared/ui/modal/Modal.tsx
similarity index 69%
rename from packages/client/src/shared/ui/modals/Modal.tsx
rename to packages/client/src/shared/ui/modal/Modal.tsx
index b3d012c..0a9601c 100644
--- a/packages/client/src/shared/ui/modals/Modal.tsx
+++ b/packages/client/src/shared/ui/modal/Modal.tsx
@@ -29,36 +29,47 @@ export default function Modal({
const isButtonExist = leftButton || rightButton;
return (
-
- {onClickGoBack && (
-
-
-
- )}
-
-
-
-
- {title}
- {topButton}
-
-
- {description && {description}}
-
-
- {children}
-
- {isButtonExist && (
-
- {leftButton}
- {rightButton}
-
+
+
+ {onClickGoBack && (
+
+
+
)}
-
-
+
+
+
+
+ {title}
+ {topButton}
+
+
+ {description && {description}}
+
+
+ {children}
+
+ {isButtonExist && (
+
+ {leftButton}
+ {rightButton}
+
+ )}
+
+
+
);
}
+const Overlay = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 998;
+`;
+
const Layout = styled.div`
position: absolute;
top: 50%;
diff --git a/packages/client/src/shared/ui/modals/ModalPortal.tsx b/packages/client/src/shared/ui/modal/ModalPortal.tsx
similarity index 100%
rename from packages/client/src/shared/ui/modals/ModalPortal.tsx
rename to packages/client/src/shared/ui/modal/ModalPortal.tsx
diff --git a/packages/client/src/shared/ui/modals/index.ts b/packages/client/src/shared/ui/modal/index.ts
similarity index 100%
rename from packages/client/src/shared/ui/modals/index.ts
rename to packages/client/src/shared/ui/modal/index.ts
diff --git a/packages/client/src/shared/ui/search/Search.tsx b/packages/client/src/shared/ui/search/Search.tsx
index 38fdc88..74adb35 100644
--- a/packages/client/src/shared/ui/search/Search.tsx
+++ b/packages/client/src/shared/ui/search/Search.tsx
@@ -34,11 +34,17 @@ export default function Search({
/>
{inputState ? (
-