diff --git a/src/app/application/ShowSection.tsx b/src/app/application/ShowSection.tsx index d3fa1eb..14c96ba 100644 --- a/src/app/application/ShowSection.tsx +++ b/src/app/application/ShowSection.tsx @@ -1,61 +1,71 @@ 'use client' import { ApplicationBox } from "@/components" -import { useState } from "react" +import { ApplicationFileType, ApplicationPreviewType, MajorType } from "@/types" +import { useSearchParams } from "next/navigation" +import { useEffect, useState } from "react" -interface ApplicationBoxProps { - tag: "포트폴리오" | "자기소개서" | "이력서" - title: string - name: string - date: string - mainMajor?: string - subMajor?: string -} - -const ApplyData: ApplicationBoxProps[] = [ - { - tag: '포트폴리오', - title: '개인적으로 완벽한 포트폴리오', - name: '이강혁', - date: '2024-04-16', - mainMajor: 'Frontend', - subMajor: 'Backend' - }, +const ApplyData: ApplicationPreviewType[] = [ { - tag: '자기소개서', - title: '자기소개의 참된 예를 잘 보여주는 글', - name: '강진현', - date: '2023-04-16', - mainMajor: 'Frontend' + "post_id": 1, + "post_post_type": "Portfolio", + "post_title": "엄청나고 위대한 포트폴리오 자료", + "post_major": "Backend", + "post_created_at": "2024-05-27T02:48:52.347Z", + "user_oauth_id": "110159413387878573726" }, { - tag: '이력서', - title: '올바른 형식의 이력서 예시', - name: '임태곤', - date: '2024-04-06' + "post_id": 2, + "post_post_type": "Portfolio", + "post_title": "야호", + "post_major": "Frontend", + "post_created_at": "2024-05-27T04:26:28.899Z", + "user_oauth_id": "107359038156703139645" }, { - tag: '포트폴리오', - title: '개인적으로 완벽한 포트폴리오', - name: '이강혁', - date: '2023-04-18', - mainMajor: 'Frontend', - subMajor: 'Backend' + "post_id": 3, + "post_post_type": "Portfolio", + "post_title": "테스트...", + "post_major": "Frontend", + "post_created_at": "2024-05-27T04:47:00.694Z", + "user_oauth_id": "107359038156703139645" } ] +const majorData: MajorType[] = ['Frontend', 'Backend', 'Android', 'iOS', 'CrossPlatform', 'AI', 'DevOps', 'Design', 'Game', 'Blockchain'] + const ShowSection = () => { const [orderType, setOrderType] = useState<'first' | 'last'>('first') + const [selectedKind, setSelectedKind] = useState<'everything' | ApplicationFileType>('everything') + const [selectedMajor, setSelectedMajor] = useState>>({}) + const [searchWord, setSearchWord] = useState('') + const searchParams = useSearchParams() + + useEffect(() => { + if (searchParams.has('word')) { + setSearchWord(searchParams.get('word') || '') + } + if (searchParams.has('kind') && ['everything', 'Portfolio', 'PersonalStatement', 'Resume'].includes(searchParams.get('kind') as string)) { + setSelectedKind(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 (
-
- 182개의 지원서 자료 -
+
+
+ {searchWord && “{searchWord}”에 대한} + 182개의 지원서 자료 +
+
{orderType === 'first' ? '최신순' : '오래된순'}
setOrderType('first')}>최신순 setOrderType('last')}>오래된순
-
+
{ ApplyData.map((item, index) => = { + 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 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 +81,7 @@ const SideBar = () => { title='지원서 종류' display={kindData} value={selectedKind} - setValue={(v) => setSelectedKind(v)} + setValue={changeParams('kind')} open />
@@ -37,7 +91,7 @@ const SideBar = () => { title='작성자 종류' display={majorData} value={selectedMajor} - setValue={(v) => setSelectedMajor((prev) => ({ ...prev, [v]: !prev[v] }))} + setValue={changeParams('major')} />
diff --git a/src/app/application/page.tsx b/src/app/application/page.tsx index 9e87a23..4ffd82f 100644 --- a/src/app/application/page.tsx +++ b/src/app/application/page.tsx @@ -1,14 +1,17 @@ import { ApplicationBanner } from "./Banner"; import SideBar from "./SideBar"; import ShowSection from "./ShowSection"; +import { Suspense } from "react"; export default function Application() { return (
- - + + + +
) diff --git a/src/app/page.tsx b/src/app/page.tsx index ebb79d0..ccab94c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,47 +1,7 @@ import { Bulb, Portfolio } from "@/assets"; import { MainBanner } from "./main"; import { Button, SubTitleSection, ApplicationBox, TipBox } from "@/components"; - -interface ApplicationBoxProps { - tag: "포트폴리오" | "자기소개서" | "이력서" - title: string - name: string - date: string - mainMajor?: string - subMajor?: string -} - -const ApplyData: ApplicationBoxProps[] = [ - { - tag: '포트폴리오', - title: '개인적으로 완벽한 포트폴리오', - name: '이강혁', - date: '2024-04-16', - mainMajor: 'Frontend', - subMajor: 'Backend' - }, - { - tag: '자기소개서', - title: '자기소개의 참된 예를 잘 보여주는 글', - name: '강진현', - date: '2023-04-16', - mainMajor: 'Frontend' - }, - { - tag: '이력서', - title: '올바른 형식의 이력서 예시', - name: '임태곤', - date: '2024-04-06' - }, - { - tag: '포트폴리오', - title: '개인적으로 완벽한 포트폴리오', - name: '이강혁', - date: '2023-04-18', - mainMajor: 'Frontend', - subMajor: 'Backend' - } -] +import { getRecommend } from "@/services"; interface TipBoxProps { title: string; @@ -73,7 +33,8 @@ const TipData: TipBoxProps[] = [ }, ] -export default function Home() { +export default async function Home() { + const applicationData = await getRecommend().then(res => res.data.posts) || [] return (
@@ -86,7 +47,7 @@ export default function Home() {
{ - ApplyData.map((item, index) => + applicationData.slice(0, 4).map((item, index) => = { + Portfolio: '포트폴리오', + PersonalStatement: '자기소개서', + Resume: '이력서', +} + +const tagColor: Record = { + Portfolio: 'bg-attentionBackground text-attention', + PersonalStatement: 'bg-coutionBackground text-coution', + Resume: 'bg-gray100 text-gray500', } /** @@ -28,22 +35,25 @@ const tagColor = { ``` */ -export const ApplicationBox = ({ tag, title, name, date, mainMajor, subMajor }: ApplicationBoxProps) => { - - const router = useRouter(); +export const ApplicationBox = ({ post_id, post_title, post_post_type, user_oauth_id, post_major, post_created_at }: ApplicationPreviewType) => { + const router = useRouter() return ( -
router.push('/application/1')} className="flex flex-col w-full p-8 gap-3 border border-gray100 bg-white rounded-3xl cursor-pointer"> -
{tag}
+
router.push(`/application/${post_id}`)} className="flex flex-col w-full p-8 gap-3 border border-gray100 bg-white rounded-3xl cursor-pointer"> +
{tagToKorean[post_post_type]}
- {title} + {post_title}
- {name} + {user_oauth_id}
- {dateToString(date)} + {dateToString(post_created_at)}
- {tag !== '이력서' && ( + { + post_post_type !== 'Resume' && + {post_major} + } + {/* {tag !== '이력서' && (
{mainMajor} { @@ -54,9 +64,9 @@ export const ApplicationBox = ({ tag, title, name, date, mainMajor, subMajor }: }
- )} + )} */}
-
+
) } diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 9d11562..331d015 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 { usePathname } from 'next/navigation' export const Header = () => { const [hasToken, setHasToken] = useState(false) const [isOpen, setIsOpen] = useState(false) const [modal, setModal] = useState(false) + const pathName = usePathname() + + useEffect(() => { + const token = getCookie('access_token') + setHasToken(!!token) + }, [pathName]) return ( <> @@ -41,42 +49,39 @@ export const Header = () => {
-
- - - - - - - {hasToken && ( - - )} -
+ + + + + + + {hasToken && ( + + )}
{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) => {