diff --git a/apps/client/src/components/cake/MyCake.tsx b/apps/client/src/components/cake/MyCake.tsx
index 110cde6..61af4ea 100644
--- a/apps/client/src/components/cake/MyCake.tsx
+++ b/apps/client/src/components/cake/MyCake.tsx
@@ -1,28 +1,83 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import axiosInstance from '#apis/axios.ts';
import Toggle from '#components/Toggle.tsx';
import GridInfo from '#components/GridInfo.tsx';
import CakeInfo from '#components/cake/CakeInfo.tsx';
import Wrapper from '#components/Wrapper.tsx';
-import { CakeUserTypeResponse } from '@isttp/types/all';
+import Button from '#components/Button.tsx';
+import { CakeUserTypeResponse, CakeColorType } from '@isttp/types/all';
-interface MyCakeProps {
+type MyCakeProps = {
+ ownerId: string;
data: CakeUserTypeResponse;
+};
+
+type CakeColorState = {
+ sheetColor: CakeColorType;
+ creamColor: CakeColorType;
+};
+
+function handleShareUrl() {
+ alert('기능 준비중');
}
-const MyCake: React.FC
= ({ data }) => {
+const MyCake: React.FC = ({ ownerId, data }) => {
+ const navigate = useNavigate();
const [toggle, setToggle] = useState(false);
+ const [cakeColor, setCakeColor] = useState({
+ sheetColor: 'chocolate',
+ creamColor: 'white',
+ });
const clickedToggle = () => {
setToggle((prev) => !prev);
};
+ async function getColors(userId: string) {
+ try {
+ const res = await axiosInstance.get(`/cake/color/${userId}`);
+
+ if (!res.data.sheetColor || !res.data.creamColor) {
+ navigate(`/cake/create/${userId}`);
+ }
+
+ setCakeColor({
+ sheetColor: res.data.sheetColor,
+ creamColor: res.data.creamColor,
+ });
+ } catch (error) {
+ console.log(error);
+ // 에러 코드에 따라 모달
+ }
+ }
+
+ useEffect(() => {
+ getColors(ownerId);
+ }, [ownerId]);
+
return (
{data.nickname} 님의 케이크
장식초를 눌러 편지를 확인해보세요
-
- {toggle ? : }
+
+ {toggle ? (
+
+ ) : (
+ <>
+
+
+ >
+ )}
);
};
diff --git a/apps/client/src/components/cake/SharedCake.tsx b/apps/client/src/components/cake/SharedCake.tsx
index d8057a0..5ea7f76 100644
--- a/apps/client/src/components/cake/SharedCake.tsx
+++ b/apps/client/src/components/cake/SharedCake.tsx
@@ -1,19 +1,91 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import axiosInstance from '#apis/axios.ts';
import CakeInfo from '#components/cake/CakeInfo.tsx';
import Wrapper from '#components/Wrapper.tsx';
-import { CakeUserTypeResponse } from '@isttp/types/all';
+import { CakeUserTypeResponse, CakeColorType } from '@isttp/types/all';
+import Button from '#components/Button.tsx';
+import { AxiosError } from 'axios';
interface MyCakeProps {
+ ownerId: string;
data: CakeUserTypeResponse;
}
-const SharedCake: React.FC = ({ data }) => {
+type CakeColorState = {
+ sheetColor: CakeColorType;
+ creamColor: CakeColorType;
+};
+
+const SharedCake: React.FC = ({ ownerId, data }) => {
+ const [cakeColor, setCakeColor] = useState({
+ sheetColor: 'chocolate',
+ creamColor: 'white',
+ });
+
+ async function getColors(userId: string) {
+ try {
+ const res = await axiosInstance.get(`/cake/color/${userId}`);
+
+ if (!res.data.sheetColor || !res.data.creamColor) return;
+
+ setCakeColor({
+ sheetColor: res.data.sheetColor,
+ creamColor: res.data.creamColor,
+ });
+ } catch (error) {
+ console.log(error);
+ // 에러 코드에 따라 모달
+ }
+ }
+
+ const navigate = useNavigate();
+
+ function handleCreateLetter() {
+ navigate(`/letter/create/${ownerId}`);
+ }
+
+ async function handleNavigateToMyCake() {
+ // 토큰 유효성 확인 후 반환받은 유저 아이디 케이크 페이지로 이동
+ axiosInstance
+ .get('/user/me')
+ .then((res) => {
+ if (res.status === 200) {
+ navigate(`/cake/${res.data.userId}`);
+ }
+ })
+ .catch((error) => {
+ if (error instanceof AxiosError) {
+ if (error.response?.status === 401) {
+ alert('로그인이 필요합니다.');
+ }
+ }
+ });
+ }
+
+ useEffect(() => {
+ getColors(ownerId);
+ }, [ownerId]);
+
return (
{data.nickname} 님의 케이크
친구의 케이크를 꾸며보세요!
-
-
+
+
+
);
};
diff --git a/apps/client/src/components/letter/ChooseLetter.tsx b/apps/client/src/components/letter/ChooseLetter.tsx
new file mode 100644
index 0000000..e60ff3a
--- /dev/null
+++ b/apps/client/src/components/letter/ChooseLetter.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import Wrapper from '#components/Wrapper.tsx';
+
+type ChooseLetterProps = {
+ handleNext: () => void;
+};
+
+const ChooseLetter = ({ handleNext }: ChooseLetterProps) => {
+ return (
+
+ Choose Letter
+
+
+ );
+};
+
+export default ChooseLetter;
diff --git a/apps/client/src/components/letter/WriteLetter.tsx b/apps/client/src/components/letter/WriteLetter.tsx
new file mode 100644
index 0000000..1ca2d23
--- /dev/null
+++ b/apps/client/src/components/letter/WriteLetter.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import Wrapper from '#components/Wrapper.tsx';
+
+type WriteLetterProps = {
+ handlePrev: () => void;
+};
+
+const WriteLetter = ({ handlePrev }: WriteLetterProps) => {
+ return (
+
+ Write Letter
+
+
+ );
+};
+
+export default WriteLetter;
diff --git a/apps/client/src/pages/Cake.tsx b/apps/client/src/pages/Cake.tsx
index de393fe..edeb5dc 100644
--- a/apps/client/src/pages/Cake.tsx
+++ b/apps/client/src/pages/Cake.tsx
@@ -7,23 +7,21 @@ import { CakeUserTypeResponse } from '@isttp/types/all';
import { useParams } from 'react-router-dom';
const Cake = () => {
- const { cakeUserId } = useParams();
+ const { ownerId } = useParams();
const [isMyCake, setIsMyCake] = useState(false);
const [cakeUserData, setCakeUserData] = useState();
- async function chooseVersion() {
- const res = await axiosInstance.get(
- `cake/version?cakeUserId=${cakeUserId}`,
- );
+ async function chooseVersion(ownerId: string) {
+ const res = await axiosInstance.get(`cake/version?cakeUserId=${ownerId}`);
setCakeUserData(res.data.data);
- if (res.data.userId.userId === cakeUserId) {
+ if (res.data.userId === ownerId) {
setIsMyCake(true);
}
}
useEffect(() => {
- chooseVersion();
- }, []);
+ chooseVersion(ownerId);
+ }, [ownerId]);
if (!cakeUserData) {
return 에러처리 해주기
;
@@ -32,9 +30,9 @@ const Cake = () => {
return (
{isMyCake ? (
-
+
) : (
-
+
)}
);
diff --git a/apps/client/src/pages/CreateCake.tsx b/apps/client/src/pages/CreateCake.tsx
new file mode 100644
index 0000000..3ef758e
--- /dev/null
+++ b/apps/client/src/pages/CreateCake.tsx
@@ -0,0 +1,113 @@
+import React, { useState, useEffect } from 'react';
+import { useNavigate, useParams } from 'react-router-dom';
+import { AxiosError } from 'axios';
+import axiosInstance from '#apis/axios.ts';
+import Button from '#components/Button.tsx';
+import Wrapper from '#components/Wrapper.tsx';
+import RenderCake from '#components/RenderCake.tsx';
+import ColorSelector from '#components/ColorSelector.tsx';
+
+import { CakeColorType } from '@isttp/types/all';
+
+async function getColors(userId: string) {
+ try {
+ const res = await axiosInstance.get(`/cake/color/${userId}`);
+ const { sheetColor, creamColor } = res.data;
+
+ if (!sheetColor || !creamColor) {
+ return null;
+ }
+
+ return { sheetColor, creamColor };
+ } catch (error) {
+ console.log(error);
+ // 로그인이 필요합니다 모달 어떤 에러인지에 따라 모달 달리 하기
+ }
+}
+
+const CreateCake = () => {
+ const navigate = useNavigate();
+ const { ownerId } = useParams();
+ const [sheetColor, setSheetColor] = useState('white');
+ const [creamColor, setCreamColor] = useState('chocolate');
+
+ async function handleCreateCake() {
+ try {
+ axiosInstance
+ .post('/cake/color', {
+ sheetColor: sheetColor,
+ creamColor: creamColor,
+ })
+ .then((res) => {
+ if (res.status === 200) {
+ alert('케이크 만들기에 성공했습니다.');
+ navigate(`/cake/${ownerId}`);
+ } else {
+ throw new Error('케이크 만들기에 실패했습니다.');
+ }
+ });
+ } catch (error) {
+ if (error instanceof AxiosError) {
+ if (error.response?.status === 401) {
+ alert('로그인이 필요합니다.');
+ navigate('/');
+ return;
+ }
+ }
+ }
+ }
+
+ useEffect(() => {
+ const fetchIsOwner = async () => {
+ axiosInstance
+ .get('/user/me')
+ .then((res) => {
+ if (res.data.userId !== ownerId) {
+ alert('잘못된 접근입니다.');
+ navigate(`/cake/${ownerId}`);
+ return;
+ }
+ })
+ .catch((error) => {
+ console.error(error);
+ if (error instanceof AxiosError) {
+ if (error.response?.status === 401) {
+ alert('로그인이 필요합니다.');
+ navigate('/');
+ return;
+ }
+ }
+ });
+ };
+
+ const fetchColors = async () => {
+ getColors(ownerId).then((res) => {
+ if (!res) {
+ return;
+ }
+
+ setSheetColor(res.sheetColor);
+ setCreamColor(res.creamColor);
+ });
+ };
+
+ fetchIsOwner();
+ fetchColors();
+ }, [ownerId]);
+
+ return (
+
+ 케이크 만들기
+
+
+
+
+ );
+};
+
+export default CreateCake;
diff --git a/apps/client/src/pages/CreateLetter.tsx b/apps/client/src/pages/CreateLetter.tsx
new file mode 100644
index 0000000..2db1037
--- /dev/null
+++ b/apps/client/src/pages/CreateLetter.tsx
@@ -0,0 +1,26 @@
+import React, { useState } from 'react';
+import Wrapper from '#components/Wrapper.tsx';
+import ChooseLetter from '#components/letter/ChooseLetter.tsx';
+import WriteLetter from '#components/letter/WriteLetter.tsx';
+
+const CreateLetter = () => {
+ const [step, setStep] = useState(1);
+
+ function handleNext() {
+ setStep(step + 1);
+ }
+
+ function handlePrev() {
+ setStep(step - 1);
+ }
+
+ return (
+
+ Create Letter
+ {step === 1 && }
+ {step === 2 && }
+
+ );
+};
+
+export default CreateLetter;
diff --git a/apps/client/src/pages/Login.tsx b/apps/client/src/pages/Login.tsx
index c50ef23..2011d9c 100644
--- a/apps/client/src/pages/Login.tsx
+++ b/apps/client/src/pages/Login.tsx
@@ -1,4 +1,5 @@
-import React from 'react';
+import React, { useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
import { Link } from 'react-router-dom';
import Wrapper from '#components/Wrapper.tsx';
import Button from '#components/Button.tsx';
@@ -15,6 +16,23 @@ async function handleGoogleLogin() {
}
const Login = () => {
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ async function handleTokenValidation() {
+ try {
+ const res = await axiosInstance.get('/user/me');
+ if (res.status === 200) {
+ navigate(`/cake/${res.data.userId}`);
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ }
+
+ handleTokenValidation();
+ }, []);
+
return (
Login
diff --git a/apps/server/src/models/candle.ts b/apps/server/src/models/candle.ts
new file mode 100644
index 0000000..94c264c
--- /dev/null
+++ b/apps/server/src/models/candle.ts
@@ -0,0 +1,6 @@
+import { PrismaClient } from '@isttp/db/all';
+const prisma = new PrismaClient();
+
+export async function getCandles() {
+ return await prisma.candle.findMany();
+}
diff --git a/apps/server/src/models/user.ts b/apps/server/src/models/user.ts
index 3cfd8e1..b1df83c 100644
--- a/apps/server/src/models/user.ts
+++ b/apps/server/src/models/user.ts
@@ -8,3 +8,12 @@ export async function getUser(userId: string) {
},
});
}
+
+export async function setUser(userId: string, data: object) {
+ return await prisma.user.update({
+ where: {
+ userId,
+ },
+ data,
+ });
+}
diff --git a/apps/server/src/routes/cake.ts b/apps/server/src/routes/cake.ts
index bbe031d..c61f831 100644
--- a/apps/server/src/routes/cake.ts
+++ b/apps/server/src/routes/cake.ts
@@ -1,13 +1,16 @@
import 'dotenv/config';
import { PrismaClient } from '@isttp/db/all';
import { CakeTypeResponse, CakeUserTypeResponse } from '@isttp/types/all';
+import { getCakeColor, setCakeColor } from '../service/cake';
+import { checkCakeColorType } from '@isttp/utils/all';
import { verifyToken, decodeToken } from '@isttp/utils/all';
import { Router } from 'express';
+import { authorize } from '../service/auth';
const router: Router = Router();
const prisma = new PrismaClient();
-router.get('/cake/:userId/:year/', async (req, res) => {
+router.get('/cake/letters/:userId/:year/', async (req, res) => {
const userId = String(req.params.userId);
const year = Number(req.params.year);
const keyword = String(req.query.keyword);
@@ -57,8 +60,6 @@ router.get('/cake/:userId/:year/', async (req, res) => {
}
});
-export default router;
-
//해당 api에서는 접속자의 accesstoken 만 체크해본다.
//체크시 유효하면 해당 요청을 보낸 접속자의 userid, 연도, 닉네임 반환
//체크시 무효하면(만료, 알수없는값, 토큰 없는 경우 모두 expired메시지처리) null, 연도, 닉네임을 반환
@@ -100,7 +101,8 @@ router.get('/cake/version', async (req, res) => {
let userId;
try {
if (verifyToken(accessToken)) {
- userId = decodeToken(accessToken);
+ const payload = decodeToken(accessToken);
+ userId = payload?.userId;
}
} catch (error) {
userId = null;
@@ -108,7 +110,7 @@ router.get('/cake/version', async (req, res) => {
if (userId) {
res.status(200).json({
- userId: userId,
+ userId,
data: responseData,
});
} else {
@@ -121,3 +123,40 @@ router.get('/cake/version', async (req, res) => {
res.status(500).json({ message: `케이크 버전 구분 실패 : ${error}` });
}
});
+
+router.get('/cake/color/:userId', async (req, res) => {
+ try {
+ const userId = req.params.userId;
+ const { sheetColor, creamColor } = await getCakeColor(userId);
+ res.status(200).json({ sheetColor, creamColor });
+ } catch (error) {
+ console.log(error);
+ res.status(500).json({ message: `색상 정보 조회 실패: ${error}` });
+ }
+});
+
+router.post('/cake/color', authorize, async (req, res) => {
+ const userId = req.userId;
+ const { sheetColor, creamColor } = req.body;
+
+ const isValid =
+ sheetColor ||
+ creamColor ||
+ checkCakeColorType(sheetColor) ||
+ checkCakeColorType(creamColor);
+
+ if (!isValid)
+ return res
+ .status(400)
+ .json({ message: '잘못된 요청: 색상 정보가 올바르지 않습니다.' });
+
+ try {
+ const updated = await setCakeColor({ userId, sheetColor, creamColor });
+ res.status(200).json(updated);
+ } catch (error) {
+ console.log(error);
+ res.status(500).json({ message: `색상 정보 수정 실패: ${error}` });
+ }
+});
+
+export default router;
diff --git a/apps/server/src/routes/candle.ts b/apps/server/src/routes/candle.ts
new file mode 100644
index 0000000..73a2a37
--- /dev/null
+++ b/apps/server/src/routes/candle.ts
@@ -0,0 +1,14 @@
+import { getCandles } from '../models/candle';
+import { Router } from 'express';
+
+const router: Router = Router();
+
+router.get('/candle', async (req, res) => {
+ try {
+ const candles = await getCandles();
+ res.status(200).json(candles);
+ } catch (error) {
+ console.log(error);
+ res.status(500).json(error);
+ }
+});
diff --git a/apps/server/src/service/cake.ts b/apps/server/src/service/cake.ts
new file mode 100644
index 0000000..419c324
--- /dev/null
+++ b/apps/server/src/service/cake.ts
@@ -0,0 +1,33 @@
+import { getUser, setUser } from '../models/user';
+
+export async function getCakeColor(userId: string) {
+ try {
+ const userInfo = await getUser(userId);
+ if (!userInfo) throw new Error('사용자 정보를 찾을 수 없습니다.');
+ const { sheetColor, creamColor } = userInfo;
+ return { sheetColor, creamColor };
+ } catch (error) {
+ throw new Error('색상 정보를 불러오는데 실패했습니다.');
+ }
+}
+
+export async function setCakeColor({
+ userId,
+ sheetColor,
+ creamColor,
+}: {
+ userId: string;
+ sheetColor: string;
+ creamColor: string;
+}) {
+ try {
+ await setUser(userId, {
+ sheetColor,
+ creamColor,
+ });
+
+ return { sheetColor, creamColor };
+ } catch (error) {
+ throw new Error('색상 정보를 저장하는데 실패했습니다.');
+ }
+}
diff --git a/packages/types/src/cake.ts b/packages/types/src/cake.ts
index c09c8c4..f8fc779 100644
--- a/packages/types/src/cake.ts
+++ b/packages/types/src/cake.ts
@@ -1,3 +1,11 @@
+export type CakeColorType =
+ | 'white'
+ | 'chocolate'
+ | 'strawberry'
+ | 'banana'
+ | 'mint'
+ | 'blueberry';
+
export type CakeTypeResponse = {
nickname: string;
candleImageUrl: string;
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index a20a215..8325cef 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -2,3 +2,4 @@ export * from './models';
export * from './auth';
export * from './cake';
export * from './jwt';
+export * from './cake';
diff --git a/packages/utils/src/checkType.ts b/packages/utils/src/checkType.ts
new file mode 100644
index 0000000..f491a68
--- /dev/null
+++ b/packages/utils/src/checkType.ts
@@ -0,0 +1,10 @@
+export function checkCakeColorType(color: string) {
+ return [
+ 'white',
+ 'chocolate',
+ 'strawberry',
+ 'banana',
+ 'mint',
+ 'blueberry',
+ ].includes(color);
+}
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
index 511b15a..2919621 100644
--- a/packages/utils/src/index.ts
+++ b/packages/utils/src/index.ts
@@ -1 +1,2 @@
export * from './jwt';
+export * from './checkType';