Skip to content

Commit

Permalink
feat : 작성 페이지 api 연동 (#255)
Browse files Browse the repository at this point in the history
* feat : axios 전역 핸들러 처리

* feat : api 호출 로직 작성

* env : yarn 버전 업그레이드

* chore : 서버에서 오는 에러에 맞게 수정

* feat : 편지 작성 훅 개발

* env : 리엑트 쿼리 설치

* feat : api 커스텀 에러 설정 및 에러 반환 로직 구현

* feat : 편지 전송

* chore : 사용하지 않는 의존성 제거

* chore : 훅 index 경로 수정

* feat : 쿼리 프로바이더 추가

* env : tanksquery 버전 업그레이드

* feat : 전송 로직 수정
  • Loading branch information
HelloWook authored Dec 5, 2024
1 parent 784e49a commit 1b202a7
Show file tree
Hide file tree
Showing 19 changed files with 7,552 additions and 8,952 deletions.
15,921 changes: 7,126 additions & 8,795 deletions .pnp.cjs

Large diffs are not rendered by default.

160 changes: 80 additions & 80 deletions .yarn/releases/yarn-4.5.2.cjs → .yarn/releases/yarn-4.5.3.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
nodeLinker: pnp

yarnPath: .yarn/releases/yarn-4.5.2.cjs
yarnPath: .yarn/releases/yarn-4.5.3.cjs
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"gen-pwa": "vite build && vite preview"
},
"dependencies": {
"@tanstack/react-query": "^5.60.2",
"@tanstack/react-query": "^5.62.2",
"@tanstack/react-query-devtools": "^5.62.2",
"@turf/turf": "^7.1.0",
"axios": "^1.7.7",
"canvas-confetti": "^1.9.3",
Expand All @@ -29,6 +30,7 @@
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
"react-map-gl": "^7.1.7",
"react-query": "^3.39.3",
"react-router-dom": "^7.0.1",
"react-spring": "^9.7.4",
"react-spring-bottom-sheet": "3.5.0-alpha.0",
Expand Down Expand Up @@ -90,7 +92,7 @@
"vite-plugin-pwa": "^0.21.0",
"vite-plugin-svgr": "^4.3.0"
},
"packageManager": "[email protected].2",
"packageManager": "[email protected].3",
"eslintConfig": {
"extends": [
"plugin:storybook/recommended"
Expand Down
9 changes: 7 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import ToastContainer from './components/Common/ToastContainer/ToastContainer';
import { RouterProvider } from 'react-router-dom';
import { router } from '@/router';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

export const App = () => {
const queryClient = new QueryClient();

return (
<>
<QueryClientProvider client={queryClient}>
<ToastContainer />
<RouterProvider router={router} />
</>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ export const PostLetterCotainer = () => {

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const inputValue = e.target.value;
if (inputValue.length > 100) {
addToast('제목은 100자 이상 쓸 수 없습니다.', 'warning');
if (inputValue.length > 20) {
addToast('제목은 20자 이상 쓸 수 없습니다.', 'warning');
} else {
setTitle(inputValue);
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/SelectItemPage/ItemGroup/ItemGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type ItemGroupProps = {
isLabel: boolean;
labels: LabelProps[];
onLabelSelect: (label: number) => void;
selectedLabels: number | null;
selectedLabel: number | null;
keywordProps: {
title: string;
subTitle: string;
Expand All @@ -21,7 +21,7 @@ export const ItemGroup: React.FC<ItemGroupProps> = ({
isLabel,
labels,
onLabelSelect,
selectedLabels,
selectedLabel,
keywordProps,
onKeywordSelect,
selectedKeywords
Expand All @@ -30,7 +30,7 @@ export const ItemGroup: React.FC<ItemGroupProps> = ({
<LabelList
labels={labels}
onLabelSelect={onLabelSelect}
selectedLabels={selectedLabels}
selectedLabel={selectedLabel}
/>
) : (
<KeywordList
Expand Down
6 changes: 3 additions & 3 deletions src/components/SelectItemPage/LabelList/LabelList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { Label } from '@/components/Common/BottleLetter/Label/Label';

type LableListProps = {
labels: LabelProps[];
selectedLabels: number | null;
selectedLabel: number | null;
onLabelSelect: (index: number) => void;
};

export const LabelList = ({
labels,
selectedLabels,
selectedLabel,
onLabelSelect
}: LableListProps) => {
return (
Expand All @@ -23,7 +23,7 @@ export const LabelList = ({
>
<Label
imgSrc={label.imgSrc}
isActive={selectedLabels === idx}
isActive={selectedLabel === idx}
/>
</div>
))}
Expand Down
113 changes: 72 additions & 41 deletions src/components/SelectItemPage/SelectItem/SelectItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,96 @@ import { SelectToggle } from '../SelectToggle/SelectToggle';
import { Margin } from '@/components/Common/Margin/Margin';
import { SliderMenuContainer } from '@/components/Common/SliderMenuContainer/SliderMenuContainer';
import { CreateButton } from '../CreateButton/CreateButton';
import { useNavigate } from 'react-router-dom';
import { LabelProps } from '@/types/label';
import { ItemGroup } from '../ItemGroup/ItemGroup';
import { useCreateLetter } from '@/hooks/useCreateLetter';
import { useLocalStorage, useToastStore } from '@/hooks';

type SelectItemProps = {
isActive: boolean;
setIsActive: (isActive: boolean) => void;
};

const testLable: LabelProps[] = [
{
imgSrc: 'public/라벨_샘플.png'
},
{
imgSrc: 'public/라벨_샘플.png'
},
{
imgSrc: 'public/라벨_샘플.png'
},
{
imgSrc: 'public/라벨_샘플.png'
},
{
imgSrc: 'public/라벨_샘플.png'
}
];

const testKeywordListProps = {
title: 'Frontend Technologies',
subTitle: 'Trending Tools',
keywordGroup: [
{ content: 'React' },
{ content: 'TypeScript' },
{ content: 'Tailwind CSS' },
{ content: 'Next.js' },
{ content: 'jquey' },
{ content: 'docker' },
{ content: 'xocde' },
{ content: 'Next.js' }
]
};

export const SelectItem = ({ isActive, setIsActive }: SelectItemProps) => {
const [isLabel, setIsLabel] = useState(true);
const [selectedLabels, setSelectedLabels] = useState<number | null>(null);
const [selectedLabel, setSelectedLabel] = useState<number | null>(null);
const [selectedKeywords, setSelectedKeywords] = useState<number[]>([]);

const navigate = useNavigate();
const { mutate } = useCreateLetter();

const { storedValue: title } = useLocalStorage<string>('title', '');
const { storedValue: letter } = useLocalStorage<string>('letter', '');
const { storedValue: letterContent } = useLocalStorage<string>(
'letterContent',
''
);
const { storedValue: font } = useLocalStorage<string>('font', '');

const keywords = selectedKeywords
.map((x) => testKeywordListProps.keywordGroup[x]?.content)
.filter((content): content is string => content !== undefined);

const { addToast } = useToastStore();

const handdleClick = () => {
if (!selectedLabel || selectedKeywords.length === 0) {
addToast('라벨과 키워드를 선택해주세요.', 'error');
return;
}

mutate({
title: title,
content: letterContent,
font: font,
paper: letter,
keywords: keywords,
label: testLable[selectedLabel].imgSrc
});
};

useEffect(() => {
if (selectedLabels && selectedKeywords.length > 0) {
if (selectedLabel && selectedKeywords.length > 0) {
setIsActive(true);
} else {
setIsActive(false);
}
}, [selectedLabels, selectedKeywords]);
}, [selectedLabel, selectedKeywords]);

const handleLabelSelection = (label: number) => {
setSelectedLabels(label);
setSelectedLabel(label);
};

const handleKeywordSelection = (keyword: number) => {
Expand All @@ -39,39 +103,6 @@ export const SelectItem = ({ isActive, setIsActive }: SelectItemProps) => {
);
};

const testLable: LabelProps[] = [
{
imgSrc: 'public/라벨_샘플.png'
},
{
imgSrc: 'public/라벨_샘플.png'
},
{
imgSrc: 'public/라벨_샘플.png'
},
{
imgSrc: 'public/라벨_샘플.png'
},
{
imgSrc: 'public/라벨_샘플.png'
}
];

const testKeywordListProps = {
title: 'Frontend Technologies',
subTitle: 'Trending Tools',
keywordGroup: [
{ content: 'React' },
{ content: 'TypeScript' },
{ content: 'Tailwind CSS' },
{ content: 'Next.js' },
{ content: 'React' },
{ content: 'TypeScript' },
{ content: 'Tailwind CSS' },
{ content: 'Next.js' }
]
};

return (
<div className="relative">
<SliderMenuContainer
Expand All @@ -81,7 +112,7 @@ export const SelectItem = ({ isActive, setIsActive }: SelectItemProps) => {
isActive={isActive}
handleClickHandler={() => {
if (isActive) {
navigate('/letter/success');
handdleClick();
}
}}
>
Expand All @@ -96,7 +127,7 @@ export const SelectItem = ({ isActive, setIsActive }: SelectItemProps) => {
isLabel={isLabel}
labels={testLable}
onLabelSelect={handleLabelSelection}
selectedLabels={selectedLabels}
selectedLabel={selectedLabel}
keywordProps={testKeywordListProps}
onKeywordSelect={handleKeywordSelection}
selectedKeywords={selectedKeywords}
Expand Down
23 changes: 23 additions & 0 deletions src/hooks/useCreateLetter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useMutation } from '@tanstack/react-query';
import { createLetter } from '@/service/letter/create/createLetter';
import { useToastStore } from '@/hooks/useToastStore';
import { LetterType } from '@/types/letter';
import { ApiErrorType } from './../types/apiError';
import { useNavigate } from 'react-router-dom';

export const useCreateLetter = () => {
const { addToast } = useToastStore();
const navigate = useNavigate();

return useMutation({
mutationKey: ['createLetter'],
mutationFn: (letterData: LetterType) => createLetter(letterData),

onSuccess: () => {
navigate('/letter/success');
},
onError: (error: ApiErrorType) => {
addToast(`${error.message}`, 'warning');
}
});
};
4 changes: 2 additions & 2 deletions src/hooks/useLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { useState } from 'react';
import { useToastStore } from './useToastStore';

export function useLocalStorage<T>(key: string, initialValue: T) {
export function useLocalStorage<T>(key: string, initialValue?: T) {
const { addToast } = useToastStore();
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
addToast('정보를 불러오는데 실패했습니다.', 'success');
addToast('정보를 불러오는데 실패했습니다.', 'error');
console.error(error);
return initialValue;
}
Expand Down
12 changes: 3 additions & 9 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import './index.css';
import { App } from '@/App';
import './mocks';

const queryClient = new QueryClient();

createRoot(document.getElementById('root')!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</StrictMode>
<div>
<App />
</div>
);
14 changes: 14 additions & 0 deletions src/service/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axios, { AxiosRequestConfig, AxiosInstance } from 'axios';
import { tokenStorage } from './auth/tokenStorage';
import { refreshAccessToken } from '@/service/auth/refreshAccessToken';
import { formatApiError } from '@/util/formatApiError';

const baseUrl = import.meta.env.VITE_API_URL as string;

Expand Down Expand Up @@ -50,5 +51,18 @@ export const defaultApi = (option?: AxiosRequestConfig): AxiosInstance => {
}
);

//에러처리
instance.interceptors.response.use(
(response) => {
if (response.data.isSuccess === false) {
throw formatApiError(response.data.code, response.data.message);
}
return response;
},
(error) => {
throw formatApiError('ERROR500', '네트워크 요청에 실패했습니다.');
}
);

return instance;
};
Empty file.
23 changes: 23 additions & 0 deletions src/service/letter/create/createLetter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { LetterType, CreateLetterResponseType } from '@/types/letter';
import { defaultApi } from '@/service/api';

export const createLetter = async ({
title,
content,
keywords,
font,
paper,
label
}: LetterType): Promise<CreateLetterResponseType> => {
const api = defaultApi();

const response = await api.post('/letters', {
title,
content,
keywords,
font,
paper,
label
});
return response.data;
};
5 changes: 5 additions & 0 deletions src/types/apiError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type ApiErrorType = {
isSuccess: boolean;
code: string;
message: string;
};
Loading

0 comments on commit 1b202a7

Please sign in to comment.