Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.54.2",
"react-icons": "^5.5.0",
"react-router-dom": "^7.1.1",
"react-textarea-autosize": "^8.5.7",
"sanitize.css": "^13.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import {
Control,
FieldArrayWithId,
UseFieldArrayAppend,
} from 'react-hook-form';
import { Control, useFieldArray } from 'react-hook-form';
import { ApplySchemeType } from '../../../pages/apply/Apply';
import * as S from './CareersComponent.styled';
import CareerInput from './careersInputComponent/CareersComponentInput';
import { CAREER_INPUT } from '../../../constants/projectConstants';

interface CareersComponentProps {
fieldsCareers: FieldArrayWithId<ApplySchemeType, 'careers'>[];
appendCareers: UseFieldArrayAppend<ApplySchemeType, 'careers'>;
control: Control<ApplySchemeType>;
}

const CareersComponent = ({
fieldsCareers,
appendCareers,
control,
}: CareersComponentProps) => {
const CareersComponent = ({ control }: CareersComponentProps) => {
const { fields: fieldsCareers, append: appendCareers } = useFieldArray({
name: 'careers',
control,
});

return (
<S.Container>
{fieldsCareers.map((field, index) => (
Expand Down
13 changes: 13 additions & 0 deletions src/components/common/loadingSpinner/LoadingSpinner.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Meta, StoryObj } from '@storybook/react';
import LoadingSpinner from './LoadingSpinner';

const meta = {
title: 'Component/Common/LoadingSpinner',
component: LoadingSpinner,
tags: ['autodocs'],
} satisfies Meta<typeof LoadingSpinner>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import styled from 'styled-components';

export const Container = styled.div`
position: relative;
display: flex;
align-items: center;
justify-content: space-evenly;
width: 100%;
min-height: 50px;
margin: 20px 0;
`;

export const Line = styled.div`
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 2px;
background-color: ${({ theme }) => theme.color.primary};
transform: translateY(-50%);
z-index: 0;
`;

export const StepWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20px;
`;

export const Circle = styled.div<{ isActive: boolean }>`
z-index: 1;
width: 30px;
height: 30px;
border-radius: 50%;
background-color: ${({ theme }) => theme.color.white};
border: 2px solid
${({ isActive, theme }) => (isActive ? theme.color.primary : '#ccc')};
align-items: center;
justify-content: center;
`;

export const Label = styled.span`
color: #333;
font-size: ${({ theme }) => theme.heading.small.tabletFontSize};
margin-top: 5px;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { Dispatch, SetStateAction } from 'react';
import * as S from './StepComponent.styled';
import { FaCheck } from 'react-icons/fa';
import { IoClose } from 'react-icons/io5';
import { StepProp } from '../../../hooks/useMultiStepForm';

type StepComponentProps = {
steps: StepProp[];
currentStepIndex: number;
setCurrentStepIndex: Dispatch<SetStateAction<number>>;
};

const StepComponent: React.FC<StepComponentProps> = ({
steps,
currentStepIndex,
setCurrentStepIndex,
}) => {
const handleClick = (index: number) => {
setCurrentStepIndex(index);
};

return (
<S.Container>
<S.Line />
{steps.map((step, index) => {
const isActive = index === currentStepIndex;

return (
<S.StepWrapper key={index}>
<S.Circle
isActive={isActive}
onClick={() => handleClick(index)}
></S.Circle>
<S.Label>{step.title}</S.Label>
</S.StepWrapper>
);
})}
</S.Container>
);
};

export default StepComponent;
33 changes: 33 additions & 0 deletions src/hooks/useMultiStepForm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useState, ReactElement } from 'react';

export type StepProp = {
title: string;
element: ReactElement;
};

const useMultiStepForm = (steps: StepProp[]) => {
const [currentStepIndex, setCurrentStepIndex] = useState(0);

const prev = () => {
setCurrentStepIndex((index) => (index <= 0 ? 0 : index - 1));
};

const next = () => {
setCurrentStepIndex((index) =>
index >= steps.length - 1 ? index : index + 1
);
};

return {
currentStepIndex,
currentTitle: steps[currentStepIndex].title,
currentStep: steps[currentStepIndex].element,
isFirstStep: currentStepIndex === 0,
isLastStep: currentStepIndex === steps.length - 1,
prev,
next,
setCurrentStepIndex,
};
};

export default useMultiStepForm;
9 changes: 9 additions & 0 deletions src/mock/applicant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,12 @@ export const passNonPass = http.patch(
);
}
);

export const createApplicant = http.post(
`${import.meta.env.VITE_API_BASE_URL}/project/:projectId/applicant`,
() => {
return HttpResponse.json({
status: 200,
});
}
);
4 changes: 4 additions & 0 deletions src/mock/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { myProjectList, sendResult } from './manageProjectList';
import {
applicantInfo,
applicantList,
createApplicant,
passNonPass,
passNonPassList,
} from './applicant';
Expand All @@ -23,6 +24,7 @@ import {
fetchPositionTag,
fetchSkillTag,
} from './projectSearchFiltering';
import { createProject } from './createProject.ts';

export const handlers = [
fetchProjectLists,
Expand All @@ -47,6 +49,8 @@ export const handlers = [
passNonPassList,
mypageEditProfile,
login,
createApplicant,
createProject,
];

export const worker = setupWorker(...handlers);
10 changes: 10 additions & 0 deletions src/mock/createProject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { http, HttpResponse } from 'msw';

export const createProject = http.post(
`${import.meta.env.VITE_API_BASE_URL}/project`,
() => {
return HttpResponse.json({
status: 200,
});
}
);
13 changes: 2 additions & 11 deletions src/pages/apply/Apply.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as S from './Apply.styled';
import Input from '../../components/projectFormComponents/inputComponent/InputComponent';
import { useFieldArray, useForm } from 'react-hook-form';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useParams } from 'react-router-dom';
Expand Down Expand Up @@ -84,11 +84,6 @@ const Apply = () => {
if (userEmail) setValue('email', userEmail);
}, [userEmail, setValue]);

const { fields: fieldsCareers, append: appendCareers } = useFieldArray({
name: 'careers',
control,
});

const handleSubmit = (data: ApplySchemeType) => {
const formData: joinProject = {
email: data.email,
Expand Down Expand Up @@ -150,11 +145,7 @@ const Apply = () => {
<S.Section>
<S.Label>경력사항 / 수상이력</S.Label>

<CareersComponent
fieldsCareers={fieldsCareers}
appendCareers={appendCareers}
control={control}
/>
<CareersComponent control={control} />
</S.Section>

<S.SubmitButton
Expand Down
74 changes: 74 additions & 0 deletions src/pages/apply/ApplyStep.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import styled from 'styled-components';
import Button from '../../components/common/Button/Button';

export const Container = styled.div`
max-width: 100%;
padding: 40px;

@media ${({ theme }) => theme.mediaQuery.tablet} {
padding: 20px;
}
`;

export const Title = styled.h1`
font-size: 2.3rem;
font-weight: bold;
margin-bottom: 50px;

@media ${({ theme }) => theme.mediaQuery.tablet} {
font-size: 1.8rem;
}
`;

export const Subtitle = styled.h2`
font-size: 1.5rem;
color: ${({ theme }) => theme.color.primary};
margin-bottom: 20px;

@media ${({ theme }) => theme.mediaQuery.tablet} {
font-size: 1.4rem;
}
`;

export const Dates = styled.p`
font-size: ${({ theme }) => theme.heading.small.fontSize};
color: ${({ theme }) => theme.color.placeholder};
margin-bottom: 50px;

@media ${({ theme }) => theme.mediaQuery.tablet} {
font-size: ${({ theme }) => theme.heading.small.fontSize};
}
`;

export const StepContainer = styled.div`
margin-bottom: 20px;
`;

export const StepButton = styled.div`
display: flex;
gap: 20px;
`;

export const StepLabel = styled.div`
font-size: 1rem;
font-weight: bold;
color: ${({ theme }) => theme.color.black};
margin-bottom: 10px;

@media ${({ theme }) => theme.mediaQuery.tablet} {
font-size: 1.1rem;
}
`;

export const StepWrapper = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
`;

export const SubmitButton = styled(Button)`
width: 100px;
padding: 15px;
margin: 0 auto;
cursor: pointer;
`;
Loading