diff --git a/package-lock.json b/package-lock.json
index 4fb7df25..7ecb2ee3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,7 @@
"@tanstack/react-query-devtools": "^5.64.1",
"@uiw/react-md-editor": "^4.0.5",
"axios": "^1.7.9",
+ "crypto-js": "^4.2.0",
"date-fns": "^4.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -41,6 +42,7 @@
"@storybook/test-runner": "^0.21.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
+ "@types/crypto-js": "^4.2.2",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/styled-components": "^5.1.34",
@@ -3745,6 +3747,13 @@
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
"license": "MIT"
},
+ "node_modules/@types/crypto-js": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
+ "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
@@ -5818,6 +5827,12 @@
"node": ">= 8"
}
},
+ "node_modules/crypto-js": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
+ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+ "license": "MIT"
+ },
"node_modules/css-color-keywords": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
diff --git a/package.json b/package.json
index 2c8b312a..b785325d 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
"@tanstack/react-query-devtools": "^5.64.1",
"@uiw/react-md-editor": "^4.0.5",
"axios": "^1.7.9",
+ "crypto-js": "^4.2.0",
"date-fns": "^4.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -49,6 +50,7 @@
"@storybook/test-runner": "^0.21.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
+ "@types/crypto-js": "^4.2.2",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/styled-components": "^5.1.34",
diff --git a/src/components/applyComponents/careersComponent/CareersComponent.tsx b/src/components/applyComponents/careersComponent/CareersComponent.tsx
index 655503c8..6f8ab5b7 100644
--- a/src/components/applyComponents/careersComponent/CareersComponent.tsx
+++ b/src/components/applyComponents/careersComponent/CareersComponent.tsx
@@ -38,6 +38,7 @@ const CareersComponent = ({
size='primary'
schema='primary'
radius='primary'
+ type='button'
onClick={() =>
appendCareers({
name: '',
diff --git a/src/components/applyComponents/careersComponent/careersInputComponent/CareersComponentInput.styled.ts b/src/components/applyComponents/careersComponent/careersInputComponent/CareersComponentInput.styled.ts
index f9d89e81..5217b795 100644
--- a/src/components/applyComponents/careersComponent/careersInputComponent/CareersComponentInput.styled.ts
+++ b/src/components/applyComponents/careersComponent/careersInputComponent/CareersComponentInput.styled.ts
@@ -13,12 +13,12 @@ const dateStyle = css`
export const CareerInput = styled.input`
${basicStyle}
padding: 10px;
- font-size: 16px;
+ font-size: ${({ theme }) => theme.heading.semiSmall.fontSize};
&:nth-child(1),
&:nth-child(4) {
flex: 2;
- font-size: ${({ theme }) => theme.heading.small.fontSize};
+ font-size: ${({ theme }) => theme.heading.semiSmall.fontSize};
}
&:nth-child(2),
@@ -29,13 +29,13 @@ export const CareerInput = styled.input`
@media screen and ${({ theme }) => theme.mediaQuery.tablet} {
width: 100%;
margin-bottom: 12px;
- font-size: 14px;
+ font-size: ${({ theme }) => theme.heading.small.fontSize};
}
`;
export const FormError = styled.p`
margin-top: 0.3px;
- font-size: 1rem;
+ font-size: ${({ theme }) => theme.heading.small.fontSize};
color: ${({ theme }) => theme.color.red};
position: absolute;
top: 100%;
diff --git a/src/components/applyComponents/phoneComponent/PhoneComponent.styled.ts b/src/components/applyComponents/phoneComponent/PhoneComponent.styled.ts
index 52b0919b..caecef9a 100644
--- a/src/components/applyComponents/phoneComponent/PhoneComponent.styled.ts
+++ b/src/components/applyComponents/phoneComponent/PhoneComponent.styled.ts
@@ -10,13 +10,13 @@ export const PhoneInputContainer = styled.div`
export const Dash = styled.span`
align-self: center;
- font-size: 25px;
+ font-size: ${({ theme }) => theme.heading.semiLarge.fontSize};
color: #888;
`;
export const FormError = styled.p`
margin-top: 0.3px;
- font-size: 0.9rem;
+ font-size: ${({ theme }) => theme.heading.small.fontSize};
color: ${({ theme }) => theme.color.red};
position: absolute;
top: 115%;
diff --git a/src/components/applyComponents/phoneComponent/PhoneComponent.tsx b/src/components/applyComponents/phoneComponent/PhoneComponent.tsx
index 9db54192..adaee0ab 100644
--- a/src/components/applyComponents/phoneComponent/PhoneComponent.tsx
+++ b/src/components/applyComponents/phoneComponent/PhoneComponent.tsx
@@ -33,7 +33,15 @@ const PhoneComponent = ({ control, errors }: PhoneComponentProps) => {
/>
{errors.phone && (
- {String(errors?.phone[0]?.message)}
+
+ {String(
+ errors?.phone[0]?.message
+ ? errors?.phone[0]?.message
+ : errors?.phone[1]?.message
+ ? errors?.phone[1].message
+ : errors?.phone[2]?.message
+ )}
+
)}
);
diff --git a/src/components/applyComponents/phoneComponent/phoneComponentInput/PhoneComponentInput.styled.ts b/src/components/applyComponents/phoneComponent/phoneComponentInput/PhoneComponentInput.styled.ts
index d0e56279..7a722b4e 100644
--- a/src/components/applyComponents/phoneComponent/phoneComponentInput/PhoneComponentInput.styled.ts
+++ b/src/components/applyComponents/phoneComponent/phoneComponentInput/PhoneComponentInput.styled.ts
@@ -6,9 +6,9 @@ export const PhoneInput = styled.input`
border: 1px solid ${({ theme }) => theme.color.border};
border-radius: ${({ theme }) => theme.borderRadius.primary};
text-align: center;
- font-size: 17px;
+ font-size: ${({ theme }) => theme.heading.semiSmall.fontSize};
@media screen and ${({ theme }) => theme.mediaQuery.tablet} {
- font-size: 15px;
+ font-size: ${({ theme }) => theme.heading.small.fontSize};
}
`;
diff --git a/src/components/common/positionButton/PositionButton.tsx b/src/components/common/positionButton/PositionButton.tsx
index 6d6141c3..c0cdea11 100644
--- a/src/components/common/positionButton/PositionButton.tsx
+++ b/src/components/common/positionButton/PositionButton.tsx
@@ -15,7 +15,11 @@ export default function PositionButton({
}: PositionButtonProps) {
return (
-
+
{position}
diff --git a/src/components/projectFormComponents/editor/MarkdownEditor.styled.ts b/src/components/projectFormComponents/editor/MarkdownEditor.styled.ts
index cdd3b482..7ca7cc0d 100644
--- a/src/components/projectFormComponents/editor/MarkdownEditor.styled.ts
+++ b/src/components/projectFormComponents/editor/MarkdownEditor.styled.ts
@@ -3,7 +3,7 @@ import styled from 'styled-components';
export const StyledMDEditor = styled(MDEditor)`
border: 1px solid ${({ theme }) => theme.color.border};
- border-radius: ${({ theme }) => theme.borderRadius.primary};
+ border-radius: ${({ theme }) => theme.borderRadius.large};
background-color: ${({ theme }) => theme.color.white};
padding: 10px;
`;
diff --git a/src/components/projectFormComponents/inputComponent/inputComponent.styled.ts b/src/components/projectFormComponents/inputComponent/inputComponent.styled.ts
index d052beab..372ea499 100644
--- a/src/components/projectFormComponents/inputComponent/inputComponent.styled.ts
+++ b/src/components/projectFormComponents/inputComponent/inputComponent.styled.ts
@@ -79,4 +79,8 @@ export const FormError = styled.p`
top: 115%;
left: 5px;
white-space: nowrap;
+
+ @media screen and ${({ theme }) => theme.mediaQuery.tablet} {
+ font-size: ${({ theme }) => theme.heading.small.tabletFontSize};
+ }
`;
diff --git a/src/components/projectFormComponents/projectInformationInput/ProjectInformationInput.styled.ts b/src/components/projectFormComponents/projectInformationInput/ProjectInformationInput.styled.ts
index 7ec4c008..5aaa430f 100644
--- a/src/components/projectFormComponents/projectInformationInput/ProjectInformationInput.styled.ts
+++ b/src/components/projectFormComponents/projectInformationInput/ProjectInformationInput.styled.ts
@@ -6,13 +6,19 @@ export const InfoRow = styled.div`
margin-bottom: 1.8rem;
display: flex;
- label {
- font-size: 1rem;
- font-weight: bold;
- color: #333;
- }
-
p {
font-size: 0.8rem;
}
`;
+
+export const InfoLabel = styled.label`
+ font-size: ${({ theme }) => theme.heading.small.fontSize};
+ font-weight: bold;
+ color: #333;
+`;
+
+export const welcomeSprout = styled.p`
+ font-size: 1rem;
+ font-weight: bold;
+ color: ${({ theme }) => theme.color.placeholder};
+`;
diff --git a/src/components/projectFormComponents/projectInformationInput/ProjectInformationInput.tsx b/src/components/projectFormComponents/projectInformationInput/ProjectInformationInput.tsx
index 12800b74..2271d5ef 100644
--- a/src/components/projectFormComponents/projectInformationInput/ProjectInformationInput.tsx
+++ b/src/components/projectFormComponents/projectInformationInput/ProjectInformationInput.tsx
@@ -44,7 +44,7 @@ const ProjectInformationInput = ({
{PROJECT_DATA.map((input, index) => (
<>
-
+ {input.label}
+ {input.type === 'checkbox' && (
+ "새싹 멤버도 환영해요 !!"
+ )}
>
))}
-
+ 진행 방식
-
+ 모집 분야
-
+ 사용 언어
{
setSelectedMethod(idx);
- setValue('field', idx);
+ setValue('field', idx + 1);
};
useEffect(() => {
diff --git a/src/components/projectFormComponents/projectInformationInput/positionComponent/PositionComponent.styled.ts b/src/components/projectFormComponents/projectInformationInput/positionComponent/PositionComponent.styled.ts
index 726a10ae..989261db 100644
--- a/src/components/projectFormComponents/projectInformationInput/positionComponent/PositionComponent.styled.ts
+++ b/src/components/projectFormComponents/projectInformationInput/positionComponent/PositionComponent.styled.ts
@@ -36,7 +36,7 @@ export const PositionButtonFeat = styled(PositionButton)<{
}
.name {
- font-size: 0.7rem;
+ font-size: 0.8rem;
font-weight: 200;
color: ${({ isSelected, theme }) =>
isSelected ? theme.color.white : theme.color.primary};
diff --git a/src/hooks/useUpdateProject.ts b/src/hooks/useUpdateProject.ts
index 94fb4571..f4c252d6 100644
--- a/src/hooks/useUpdateProject.ts
+++ b/src/hooks/useUpdateProject.ts
@@ -4,6 +4,7 @@ import { FormData } from '../models/createProject';
import { MODAL_MESSAGE } from '../constants/modalMessage';
import { ROUTES } from '../constants/routes';
import { useNavigate } from 'react-router-dom';
+import { managedProjectKey } from './queries/keys';
interface UseUpdateProjectProps {
id: number;
@@ -18,7 +19,7 @@ const useUpdateProject = ({ id, handleModalOpen }: UseUpdateProjectProps) => {
mutationFn: (formData: FormData) => putProject(formData, id),
onSuccess: () => {
queryClient.invalidateQueries({
- queryKey: ['projectDataAll', id],
+ queryKey: [managedProjectKey.detail, id],
exact: true,
});
handleModalOpen(MODAL_MESSAGE.ModifyProjectSuccess);
diff --git a/src/pages/apply/Apply.styled.ts b/src/pages/apply/Apply.styled.ts
index 0e894941..4726ca8a 100644
--- a/src/pages/apply/Apply.styled.ts
+++ b/src/pages/apply/Apply.styled.ts
@@ -8,37 +8,37 @@ export const Container = styled.div`
font-family: Arial, sans-serif;
@media screen and ${({ theme }) => theme.mediaQuery.tablet} {
- padding: 25px;
+ padding: 20px;
}
`;
export const Title = styled.h1`
- font-size: 32px;
+ font-size: 2.3rem;
font-weight: bold;
margin-bottom: 50px;
@media screen and ${({ theme }) => theme.mediaQuery.tablet} {
- font-size: 27px;
+ font-size: 1.8rem;
}
`;
export const Subtitle = styled.h2`
- font-size: 25px;
+ font-size: 1.5rem;
color: ${({ theme }) => theme.color.primary};
margin-bottom: 20px;
@media screen and ${({ theme }) => theme.mediaQuery.tablet} {
- font-size: 22px;
+ font-size: 1.4rem;
}
`;
export const Dates = styled.p`
- font-size: 20px;
+ font-size: ${({ theme }) => theme.heading.small.fontSize};
color: ${({ theme }) => theme.color.placeholder};
margin-bottom: 50px;
@media screen and ${({ theme }) => theme.mediaQuery.tablet} {
- font-size: 16px;
+ font-size: ${({ theme }) => theme.heading.small.fontSize};
}
`;
@@ -56,12 +56,12 @@ export const Section = styled.div`
`;
export const Label = styled.label`
- font-size: 20px;
+ font-size: 1.2rem;
font-weight: bold;
color: ${({ theme }) => theme.color.black};
@media screen and ${({ theme }) => theme.mediaQuery.tablet} {
- font-size: 18px;
+ font-size: 1.1rem;
}
`;
diff --git a/src/pages/apply/Apply.tsx b/src/pages/apply/Apply.tsx
index 6b017214..7d2e56a6 100644
--- a/src/pages/apply/Apply.tsx
+++ b/src/pages/apply/Apply.tsx
@@ -13,6 +13,8 @@ import LoadingSpinner from '../../components/common/loadingSpinner/LoadingSpinne
import Modal from '../../components/common/modal/Modal';
import { useModal } from '../../hooks/useModal';
import useApplyProject from '../../hooks/useApplyProject';
+import useAuthStore from '../../store/authStore';
+import { useEffect } from 'react';
const ApplyScheme = z.object({
email: z
@@ -26,12 +28,30 @@ const ApplyScheme = z.object({
wantToSay: z.string().optional(),
careers: z
.array(
- z.object({
- name: z.string(),
- periodStart: z.string(),
- periodEnd: z.string(),
- role: z.string(),
- })
+ z
+ .object({
+ name: z.string().nonempty({ message: '경력명을 입력해주세요.' }),
+ periodStart: z
+ .string()
+ .nonempty({ message: '시작 날짜를 입력해주세요.' })
+ .refine((date) => !isNaN(Date.parse(date)), {
+ message: '유효한 날짜를 입력해주세요.',
+ }),
+ periodEnd: z
+ .string()
+ .nonempty({ message: '종료 날짜를 입력해주세요.' })
+ .refine((date) => !isNaN(Date.parse(date)), {
+ message: '유효한 날짜를 입력해주세요.',
+ }),
+ role: z.string().nonempty({ message: '역할을 입력해주세요.' }),
+ })
+ .refine(
+ (data) => new Date(data.periodStart) < new Date(data.periodEnd),
+ {
+ message: '시작 날짜는 종료 날짜보다 이전이어야 합니다.',
+ path: ['periodStart'],
+ }
+ )
)
.optional(),
});
@@ -44,10 +64,12 @@ const Apply = () => {
const { isOpen, handleModalOpen, handleModalClose, message } = useModal();
const { data: projectData, isLoading, isFetching } = useGetProjectData(id);
const { applyProject } = useApplyProject({ id, handleModalOpen });
+ const userEmail = useAuthStore((state) => state.userData?.email);
const {
handleSubmit: onSubmitHandler,
formState: { errors },
control,
+ setValue,
} = useForm({
resolver: zodResolver(ApplyScheme),
defaultValues: {
@@ -58,6 +80,10 @@ const Apply = () => {
},
});
+ useEffect(() => {
+ if (userEmail) setValue('email', userEmail);
+ }, [userEmail, setValue]);
+
const { fields: fieldsCareers, append: appendCareers } = useFieldArray({
name: 'careers',
control,
@@ -75,7 +101,11 @@ const Apply = () => {
};
if (!projectData) {
- return 데이터가 없습니다.
;
+ return (
+
+ {message}
+
+ );
}
if (isLoading) return ;
@@ -102,7 +132,7 @@ const Apply = () => {
- 전화번호
+ 휴대폰 전화번호
diff --git a/src/pages/createProject/CreateProject.styled.ts b/src/pages/createProject/CreateProject.styled.ts
index c2324642..ca8b21b1 100644
--- a/src/pages/createProject/CreateProject.styled.ts
+++ b/src/pages/createProject/CreateProject.styled.ts
@@ -42,7 +42,6 @@ export const Section = styled.div`
`;
export const SectionInput = styled.div`
- margin-bottom: 40px;
padding: 20px;
border: 1px solid ${({ theme }) => theme.color.border};
border-radius: ${({ theme }) => theme.borderRadius.primary};
diff --git a/src/pages/createProject/CreateProject.tsx b/src/pages/createProject/CreateProject.tsx
index fe300b4f..1885626c 100644
--- a/src/pages/createProject/CreateProject.tsx
+++ b/src/pages/createProject/CreateProject.tsx
@@ -9,6 +9,7 @@ import { useState } from 'react';
import Modal from '../../components/common/modal/Modal';
import { useModal } from '../../hooks/useModal';
import useCreateProject from '../../hooks/useCreateProject';
+import LoadingSpinner from '../../components/common/loadingSpinner/LoadingSpinner';
export const createProjectScheme = z.object({
startDate: z
@@ -76,7 +77,7 @@ const CreateProject = () => {
const [isSubmit, setIsSubmit] = useState(false);
const { isOpen, message, handleModalClose, handleModalOpen } = useModal();
- const { createProject } = useCreateProject({
+ const { createProject, isLoading } = useCreateProject({
handleModalOpen,
setIsSubmit,
});
@@ -99,6 +100,8 @@ const CreateProject = () => {
createProject(formData);
};
+ if (isLoading) return ;
+
return (
프로젝트 생성
diff --git a/src/pages/projectDetail/ProjectDetail.tsx b/src/pages/projectDetail/ProjectDetail.tsx
index dd5ab25c..e09e8356 100644
--- a/src/pages/projectDetail/ProjectDetail.tsx
+++ b/src/pages/projectDetail/ProjectDetail.tsx
@@ -9,8 +9,6 @@ import Avatar from '../../components/common/avatar/Avatar';
import { EyeIcon } from '@heroicons/react/24/outline';
import useAuthStore from '../../store/authStore';
import { ROUTES } from '../../constants/routes';
-import { useModal } from '../../hooks/useModal';
-import Modal from '../../components/common/modal/Modal';
import LoadingSpinner from '../../components/common/loadingSpinner/LoadingSpinner';
const ProjectDetail = () => {
@@ -18,7 +16,6 @@ const ProjectDetail = () => {
const id = Number(projectId);
const navigate = useNavigate();
const { data, isLoading, isFetching } = useGetProjectData(id);
- const { isOpen, message, handleModalOpen, handleModalClose } = useModal();
const { userData } = useAuthStore((state) => state);
if (isLoading) return ;
@@ -28,12 +25,7 @@ const ProjectDetail = () => {
}
const handleApplyClick = () => {
- if (userData?.id === data.User.id) {
- handleModalOpen('본인의 프로젝트는 지원할 수 없습니다.');
- return;
- } else {
- navigate(`${ROUTES.apply}/${id}`);
- }
+ navigate(`${ROUTES.apply}/${id}`);
};
const handleMovetoUserPage = () => {
@@ -70,20 +62,19 @@ const ProjectDetail = () => {
-
+ {userData?.id !== data.User.id ? (
+
+ ) : null}
-
- {message}
-
);
};
diff --git a/src/store/authStore.ts b/src/store/authStore.ts
index 28da8a1b..f04beb0c 100644
--- a/src/store/authStore.ts
+++ b/src/store/authStore.ts
@@ -1,5 +1,6 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
+import { decryptData, encryptData } from '../util/cryptoUtils';
export interface UserData {
id: number;
@@ -13,7 +14,7 @@ interface AuthState {
storeLogin: (
accessToken: string,
refreshToken: string,
- userData?: UserData
+ userData: UserData
) => void;
storeLogout: () => void;
}
@@ -24,6 +25,11 @@ const initialUserData: UserData = {
nickname: '',
};
+export const getStoredUserData = () => {
+ const encryptedData = localStorage.getItem('userData');
+ return encryptedData ? decryptData(encryptedData) : null;
+};
+
export const getTokens = () => {
const accessToken = localStorage.getItem('accessToken');
const refreshToken = localStorage.getItem('refreshToken');
@@ -50,9 +56,10 @@ const useAuthStore = create(
storeLogin: (
accessToken: string,
refreshToken: string,
- userData?: UserData
+ userData: UserData
) => {
setTokens(accessToken, refreshToken);
+ localStorage.setItem('userData', encryptData(userData));
set({ isLoggedIn: true, userData });
},
storeLogout: () => {
diff --git a/src/util/cryptoUtils.ts b/src/util/cryptoUtils.ts
new file mode 100644
index 00000000..5a650fcf
--- /dev/null
+++ b/src/util/cryptoUtils.ts
@@ -0,0 +1,17 @@
+import CryptoJS from 'crypto-js';
+import { UserData } from '../store/authStore';
+
+export const encryptData = (data: UserData) => {
+ return CryptoJS.AES.encrypt(
+ JSON.stringify(data),
+ `${import.meta.env.CRYPTO_SECRET_KEY}`
+ ).toString();
+};
+
+export const decryptData = (cipherText: string) => {
+ const bytes = CryptoJS.AES.decrypt(
+ cipherText,
+ `${import.meta.env.CRYPTO_SECRET_KEY}`
+ );
+ return JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
+};