Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8676f21
design: 컨텐트 길이만큼 가져오게 자식 높이 100% 삭제
YouD0313 May 17, 2025
b87b606
design: noContent 중간정렬 수정
YouD0313 May 18, 2025
2ca0ed5
refactor: 버튼 높이 fit-content 조정 후 중간정렬, 검색어 없을때 검색 되지않게 모달창 추가
YouD0313 May 18, 2025
1cd81f2
feat: 검색할때 쿼리스크링 + 뒤로가거나 목록으로 갈 때 검색내용이 있다면 그대로 받아 올 수 있게 ux 향상
YouD0313 May 18, 2025
1412b3d
refactor: refresh 토큰 httpOnly 이므로 스토리지 get, set 삭제
YouD0313 May 19, 2025
6976859
fix: 공고관리 마진바텀 2rem 추가, projectDetail 콘솔로그 코드 삭제
YouD0313 May 19, 2025
f49b976
refactor: 자잘한 css 수정, 프로필 수정시 데이터 변수명 수정
YouD0313 May 20, 2025
92773fb
desing: 마이페이지 스피너, noContent 중간정렬
YouD0313 May 21, 2025
5a394c9
comment: 주석-설명추가
YouD0313 May 21, 2025
ba3c47d
refactor: authStroe 구조 수정, 마이페이지 noContent 정렬 수정
YouD0313 May 22, 2025
45957a7
fix: http, auth쪽 디버깅용 콘솔로그 코드 삭제
YouD0313 May 22, 2025
2854f00
design: 스타일 컴포넌트 프롭스만 넘겨주고 css에서 구현하지 않아서 수정
YouD0313 May 22, 2025
98575cd
design: FAQ content 내릴때 부드럽게 나타나게 수정
YouD0313 May 22, 2025
2b3cd22
refactor: authStore 수정 후 구조변경에 따른 임포트 파일마다 수정
YouD0313 May 22, 2025
4927479
refacctor: 공고관리 스피너 추가
YouD0313 May 22, 2025
6e52ba2
fix: 로딩 조건 수정
YouD0313 May 22, 2025
51a9c74
feat: 문의하기 이미지파일 최대 3개 추가
YouD0313 May 25, 2025
af25a01
chore: conflict resolve
YouD0313 May 25, 2025
42b174d
design: 문의하기 첨부파일 최대 높이,너비 수정
YouD0313 May 25, 2025
e6fedbb
refactor: 에러바운더리 페이지 라우터 변경시 해당 컴포넌트로 새로 렌더
YouD0313 May 26, 2025
2fd0837
refactor: oauth 로그인시 userData 받기위해 수정
YouD0313 May 27, 2025
c08274f
design: 참여한 프로젝트 리스트 noContent 중간정렬 수정
YouD0313 May 27, 2025
489571b
design: 유저 컨텐트에서도 수정
YouD0313 May 27, 2025
de8597f
fix: 소문자로 된 파일명 변경
YouD0313 May 27, 2025
74041a3
feat: 깃허브 누끼딴 svg 추가 버튼추가
YouD0313 May 27, 2025
5e9f976
refactor: 필요없어진 코드 삭제
YouD0313 May 27, 2025
a34a1c3
refactor: 리뷰반영
YouD0313 May 27, 2025
a9640b6
refactor: 리뷰반영
YouD0313 May 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion src/api/auth.api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ApiVerifyNickname, VerifyEmail } from '../models/auth';
import type { ApiOauth, ApiVerifyNickname, VerifyEmail } from '../models/auth';
import { httpClient } from './http.api';
import { loginFormValues } from '../pages/login/Login';
import { registerFormValues } from '../pages/user/register/Register';
Expand All @@ -17,6 +17,7 @@ export const postVerificationEmail = async (email: string) => {
export const postVerifyEmailCode = async (data: VerifyEmail) => {
try {
const response = await httpClient.post('/authenticode/verify', data);

return response;
} catch (error) {
console.error('verifiyEmailCode:', error);
Expand All @@ -42,6 +43,7 @@ export const postSignUp = async (
) => {
try {
const response = await httpClient.post('/auth/sign-up', data);

return response;
} catch (error) {
console.error('signup:', error);
Expand All @@ -54,6 +56,7 @@ export const postResetPassword = async (
) => {
try {
const response = await httpClient.post('/auth/password/reset', data);

return response;
} catch (error) {
console.error('resetpassword:', error);
Expand All @@ -64,9 +67,36 @@ export const postResetPassword = async (
export const postLogin = async (data: loginFormValues) => {
try {
const response = await httpClient.post('/auth/login', data);

return response.data;
} catch (error) {
console.error('login:', error);
throw error;
}
};

export const postRefresh = async () => {
try {
const response = await httpClient.post('/auth/refresh');

return response.data;
} catch (e) {
console.error(e);
throw e;
}
};

export const getOauthLogin = async (oauthAccessToken: string) => {
try {
const response = await httpClient.get<ApiOauth>(`/auth/oauth-login`, {
headers: {
Authorization: `Bearer ${oauthAccessToken}`,
},
});

return response.data;
} catch (e) {
console.error(e);
throw e;
}
};
55 changes: 29 additions & 26 deletions src/api/http.api.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
import axios, { AxiosRequestConfig } from 'axios';
import useAuthStore, { getTokens } from '../store/authStore';
import useAuthStore from '../store/authStore';
import { postRefresh } from './auth.api';

export const BASE_URL = `${import.meta.env.VITE_APP_API_BASE_URL}`;
const DEFAULT_TIMEOUT = 15000;

export const createClient = (config?: AxiosRequestConfig) => {
const { storeLogin, storeLogout } = useAuthStore.getState();
const { login, logout } = useAuthStore.getState();

const axiosInstance = axios.create({
baseURL: BASE_URL,
timeout: DEFAULT_TIMEOUT,
headers: {
'content-type': 'application/json',
authorization:
getTokens().accessToken || getTokens().refreshToken
? `Bearer ${getTokens().accessToken}`
: '',
},
withCredentials: true,
...config,
});

axiosInstance.interceptors.request.use(
(config) => {
const { accessToken } = getTokens();
const accessToken = useAuthStore.getState().accessToken;
if (accessToken) {
config.headers['Authorization'] = `Bearer ${accessToken}`;
}
Expand All @@ -42,31 +36,40 @@ export const createClient = (config?: AxiosRequestConfig) => {
async (error) => {
const originalRequest = error.config;

if (!originalRequest.retryCount) {
originalRequest.retryCount = 0;
}

if (
error.response &&
error.response.status === 401 &&
!originalRequest._retry
originalRequest.retryCount < 5
) {
originalRequest._retry = true;
originalRequest.retryCount += 1;

try {
const refreshToken = getTokens().refreshToken;
/**
* http 로컬 환경이라 httpOnly인 refresh 토큰 전송 불가
* 배포 후 사용 (주석처리)
* 5회 시도후 안되면 로그아웃 처리하기
*/
// try {
// const refreshResponse = await postRefresh()

const refreshResponse = await axios.post(`${BASE_URL}auth/refresh`, {
refreshToken,
});
// const { accessToken: newAccessToken } = refreshResponse.data;

const { accessToken: newAccessToken, refreshToken: newRefreshToken } =
refreshResponse.data;
// login(newAccessToken,null);
// originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`;

storeLogin(newAccessToken, newRefreshToken);
originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`;
// return await axiosInstance(originalRequest);
// } catch (refreshError) {
// logout();
// return Promise.reject(refreshError);
// }

return await axios(originalRequest);
} catch (refreshError) {
storeLogout();
return Promise.reject(refreshError);
}
logout();
useAuthStore.persist.clearStorage();
window.location.href = '/login';
return Promise.reject(error);
}
return Promise.reject(error);
}
Expand Down
21 changes: 21 additions & 0 deletions src/assets/githubIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/components/common/error/QueryErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { useQueryErrorResetBoundary } from '@tanstack/react-query';
import type { PropsWithChildren } from 'react';
import ErrorBoundary from './ErrorBoundary';
import ErrorFallback from './ErrorFallback';
import { useLocation } from 'react-router-dom';

export default function QueryErrorBoundary({ children }: PropsWithChildren) {
const { reset } = useQueryErrorResetBoundary();
const location = useLocation();

return (
<ErrorBoundary
Expand All @@ -13,6 +15,7 @@ export default function QueryErrorBoundary({ children }: PropsWithChildren) {
const { error } = props;
return <ErrorFallback error={error} />;
}}
key={location.pathname}
>
{children}
</ErrorBoundary>
Expand Down
2 changes: 2 additions & 0 deletions src/components/common/noContent/NoContent.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import styled from 'styled-components';
export const Wrapper = styled.div`
width: 100%;
height: 100%;
min-height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
Expand All @@ -14,6 +15,7 @@ export const Wrapper = styled.div`
`;

export const NoContentText = styled.h1`
height: 100%;
font-size: 2rem;
font-weight: 600;
color: ${({ theme }) => theme.color.navy};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ export const SearchBarInput = styled.input`

export const ButtonWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
`;

export const UturnButton = styled.button`
export const XButton = styled.button`
height: fit-content;
&:hover {
color: ${({ theme }) => theme.color.lightnavy};
}
Expand Down
37 changes: 31 additions & 6 deletions src/components/user/customerService/CustomerServiceHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { MagnifyingGlassIcon, XCircleIcon } from '@heroicons/react/24/outline';
import * as S from './CustomerServiceHeader.styled';
import MovedInquiredLink from './MoveInquiredLink';
import { Outlet } from 'react-router-dom';
import { useState } from 'react';
import { Outlet, useLocation, useSearchParams } from 'react-router-dom';
import { useEffect, useState } from 'react';
import Modal from '../../common/modal/Modal';
import { useModal } from '../../../hooks/useModal';
import { MODAL_MESSAGE_CUSTOMER_SERVICE } from '../../../constants/user/customerService';

interface CustomerServiceHeaderProps {
title: string;
Expand All @@ -16,10 +19,28 @@ export default function CustomerServiceHeader({
onGetKeyword,
}: CustomerServiceHeaderProps) {
const [inputValue, setInputValue] = useState<string>('');
const { isOpen, message, handleModalOpen, handleModalClose } = useModal();
const [searchParams, setSearchParams] = useSearchParams();

const handleKeyword = (inputValue: string) => {
const newSearchParams = new URLSearchParams(searchParams);
if (inputValue === '') {
newSearchParams.delete('keyword');
} else {
newSearchParams.set('keyword', inputValue);
}
setSearchParams(newSearchParams);
};

const handleSubmitKeyword = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
onGetKeyword(inputValue);
if (inputValue.trim() === '') {
return handleModalOpen(MODAL_MESSAGE_CUSTOMER_SERVICE.noKeyword);
} else {
onGetKeyword(inputValue);
handleKeyword(inputValue);
return;
}
};

const handleChangeValue = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -30,6 +51,7 @@ export default function CustomerServiceHeader({
const handleReset = () => {
onGetKeyword('');
setInputValue('');
handleKeyword('');
};

return (
Expand All @@ -42,18 +64,18 @@ export default function CustomerServiceHeader({
<S.SearchBarInput
type='text'
placeholder='궁금한 내용을 검색해보세요.'
value={inputValue || keyword}
value={keyword ? keyword : inputValue}
onChange={handleChangeValue}
/>
<S.ButtonWrapper>
{keyword !== '' && (
<S.UturnButton
<S.XButton
type='button'
aria-label='show all result'
onClick={handleReset}
>
<XCircleIcon />
</S.UturnButton>
</S.XButton>
)}
<S.SearchButton type='submit' aria-label='search'>
<MagnifyingGlassIcon />
Expand All @@ -63,6 +85,9 @@ export default function CustomerServiceHeader({
<MovedInquiredLink />
</S.WrapperNav>
<Outlet />
<Modal isOpen={isOpen} onClose={handleModalClose}>
{message}
</Modal>
</S.Container>
);
}
34 changes: 28 additions & 6 deletions src/components/user/customerService/faq/FAQContent.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,34 @@ export const ListPlusIcon = styled.div<{ $isOpen: boolean }>`
}
`;

export const ListContentWrapper = styled.div`
cursor: auto;
background: ${({ theme }) => theme.color.lightgrey};
padding: 1.5rem 1rem;
display: flex;
gap: 0.5rem;
export const ListContentWrapper = styled.div<{ $isShowContent: boolean }>`
max-height: 0;
overflow: hidden;

@keyframes slice-show {
0% {
opacity: 0;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}

${({ $isShowContent }) =>
$isShowContent &&
css`
max-height: 100vh;
opacity: 1;
cursor: auto;
background: ${({ theme }) => theme.color.lightgrey};
padding: 1.5rem 1rem;
display: flex;
gap: 0.5rem;
animation: slice-show 300ms;
`}
`;

export const ListButtonWrapper = styled.div`
Expand Down
14 changes: 6 additions & 8 deletions src/components/user/customerService/faq/FAQContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@ export default function FAQContent({ list }: FAQContentProps) {
<PlusIcon />
</S.ListPlusIcon>
</S.ListWrapper>
{isFAQContentOpen && (
<S.ListContentWrapper>
<S.ListButtonWrapper>
<ChevronRightIcon />
</S.ListButtonWrapper>
<S.ListContent>{list.content}</S.ListContent>
</S.ListContentWrapper>
)}
<S.ListContentWrapper $isShowContent={isFAQContentOpen}>
<S.ListButtonWrapper>
<ChevronRightIcon />
</S.ListButtonWrapper>
<S.ListContent>{list.content}</S.ListContent>
</S.ListContentWrapper>
</S.ListContainer>
);
}
Loading