From 0a84f2a343c6be3074643dcbb178d6fa87ad1ec7 Mon Sep 17 00:00:00 2001 From: eternrust Date: Mon, 27 May 2024 12:28:39 +0900 Subject: [PATCH 1/3] ADD :: application api MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 미완 --- src/app/application/SideBar.tsx | 69 +++++++++++-- src/components/Header.tsx | 15 ++- src/components/Input.tsx | 2 +- src/components/modal/ApplicationModal.tsx | 116 ++++++++++++++++------ src/services/post/postFile.ts | 20 ++++ src/services/post/postLink.ts | 12 +++ src/types/applicationFile.type.ts | 1 + src/types/index.ts | 2 + src/types/majorType.ts | 1 + src/types/post/index.ts | 1 + src/types/post/postLink.type.ts | 8 ++ 11 files changed, 207 insertions(+), 40 deletions(-) create mode 100644 src/services/post/postFile.ts create mode 100644 src/services/post/postLink.ts create mode 100644 src/types/applicationFile.type.ts create mode 100644 src/types/post/index.ts create mode 100644 src/types/post/postLink.type.ts diff --git a/src/app/application/SideBar.tsx b/src/app/application/SideBar.tsx index caea474..297866e 100644 --- a/src/app/application/SideBar.tsx +++ b/src/app/application/SideBar.tsx @@ -1,23 +1,78 @@ 'use client' import { Bag, Portfolio, Search } from "@/assets" -import { useState } from "react" +import { useCallback, useEffect, useState } from "react" import { SideSelect } from "./SideSelect" +import { usePathname, useRouter, useSearchParams } from "next/navigation" +import { ApplicationFileType, MajorType } from "@/types" type KindType = '모든 종류' | '포트폴리오' | '자기소개서' | '이력서' const kindData: KindType[] = ['모든 종류', '포트폴리오', '자기소개서', '이력서'] +const tagToKorean: Record<'everything' | ApplicationFileType, KindType> = { + everything: '모든 종류', + Portfolio: '포트폴리오', + PersonalStatement: '자기소개서', + Resume: '이력서', +} + +const tagToEnglish: Record = { + '모든 종류': 'everything', + 포트폴리오: 'Portfolio', + 자기소개서: 'PersonalStatement', + 이력서: 'Resume', +} -type MajorType = 'Frontend' | 'Backend' | 'Android' | 'iOS' | 'CrossPlatform' | 'Ai' | 'DevOps' | 'Embeded' | 'Design' | 'Game' | 'BlockChain' -const majorData: MajorType[] = ['Frontend', 'Backend', 'Android', 'iOS', 'CrossPlatform', 'Ai', 'DevOps', 'Embeded', 'Design', 'Game', 'BlockChain'] +const majorData: MajorType[] = ['Frontend', 'Backend', 'Android', 'iOS', 'CrossPlatform', 'AI', 'DevOps', 'Design', 'Game', 'Blockchain'] const SideBar = () => { const [selectedKind, setSelectedKind] = useState('모든 종류') - const [selectedMajor, setSelectedMajor] = useState<{ [key: string]: boolean }>({}) + const [selectedMajor, setSelectedMajor] = useState>>({}) + const [searchWord, setSearchWord] = useState('') + const router = useRouter() + const pathName = usePathname() + const searchParams = useSearchParams() + + const changeParams = useCallback((name: 'kind' | 'major' | 'word') => (value: string) => { + const param = new URLSearchParams(searchParams.toString()) + + if (name == 'major') { + setSelectedMajor((prev) => ({ ...prev, [value as MajorType]: !prev[value as MajorType] })) + const majorParams = [...param.getAll('major')] + param.set(name, value) + Object.entries(selectedMajor).forEach(v => v[1] && majorParams.includes(v[0]) && param.append(name, v[0])) + } else if (name === 'kind') { + setSelectedKind(value) + param.set(name, tagToEnglish[value as KindType]) + } else { + param.set(name, value) + } + + router.push(`application?${param}`) + }, [searchParams]) + + useEffect(() => { + if (searchParams.has('word')) { + setSearchWord(searchParams.get('word') || '') + } + if (searchParams.has('kind') && ['everything', 'Portfolio', 'PersonalStatement', 'Resume'].includes(searchParams.get('kind') as string)) { + setSelectedKind(tagToKorean[searchParams.get('kind') as ('everything' | ApplicationFileType)]) + } + if (searchParams.has('major') && majorData.some(v => searchParams.getAll('major').includes(v))) { + setSelectedMajor(majorData.filter(v => searchParams.getAll('major').includes(v)).reduce((acc, v) => ({ ...acc, [v]: true }), {})) + } + }, [searchParams]) return (
- + setSearchWord(e.currentTarget.value)} + onKeyDown={(e) => e.key === 'Enter' && changeParams('word')(searchWord)} + />
@@ -27,7 +82,7 @@ const SideBar = () => { title='지원서 종류' display={kindData} value={selectedKind} - setValue={(v) => setSelectedKind(v)} + setValue={changeParams('kind')} open />
@@ -37,7 +92,7 @@ const SideBar = () => { title='작성자 종류' display={majorData} value={selectedMajor} - setValue={(v) => setSelectedMajor((prev) => ({ ...prev, [v]: !prev[v] }))} + setValue={changeParams('major')} />
diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 9d11562..c873ce6 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -4,13 +4,21 @@ import { PropofolFullLogo, PropofolLogo } from '@/assets' import Image from 'next/image' import { ApplicationModal, Button } from '.' import Link from 'next/link' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { LoginModal } from './modal/LoginModal' +import { getCookie } from '@/utils' +import { useSearchParams } from 'next/navigation' export const Header = () => { const [hasToken, setHasToken] = useState(false) const [isOpen, setIsOpen] = useState(false) const [modal, setModal] = useState(false) + const searchParams = useSearchParams() + + useEffect(() => { + const token = getCookie('access_token') + setHasToken(!!token) + }, [searchParams]) return ( <> @@ -44,7 +52,7 @@ export const Header = () => {
- + @@ -76,7 +85,7 @@ export const Header = () => { {hasToken ? (
- - {label ?? } + {label && } > } -type MajorType = 'Frontend' | 'Backend' | 'Android' | 'iOS' | 'CrossPlatform' | 'Ai' | 'DevOps' | 'Embeded' | 'Design' | 'Game' | 'BlockChain' -const majorData: MajorType[] = ['Frontend', 'Backend', 'Android', 'iOS', 'CrossPlatform', 'Ai', 'DevOps', 'Embeded', 'Design', 'Game', 'BlockChain'] +interface ApplicationDataType { + kind: ApplicationFileType + title: '' + dataType: 'link' | 'file' + link: '', + file: File | null + major: MajorType[] +} + +const majorData: MajorType[] = ['Frontend', 'Backend', 'Android', 'iOS', 'CrossPlatform', 'AI', 'DevOps', 'Design', 'Game', 'Blockchain'] -const tagColor = { - 포트폴리오: 'bg-attentionBackground text-attention', - 자기소개서: 'bg-coutionBackground text-coution', - 이력서: 'bg-gray100 text-gray500', +const tagToKorean: Record = { + Portfolio: '포트폴리오', + PersonalStatement: '자기소개서', + Resume: '이력서', +} + +const tagColor: Record = { + Portfolio: 'bg-attentionBackground text-attention', + PersonalStatement: 'bg-coutionBackground text-coution', + Resume: 'bg-gray100 text-gray500', } export const ApplicationModal = ({ click }: ApplicationModalType) => { - const [selectedKind, setSelectedKind] = useState('포트폴리오') - const [selectedMajor, setSelectedMajor] = useState([]) - const [dataType, setDataType] = useState<'link' | 'file'>('link') - const [dataFile, setDataFile] = useState(null) + const [data, setData] = useState({ + kind: 'Portfolio', + title: '', + dataType: 'link', + link: '', + file: null, + major: [] + }) + + const changeData = useCallback((name: string, value: T): void => { + setData((prev) => ({ ...prev, [name]: value })) + }, []) const UploadFile = useCallback(( e: React.ChangeEvent @@ -30,7 +55,7 @@ export const ApplicationModal = ({ click }: ApplicationModalType) => { if (selectedFile !== null) { if (selectedFile?.name.match(/^.*\.pdf$/)) { - setDataFile(selectedFile) + changeData('file', selectedFile) } } }, []) @@ -42,6 +67,28 @@ export const ApplicationModal = ({ click }: ApplicationModalType) => { return `1GB+` }, []) + const submitApplication = useCallback(async () => { + const token = getCookie('access_token') + if (!token) { + toast.error('토큰이 없습니다!') + return + } + + if (data.dataType === 'link') { + await postLink(token, { + title: data.title, + link: data.link, + type: data.kind, + major: data.major[0] + }).then(() => { + toast.success('성공적으로 업로드되었습니다!') + click(false) + }).catch(err => { + toast.error('업로드에 실패했습니다.') + }) + } + }, [data]) + return ( { {/* 유형 선택 */}
{ - (Object.keys(tagColor) as (keyof typeof tagColor)[]).map(v => ( -
setSelectedKind(v)}> + (Object.keys(tagColor) as ApplicationFileType[]).map(v => ( +
changeData('kind', v)}> { - selectedKind === v ? + data.kind === v ?
:
} -
{v}
+
{tagToKorean[v]}
)) }
{/* 자료 제목 입력 */} - + changeData('title', e.currentTarget.value)} + value={data.title} + err={data.title.length > 55} + /> {/* 자료 업로드 */}
-
{dataType === 'link' ? '자료 링크' : '자료 파일'}
- setDataType('link')}>자료 링크 - setDataType('file')}>자료 파일 +
{data.dataType === 'link' ? '자료 링크' : '자료 파일'}
+ changeData('dataType', 'link')}>자료 링크 + changeData('dataType', 'file')}>자료 파일
{ - dataType === 'link' ? - + data.dataType === 'link' ? + changeData('link', e.currentTarget.value)} + value={data.link} + err={data.link.length > 93} + /> :
{ - dataFile ? + data.file ?
{/* 이거 왜 말줄임 안됨? */} - {dataFile.name.split('.')[0]} - {`.${dataFile.name.split('.')[1]} (${fileSizeToString(dataFile.size)})`} + {data.file.name.split('.')[0]} + {`.${data.file.name.split('.')[1]} (${fileSizeToString(data.file.size)})`}
: 최대 5MB까지 업로드 할 수 있습니다. @@ -102,23 +160,23 @@ export const ApplicationModal = ({ click }: ApplicationModalType) => {