diff --git a/.prettierrc.json b/.prettierrc.json index 99fcc78..ea15e31 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -3,5 +3,11 @@ "singleQuote": true, "trailingComma": "all", "useTabs": false, - "tabWidth": 2 + "tabWidth": 2, + "bracketSpacing": true, + "jsxSingleQuote": false, + "printWidth": 80, + "proseWrap": "preserve", + "quoteProps": "as-needed", + "endOfLine": "lf" } diff --git a/next.config.mjs b/next.config.mjs index 4678774..b639db9 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,15 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 'lh3.googleusercontent.com', + port: '', + pathname: '/**', + }, + ], + }, +} -export default nextConfig; +export default nextConfig diff --git a/src/app/application/[id]/page.tsx b/src/app/application/[id]/page.tsx index b44b9ba..491ce5c 100644 --- a/src/app/application/[id]/page.tsx +++ b/src/app/application/[id]/page.tsx @@ -1,107 +1,153 @@ -"use client" +'use client' -import { Arrow, Bag, Delete, FileUpload, Linking, More } from "@/assets"; -import { ApplicationDeleteModal } from "@/components/modal/ApplicationDelete"; -import { getApplicationDetail } from "@/services/getApplicationDetail"; -import { applicationType } from "@/types/applicationType"; -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { useEffect, useState } from "react"; +import { Arrow, Bag, Delete, FileUpload, Linking, More } from '@/assets' +import { ApplicationDeleteModal } from '@/components/modal/ApplicationDelete' +import { getApplicationDetail, getMe, getUser } from '@/services' +import { ApplicationType, UserType } from '@/types' +import Link from 'next/link' +import { useEffect, useState } from 'react' -export default function Detail({params}: {params: {id: number}}){ +export default function Detail({ params }: { params: { id: number } }) { + const [open, setOpen] = useState(false) + const [modal, setModal] = useState(false) + const [detailData, setDetailData] = useState() + const [userData, setUserData] = useState() + const [myData, setMyData] = useState() - const [open, setOpen] = useState(false) - const [modal, setModal] = useState(false) - const [detailData, setDetailData] = useState() + const getData = async () => { + const data: ApplicationType = await getApplicationDetail(params.id) + const user: UserType = await getUser(data.post_writer_id) + setUserData(user) + setDetailData(data) + } - useEffect(()=>{ - async()=>{ - const data = await getApplicationDetail(params.id) - console.log(data) - setDetailData(data) - } - },[detailData]) + const MyData = async () => { + const my = await getMe() + setMyData(my) + } - return( - <> - { - modal && - - } -
-
-
-
- -
-
setOpen(!open)}> - - { - open && - - } -
-
-
-
- - 포트폴리오 - -
-

개인적으로 완벽한 포트폴리오

-
-

이강혁

-

-

2024.04.22. 14:07

-
-
-
-
-
-
- -
-
-

자료분야

-
-

Frontend

-

|

-

Backend

-
-
-
-
-
- -
-
-

자료형식

-
-

Web Link

-
-
-
-
-
-
-

자료 미리보기

- - -
-

자료링크 이동

-

https://velog.io/@lgb9811/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A1%9C-%EC%B7%A8%EC%A7%81%EA%B9%8C%EC%A7%80-%ED%95%B4%EB%86%93%EA%B3%A0-%EC%95%84%EC%A7%81%EB%8F%84-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%B4-%EB%AD%94%EC%A7%80-%EB%AA%A8%EB%A5%B8%EB%8B%A4%EA%B3%A0

-
- - -
-
-
- - ); + const DateFormat = () => { + if (detailData?.post_created_at) { + const DateObj = new Date(detailData.post_created_at) + + const year = String(DateObj.getFullYear()) + let month = String(DateObj.getMonth() + 1) + let day = String(DateObj.getDate()) + let hour = String(DateObj.getHours()) + let minute = String(DateObj.getMinutes()) + + month = Number(month) >= 10 ? month : '0' + month + day = Number(day) >= 10 ? day : '0' + day + hour = Number(hour) >= 10 ? hour : '0' + hour + minute = Number(minute) >= 10 ? minute : '0' + minute + return year + '.' + month + '.' + day + '.' + ' ' + hour + ':' + minute + } + } + + useEffect(() => { + getData() + MyData() + }, []) + + return ( + <> + {modal && ( + + )} +
+
+
+
+ +
+ {userData?.oauth_id === myData?.oauth_id && ( +
setOpen(!open)} + > + + {open && ( + + )} +
+ )} +
+
+
+ + {detailData?.post_post_type === 'Portfolio' + ? '포트폴리오' + : detailData?.post_post_type === 'Resume' + ? '이력서' + : detailData?.post_post_type === 'PersonalStatement' + ? '자기소개서' + : ''} + +
+

+ {detailData?.post_title} +

+
+

{userData?.name}

+

+

{DateFormat()}

+
+
+
+
+
+
+ +
+
+

자료분야

+
+

+ {detailData?.post_major} +

+
+
+
+
+
+ +
+
+

자료형식

+
+

Web Link

+
+
+
+
+
+
+

자료 미리보기

+ + {detailData?.post_link && ( + +
+

자료링크 이동

+

+ {detailData.post_link} +

+
+ + + )} +
+
+
+ + ) } diff --git a/src/app/profile/LogoutButton.tsx b/src/app/profile/LogoutButton.tsx new file mode 100644 index 0000000..a34631f --- /dev/null +++ b/src/app/profile/LogoutButton.tsx @@ -0,0 +1,22 @@ +'use client' + +import { Button } from '@/components' +import { removeCookie } from '@/utils' +import { useRouter } from 'next/navigation' + +export const LogoutButton = () => { + const router = useRouter() + + const logout = () => { + console.log('logout!!') + removeCookie('RF-TOKEN') + removeCookie('access_token') + removeCookie('Access_Token') + router.replace('/') + } + return ( + + ) +} diff --git a/src/app/profile/ProfileInfo.tsx b/src/app/profile/ProfileInfo.tsx new file mode 100644 index 0000000..14432ac --- /dev/null +++ b/src/app/profile/ProfileInfo.tsx @@ -0,0 +1,124 @@ +import { Arrow, Bag, DefaultProfile, GradientImg, User } from '@/assets' +import { ApplicationBox, Button, TipBox } from '@/components' +import { applicationData, tipData } from '@/constants' +import { getMe } from '@/services' +import Image from 'next/image' +import Link from 'next/link' +import { LogoutButton } from './LogoutButton' + +export const ProfileInfo = async () => { + const { generation, major, name, oauth_id, profile_image } = await getMe() + return ( + <> +
+ Profile Banner + User Profile +
+
+
+

{name}

+

팔로잉 0 | 팔로워 0

+
+
+ + + + +
+
+
기본 정보
+
+
+
+ +
+
+

기수

+

{generation}기

+
+
+
+
+ +
+
+

전공

+

{major}

+
+
+
+
+
+
+
+
공유한 지원서 자료
+ {/* +

더보기

+ + */} +
+ {/*
+ {applicationData.map((value, index) => ( + + ))} +
*/} +
+

+ 아직 공유한 지원서가 없어요. +

+
+
+
+
+
+
공유한 지원서 팁
+ {/* +

더보기

+ + */} +
+ {/*
+ {tipData.map((value, index) => ( + + ))} +
*/} +
+

+ 아직 공유한 지원서 팁이 없어요. +

+
+
+
+ + ) +} diff --git a/src/app/profile/application/page.tsx b/src/app/profile/application/page.tsx new file mode 100644 index 0000000..a821e7a --- /dev/null +++ b/src/app/profile/application/page.tsx @@ -0,0 +1,34 @@ +'use client' + +import { Arrow } from '@/assets' +import { ApplicationBox } from '@/components' +import { applicationData } from '@/constants' +import { useRouter } from 'next/navigation' + +export default function MyApplicationPage() { + const router = useRouter() + + return ( +
+
+
+ +
+

공유한 지원서 자료

+

8개 내 지원서 자료

+
+
+
+ {applicationData.map((value, index) => ( + + ))} +
+
+
+ ) +} diff --git a/src/app/profile/edit/ProfileEdit.tsx b/src/app/profile/edit/ProfileEdit.tsx new file mode 100644 index 0000000..361061d --- /dev/null +++ b/src/app/profile/edit/ProfileEdit.tsx @@ -0,0 +1,79 @@ +'use client' + +import { BackButton, Button, Input, Select } from '@/components' +import { Add, DefaultProfile } from '@/assets' +import Image from 'next/image' +import { UserType } from '@/types' +import { majorOption } from '@/constants' +import { useState } from 'react' +import { uploadFile } from '@/utils' + +interface ImgDataType { + img?: File + imgString?: string +} + +export const ProfileEdit = ({ myData }: { myData: UserType }) => { + const [generation, setGeneration] = useState(`${myData.generation}`) + const [major, setMajor] = useState(myData.major) + const [imgData, setImgData] = useState({ + imgString: myData.profile_image, + }) + + const editProfile = () => {} + + return ( + <> +
+ +

프로필 수정

+
+
+ Profile Image + + uploadFile( + e, + (file) => setImgData((data) => ({ ...data, img: file })), + (str) => setImgData((data) => ({ ...data, imgString: str })), + ) + } + /> + +
+
+ setGeneration(e.target.value.slice(0, 2))} + /> + diff --git a/src/constants/dummy.ts b/src/constants/dummy.ts new file mode 100644 index 0000000..eacc163 --- /dev/null +++ b/src/constants/dummy.ts @@ -0,0 +1,59 @@ +import { ApplicationPreviewType } from '@/types' + +export const applicationData: ApplicationPreviewType[] = [ + { + post_id: 0, + post_title: '개인적으로 완벽한 포트폴리오', + post_post_type: 'Portfolio', + user_oauth_id: '이강혁', + post_major: 'Backend', + post_created_at: '2024-05-16 01:00:01+00', + }, + { + post_id: 1, + post_title: '완벽에 가까운 포트폴리오', + post_post_type: 'Portfolio', + user_oauth_id: '이강혁', + post_major: 'Backend', + post_created_at: '2024-05-16 01:00:01+00', + }, + { + post_id: 2, + post_title: '자기소개의 완벽한 예', + post_post_type: 'PersonalStatement', + user_oauth_id: '강진현', + post_major: 'Frontend', + post_created_at: '2024-05-16 01:00:01+00', + }, + { + post_id: 3, + post_title: '자기소개의 완벽한 예', + post_post_type: 'PersonalStatement', + user_oauth_id: '강진현', + post_major: 'Frontend', + post_created_at: '2024-05-16 01:00:01+00', + }, +] + +export const tipData = [ + { + title: '지원서 작성 팁 3가지', + content: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim', + name: '이강혁', + date: '2024-05-16 01:00:01+00', + }, + { + title: '지원서 작성 팁 3가지', + content: 'eiusmod tembna aliqua. Ut enim', + name: '이강혁', + date: '2024-05-16 01:00:01+00', + }, + { + title: '지원서 작성 팁 3가지', + content: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim', + name: '이강혁', + date: '2024-05-16 01:00:01+00', + }, +] diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000..c61d79b --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,2 @@ +export { applicationData, tipData } from './dummy' +export { majorOption } from './major' diff --git a/src/constants/major.ts b/src/constants/major.ts new file mode 100644 index 0000000..b68ab52 --- /dev/null +++ b/src/constants/major.ts @@ -0,0 +1,11 @@ +export const majorOption = [ + { value: '1', name: 'Backend' }, + { value: '2', name: 'Frontend' }, + { value: '3', name: 'iOS' }, + { value: '4', name: 'Android' }, + { value: '5', name: 'DevOps' }, + { value: '6', name: 'Design' }, + { value: '7', name: 'AI' }, + { value: '8', name: 'CrossPlatform' }, + { value: '9', name: 'Blockchain' }, +] diff --git a/src/services/authLogin.ts b/src/services/authLogin.ts index 8fbcccf..b1ab423 100644 --- a/src/services/authLogin.ts +++ b/src/services/authLogin.ts @@ -17,8 +17,8 @@ export const authLogin = async (kind: AuthKindType, token: string) => { cookies().set({ name: 'RF-TOKEN', value: setCookie?.[0].split(';')[0].split('=')[1] as string, - httpOnly: true, - secure: true, + httpOnly: false, + secure: false, path: '/', sameSite: 'strict', }) diff --git a/src/services/deleteApplication.ts b/src/services/deleteApplication.ts new file mode 100644 index 0000000..aa5548a --- /dev/null +++ b/src/services/deleteApplication.ts @@ -0,0 +1,18 @@ +'use server' + +import { cookies } from 'next/headers' +import { instance } from './interceptor' + +export const deleteApplication = async (postId: number | undefined) => { + const token = cookies().get('access_token') + + return await instance({ + method: 'DELETE', + url: `/post/${postId}`, + headers: { + Authorization: token?.value, + }, + }).then((response)=>{ + return response.data + }) +} diff --git a/src/services/getApplicationDetail.ts b/src/services/getApplicationDetail.ts index a41deef..0e73261 100644 --- a/src/services/getApplicationDetail.ts +++ b/src/services/getApplicationDetail.ts @@ -1,21 +1,18 @@ 'use server' -import { cookies } from "next/headers" -import { instance } from "./interceptor" +import { cookies } from 'next/headers' +import { instance } from './interceptor' -export const getApplicationDetail = async (postId:number) =>{ +export const getApplicationDetail = async (postId: number) => { + const token = cookies().get('access_token') - const token = cookies().get('access_token') - - return await instance({ - method: 'GET', - url: `/post/read/${postId}`, - headers:{ - Authorization: token?.value - } - }).then((response)=>{ - return response.data - }).catch((err)=>{ - console.error(err) - }) -} \ No newline at end of file + return await instance({ + method: 'GET', + url: `/post/read/${postId}`, + headers: { + Authorization: token?.value, + }, + }).then((response) => { + return response.data + }) +} diff --git a/src/services/getMe.ts b/src/services/getMe.ts new file mode 100644 index 0000000..ae5acdd --- /dev/null +++ b/src/services/getMe.ts @@ -0,0 +1,19 @@ +'use server' + +import { cookies } from 'next/headers' +import { instance } from './interceptor' +import { UserType } from '@/types' + +export const getMe = async () => { + const token = cookies().get('access_token') + + return await instance({ + method: 'GET', + url: '/users/me', + headers: { + Authorization: token?.value, + }, + }).then((response) => { + return response.data + }) +} diff --git a/src/services/getUser.ts b/src/services/getUser.ts new file mode 100644 index 0000000..defc45a --- /dev/null +++ b/src/services/getUser.ts @@ -0,0 +1,10 @@ +import { instance } from './interceptor' + +export const getUser = async (oauthId: string) => { + return await instance({ + method: 'GET', + url: `/users/${oauthId}`, + }).then((response) => { + return response.data + }) +} diff --git a/src/services/index.ts b/src/services/index.ts index 2964943..d2e524b 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -2,6 +2,11 @@ export { instance } from './interceptor' export { authLogin } from './authLogin' export { authSignup } from './authSignup' export { getKakaoToken } from './getKakaoToken' +export { getApplicationDetail } from './getApplicationDetail' +export { getMe } from './getMe' +export { getUser } from './getUser' +export { deleteApplication } from './deleteApplication' + export * from './post' export * from './user' export * from './tip' \ No newline at end of file diff --git a/src/types/applicationType.ts b/src/types/applicationType.ts index f659fb2..bd84ee7 100644 --- a/src/types/applicationType.ts +++ b/src/types/applicationType.ts @@ -1,13 +1,12 @@ -import { MajorType } from "./majorType" -import { PostType } from "./postType" +import { MajorType } from './majorType' +import { PostType } from './postType' -export type applicationType = { - id: number - post_type: PostType - title: string - content: string - link: string - major: MajorType - writer_id: string - created_at: Date -} \ No newline at end of file +export type ApplicationType = { + post_id: number + post_post_type: PostType + post_title: string + post_link?: string + post_major: MajorType + post_created_at: Date + post_writer_id: string +} diff --git a/src/types/index.ts b/src/types/index.ts index 5c0d656..664f468 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,6 +1,10 @@ export { type AuthKindType } from './authKind.type' export { type MajorType } from './majorType' export { type AuthSignupType } from './authSignupType' +export { type UserType } from './userType' +export { type ApplicationType } from './applicationType' +export { type PostType } from './postType' + export { type ApplicationFileType } from './applicationFile.type' export { type ApplicationPreviewType } from './applicationPreview.type' export { type UserType } from './user.type' diff --git a/src/types/postType.ts b/src/types/postType.ts index 7638d8f..e664518 100644 --- a/src/types/postType.ts +++ b/src/types/postType.ts @@ -1 +1 @@ -export type PostType = 'Link' | 'PDF' \ No newline at end of file +export type PostType = 'Portfolio' | 'Resume' | 'PersonalStatement' \ No newline at end of file diff --git a/src/types/userType.ts b/src/types/userType.ts new file mode 100644 index 0000000..8e2062f --- /dev/null +++ b/src/types/userType.ts @@ -0,0 +1,7 @@ +export type UserType = { + generation: number + major: string + name: string + oauth_id: string + profile_image: string +} \ No newline at end of file diff --git a/src/utils/cookie.ts b/src/utils/cookie.ts index c501169..ffa30f4 100644 --- a/src/utils/cookie.ts +++ b/src/utils/cookie.ts @@ -9,3 +9,7 @@ export const getCookie = (name: string) => { ) return matches ? decodeURIComponent(matches[1]) : undefined } + +export const removeCookie = (name: string) => { + document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC;` +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 12bcd7c..f1b4fed 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -2,3 +2,4 @@ export * from './dateToString' export { toast } from './toast/toast' export * from './loginRedirect' export * from './cookie' +export { uploadFile } from './uploadFile' diff --git a/src/utils/uploadFile.ts b/src/utils/uploadFile.ts new file mode 100644 index 0000000..b7dc32b --- /dev/null +++ b/src/utils/uploadFile.ts @@ -0,0 +1,14 @@ +export const uploadFile = ( + e: React.ChangeEvent, + setFile: (e: File) => void, + setString: (e: string) => void, +) => { + const selectedFile = (e.target.files as FileList)[0] + + if (selectedFile !== null) { + if (selectedFile?.name.match(/^.*\.(jpg|jpeg|png|heic|webp)$/)) { + setFile(selectedFile) + setString(URL.createObjectURL(selectedFile)) + } + } +}