diff --git a/src/api/command.api.ts b/src/api/command.api.ts new file mode 100644 index 00000000..270538c8 --- /dev/null +++ b/src/api/command.api.ts @@ -0,0 +1,55 @@ +import { CommandType } from '../models/command'; +import { httpClient } from './http.api'; + +export const postCommand = async (id: number, content: string) => { + try { + const response = await httpClient.post(`/project/${id}/comment`, { + content: content, + }); + if (response.status !== 200) { + throw new Error(`${response.status}`); + } + return response.status; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const getCommand = async (id: number): Promise => { + try { + const response = await httpClient.get(`/project/${id}/comment`); + return response.data.data; + } catch (error) { + console.error(error); + throw ErrorEvent; + } +}; + +export const deleteCommand = async (id: number, commandId: number) => { + try { + const response = await httpClient.delete( + `/project/${id}/comment/${commandId}` + ); + return response; + } catch (error) { + console.error(error); + throw ErrorEvent; + } +}; + +export const patchCommand = async ( + id: number, + commandId: number, + content: string +) => { + try { + const response = await httpClient.patch( + `/project/${id}/comment/${commandId}?content=${content}` + ); + return response.status; + } catch (error) { + console.error(error); + throw ErrorEvent; + } +}; diff --git a/src/api/http.api.ts b/src/api/http.api.ts index cf02a9eb..a5a163da 100644 --- a/src/api/http.api.ts +++ b/src/api/http.api.ts @@ -1,7 +1,7 @@ import axios, { AxiosRequestConfig } from 'axios'; import useAuthStore, { getTokens } from '../store/authStore'; -const BASE_URL = `${import.meta.env.VITE_APP_API_BASE_URL}`; +export const BASE_URL = `${import.meta.env.VITE_APP_API_BASE_URL}`; const DEFAULT_TIMEOUT = 15000; export const createClient = (config?: AxiosRequestConfig) => { diff --git a/src/components/command/CommandLayout.styled.ts b/src/components/command/CommandLayout.styled.ts index a888cabf..d5fbd460 100644 --- a/src/components/command/CommandLayout.styled.ts +++ b/src/components/command/CommandLayout.styled.ts @@ -6,7 +6,11 @@ export const CommandCountsContainer = styled.div` margin-bottom: 20px; `; -export const Count = styled.span``; +export const Count = styled.span` + font-size: 20px; + font-weight: bold; + margin-top: 10px; +`; export const CommandContainer = styled.div``; diff --git a/src/components/command/CommandLayout.tsx b/src/components/command/CommandLayout.tsx index 13ed3db0..92c24c50 100644 --- a/src/components/command/CommandLayout.tsx +++ b/src/components/command/CommandLayout.tsx @@ -5,38 +5,71 @@ import { IoIosArrowDown } from 'react-icons/io'; import { IoIosArrowUp } from 'react-icons/io'; import CommandInput from './commandInput/CommandInput'; +import useGetCommand from '../../hooks/CommandHooks/useGetCommand'; +import LoadingSpinner from '../common/loadingSpinner/LoadingSpinner'; -const CommandLayout = () => { +interface CommandLayoutProps { + projectId: number; + createrId: number; + loginUserId: number | undefined; +} + +const CommandLayout = ({ + projectId, + createrId, + loginUserId, +}: CommandLayoutProps) => { const [isShowReply, setIsShowReply] = useState(false); + const { getCommandList, isLoading, isFetching, isError } = + useGetCommand(projectId); + const handleClick = () => { setIsShowReply(!isShowReply); }; + if (isLoading || isFetching) { + return ; + } + + if (isError) { + console.error(isError); + } + return ( - 댓글 {2}개 + 댓글 {getCommandList?.length}개 - + - + + + + + {isShowReply ? : } + + 답글 확인하기 + + - - - {isShowReply ? : } - 답글 확인하기 - - - {isShowReply && ( - + )} diff --git a/src/components/command/commandComponent/CommandComponent.styled.ts b/src/components/command/commandComponent/CommandComponent.styled.ts index 3c326dad..7f24d285 100644 --- a/src/components/command/commandComponent/CommandComponent.styled.ts +++ b/src/components/command/commandComponent/CommandComponent.styled.ts @@ -4,6 +4,7 @@ export const Container = styled.div` display: flex; justify-content: space-between; width: 100%; + margin-bottom: 20px; `; export const Wrapper = styled.div` @@ -52,4 +53,15 @@ export const CommandInput = styled.div` export const ReplyInput = styled.div` width: 100%; padding-left: 15px; + margin-bottom: 10px; +`; + +export const ErrorMessage = styled.div` + padding-left: 15px; + margin-bottom: 10px; +`; + +export const Message = styled.p` + color: ${({ theme }) => theme.color.red}; + font-size: 10px; `; diff --git a/src/components/command/commandComponent/CommandComponent.tsx b/src/components/command/commandComponent/CommandComponent.tsx index 045dff05..0788506c 100644 --- a/src/components/command/commandComponent/CommandComponent.tsx +++ b/src/components/command/commandComponent/CommandComponent.tsx @@ -7,72 +7,106 @@ import CommandInput from '../commandInput/CommandInput'; import { CiMenuKebab } from 'react-icons/ci'; import DropDown from '../../common/dropDown/DropDown'; import DropDownItem from '../../common/dropDown/DropDownItem'; -import useDropDownItem from '../../../hooks/useDropDownItem'; +import { CommandType } from '../../../models/command'; interface CommandLayoutProps { - data: []; + projectId: number; + getCommandList: CommandType[] | undefined; reply?: boolean; + createrId?: number; + loginUserId?: number | undefined; } -const command = '안녕하세요'; - -const CommandComponent = ({ data, reply }: CommandLayoutProps) => { - const [showReplyInput, setShowReplyInput] = useState(false); +const CommandComponent = ({ + projectId, + getCommandList, + reply, + createrId, + loginUserId, +}: CommandLayoutProps) => { + const [activeReplyId, setActiveReplyId] = useState(null); + const [activateEditMode, setActivateEditMode] = useState(null); const [showMenu, setShowMenu] = useState(false); + const [onReplyMessage, setOnReplyMessage] = useState(false); - const { onReport, onEdit, onDelete, isEditMode } = useDropDownItem(); + const handleClick = (commandId: number) => { + setActiveReplyId((prev) => (prev === commandId ? null : commandId)); - const handleClick = () => { - setShowReplyInput(!showReplyInput); + if (createrId !== loginUserId) { + setOnReplyMessage(true); + setTimeout(() => { + setOnReplyMessage(false); + }, 2000); + } }; - console.log(isEditMode); - const onClick = () => { setShowMenu(!showMenu); }; + const onEdit = (commandId: number) => { + setActivateEditMode((prev) => (prev === commandId ? null : commandId)); + }; + return ( - - {/* 전체를 map으로 감싸 하기 */} - - - - SeungYeon - {isEditMode ? ( - + {getCommandList?.map((item, index) => ( + + + + + {item.user.nickname} + {activateEditMode === item.id ? ( + + ) : ( + {item.content} + )} + {!reply && ( + handleClick(item.id)}> + + + + 댓글 달기 + + )} + + {activeReplyId === item.id && onReplyMessage && ( + + + 프로젝트 생성자만 답글을 달 수 있습니다. + + + )} + {activeReplyId === item.id && createrId === loginUserId && ( + + + + )} + + + }> + onEdit(item.id)} + loginUserId={loginUserId} + commandUserId={item.user.id} + activateEditMode={activateEditMode} /> - ) : ( - {command} - )} - {!reply && ( - - - - - 댓글 달기 - - )} - {showReplyInput && ( - - - - )} - - - }> - - - + + + ))} + ); }; diff --git a/src/components/command/commandInput/CommandInput.styled.ts b/src/components/command/commandInput/CommandInput.styled.ts index d254e270..e39ce386 100644 --- a/src/components/command/commandInput/CommandInput.styled.ts +++ b/src/components/command/commandInput/CommandInput.styled.ts @@ -3,11 +3,11 @@ import Button from '../../common/Button/Button'; export const InputContainer = styled.div` display: flex; - margin-left: 8px; `; export const Input = styled.input` width: 100%; + margin-left: 10px; `; export const InputWrapper = styled.div` @@ -24,6 +24,7 @@ export const ButtonWrapper = styled.div` export const Line = styled.hr<{ $isFocused: boolean }>` opacity: ${({ $isFocused }) => ($isFocused ? 1.0 : 0.2)}; border: ${({ $isFocused }) => ($isFocused ? 2 : 1)}; + margin-left: 10px; `; export const ButtonCancel = styled(Button)``; diff --git a/src/components/command/commandInput/CommandInput.tsx b/src/components/command/commandInput/CommandInput.tsx index e2a8f34b..7c2b4e4a 100644 --- a/src/components/command/commandInput/CommandInput.tsx +++ b/src/components/command/commandInput/CommandInput.tsx @@ -5,24 +5,30 @@ import DefaultImg from '../../../assets/defaultImg.png'; import Avatar from '../../common/avatar/Avatar'; import { useForm } from 'react-hook-form'; import useInputFocus from '../../../hooks/useInputFocus'; -import { useEffect } from 'react'; +import { Dispatch, SetStateAction, useEffect } from 'react'; +import usePostCommand from '../../../hooks/CommandHooks/usePostCommand'; +import usePutCommand from '../../../hooks/CommandHooks/usePutCommand'; type FormValue = { commandInput: string; }; interface CommandInputProps { + projectId: number; reply?: boolean; - isEditMode?: boolean; - onEdit?: () => void; + activateEditMode?: number | null; command?: string; + commandId: number; + setActivateEditMode?: Dispatch>; } const CommandInput = ({ + projectId, reply, - isEditMode, - onEdit, + activateEditMode, command, + commandId, + setActivateEditMode, }: CommandInputProps) => { const { myData } = useMyProfileInfo(); const { @@ -31,7 +37,10 @@ const CommandInput = ({ register, setValue, } = useForm(); + const { isFocused, handleFocus, handleClick } = useInputFocus(); + const { createCommand } = usePostCommand(projectId); + const { changeCommand } = usePutCommand(projectId, commandId); const profileImg = myData?.profileImg ? `${import.meta.env.VITE_APP_IMAGE_CDN_URL}/${formatImgPath( @@ -41,9 +50,17 @@ const CommandInput = ({ const hasInput = Boolean(watch('commandInput', '')); - const handleSubmit = (data) => { - // reply, edit-reply, command, command-reply - console.log(data); + const handleSubmit = (data: { commandInput: string }) => { + // reply, edit-reply + if (activateEditMode) { + changeCommand(data.commandInput); + setActivateEditMode?.((prev) => (prev === commandId ? null : commandId)); + } else { + createCommand(data.commandInput); + } + + setValue('commandInput', ''); + handleClick(); }; useEffect(() => { @@ -52,7 +69,7 @@ const CommandInput = ({ return ( - {!isEditMode && } + {!activateEditMode && }
- {isEditMode ? '수정' : '등록'} + {activateEditMode ? '수정' : '등록'} )} diff --git a/src/components/common/dropDown/DropDownItem.tsx b/src/components/common/dropDown/DropDownItem.tsx index b8722e39..ed3563c5 100644 --- a/src/components/common/dropDown/DropDownItem.tsx +++ b/src/components/common/dropDown/DropDownItem.tsx @@ -1,28 +1,47 @@ -import useDropDownItem from '../../../hooks/useDropDownItem'; +import useDeleteCommand from '../../../hooks/CommandHooks/useDeleteCommand'; import * as S from './DropDownItem.styled'; interface DropdownProps { - isEditMode?: boolean; - commandId?: number; - onReport?: () => void; + projectId: number; + activateEditMode?: number | null; + commandId: number; + loginUserId?: number; + commandUserId: number; onEdit?: () => void; - onDelete?: () => void; } const DropDownItem = ({ - onReport, + projectId, onEdit, - onDelete, + activateEditMode, commandId, - isEditMode, + commandUserId, + loginUserId, }: DropdownProps) => { + const { removeCommand } = useDeleteCommand(projectId); + + const onReport = () => {}; + + const onDelete = (commandId: number) => { + if (confirm('댓글을 완성히 삭제할까요?')) { + removeCommand(commandId); + } + }; + + console.log(loginUserId); + return ( 신고하기 - - {isEditMode ? '수정 취소하기' : '수정하기'} - - 삭제하기 + + {loginUserId === commandUserId && ( + <> + + {activateEditMode === commandId ? '수정 취소하기' : '수정하기'} + + onDelete(commandId)}>삭제하기{' '} + + )} ); }; diff --git a/src/components/common/header/Header.styled.ts b/src/components/common/header/Header.styled.ts index 0e91c959..65b21a9e 100644 --- a/src/components/common/header/Header.styled.ts +++ b/src/components/common/header/Header.styled.ts @@ -17,7 +17,16 @@ export const HeaderContainer = styled.div` } `; -export const Wrapper = styled.nav``; +export const Wrapper = styled.nav` + display: flex; +`; + +export const Alarm = styled.div` + margin-right: 20px; + display: flex; + justify-content: center; + align-items: center; +`; export const LogoImg = styled.img` width: 80px; @@ -34,6 +43,13 @@ export const LogoImg = styled.img` } `; +export const HeaderLinkContainer = styled.div` + display: flex; + justify-center: center; + align-items: center; + // item과 content의 차이가 뭘까? +`; + export const List = styled.ul` display: flex; flex-direction: column; @@ -66,6 +82,13 @@ export const List = styled.ul` } `; +export const HeaderLink = styled.span` + font-size: 1.2rem; + font-weight: bold; + text-align: center; + margin-right: 15px; +`; + export const Item = styled.li` width: 100%; padding: 1rem; diff --git a/src/components/common/header/Header.tsx b/src/components/common/header/Header.tsx index d53e9c42..a9ac0cd1 100644 --- a/src/components/common/header/Header.tsx +++ b/src/components/common/header/Header.tsx @@ -13,12 +13,19 @@ import loadingImg from '../../../assets/loadingImg.svg'; import { useModal } from '../../../hooks/useModal'; import Modal from '../modal/Modal'; import { formatImgPath } from '../../../util/formatImgPath'; +import { GoBell } from 'react-icons/go'; +import { useState } from 'react'; +import Notification from './Notification/Notification'; +import useNotification from '../../../hooks/useNotification'; function Header() { const { isOpen, message, handleModalOpen, handleModalClose } = useModal(); const { userLogout } = useAuth(handleModalOpen); const isLoggedIn = useAuthStore((state) => state.isLoggedIn); const { myData, isLoading } = useMyProfileInfo(); + const { isSignal } = useNotification(); + + const [isAlarmClicked, setIsAlarmClicked] = useState(false); const profileImg = myData?.profileImg ? `${import.meta.env.VITE_APP_IMAGE_CDN_URL}/${formatImgPath( @@ -26,12 +33,30 @@ function Header() { )}?w=86&h=86&fit=crop&crop=entropy&auto=format,enhance&q=60` : DefaultImg; + const handleClick = () => { + setIsAlarmClicked(!isAlarmClicked); + }; + return ( + + + FAQ + + + 공지사항 + + + + }> + + {isSignal && '가능'} + + 공고관리 + + 문의하기 + e.preventDefault()}> 로그아웃 diff --git a/src/components/common/header/Notification/Notification.styled.ts b/src/components/common/header/Notification/Notification.styled.ts new file mode 100644 index 00000000..f23a35f4 --- /dev/null +++ b/src/components/common/header/Notification/Notification.styled.ts @@ -0,0 +1,7 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + width: 400px; + display: flex; + flex-direction: column; +`; diff --git a/src/components/common/header/Notification/Notification.tsx b/src/components/common/header/Notification/Notification.tsx new file mode 100644 index 00000000..064ea1ac --- /dev/null +++ b/src/components/common/header/Notification/Notification.tsx @@ -0,0 +1,37 @@ +import * as S from './Notification.styled'; +import NotificationItem from './NotificationItem/NotificationItem'; + +const Notification = () => { + const dummyNotifications = [ + { + type: 'command' as const, + id: 40, + nickName: '운영자', + message: '1111111111', + time: '2025.04.12 15:30', + }, + { + type: 'pass/nonPass' as const, + id: 1, + message: '프로젝트 AI', + pass: true, + time: '2025.04.12 15:30', + }, + { + type: 'inquiry' as const, + id: 1, + message: '이거 이렇게 하는 거 맞나요?', + time: '2025.04.12 15:30', + }, + ]; + + return ( + + {dummyNotifications.map((item, index) => ( + + ))} + + ); +}; + +export default Notification; diff --git a/src/components/common/header/Notification/NotificationItem/NotificationItem.styled.ts b/src/components/common/header/Notification/NotificationItem/NotificationItem.styled.ts new file mode 100644 index 00000000..f0dada6e --- /dev/null +++ b/src/components/common/header/Notification/NotificationItem/NotificationItem.styled.ts @@ -0,0 +1,22 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + padding: 8px 0; + font-size: 14px; + color: #333; + border-bottom: 1px solid #eee; + + &:last-child { + border-bottom: none; + } +`; + +export const Message = styled.p` + margin-left: 6px; +`; + +export const Time = styled.span` + margin-left: 6px; + color: #999; + font-size: 12px; +`; diff --git a/src/components/common/header/Notification/NotificationItem/NotificationItem.tsx b/src/components/common/header/Notification/NotificationItem/NotificationItem.tsx new file mode 100644 index 00000000..af137dc3 --- /dev/null +++ b/src/components/common/header/Notification/NotificationItem/NotificationItem.tsx @@ -0,0 +1,36 @@ +import { + COMMAND, + INQUIRY, + PASSNONPASS, +} from '../../../../../constants/commandConstants'; +import * as S from './NotificationItem.styled'; + +interface NotificationItemProps { + NotificationData: { + type: 'command' | 'pass/nonPass' | 'inquiry'; + id: number; + time: string; + message: string; + nickName?: string; + pass?: boolean; + }; +} + +const NotificationItem = ({ NotificationData }: NotificationItemProps) => { + return ( + + + {NotificationData.type === 'command' + ? `'${NotificationData.nickName}' ${COMMAND}${NotificationData.message}` + : NotificationData.type === 'pass/nonPass' + ? `'${NotificationData.message}' ${PASSNONPASS} ${ + NotificationData.pass ? '합격' : '불합격' + }` + : `'${NotificationData.message}' ${INQUIRY}`} + + {NotificationData.time} + + ); +}; + +export default NotificationItem; diff --git a/src/components/projectFormComponents/projectInformationText/ProjectInformation.tsx b/src/components/projectFormComponents/projectInformationText/ProjectInformation.tsx index 93c98da4..2db6546f 100644 --- a/src/components/projectFormComponents/projectInformationText/ProjectInformation.tsx +++ b/src/components/projectFormComponents/projectInformationText/ProjectInformation.tsx @@ -47,12 +47,8 @@ const ProjectInformation = ({ data }: ProjectInformationProps) => { {data.skills.map((skillTag) => ( - {skillTag.skillName} - {skillTag.skillName} + {skillTag.name} + {skillTag.name} ))} diff --git a/src/components/projectFormComponents/stepComponent/StepComponent.tsx b/src/components/projectFormComponents/stepComponent/StepComponent.tsx index 15601587..809aa0a4 100644 --- a/src/components/projectFormComponents/stepComponent/StepComponent.tsx +++ b/src/components/projectFormComponents/stepComponent/StepComponent.tsx @@ -1,6 +1,6 @@ import React, { Dispatch, SetStateAction } from 'react'; import * as S from './StepComponent.styled'; -import { StepProp } from '../../../hooks/useMultiStepForm'; +import { StepProp } from '../../../hooks/ProjectHooks/useMultiStepForm'; type StepComponentProps = { steps: StepProp[]; diff --git a/src/constants/commandConstants.tsx b/src/constants/commandConstants.tsx new file mode 100644 index 00000000..78ca7a06 --- /dev/null +++ b/src/constants/commandConstants.tsx @@ -0,0 +1,3 @@ +export const COMMAND = '님이 댓글을 작성하셨습니다 : '; +export const PASSNONPASS = '공고에 대한 결과입니다 : '; +export const INQUIRY = '문의하신 내용에 대해 답변이 달렸습니다.'; diff --git a/src/constants/routes.ts b/src/constants/routes.ts index 5110fd63..0e8c5ac1 100644 --- a/src/constants/routes.ts +++ b/src/constants/routes.ts @@ -16,4 +16,7 @@ export const ROUTES = { userJoinedProject: 'join-projects', modifyProject: '/project-modify', notFound: '/not-found', + support: '/inquiry', + FAQ: '/faq', + notice: '/notice', } as const; diff --git a/src/hooks/CommandHooks/useDeleteCommand.ts b/src/hooks/CommandHooks/useDeleteCommand.ts new file mode 100644 index 00000000..72ba147b --- /dev/null +++ b/src/hooks/CommandHooks/useDeleteCommand.ts @@ -0,0 +1,29 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { ProjectCommandList } from '../queries/keys'; +import { deleteCommand } from '../../api/command.api'; + +const useDeleteCommand = (id: number) => { + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: (commandId: number) => deleteCommand(id, commandId), + + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [ProjectCommandList.projectCommand, id], + exact: true, + }); + }, + onError: (error) => { + console.error(error); + }, + }); + + const removeCommand = async (commandId: number) => { + mutation.mutate(commandId); + }; + + return { removeCommand }; +}; + +export default useDeleteCommand; diff --git a/src/hooks/CommandHooks/useGetCommand.ts b/src/hooks/CommandHooks/useGetCommand.ts new file mode 100644 index 00000000..81582322 --- /dev/null +++ b/src/hooks/CommandHooks/useGetCommand.ts @@ -0,0 +1,20 @@ +import { useQuery } from '@tanstack/react-query'; +import { getCommand } from '../../api/command.api'; +import { ProjectCommandList } from '../queries/keys'; + +const useGetCommand = (id: number) => { + const { data, isLoading, isFetching, isError } = useQuery({ + queryKey: [ProjectCommandList.projectCommand, id], + queryFn: async () => await getCommand(id), + staleTime: 1000 * 60 * 5, + }); + + return { + getCommandList: data, + isLoading, + isFetching, + isError, + }; +}; + +export default useGetCommand; diff --git a/src/hooks/CommandHooks/usePostCommand.ts b/src/hooks/CommandHooks/usePostCommand.ts new file mode 100644 index 00000000..740d4db3 --- /dev/null +++ b/src/hooks/CommandHooks/usePostCommand.ts @@ -0,0 +1,34 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { postCommand } from '../../api/command.api'; +import { ProjectCommandList } from '../queries/keys'; + +const usePostCommand = (id: number) => { + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: (content: string) => postCommand(id, content), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [ProjectCommandList.projectCommand, id], + exact: true, + }); + }, + onError: (error) => { + console.log(error); + }, + }); + + const createCommand = async (content: string) => { + mutation.mutate(content); + }; + + return { + createCommand, + isLoading: mutation.isPending, + isError: mutation.isError, + error: mutation.error, + isSuccess: mutation.isSuccess, + }; +}; + +export default usePostCommand; diff --git a/src/hooks/CommandHooks/usePutCommand.ts b/src/hooks/CommandHooks/usePutCommand.ts new file mode 100644 index 00000000..7a522673 --- /dev/null +++ b/src/hooks/CommandHooks/usePutCommand.ts @@ -0,0 +1,34 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { ProjectCommandList } from '../queries/keys'; +import { patchCommand } from '../../api/command.api'; + +const usePutCommand = (id: number, commandId: number) => { + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: (content: string) => patchCommand(id, commandId, content), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [ProjectCommandList.projectCommand, id], + exact: true, + }); + }, + onError: (error) => { + console.log(error); + }, + }); + + const changeCommand = async (content: string) => { + mutation.mutate(content); + }; + + return { + changeCommand, + isLoading: mutation.isPending, + isError: mutation.isError, + error: mutation.error, + isSuccess: mutation.isSuccess, + }; +}; + +export default usePutCommand; diff --git a/src/hooks/useApplyProject.ts b/src/hooks/ProjectHooks/useApplyProject.ts similarity index 82% rename from src/hooks/useApplyProject.ts rename to src/hooks/ProjectHooks/useApplyProject.ts index 3048dce0..8329f4b3 100644 --- a/src/hooks/useApplyProject.ts +++ b/src/hooks/ProjectHooks/useApplyProject.ts @@ -1,10 +1,10 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { MODAL_MESSAGE } from '../constants/modalMessage'; -import { postApplicantProject } from '../api/joinProject.api'; -import { ROUTES } from '../constants/routes'; +import { joinProject } from '../../models/joinProject'; +import { ProjectListKey, userInfoKey } from '../queries/keys'; +import { postApplicantProject } from '../../api/joinProject.api'; +import { MODAL_MESSAGE } from '../../constants/modalMessage'; +import { ROUTES } from '../../constants/routes'; import { useNavigate } from 'react-router-dom'; -import { joinProject } from '../models/joinProject'; -import { ProjectListKey, userInfoKey } from './queries/keys'; interface UseApplyProjectProps { id: number; diff --git a/src/hooks/useCreateProject.ts b/src/hooks/ProjectHooks/useCreateProject.ts similarity index 80% rename from src/hooks/useCreateProject.ts rename to src/hooks/ProjectHooks/useCreateProject.ts index ebeb78dd..9d2d4445 100644 --- a/src/hooks/useCreateProject.ts +++ b/src/hooks/ProjectHooks/useCreateProject.ts @@ -1,12 +1,12 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { FormData } from '../models/createProject'; -import { MODAL_MESSAGE } from '../constants/modalMessage'; -import { postProject } from '../api/joinProject.api'; import { Dispatch, SetStateAction } from 'react'; -import { useSaveSearchFiltering } from './useSaveSearchFiltering'; -import { ROUTES } from '../constants/routes'; import { useNavigate } from 'react-router-dom'; -import { managedProjectKey } from './queries/keys'; +import { useSaveSearchFiltering } from '../useSaveSearchFiltering'; +import { postProject } from '../../api/joinProject.api'; +import { MODAL_MESSAGE } from '../../constants/modalMessage'; +import { managedProjectKey } from '../queries/keys'; +import { ROUTES } from '../../constants/routes'; +import { FormData } from '../../models/createProject'; interface UseCreateProjectProps { handleModalOpen: (newMessage: string) => void; diff --git a/src/hooks/useMultiStepForm.ts b/src/hooks/ProjectHooks/useMultiStepForm.ts similarity index 100% rename from src/hooks/useMultiStepForm.ts rename to src/hooks/ProjectHooks/useMultiStepForm.ts diff --git a/src/hooks/useUpdateProject.ts b/src/hooks/ProjectHooks/useUpdateProject.ts similarity index 82% rename from src/hooks/useUpdateProject.ts rename to src/hooks/ProjectHooks/useUpdateProject.ts index f4c252d6..3e542279 100644 --- a/src/hooks/useUpdateProject.ts +++ b/src/hooks/ProjectHooks/useUpdateProject.ts @@ -1,10 +1,10 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { putProject } from '../api/joinProject.api'; -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'; +import { putProject } from '../../api/joinProject.api'; +import { managedProjectKey } from '../queries/keys'; +import { FormData } from '../../models/createProject'; +import { MODAL_MESSAGE } from '../../constants/modalMessage'; +import { ROUTES } from '../../constants/routes'; interface UseUpdateProjectProps { id: number; diff --git a/src/hooks/queries/keys.ts b/src/hooks/queries/keys.ts index 0dfc420b..6df80a53 100644 --- a/src/hooks/queries/keys.ts +++ b/src/hooks/queries/keys.ts @@ -22,3 +22,7 @@ export const ProjectListKey = { myJoinedList: ['myJoinedProjectList'], myAppliedStatusList: ['myAppliedProjectStatusList'], } as const; + +export const ProjectCommandList = { + projectCommand: ['projectCommand'], +}; diff --git a/src/hooks/useDropDownItem.ts b/src/hooks/useDropDownItem.ts deleted file mode 100644 index 9acde92c..00000000 --- a/src/hooks/useDropDownItem.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; - -const useDropDownItem = (commandId?: number) => { - const navigate = useNavigate(); - const [isEditMode, setIsEditMode] = useState(false); - - const onReport = () => { - console.log('신고하기'); - // navigate(''); - }; - - const onEdit = () => { - setIsEditMode(!isEditMode); - }; - - const onDelete = () => {}; - return { onReport, onEdit, onDelete, isEditMode }; -}; - -export default useDropDownItem; diff --git a/src/hooks/useJoinProject.ts b/src/hooks/useGetProjectData.ts similarity index 100% rename from src/hooks/useJoinProject.ts rename to src/hooks/useGetProjectData.ts index a0f8614e..f89ab19d 100644 --- a/src/hooks/useJoinProject.ts +++ b/src/hooks/useGetProjectData.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; -import { getProjectData } from '../api/joinProject.api'; import { managedProjectKey } from './queries/keys'; +import { getProjectData } from '../api/joinProject.api'; const useGetProjectData = (projectId: number) => { const { data, isLoading, isFetching } = useQuery({ diff --git a/src/hooks/useNotification.ts b/src/hooks/useNotification.ts new file mode 100644 index 00000000..855e1489 --- /dev/null +++ b/src/hooks/useNotification.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from 'react'; + +const useNotification = () => { + const [isSignal, setIsSignal] = useState(false); + + useEffect(() => { + const eventSource = new EventSource('http://220.90.136.205:8082/user/sse'); + + eventSource.onmessage = (e) => { + console.log(e); + }; + + eventSource.onerror = (e) => { + console.log(e); + }; + }, [isSignal]); + + return { isSignal }; +}; + +export default useNotification; diff --git a/src/models/apiCommon.ts b/src/models/apiCommon.ts index 9cfdd127..0a096d98 100644 --- a/src/models/apiCommon.ts +++ b/src/models/apiCommon.ts @@ -2,3 +2,9 @@ export interface ApiCommonType { success: boolean; message: string; } + +export interface User { + id: number; + nickname: string; + img: string; +} diff --git a/src/models/command.ts b/src/models/command.ts new file mode 100644 index 00000000..9bf720a0 --- /dev/null +++ b/src/models/command.ts @@ -0,0 +1,12 @@ +import { ApiCommonType, User } from './apiCommon'; + +export interface GetCommandType extends ApiCommonType { + data: CommandType[]; +} + +export interface CommandType { + id: number; + content: string; + user: User; + commentCount: number; +} diff --git a/src/models/createProject.ts b/src/models/createProject.ts index 75972e02..d61322d6 100644 --- a/src/models/createProject.ts +++ b/src/models/createProject.ts @@ -9,11 +9,11 @@ export interface FormData { totalMember: number; startDate: string; estimatedPeriod: string; - methodType: number; + methodTypeId: number; isBeginner?: boolean; recruitmentStartDate: string; recruitmentEndDate: string; - authorId: number; positionTagIds: number[]; skillTagIds: number[]; + isDone?: boolean; } diff --git a/src/models/projectDetail.ts b/src/models/projectDetail.ts index 3f33f628..608868f2 100644 --- a/src/models/projectDetail.ts +++ b/src/models/projectDetail.ts @@ -1,10 +1,12 @@ +import { ApiCommonType, User } from './apiCommon'; import { joinProject } from './joinProject'; import type { PositionTag, SkillTag } from './tags'; -export interface User { +export interface SkillTag { id: number; - nickname: string; + name: string; img: string; + createdAt: string; } export interface ProjectSkillTagList { @@ -59,8 +61,8 @@ export interface Position { export interface Skill { id: number; - skillName: string; - skillImg: string; + name: string; + img: string; } export interface ProjectDetailPlus { @@ -85,8 +87,6 @@ export interface ProjectDetailPlusExtended extends ProjectDetailPlus { skills: Skill[]; } -export interface dataPlus { - success: boolean; - message: string; +export interface dataPlus extends ApiCommonType { data: ProjectDetailPlus; } diff --git a/src/pages/apply/Apply.tsx b/src/pages/apply/Apply.tsx index 124e7200..5e9c2fe8 100644 --- a/src/pages/apply/Apply.tsx +++ b/src/pages/apply/Apply.tsx @@ -6,15 +6,15 @@ import { z } from 'zod'; import { useParams } from 'react-router-dom'; import { formatDate } from '../../util/format'; import { joinProject } from '../../models/joinProject'; -import useGetProjectData from '../../hooks/useJoinProject'; import CareersComponent from '../../components/applyComponents/careersComponent/CareersComponent'; import PhoneComponent from '../../components/applyComponents/phoneComponent/PhoneComponent'; import LoadingSpinner from '../../components/common/loadingSpinner/LoadingSpinner'; 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'; +import useGetProjectData from '../../hooks/useGetProjectData'; +import useApplyProject from '../../hooks/ProjectHooks/useApplyProject'; const ApplyScheme = z.object({ email: z diff --git a/src/pages/apply/ApplyStep.tsx b/src/pages/apply/ApplyStep.tsx index b800cdd7..f8e50701 100644 --- a/src/pages/apply/ApplyStep.tsx +++ b/src/pages/apply/ApplyStep.tsx @@ -6,18 +6,18 @@ import { z } from 'zod'; import { useParams } from 'react-router-dom'; import { formatDate } from '../../util/format'; import { joinProject } from '../../models/joinProject'; -import useGetProjectData from '../../hooks/useJoinProject'; import CareersComponent from '../../components/applyComponents/careersComponent/CareersComponent'; import PhoneComponent from '../../components/applyComponents/phoneComponent/PhoneComponent'; import LoadingSpinner from '../../components/common/loadingSpinner/LoadingSpinner'; 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'; -import useMultiStepForm from '../../hooks/useMultiStepForm'; +import useMultiStepForm from '../../hooks/ProjectHooks/useMultiStepForm'; import StepComponent from '../../components/projectFormComponents/stepComponent/StepComponent'; import Button from '../../components/common/Button/Button'; +import useGetProjectData from '../../hooks/useGetProjectData'; +import useApplyProject from '../../hooks/ProjectHooks/useApplyProject'; const ApplyScheme = z.object({ email: z diff --git a/src/pages/createProject/CreateProject.tsx b/src/pages/createProject/CreateProject.tsx index 6d235c7d..2a2d241e 100644 --- a/src/pages/createProject/CreateProject.tsx +++ b/src/pages/createProject/CreateProject.tsx @@ -100,11 +100,10 @@ const CreateProject = () => { startDate: data.startDatePre, positionTagIds: data.position, estimatedPeriod: `${data.duration}개월`, - methodType: data.field, + methodTypeId: data.field, isBeginner: data.newBy, skillTagIds: data.languages, description: data.markdownEditor, - authorId: userId, }; console.log(formData); diff --git a/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx b/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx index d238e614..ee1b0b8d 100644 --- a/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx +++ b/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx @@ -6,7 +6,7 @@ import InfoCard from '../../../components/common/infoCard/InfoCard'; import ApplicantList from '../../../components/manageProjects/applicantList/ApplicantList'; import ApplicantInfo from '../../../components/manageProjects/applicantInfo/ApplicantInfo'; -import useGetProjectData from '../../../hooks/useJoinProject'; +import useGetProjectData from '../../../hooks/useGetProjectData'; import { useApllicantList } from '../../../hooks/useApllicantList'; import { applicantsMenuItems } from '../../../constants/sidebarItems'; diff --git a/src/pages/modifyProject/ModifyProject.tsx b/src/pages/modifyProject/ModifyProject.tsx index 428575c9..b064edf6 100644 --- a/src/pages/modifyProject/ModifyProject.tsx +++ b/src/pages/modifyProject/ModifyProject.tsx @@ -7,12 +7,12 @@ import { CreateProjectFormValues, FormData } from '../../models/createProject'; import { useParams } from 'react-router-dom'; import ProjectInformationInput from '../../components/projectFormComponents/projectInformationInput/ProjectInformationInput'; import { createProjectScheme } from '../createProject/CreateProject'; -import useGetProjectData from '../../hooks/useJoinProject'; import { useEffect } from 'react'; import { formatDate } from '../../util/format'; -import useUpdateProject from '../../hooks/useUpdateProject'; import { useModal } from '../../hooks/useModal'; import Modal from '../../components/common/modal/Modal'; +import useGetProjectData from '../../hooks/useGetProjectData'; +import useUpdateProject from '../../hooks/ProjectHooks/useUpdateProject'; const ModifyProject = () => { const { projectId } = useParams(); @@ -46,8 +46,6 @@ const ModifyProject = () => { }, }); - console.log(projectData); - useEffect(() => { if (projectData) { setValue('startDatePre', formatDate(projectData.startDate)); @@ -69,6 +67,8 @@ const ModifyProject = () => { ); } + console.log(projectData); + const handleSubmit = async (data: z.infer) => { const formData: FormData = { title: data.title, @@ -78,11 +78,11 @@ const ModifyProject = () => { startDate: data.startDatePre, positionTagIds: data.position, estimatedPeriod: `${data.duration}개월`, - methodType: data.field, + methodTypeId: data.field, isBeginner: data.newBy, skillTagIds: data.languages, description: data.markdownEditor, - authorId: userId, + isDone: projectData.isDone, }; updateProject(formData); diff --git a/src/pages/projectDetail/ProjectDetail.tsx b/src/pages/projectDetail/ProjectDetail.tsx index 0bc7d71b..6dc5972c 100644 --- a/src/pages/projectDetail/ProjectDetail.tsx +++ b/src/pages/projectDetail/ProjectDetail.tsx @@ -1,6 +1,5 @@ import { ScrollRestoration, useNavigate, useParams } from 'react-router-dom'; import ProjectInformation from '../../components/projectFormComponents/projectInformationText/ProjectInformation'; -import useGetProjectData from '../../hooks/useJoinProject'; import * as S from './ProjectDetail.styled'; import { formatDate } from '../../util/format'; import MarkdownEditorView from '../../components/projectFormComponents/editor/MarkdownEditorView'; @@ -15,6 +14,7 @@ import { useEffect } from 'react'; import CommandLayout from '../../components/command/CommandLayout'; import Avatar from '../../components/common/avatar/Avatar'; import Button from '../../components/common/Button/Button'; +import useGetProjectData from '../../hooks/useGetProjectData'; const ProjectDetail = () => { const { projectId } = useParams(); @@ -94,7 +94,11 @@ const ProjectDetail = () => {
- + ); };