Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge-fe: cruru Frontend version 2.2.0 배포 (24.10.24) #939

Merged
merged 6 commits into from
Oct 24, 2024
4 changes: 2 additions & 2 deletions frontend/src/components/_common/molecules/Tab/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ const Tab = styled.li`

position: absolute;
transform: translateY(75%);
color: ${({ theme }) => theme.baseColors.grayscale[500]};
color: ${({ theme }) => theme.colors.text.small};
}
`;

const TabButton = styled.button<{ isActive: boolean }>`
${({ theme }) => theme.typography.heading[500]};
color: ${({ isActive, theme }) => (isActive ? theme.baseColors.grayscale[950] : theme.baseColors.grayscale[500])};
color: ${({ isActive, theme }) => (isActive ? theme.baseColors.grayscale[950] : theme.colors.text.small)};

cursor: pointer;
transition: color 0.3s ease;
Expand Down
15 changes: 12 additions & 3 deletions frontend/src/components/dashboard/DashboardSidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export default function DashboardSidebar({

<S.SidebarNav>
<S.Contents>
<S.SidebarItem>
<S.SidebarItem isSidebarOpen={sidebarStyle.isSidebarOpen}>
<Link to={routes.dashboard.list()}>
<S.SidebarItemLink
isSelected={location.pathname === routes.dashboard.list()}
Expand All @@ -125,6 +125,7 @@ export default function DashboardSidebar({
{sidebarStyle.isSidebarOpen && <S.SidebarItemTextHeader>모집 공고</S.SidebarItemTextHeader>}
</S.SidebarItemLink>
</Link>
{!sidebarStyle.isSidebarOpen && <div className="sidebar-tooltip">모집 공고</div>}
</S.SidebarItem>

{!!options?.length && <S.Divider />}
Expand All @@ -137,7 +138,10 @@ export default function DashboardSidebar({

return (
/* eslint-disable react/jsx-indent */
<S.SidebarItem key={label}>
<S.SidebarItem
isSidebarOpen={sidebarStyle.isSidebarOpen}
key={label}
>
<S.SidebarItemLink
isSelected={currentMenu === label}
isSidebarOpen={sidebarStyle.isSidebarOpen}
Expand All @@ -151,6 +155,7 @@ export default function DashboardSidebar({
</S.IconContainer>
{sidebarStyle.isSidebarOpen && <S.SidebarItemText>{label}</S.SidebarItemText>}
</S.SidebarItemLink>
{!sidebarStyle.isSidebarOpen && <div className="sidebar-tooltip">{label}</div>}
</S.SidebarItem>
);
})}
Expand All @@ -167,7 +172,10 @@ export default function DashboardSidebar({
{posts?.map(({ text, isSelected, applyFormId, dashboardId, status }) => {
const Icon = IconObj[status.status];
return (
<S.SidebarItem key={applyFormId}>
<S.SidebarItem
isSidebarOpen={sidebarStyle.isSidebarOpen}
key={applyFormId}
>
<Link
to={routes.dashboard.post({ dashboardId: String(dashboardId), applyFormId })}
onClick={onResetTab}
Expand All @@ -185,6 +193,7 @@ export default function DashboardSidebar({
{sidebarStyle.isSidebarOpen && <S.SidebarItemText>{text}</S.SidebarItemText>}
</S.SidebarItemLink>
</Link>
{!sidebarStyle.isSidebarOpen && <div className="sidebar-tooltip">{text}</div>}
</S.SidebarItem>
);
})}
Expand Down
31 changes: 28 additions & 3 deletions frontend/src/components/dashboard/DashboardSidebar/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,49 @@ const Contents = styled.ul`
max-height: 100%;
`;

const SidebarScrollBox = styled.div`
const SidebarScrollBox = styled.li`
overflow-y: scroll;
${hideScrollBar};
`;

const SidebarItemGroup = styled.li`
const SidebarItemGroup = styled.ul`
display: flex;
flex-direction: column;
gap: 2rem;
margin-bottom: 2.4rem;
`;

const SidebarItem = styled.li`
const SidebarItem = styled.li<{ isSidebarOpen: boolean }>`
display: flex;
align-items: center;

list-style: none;
height: 2.4rem;

& .sidebar-tooltip {
position: absolute;
transform: ${({ isSidebarOpen }) => (isSidebarOpen ? 'translate(26rem, 1.4rem)' : 'translate(4rem, 1.4rem)')};
transition: all 0.5s ease;
z-index: 5;

background: ${({ theme }) => theme.baseColors.grayscale[900]};
${({ theme }) => theme.typography.common.block};
color: ${({ theme }) => theme.baseColors.grayscale[100]};
line-height: inherit;

box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
border-radius: 0.8rem;
padding: 0.8rem 1.2rem;
opacity: 0;
white-space: nowrap;
pointer-events: none;
}

&:hover .sidebar-tooltip {
opacity: 1;
pointer-events: auto;
transform: ${({ isSidebarOpen }) => (isSidebarOpen ? 'translate(26.5rem, 1.4rem)' : 'translate(4.5rem, 1.4rem)')};
}
`;

const SidebarItemLink = styled.div<{ isSelected: boolean; isSidebarOpen?: boolean }>`
Expand Down
10 changes: 6 additions & 4 deletions frontend/src/components/recruitmentPost/ApplyForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export default function ApplyForm({ questions, isClosed }: ApplyFormProps) {

const { error } = useToast();

const questionCount = questions.length + 4;

const handleSubmit: FormEventHandler<HTMLFormElement> = (e) => {
e.preventDefault();

Expand Down Expand Up @@ -78,7 +80,7 @@ export default function ApplyForm({ questions, isClosed }: ApplyFormProps) {
return (
<C.ContentContainer>
<S.Form onSubmit={handleSubmit}>
<S.AriaCustomQuestion aria-label={`총 ${questions.length}의 입력 중 1번째 입력입니다.`}>
<S.AriaCustomQuestion aria-label={`총 ${questionCount}의 입력 중 1번째 입력입니다.`}>
<InputField
{...register('name', { validate: { onBlur: validateName.onBlur, onChange: validateName.onChange } })}
name="name"
Expand All @@ -89,7 +91,7 @@ export default function ApplyForm({ questions, isClosed }: ApplyFormProps) {
/>
</S.AriaCustomQuestion>

<S.AriaCustomQuestion aria-label={`총 ${questions.length}의 입력 중 2번째 입력입니다.`}>
<S.AriaCustomQuestion aria-label={`총 ${questionCount}의 입력 중 2번째 입력입니다.`}>
<InputField
{...register('email', { validate: { onBlur: validateEmail.onBlur } })}
label="이메일"
Expand All @@ -98,7 +100,7 @@ export default function ApplyForm({ questions, isClosed }: ApplyFormProps) {
/>
</S.AriaCustomQuestion>

<S.AriaCustomQuestion aria-label={`총 ${questions.length}의 입력 중 3번째 입력입니다.`}>
<S.AriaCustomQuestion aria-label={`총 ${questionCount}의 입력 중 3번째 입력입니다.`}>
<InputField
{...register('phone', {
validate: {
Expand All @@ -117,7 +119,7 @@ export default function ApplyForm({ questions, isClosed }: ApplyFormProps) {
{questions.map((question, index) => (
<S.AriaCustomQuestion
key={question.questionId}
aria-label={`총 ${questions.length}의 입력 중 ${index + 4}번째 질문입니다.`}
aria-label={`총 ${questionCount}의 입력 중 ${index + 4}번째 질문입니다.`}
>
<CustomQuestion
question={question}
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/hooks/service/useClubId.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useSyncExternalStore } from 'react';
import ApiError from '@api/ApiError';
import { useNavigate } from 'react-router-dom';
import { useToast } from '@contexts/ToastContext';
import { routes } from '@router/path';

const CLUB_ID_KEY = 'clubId';

Expand All @@ -13,6 +14,7 @@ const getSnapshot = () => localStorage.getItem(CLUB_ID_KEY);

export default function useClubId() {
const { error } = useToast();
const navigate = useNavigate();

const saveClubId = (clubId: string) => {
localStorage.setItem(CLUB_ID_KEY, clubId);
Expand All @@ -22,8 +24,8 @@ export default function useClubId() {
const clubId = getSnapshot();

if (!clubId) {
error('세션이 만료되었습니다. 다시 로그인해주세요.');
throw new ApiError({ message: '세션이 만료되었습니다. 다시 로그인해주세요.', statusCode: 401, method: 'GET' });
error('사용자 정보가 만료되었습니다. 다시 로그인해주세요.');
navigate(routes.home());
}

return clubId;
Expand Down
13 changes: 5 additions & 8 deletions frontend/src/hooks/useApplyManagement/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { useEffect, useState } from 'react';
import type { Question, QuestionOptionValue } from '@customTypes/dashboard';
import { Question as QuestionData } from '@customTypes/apply';
import type { Question, QuestionOptionValue } from '@customTypes/dashboard';
import { useEffect, useState } from 'react';

import { applyQueries } from '@hooks/apply';
import { DEFAULT_QUESTIONS } from '@constants/constants';
import { useMutation, UseMutationResult, useQueryClient } from '@tanstack/react-query';
import questionApis from '@api/domain/question';
import QUERY_KEYS from '@hooks/queryKeys';
import { DEFAULT_QUESTIONS } from '@constants/constants';
import { applyQueries } from '@hooks/apply';
import { useMutation, UseMutationResult } from '@tanstack/react-query';
import { useToast } from '@contexts/ToastContext';

interface UseApplyManagementReturn {
Expand Down Expand Up @@ -49,7 +48,6 @@ export default function useApplyManagement({ applyFormId }: UseApplyManagementPr
const [applyState, setApplyState] = useState(getQuestions(data));
const [uniqueId, setUniqueId] = useState(DEFAULT_QUESTIONS.length);
const toast = useToast();
const queryClient = useQueryClient();

useEffect(() => {
if (data && data.length > 0) {
Expand Down Expand Up @@ -77,7 +75,6 @@ export default function useApplyManagement({ applyFormId }: UseApplyManagementPr
})),
}),
onSuccess: async () => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.RECRUITMENT_INFO, applyFormId] });
toast.success('지원서의 사전 질문 항목 수정에 성공했습니다.');
},
onError: () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { act, renderHook } from '@testing-library/react';
import ToastProvider from '@contexts/ToastContext';
import useDashboardCreateForm from '.';

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: jest.fn(),
}));

describe('useDashboardCreateForm', () => {
beforeAll(() => {
localStorage.setItem('clubId', '1');
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/hooks/useGetDashboards/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ export default function useGetDashboards() {
enabled: !!clubId,
});

return { data, error, isLoading };
const findDashboard = (dashboardId: string) =>
data?.dashboards.find((dashboard) => String(dashboard.dashboardId) === dashboardId);

return { data, error, isLoading, findDashboard };
}
17 changes: 10 additions & 7 deletions frontend/src/hooks/usePostManagement/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { useEffect, useState } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useEffect, useState } from 'react';

import applyApis from '@api/domain/apply/apply';
import { useToast } from '@contexts/ToastContext';
import { RecruitmentPost } from '@customTypes/apply';
import { RecruitmentInfoState } from '@customTypes/dashboard';
import { applyQueries } from '@hooks/apply';
import applyApis from '@api/domain/apply/apply';
import QUERY_KEYS from '@hooks/queryKeys';
import { RecruitmentPost } from '@customTypes/apply';
import { useToast } from '@contexts/ToastContext';
import useClubId from '@hooks/service/useClubId';

interface usePostManagementProps {
applyFormId: string;
Expand All @@ -20,10 +21,12 @@ const INITIAL_POST_INFO: RecruitmentInfoState = {
};

export default function usePostManagement({ applyFormId }: usePostManagementProps) {
const { data: postInfo, isLoading } = applyQueries.useGetRecruitmentPost({ applyFormId });
const [postState, setPostState] = useState<RecruitmentInfoState>(INITIAL_POST_INFO);
const toast = useToast();
const queryClient = useQueryClient();
const { clubId } = useClubId();

const { data: postInfo, isLoading } = applyQueries.useGetRecruitmentPost({ applyFormId });
const [postState, setPostState] = useState<RecruitmentInfoState>(INITIAL_POST_INFO);

useEffect(() => {
if (!isLoading && postInfo && Object.keys(postInfo).length > 0) {
Expand All @@ -34,7 +37,7 @@ export default function usePostManagement({ applyFormId }: usePostManagementProp
const modifyPostMutator = useMutation({
mutationFn: () => applyApis.modify({ applyFormId, body: postState as RecruitmentPost }),
onSuccess: async () => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.RECRUITMENT_INFO, applyFormId] });
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.DASHBOARD, clubId] });
toast.success('공고의 내용 수정에 성공했습니다.');
},
onError: () => {
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/pages/Dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import PostManageBoard from '@components/postManagement/PostManageBoard';
import ProcessManageBoard from '@components/processManagement/ProcessManageBoard';

import useProcess from '@hooks/useProcess';
import useGetDashboards from '@hooks/useGetDashboards';

import { FloatingEmailFormProvider } from '@contexts/FloatingEmailFormContext';
import { MultiApplicantContextProvider } from '@contexts/MultiApplicantContext';
Expand All @@ -22,7 +23,11 @@ import S from './style';
export default function Dashboard() {
const { currentMenu } = useOutletContext<{ currentMenu: DashboardTabItems }>();
const { dashboardId, applyFormId } = useParams() as { dashboardId: string; applyFormId: string };
const { processes, title, postUrl, startDate, endDate, ratingFilterProps, applicantSortDropdownProps } = useProcess({

const { findDashboard } = useGetDashboards();
const { title = '', startDate = '', endDate = '' } = findDashboard(dashboardId) || {};

const { processes, postUrl, ratingFilterProps, applicantSortDropdownProps } = useProcess({
dashboardId,
applyFormId,
});
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/pages/RecruitmentPost/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ export default function RecruitmentPost() {
<S.PageLayout>
<S.Wrapper>
<S.Header>
<S.Title>{recruitmentPost?.title ?? ''}</S.Title>
<S.Title aria-label="모집 공고의 제목입니다. ">{recruitmentPost?.title ?? ''}</S.Title>
<S.PeriodContainer>
<HiOutlineClock />
<S.Period>{Object.values(recruitmentPeriod).join(' ~ ')}</S.Period>
<S.Period aria-label="모집 공고의 모집 날짜입니다.">
{Object.values(recruitmentPeriod).join(' ~ ')}
</S.Period>
</S.PeriodContainer>
</S.Header>

Expand Down
1 change: 1 addition & 0 deletions frontend/src/styles/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const colors: Colors = {
text: {
default: '#172B4D',
block: '#626F86',
small: '#777676',
},
feedback: {
error: baseColors.redscale[500],
Expand Down
1 change: 1 addition & 0 deletions frontend/src/styles/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface Colors {
text: {
default: string;
block: string;
small: string;
};
feedback: {
error: string;
Expand Down
Loading