diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index 34329978..9e4f18db 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -1,5 +1,6 @@ import { API } from '../types/universal'; import { remoteAboutAPI } from './remote/about'; +import { remoteAdminAPI } from './remote/admin'; import { remoteProjectAPI } from './remote/project'; import { remoteReviewAPI } from './remote/review'; import { remoteSopticleAPI } from './remote/sopticle'; diff --git a/src/lib/api/mock/about.ts b/src/lib/api/mock/about.ts deleted file mode 100644 index c89f9436..00000000 --- a/src/lib/api/mock/about.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { GetAboutInfoResponse, GetMembersInfoResponse } from '@src/lib/types/about'; -import { Part } from '@src/lib/types/universal'; - -const BANNER_SRC = 'https://i.ibb.co/84ybMKQ/image-76.png'; -const SRC = 'https://avatars.githubusercontent.com/u/47105088?v=4'; -const SRC_MAIN = 'https://avatars.githubusercontent.com/u/48249505?v=4'; - -const getAboutInfo = async (): Promise => ({ - aboutInfo: { - generation: 32, - title: 'GO SOPT', - bannerImage: BANNER_SRC, - coreValue: { - mainDescription: - '32기 Go SOPT는 협업을 중시하는 멤버들과 함께 나아가며\n끊임없이 실천하는 태도를 지향합니다.', - eachValues: [ - { - title: 'Protect', - description: '몸살으로부터\n주함을 지킵니다.', - src: SRC, - }, - { - title: 'Health', - description: '주함 항상\n건강해야 합니다.', - src: SRC, - }, - { - title: 'Retrieve', - description: '푹 쉬고 건강을\n되찾습니다.', - src: SRC, - }, - ], - }, - curriculums: { - [Part.PLAN]: SRC_MAIN, - [Part.DESIGN]: BANNER_SRC, - [Part.ANDROID]: BANNER_SRC, - [Part.IOS]: BANNER_SRC, - [Part.SERVER]: BANNER_SRC, - [Part.WEB]: SRC, - }, - records: { - memberCount: 200, - projectCount: 24, - studyCount: 300, - }, - }, -}); - -const getMemberInfo = async (): Promise => ({ - members: [ - { - id: 1, - name: '정건', - position: '회장', - currentProject: '건국대학교, 티키', - description: '동아리, 그 이상의 가치', - imageSrc: '/images/members/1.png', - gmail: 'president@sopt.org', - linkedin: '건-정-4aa699303/?trk=public-profile-join-page', - }, - { - id: 12, - name: '김송이', - position: '부회장', - currentProject: '아주대학교', - description: '빛나는 열정을 펼칠 수 있도록', - imageSrc: '/images/members/12.png', - gmail: 'v_president@sopt.org', - }, - { - id: 2, - name: '윤영서', - position: '총무', - currentProject: '동국대학교, doorip', - description: '열정을 쏟을 이곳에서 동료들과', - imageSrc: '/images/members/2.png', - gmail: 'manager@sopt.org', - github: '0seoYun', - }, - { - id: 5, - name: '이현진', - position: '운영 팀장', - currentProject: '가톨릭대학교, dateroad', - description: '매 순간을 소중한 추억으로!', - imageSrc: '/images/members/5.png', - gmail: 'master@sopt.org', - github: '2hyunjinn', - }, - { - id: 4, - name: '방민지', - position: '미디어 팀장', - currentProject: 'PINGLE', - description: '우리의 발자취를 기록하다.', - imageSrc: '/images/members/4.png', - gmail: 'media@sopt.org', - github: 'bangMinjI98', - }, - { - id: 3, - name: '김가연', - position: '메이커스 팀장', - currentProject: '이화여자대학교, OFFROAD', - description: 'SOPT다움을 찾아서', - imageSrc: '/images/members/3.png', - gmail: 'makers@sopt.org', - }, - { - id: 6, - name: '김소희', - position: '기획 파트장', - currentProject: '성신여자대학교', - description: '함께라면, 어떤 꿈도 현실이 됩니다', - imageSrc: '/images/members/6.png', - gmail: 'plan@sopt.org', - }, - { - id: 7, - name: '송예솔', - position: '디자인 파트장', - currentProject: 'Lecue, 선약', - description: '세상을 바꾸는 디자이너들', - imageSrc: '/images/members/7.png', - gmail: 'design@sopt.org', - behance: '208a08e8', - }, - { - id: 8, - name: '배지현', - position: '안드로이드 파트장', - currentProject: '건빵, PINGLE, dateroad', - description: '함께의 힘을 믿습니다.', - imageSrc: '/images/members/8.png', - gmail: 'develop@sopt.org', - github: 'jihyunniiii', - }, - { - id: 9, - name: '한지석', - position: 'iOS 파트장', - currentProject: '수원대학교, Recordy', - description: '나는 더 나은 미래를 위해 싸운다', - imageSrc: '/images/members/9.png', - gmail: 'develop@sopt.org', - github: 'sozohoy', - }, - { - id: 10, - name: '유태승', - position: '웹 파트장', - currentProject: '건국대학교, PICKPLE', - description: '저와 함께 도약할 준비 되셨나요?', - imageSrc: '/images/members/10.png', - gmail: 'develop@sopt.org', - github: 'gudusol', - }, - { - id: 11, - name: '계대환', - position: '서버 파트장', - currentProject: 'smeem', - description: '부드럽고, 성실하고, 능숙하게', - imageSrc: '/images/members/11.png', - gmail: 'develop@sopt.org', - }, - ], -}); - -export const mockAboutAPI = { - getAboutInfo, - getMemberInfo, -}; diff --git a/src/lib/api/mock/admin.ts b/src/lib/api/mock/admin.ts new file mode 100644 index 00000000..a1363dee --- /dev/null +++ b/src/lib/api/mock/admin.ts @@ -0,0 +1,539 @@ +import { + GetAboutpageResponse, + GetHomepageResponse, + GetRecruitpageResponse, +} from '@src/lib/types/admin'; + +const getRecruitpage = async (): Promise => ({ + generation: 34, + name: 'SOPT', + recruitHeaderImage: 'https://recruit_header.png', + brandingColor: { + main: '#FF0000', + low: '#CC0000', + high: '#FF3333', + point: '#FF9999', + }, + recruitSchedule: [ + { + type: 'OB', + schedule: { + applicationStartTime: '2024-01-01 09:00:00', + applicationEndTime: '2024-01-31 18:00:00', + applicationResultTime: '2024-02-01 12:00:00', + interviewStartTime: '2024-02-05 09:00:00', + interviewEndTime: '2024-02-05 18:00:00', + finalResultTime: '2024-02-10 12:00:00', + }, + }, + ], + recruitPartCurriculum: [ + { + part: '기획', + introduction: { + content: + '고객 시장 탐색부터 프로덕트 설계와 운영까지, 더블 다이아몬드 프로세스를 이용하여 더욱 탄탄한 프로덕트를 설계해볼 수 있습니다.', + preference: + '- 어려움과 고민을 편하게 나누고 공감할 수 있는 유대감과 열린 마음을 가진 분\n- 타 파트와 협업하며 존중과 신뢰를 바탕으로 원활한 팀워크를 만들어갈 수 있는 분\n- 도전과 성장의 자세로 한계를 두지 않고 앞으로 나아가고자 하는 열정이 있는 분\n- 본인이 가진 지식과 경험을 아낌없이 공유하고, 책임감을 가지고 팀 활동에 임할 수 있는 분', + }, + }, + { + part: '디자인', + introduction: { + content: + 'UX 설계, UI, GUI, 브랜딩, 인터랙션 디자인 등 프로덕트 디자인의 전 과정을 학습하며 기획자, 개발자와의 체계적인 협업 방식을 통해 문제의 근원을 주도적으로 해결합니다.', + preference: + '- 어려움과 고민을 편하게 나누고 공감할 수 있는 유대감과 열린 마음을 가진 분\n- 타 파트와 협업하며 존중과 신뢰를 바탕으로 원활한 팀워크를 만들어갈 수 있는 분\n- 도전과 성장의 자세로 한계를 두지 않고 앞으로 나아가고자 하는 열정이 있는 분\n- 본인이 가진 지식과 경험을 아낌없이 공유하고, 책임감을 가지고 팀 활동에 임할 수 있는 분', + }, + }, + { + part: '웹', + introduction: { + content: + 'React를 활용한 웹 서비스 개발을 기초부터 심화까지 학습하며, 협업 과정에서는 기획자, 디자이너, 서버 개발자와의 원활한 소통 방법을 배웁니다. 이를 통해 최종적으로 자신만의 웹 서비스를 출시하는 것을 목표로 합니다.', + preference: + '- 어려움과 고민을 편하게 나누고 공감할 수 있는 유대감과 열린 마음을 가진 분\n- 타 파트와 협업하며 존중과 신뢰를 바탕으로 원활한 팀워크를 만들어갈 수 있는 분\n- 도전과 성장의 자세로 한계를 두지 않고 앞으로 나아가고자 하는 열정이 있는 분\n- 본인이 가진 지식과 경험을 아낌없이 공유하고, 책임감을 가지고 팀 활동에 임할 수 있는 분', + }, + }, + { + part: '서버', + introduction: { + content: '설계와 의사소통을 배우며, Spring framework 로 application 을 구현합니다.', + preference: + '- 어려움과 고민을 편하게 나누고 공감할 수 있는 유대감과 열린 마음을 가진 분\n- 타 파트와 협업하며 존중과 신뢰를 바탕으로 원활한 팀워크를 만들어갈 수 있는 분\n- 도전과 성장의 자세로 한계를 두지 않고 앞으로 나아가고자 하는 열정이 있는 분\n- 본인이 가진 지식과 경험을 아낌없이 공유하고, 책임감을 가지고 팀 활동에 임할 수 있는 분', + }, + }, + { + part: 'iOS', + introduction: { + content: + 'UIKit & SwiftUI를 활용한 앱 개발을 진행하며 본인의 프로젝트를 직접 기획하고 개발하는 경험을 얻습니다.', + preference: + '- 어려움과 고민을 편하게 나누고 공감할 수 있는 유대감과 열린 마음을 가진 분\n- 타 파트와 협업하며 존중과 신뢰를 바탕으로 원활한 팀워크를 만들어갈 수 있는 분\n- 도전과 성장의 자세로 한계를 두지 않고 앞으로 나아가고자 하는 열정이 있는 분\n- 본인이 가진 지식과 경험을 아낌없이 공유하고, 책임감을 가지고 팀 활동에 임할 수 있는 분', + }, + }, + { + part: '안드로이드', + introduction: { + content: + 'Kotlin을 활용해 UI 구현 및 서버 통신 등 안드로이드 개발에 대한 전반적인 지식을 학습하며, 기획, 디자인, 서버 파트와의 협업을 통해 열정이 담긴 IT 서비스를 출시하는 경험을 할 수 있습니다.', + preference: + '- 어려움과 고민을 편하게 나누고 공감할 수 있는 유대감과 열린 마음을 가진 분\n- 타 파트와 협업하며 존중과 신뢰를 바탕으로 원활한 팀워크를 만들어갈 수 있는 분\n- 도전과 성장의 자세로 한계를 두지 않고 앞으로 나아가고자 하는 열정이 있는 분\n- 본인이 가진 지식과 경험을 아낌없이 공유하고, 책임감을 가지고 팀 활동에 임할 수 있는 분', + }, + }, + ], + recruitQuestion: [ + { + part: '기획', + questions: [ + { + question: '서비스 기획에 대한 경험과 경력이 없어도 괜찮을까요?', + answer: + '기획 파트에서는 자신의 아이디어를 발전시키고, 팀과 협업하며 성장할 수 있는 열정과 의지가 중요합니다. 총 8번의 세미나를 통해 기획에 대해 학습하고, 타 파트와 협업해볼 수 있는 기회가 제공되니, 기획에 대한 관심과 열정이 있다면, 누구나 지원 가능합니다.', + }, + { + question: '포트폴리오는 필수인가요?', + answer: '포트폴리오는 필수가 아닌 선택사항입니다. 제출 여부에 따른 불이익은 없습니다.', + }, + { + question: '개발지식이 필요한가요?', + answer: + '기획파트에서는 개발지식에 대한 이해도보다, 본인이 관심있는 시장에 대한 이해도를 더 중요시합니다. 개발지식은 미니 세미나 혹은 스터디를 통해 충분이 다뤄질 것이므로 개발지식에 대한 걱정은 하지 않으셔도 됩니다.', + }, + ], + }, + { + part: '디자인', + questions: [ + { + question: '디자인 경험이 없는데 지원해도 되나요?', + answer: + '이번 35기에는 UX설계, UI, GUI, 브랜딩, 인터랙션 디자인까지 프로덕트 디자인의 전반적인 과정을 모두 알려드릴 예정입니다. 디자인에 대한 열정을 가지고 끝까지 노력할 수 있다면, 누구나 지원 가능합니다.', + }, + { + question: '함께하고 싶은 디자이너는 어떤 모습인가요?', + answer: + '디자인 파트는 다른 직군과의 협업이 가장 많은 파트입니다. 실력적인 면도 중요하지만, 다른 디자이너, 기획자, 개발자와 원활한 커뮤니케이션이 가능한 디자이너와 함께하고 싶습니다. 타인의 피드백을 두려워하지 않는 열린 마음을 가지신 분은 정말 환영입니다!', + }, + { + question: '어떤 디자인 툴을 사용하나요?', + answer: + '기획자, 개발자와의 협업을 위한 툴인 Figma를 주로 사용합니다. 그리고 그래픽 작업 시에는 Adobe Illustrator, Adobe Photoshop와 3D 그래픽을 위한 Cinema 4D, Blender 등을 사용할 예정입니다. 그리고 프로토타이핑 툴로 Protopie도 사용할 예정이니, 미리 설치해 두시면 좋습니다.', + }, + ], + }, + { + part: '안드로이드', + questions: [ + { + question: + '안드로이드 핸드폰을 사용하고 있지 않은데 지원이 가능한가요? 맥북을 사용하고 있는데 지원이 가능한가요?', + answer: + '가능합니다! 안드로이드 핸드폰이나 공기계가 없어도 가상 디바이스를 통해 본인이 개발한 앱을 확인해볼 수 있습니다. 또한, MacOS 환경에서의 안드로이드 개발도 가능합니다.', + }, + { + question: '개발 경험이 없는데 지원해도 되나요?', + answer: + '물론입니다! 안드로이드 개발에 대한 흥미와 배우고자 하는 열정을 가지고 계신다면 주저말고 지원해주세요. 총 8번의 정규 세미나와 미니 세미나 등을 통해 안드로이드 기초 지식부터 함께 배워가며 하나의 서비스를 완성하는 안드로이드 개발자로 성장할 수 있습니다.', + }, + { + question: '어떤 UI 기술 스택을 사용해 세미나를 진행하나요?', + answer: + '35기 안드로이드 파트의 정규 세미나에서는 Jetpack Compose를 활용하여 UI를 구현하는 방법을 배웁니다. 하지만 XML을 아예 다루지 않는 것은 아닙니다. 미니 세미나를 통해 XML을 활용해 UI를 구현하는 방법을 소개할 예정입니다.', + }, + ], + }, + { + part: 'iOS', + questions: [ + { + question: '맥북이 필수인가요?', + answer: + 'iOS 앱 개발을 위해 Swift로 세미나를 진행합니다. 이를 위해서는 맥북이 필수입니다!', + }, + { + question: '개발 경험이 아예 없는데 지원해도 되나요?', + answer: + '매주 세미나를 통해 iOS의 기본부터 함께 배워가기 때문에, 개발경험은 상관이 없습니다. 하고자 하는 열정이 가장 중요한 요소라고 생각합니다.', + }, + { + question: '어떤 기술로 UI를 구현하나요?', + answer: '이번 iOS 파트는 UIKit과 SwiftUI를 활용한 앱 개발을 진행합니다.', + }, + ], + }, + { + part: '웹', + questions: [ + { + question: '개발 실력이 부족한데 지원해도 되나요?', + answer: + '물론입니다! 웹 개발에 대한 열정과 끈기, 그리고 성장하고자 하는 의지만 있다면 충분합니다. 함께 배우고 성장하는 과정에서 실력을 키워나가실 수 있습니다.', + }, + { + question: '세미나 진행은 어떻게 되나요?', + answer: + '웹 프론트엔드 개발을 학습하는 8주간의 정기 세미나가 진행됩니다. 실습과 다양한 과제가 함께하는 세미나를 통해, 프론트엔드 개발에 필요한 역량을 체계적으로 쌓을 수 있습니다. 자세한 내용은 35기 커리큘럼을 참고해 주세요!', + }, + { + question: '어떤 스터디가 있나요?', + answer: + '대표적으로 React, JavaScript, TypeScript, 그리고 웹 심화 스터디가 개설됩니다. 이 외에도 자신이 원하는 스터디를 자유롭게 개설하고 진행할 수 있습니다.', + }, + ], + }, + { + part: '서버', + questions: [ + { + question: '어떤 언어로 진행되나요?', + answer: + '세미나는 java 로 진행됩니다. 다만 언어적 장벽은 없어 지원 및 학습에는 제한이 없습니다.', + }, + { + question: '세미나는 어떻게 진행되나요?', + answer: + '요구사항 분석과 팀워크를 다지고, 개발 기본에 집중해 프로젝트를 완성할 수 있도록 진행할 예정입니다.', + }, + { + question: '개발 경험이 없는데 지원해도 되나요?', + answer: '경험은 상관없으나, 끝까지 따라오실 수 있는 분을 환영합니다.', + }, + ], + }, + ], +}); + +const getAboutpage = async (): Promise => ({ + generation: 34, + name: 'SOPT', + headerImage: + 'https://s3.ap-northeast-2.amazonaws.com/sopt.org/admin/origin/about/35th/header_fix.png', + brandingColor: { + main: '#FF0000', + low: '#CC0000', + high: '#FF3333', + point: '#FF9999', + }, + coreValue: [ + { + value: '용기', + description: '새로운 도전을 위해 과감히 용기내는 사람', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt.org/admin/origin/about/35th/%E1%84%8B%E1%85%AD%E1%86%BC%E1%84%80%E1%85%B5_1.png', + }, + { + value: '몰입', + description: '포기하지 않고 깊이 몰입하는 사람', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt.org/admin/origin/about/35th/%E1%84%86%E1%85%A9%E1%86%AF%E1%84%8B%E1%85%B5%E1%86%B8_1.png', + }, + { + value: '화합', + description: '서로를 배려하며 함께 화합하는 사람', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt.org/admin/origin/about/35th/%E1%84%92%E1%85%AA%E1%84%92%E1%85%A1%E1%86%B8_1.png', + }, + ], + partCurriculum: [ + { + part: '기획', + curriculums: [ + '기획 1주차', + '기획 2주차', + '기획 3주차', + '기획 4주차', + '기획 5주차', + '기획 6주차', + '기획 7주차', + '기획 8주차', + ], + }, + { + part: '디자인', + curriculums: [ + '디자인 1주차', + '디자인 2주차', + '디자인 3주차', + '디자인 4주차', + '디자인 5주차', + '디자인 6주차', + '디자인 7주차', + '디자인 8주차', + ], + }, + { + part: '웹', + curriculums: [ + '웹 1주차', + '웹 2주차', + '웹 3주차', + '웹 4주차', + '웹 5주차', + '웹 6주차', + '웹 7주차', + '웹 8주차', + ], + }, + { + part: '서버', + curriculums: [ + '서버 1주차', + '서버 2주차', + '서버 3주차', + '서버 4주차', + '서버 5주차', + '서버 6주차', + '서버 7주차', + '서버 8주차', + ], + }, + { + part: 'iOS', + curriculums: [ + 'iOS 1주차', + 'iOS 2주차', + 'iOS 3주차', + 'iOS 4주차', + 'iOS 5주차', + 'iOS 6주차', + 'iOS 7주차', + 'iOS 8주차', + ], + }, + { + part: '안드로이드', + curriculums: [ + '안드로이드 1주차', + '안드로이드 2주차', + '안드로이드 3주차', + '안드로이드 4주차', + '안드로이드 5주차', + '안드로이드 6주차', + '안드로이드 7주차', + '안드로이드 8주차', + ], + }, + ], + member: [ + { + name: '정건', + role: '회장', + affiliation: '건국대학교, 티키', + introduction: '동아리, 그 이상의 가치', + profileImage: '/images/members/1.png', + sns: { + email: 'president@sopt.org', + linkedin: 'https://www.linkedin.com/in/건-정-4aa699303/?trk=public-profile-join-page', + }, + }, + { + name: '김송이', + role: '부회장', + affiliation: '아주대학교', + introduction: '빛나는 열정을 펼칠 수 있도록', + profileImage: '/images/members/12.png', + sns: { + email: 'v_president@sopt.org', + }, + }, + { + name: '윤영서', + role: '총무', + affiliation: '동국대학교, doorip', + introduction: '열정을 쏟을 이곳에서 동료들과', + profileImage: '/images/members/2.png', + sns: { + email: 'manager@sopt.org', + github: 'https://github.com/0seoYun', + }, + }, + { + name: '이현진', + role: '운영 팀장', + affiliation: '가톨릭대학교, dateroad', + introduction: '매 순간을 소중한 추억으로!', + profileImage: '/images/members/5.png', + sns: { + email: 'master@sopt.org', + github: 'https://github.com/2hyunjinn', + }, + }, + { + name: '방민지', + role: '미디어 팀장', + affiliation: 'PINGLE', + introduction: '우리의 발자취를 기록하다.', + profileImage: '/images/members/4.png', + sns: { + email: 'media@sopt.org', + github: 'https://github.com/bangMinjI98', + }, + }, + { + name: '김가연', + role: '메이커스 팀장', + affiliation: '이화여자대학교, OFFROAD', + introduction: 'SOPT다움을 찾아서', + profileImage: '/images/members/3.png', + sns: { + email: 'makers@sopt.org', + }, + }, + { + name: '김소희', + role: '기획', + affiliation: '성신여자대학교', + introduction: '함께라면, 어떤 꿈도 현실이 됩니다', + profileImage: '/images/members/6.png', + sns: { + email: 'plan@sopt.org', + }, + }, + { + name: '송예솔', + role: '디자인', + affiliation: 'Lecue, 선약', + introduction: '세상을 바꾸는 디자이너들', + profileImage: '/images/members/7.png', + sns: { + email: 'design@sopt.org', + behance: 'https://www.behance.net/208a08e8', + }, + }, + { + name: '배지현', + role: '안드로이드', + affiliation: '건빵, PINGLE, dateroad', + introduction: '함께의 힘을 믿습니다.', + profileImage: '/images/members/8.png', + sns: { + email: 'develop@sopt.org', + github: 'https://github.com/jihyunniiii', + }, + }, + { + name: '한지석', + role: 'iOS', + affiliation: '수원대학교, Recordy', + introduction: '나는 더 나은 미래를 위해 싸운다', + profileImage: '/images/members/9.png', + sns: { + email: 'develop@sopt.org', + github: 'https://github.com/sozohoy', + }, + }, + { + name: '유태승', + role: '웹', + affiliation: '건국대학교, PICKPLE', + introduction: '저와 함께 도약할 준비 되셨나요?', + profileImage: '/images/members/10.png', + sns: { + email: 'develop@sopt.org', + github: 'https://github.com/gudusol', + }, + }, + { + name: '계대환', + role: '서버', + affiliation: 'smeem', + introduction: '부드럽고, 성실하고, 능숙하게', + profileImage: '/images/members/11.png', + sns: { + email: 'develop@sopt.org', + }, + }, + ], +}); + +const getHomepage = async (): Promise => ({ + generation: 34, + name: 'SOPT', + brandingColor: { + main: '#FF0000', + low: '#CC0000', + high: '#FF3333', + point: '#FF9999', + }, + mainButton: { + text: '지원하기', + keyColor: '#FF0000', + subColor: '#CC0000', + }, + partIntroduction: [ + { + part: '기획', + description: + 'Test-린스타트업에 기초해 고객 문제정의 - 고객 발굴 - 검증 과정을 거쳐 비즈니스 전략과 핵심지표 설계까지 고객 관점 프로덕트를 만들고 운영하기 위한 모든 과정을 다룹니다.', + }, + { + part: '디자인', + description: + 'Test-Figma를 활용하여 UX/UI 디자인의 전반적인 프로세스를 배우고, 세미나에서 학습한 UX심리학, 브랜딩, 디자인 시스템 등의 이론을 기획자, 개발자와의 협업 과정에 적용해 보며 근거에 기반한 문제 해결을 경험합니다.', + }, + { + part: '안드로이드', + description: + 'Test-Kotlin 언어를 활용해 안드로이드 UI(XML/Compose) 구현 기초/심화, 서버 통신 등 앱 제작에 필요한 내용들을 배웁니다. 세미나, 정규 미미나를 통해 다양한 문제 상황을 해결할 수 있는 역량을 기르며 타 파트와의 협업을 통해 직접 서비스를 제작하는 경험을 얻을 수 있습니다.', + }, + { + part: 'iOS', + description: + 'Swift와 UI Kit를 이용해 iOS 앱 서비스를 만들 수 있습니다. iOS가 처음인 분들을 위한왕초보 스터디와 보충 세미나, 실력적 도약을 위한 심화 세미나까지 존재합니다.', + }, + { + part: '웹', + description: + 'HTML, CSS, JavaScript로 기초를 다지고 React를 활용해 UI구현, 서버 통신, 다양한 라이브러리 사용 등 웹 서비스 개발에 필요한 역량들을 기초부터 심화까지 학습합니다. 또한 기획자, 디자이너, 서버 개발자와의 협업을 통해 나만의 웹 서비스를 만드는 경험을 해보실 수 있습니다.', + }, + { + part: '서버', + description: + '세미나를 통해 Spring 프레임워크, 관계형 데이터베이스, AWS, Docker를 기반으로 실제 서비스를 위한 서버 구축의 전반적인 내용을 배웁니다.또한 스터디와 코드리뷰, 미니 세미나를 등 개발 실력의 도약과 기획파트, 디자인파트, 클라이언트 파트와 협업을 통해 협업 방식을 익힐 수 있습니다.', + }, + ], + latestNews: [ + { + id: 36, + title: 'MIND 23', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt.org/develop/news/a906be00-0b6e-4ca6-bedc-69cc734f4318_%E1%84%8E%E1%85%AC%E1%84%8C%E1%85%AE%E1%84%8B%E1%85%AD%E1%86%BC.png', + link: 'https://disquiet.io/product/mind-23-%EC%98%A4%EB%8A%98%EB%8F%84-%EB%A9%88%EC%B6%94%EC%A7%80-%EC%95%8A%EB%8A%94-it%EC%9D%B8%EB%93%A4', + }, + { + id: 40, + title: 'WUZOO', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt.org/develop/news/3a3df4a9-c7f4-48ce-a9af-14a62a139c77_%E1%84%8E%E1%85%AC%E1%84%8C%E1%85%AE%E1%84%8B%E1%85%AD%E1%86%BC.png', + link: '', + }, + { + id: 41, + title: 'WUZOO', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt.org/develop/news/298cf8d3-0d17-4e71-919f-b70baaa260ec_%E1%84%8E%E1%85%AC%E1%84%8C%E1%85%AE%E1%84%8B%E1%85%AD%E1%86%BC.png', + link: '', + }, + { + id: 42, + title: 'wuzoo', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt.org/develop/news/781d5e93-d98b-4a7b-a2d7-b20a4473c2d4_%E1%84%8E%E1%85%AC%E1%84%8C%E1%85%AE%E1%84%8B%E1%85%AD%E1%86%BC.png', + link: '', + }, + { + id: 43, + title: 'wuzoo', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt.org/develop/news/500642d1-727f-4041-9935-f472e3913b79_%E1%84%8E%E1%85%AC%E1%84%8C%E1%85%AE%E1%84%8B%E1%85%AD%E1%86%BC.png', + link: '', + }, + ], +}); + +export const mockAdminAPI = { + getRecruitpage, + getAboutpage, + getHomepage, +}; diff --git a/src/lib/api/remote/about.ts b/src/lib/api/remote/about.ts index ed93057b..62a08b6d 100644 --- a/src/lib/api/remote/about.ts +++ b/src/lib/api/remote/about.ts @@ -1,56 +1,46 @@ import { BASE_URL, DEFAULT_TIMEOUT } from '@src/lib/constants/client'; -import { GetAboutInfoResponse, GetMembersInfoResponse } from '@src/lib/types/about'; -import { CoreValueResponseDto } from '@src/lib/types/dto'; -import { Part } from '@src/lib/types/universal'; +import { GetAboutInfoResponse } from '@src/lib/types/about'; +import { CoreValueType } from '@src/lib/types/admin'; import axios from 'axios'; -import { mockAboutAPI } from '../mock/about'; +import { remoteAdminAPI } from './admin'; const client = axios.create({ baseURL: BASE_URL, timeout: DEFAULT_TIMEOUT, }); -const CURRENT_GENERATION = 35; - const getAboutInfo = async (): Promise => { - const { data: dataCurrent } = await client.get(`/aboutsopt?generation=${CURRENT_GENERATION}`); - // const { data: dataPrev } = await client.get(`/aboutsopt?generation=${CURRENT_GENERATION - 1}`); + const dataCurrent = await remoteAdminAPI.getAboutpage(); + // TODO: 기존 API 의존성 떼기 + const { data: dataPrev } = await client.get( + `/aboutsopt?generation=${dataCurrent.generation - 1}`, + ); return { aboutInfo: { - generation: dataCurrent.aboutSopt.id, - title: dataCurrent.aboutSopt.title, - bannerImage: dataCurrent.aboutSopt.bannerImage, + generation: dataCurrent.generation, + title: dataCurrent.name, + brandingColor: dataCurrent.brandingColor, + bannerImage: dataCurrent.headerImage, coreValue: { - mainDescription: dataCurrent.aboutSopt.coreDescription, - eachValues: dataCurrent.aboutSopt.coreValues.map((coreValue: CoreValueResponseDto) => ({ - title: coreValue.title, - description: coreValue.subTitle, - src: coreValue.imageUrl, + mainDescription: `${dataCurrent.generation}기 ${dataCurrent.name}가 함께 나아가고 싶은 사람입니다.`, + eachValues: dataCurrent.coreValue.map((coreValue: CoreValueType) => ({ + title: coreValue.value, + description: coreValue.description, + src: coreValue.image, })), }, - curriculums: { - [Part.PLAN]: dataCurrent.aboutSopt.planCurriculum, - [Part.DESIGN]: dataCurrent.aboutSopt.designCurriculum, - [Part.ANDROID]: dataCurrent.aboutSopt.androidCurriculum, - [Part.IOS]: dataCurrent.aboutSopt.iosCurriculum, - [Part.SERVER]: dataCurrent.aboutSopt.serverCurriculum, - [Part.WEB]: dataCurrent.aboutSopt.webCurriculum, - }, + curriculums: dataCurrent.partCurriculum, records: { - memberCount: dataCurrent.activitiesRecords.activitiesMemberCount, - projectCount: dataCurrent.activitiesRecords.projectCounts, - studyCount: dataCurrent.activitiesRecords.studyCounts, + memberCount: dataPrev.activitiesRecords.activitiesMemberCount, + projectCount: dataPrev.activitiesRecords.projectCounts, + studyCount: dataPrev.activitiesRecords.studyCounts, }, + members: dataCurrent.member, }, }; }; -const getMemberInfo = async (): Promise => { - return mockAboutAPI.getMemberInfo(); // todo : implement server connection -}; - export const remoteAboutAPI = { getAboutInfo, - getMemberInfo, }; diff --git a/src/lib/api/remote/admin.ts b/src/lib/api/remote/admin.ts new file mode 100644 index 00000000..007eafeb --- /dev/null +++ b/src/lib/api/remote/admin.ts @@ -0,0 +1,23 @@ +import { BASE_URL } from '@src/lib/constants/client'; +import axios from 'axios'; + +const adminClient = axios.create({ baseURL: `${BASE_URL}v2` }); + +const getHomepage = async () => { + const { data } = await adminClient.get('/homepage'); + return data; +}; +const getAboutpage = async () => { + const { data } = await adminClient.get('/homepage/about'); + return data; +}; +const getRecruitpage = async () => { + const { data } = await adminClient.get('/homepage/recruit'); + return data; +}; + +export const remoteAdminAPI = { + getHomepage, + getAboutpage, + getRecruitpage, +}; diff --git a/src/lib/types/about.ts b/src/lib/types/about.ts index 5b1aee29..fe81861b 100644 --- a/src/lib/types/about.ts +++ b/src/lib/types/about.ts @@ -1,4 +1,4 @@ -import { Part } from './universal'; +import { MemberType, PartCurriculumType } from './admin'; export interface CoreValueType { title: string; @@ -6,19 +6,6 @@ export interface CoreValueType { src: string; } -export interface MemberType { - id: number; - name: string; - position: PositionType; - currentProject: string; - description?: string; - imageSrc?: string; - gmail?: string; - linkedin?: string; - github?: string; - behance?: string; -} - export type PositionType = | '회장' | '부회장' @@ -37,20 +24,23 @@ export type PositionType = export interface AboutInfoType { generation: number; title: string; + brandingColor: { + main: string; + low: string; + high: string; + point: string; + }; bannerImage: string; coreValue: { mainDescription: string; eachValues: CoreValueType[]; }; - curriculums: Record; + curriculums: Array; records: { memberCount: number; projectCount: number; studyCount: number; }; -} - -export interface GetMembersInfoResponse { members: MemberType[]; } @@ -60,7 +50,6 @@ export interface GetAboutInfoResponse { export interface AboutAPI { getAboutInfo(): Promise; - getMemberInfo(): Promise; } export type RecordTitle = '활동 멤버' | '프로젝트' | '스터디'; diff --git a/src/lib/types/admin.ts b/src/lib/types/admin.ts new file mode 100644 index 00000000..4f2fd346 --- /dev/null +++ b/src/lib/types/admin.ts @@ -0,0 +1,116 @@ +export type PartType = '기획' | '디자인' | '안드로이드' | 'iOS' | '웹' | '서버'; +export type ExecutivesType = + | '회장' + | '부회장' + | '총무' + | '운영 팀장' + | '미디어 팀장' + | '메이커스 팀장'; + +export interface PartIntroType { + part: string; + description: string; +} +export interface LatestNewsType { + id: number; + title: string; + image: string; + link: string; +} +export interface GetHomepageResponse { + generation: number; + name: string; + brandingColor: { + main: string; + low: string; + high: string; + point: string; + }; + mainButton: { + text: string; + keyColor: string; + subColor: string; + }; + partIntroduction: PartIntroType[]; + latestNews: LatestNewsType[]; +} + +export interface MemberType { + role: ExecutivesType | PartType; + name: string; + affiliation: string; + introduction: string; + profileImage: string; + sns: { + email?: string; + linkedin?: string; + github?: string; + behance?: string; + }; +} + +export interface PartCurriculumType { + part: PartType; + curriculums: string[]; +} +export interface CoreValueType { + value: string; + description: string; + image: string; +} +export interface GetAboutpageResponse { + generation: number; + name: string; + headerImage: string; + brandingColor: { + main: string; + low: string; + high: string; + point: string; + }; + coreValue: CoreValueType[]; + partCurriculum: PartCurriculumType[]; + member: MemberType[]; +} + +export interface PartInfoType { + part: PartType; + introduction: { + content: string; + preference: string; + }; +} +export interface QnAType { + question: string; + answer: string; +} +export interface PartQuestionType { + part: PartType; + questions: QnAType[]; +} +export interface ScheduleType { + applicationStartTime: string; + applicationEndTime: string; + applicationResultTime: string; + interviewStartTime: string; + interviewEndTime: string; + finalResultTime: string; +} +export interface RecruitScheduleType { + type: 'OB' | 'YB'; + schedule: ScheduleType; +} +export interface GetRecruitpageResponse { + generation: number; + name: string; + recruitHeaderImage: string; + brandingColor: { + main: string; + low: string; + high: string; + point: string; + }; + recruitSchedule: RecruitScheduleType[]; + recruitPartCurriculum: PartInfoType[]; + recruitQuestion: PartQuestionType[]; +} diff --git a/src/pages/about.tsx b/src/pages/about.tsx index 5557da35..bffc3f9c 100644 --- a/src/pages/about.tsx +++ b/src/pages/about.tsx @@ -1,58 +1 @@ -import styled from '@emotion/styled'; -import { InferGetServerSidePropsType } from 'next'; -import dynamic from 'next/dynamic'; -import PageLayout from '@src/components/common/PageLayout'; -import { api } from '@src/lib/api'; -import { - Banner, - CoreValueSection, - CurriculumSection, - RecordSection, -} from '@src/views/AboutPage/components'; - -const MemberSection = dynamic(() => import('@src/views/AboutPage/components/Member/Section')); - -const AboutPage = ({ - aboutInfo, - memberInfo, -}: InferGetServerSidePropsType) => { - return ( - - - - - - - - - - ); -}; - -export const getStaticProps = async () => { - const aboutInfo = await api.aboutAPI.getAboutInfo(); - const memberInfo = await api.aboutAPI.getMemberInfo(); - - return { - props: { - aboutInfo, - memberInfo, - }, - revalidate: 120, - }; -}; - -export default AboutPage; - -const Root = styled.main` - display: flex; - flex-direction: column; - justify-content: center; - min-height: 100vh; -`; +export { default } from '@src/views/AboutPage'; diff --git a/src/views/AboutPage/components/Banner/index.tsx b/src/views/AboutPage/components/Banner/index.tsx index b09548c9..02a567c8 100644 --- a/src/views/AboutPage/components/Banner/index.tsx +++ b/src/views/AboutPage/components/Banner/index.tsx @@ -3,7 +3,6 @@ import * as S from './style'; interface BannerProps { imageSrc: string; - title: string; } const Banner = (props: BannerProps) => { diff --git a/src/views/AboutPage/components/CoreValue/Item/index.tsx b/src/views/AboutPage/components/CoreValue/Item/index.tsx index b56b4b69..1156012d 100644 --- a/src/views/AboutPage/components/CoreValue/Item/index.tsx +++ b/src/views/AboutPage/components/CoreValue/Item/index.tsx @@ -1,6 +1,7 @@ -import { useState } from 'react'; +import { useContext, useState } from 'react'; import useInView from '@src/hooks/useInView'; import { CoreValueType } from '@src/lib/types/about'; +import { BrandingColorContext } from '@src/views/AboutPage'; import * as S from './style'; type CoreValueProps = { @@ -11,7 +12,7 @@ type CoreValueProps = { const CoreValueItem = ({ coreValue, order }: CoreValueProps) => { const [isHovered, setIsHovered] = useState(false); const { isInView, ref: wrapperRef } = useInView(); - + const { point } = useContext(BrandingColorContext); return ( { - {order + 1} + {order + 1} {coreValue.title} {coreValue.description} diff --git a/src/views/AboutPage/components/CoreValue/Item/style.ts b/src/views/AboutPage/components/CoreValue/Item/style.ts index a1337ffd..277874fa 100644 --- a/src/views/AboutPage/components/CoreValue/Item/style.ts +++ b/src/views/AboutPage/components/CoreValue/Item/style.ts @@ -86,7 +86,7 @@ export const ValueTop = styled.div` } `; -export const ValueNumber = styled.div` +export const ValueNumber = styled.div<{ pointColor: string }>` display: flex; justify-content: center; align-items: center; @@ -95,7 +95,7 @@ export const ValueNumber = styled.div` width: 25.154px; height: 25.154px; border-radius: 6px; - background: rgba(58, 81, 109, 1); + background: ${({ pointColor }) => pointColor}; color: ${colors.white}; font-family: SUIT; diff --git a/src/views/AboutPage/components/Curriculum/Content/index.tsx b/src/views/AboutPage/components/Curriculum/Content/index.tsx index f8a036ae..b00d00e9 100644 --- a/src/views/AboutPage/components/Curriculum/Content/index.tsx +++ b/src/views/AboutPage/components/Curriculum/Content/index.tsx @@ -1,36 +1,43 @@ import { track } from '@amplitude/analytics-browser'; -import Image from 'next/image'; -import { useState } from 'react'; +import { useContext, useState } from 'react'; +import { PartCurriculumType } from '@src/lib/types/admin'; import { Part, TabType } from '@src/lib/types/universal'; -import { parsePartToKorean } from '@src/lib/utils/parsePartToKorean'; +import { parseStringToPart } from '@src/lib/utils/parseStringToPart'; +import { BrandingColorContext } from '@src/views/AboutPage'; import TabBar from '../../@common/TabBar'; import * as S from './style'; type CurriculumContentProps = { - curriculums: Record; + curriculums: PartCurriculumType[]; }; const CurriculumContent = ({ curriculums }: CurriculumContentProps) => { const [currentPart, setCurrentPart] = useState(Part.PLAN); + const { main } = useContext(BrandingColorContext); const handleTabClick = (tab: TabType) => { setCurrentPart(tab.value); track('click_about_part', { part: tab.label }); }; + // formatter + const curriObj = Object.fromEntries( + curriculums.map((el) => [parseStringToPart(el.part), el.curriculums]), + ); + return ( - - {`${parsePartToKorean(currentPart)}파트 - + + {curriObj[currentPart].map((v, idx) => ( + + + {String(idx + 1).padStart(2, '0')} + + {v} + + ))} + ); }; diff --git a/src/views/AboutPage/components/Curriculum/Content/style.ts b/src/views/AboutPage/components/Curriculum/Content/style.ts index 61f60c42..4b0d871c 100644 --- a/src/views/AboutPage/components/Curriculum/Content/style.ts +++ b/src/views/AboutPage/components/Curriculum/Content/style.ts @@ -38,3 +38,29 @@ export const ImageWrapper = styled.div` height: 238.901px; } `; + +export const CurriList = styled.ul` + display: flex; + flex-direction: column; + gap: 12px; + align-items: center; + padding: 70px; +`; +export const CurriItem = styled.li` + width: 80%; + display: flex; + gap: 10px; + padding: 20px 41px; + background-color: #222528; + border-radius: 10px; + color: white; + font-family: SUIT; + font-size: 25px; + font-style: normal; + font-weight: 500; + line-height: normal; + letter-spacing: -0.5px; +`; +export const CurriHighlight = styled.span<{ mainColor: string }>` + color: ${({ mainColor }) => mainColor}; +`; diff --git a/src/views/AboutPage/components/Curriculum/Section/index.tsx b/src/views/AboutPage/components/Curriculum/Section/index.tsx index a5d2abde..e21b290a 100644 --- a/src/views/AboutPage/components/Curriculum/Section/index.tsx +++ b/src/views/AboutPage/components/Curriculum/Section/index.tsx @@ -1,10 +1,10 @@ -import { Part } from '@src/lib/types/universal'; +import { PartCurriculumType } from '@src/lib/types/admin'; import SectionTop from '@src/views/AboutPage/components/@common/SectionTop'; import CurriculumContent from '../Content'; import * as S from './style'; type CurriculumSectionProps = { - curriculums: Record; + curriculums: PartCurriculumType[]; }; const CurriculumSection = ({ curriculums }: CurriculumSectionProps) => { diff --git a/src/views/AboutPage/components/Member/Card/index.tsx b/src/views/AboutPage/components/Member/Card/index.tsx index 0fa03527..46c12a20 100644 --- a/src/views/AboutPage/components/Member/Card/index.tsx +++ b/src/views/AboutPage/components/Member/Card/index.tsx @@ -3,56 +3,51 @@ import { ReactComponent as IcGithub } from '@src/assets/icons/ic_github.svg'; import { ReactComponent as IcLinkedin } from '@src/assets/icons/ic_linkedin.svg'; import { ReactComponent as IcMail } from '@src/assets/icons/mail.svg'; import NullImage from '@src/assets/images/null_image.png'; -import { MemberType } from '@src/lib/types/about'; +import { MemberType } from '@src/lib/types/admin'; import * as St from './style'; -type MemberCardProps = Omit; - const MemberCard = ({ name, - position, - description, - currentProject, - imageSrc, - gmail, - linkedin, - github, - behance, -}: MemberCardProps) => { + role, + introduction, + affiliation, + profileImage, + sns: { email, linkedin, github, behance }, +}: MemberType) => { return ( - {position} + {role} {name} - {currentProject} - {description || '-'} + {affiliation} + {introduction || '-'} - {gmail && ( - + {email && ( + )} {linkedin && ( - + )} {github && ( - + )} {behance && ( - + )} diff --git a/src/views/AboutPage/components/Member/Section/index.tsx b/src/views/AboutPage/components/Member/Section/index.tsx index 6e0bc04a..43fb09c0 100644 --- a/src/views/AboutPage/components/Member/Section/index.tsx +++ b/src/views/AboutPage/components/Member/Section/index.tsx @@ -1,5 +1,5 @@ import Flex from '@src/components/common/Flex'; -import { MemberType } from '@src/lib/types/about'; +import { MemberType } from '@src/lib/types/admin'; import MemberCard from '@src/views/AboutPage/components/Member/Card'; import SectionTop from '../../@common/SectionTop'; import * as St from './style'; @@ -22,41 +22,23 @@ const MemberSection = ({ generation, members }: MemberSectionProps) => { korTitle={`${generation}기 임원진`} description="200명의 활동 회원들이 열정을 외칠 수 있도록, 35기 AND SOPT를 이끄는 임원진들이에요." /> - {/* TODO : 서버에서 description을 받아오도록 수정 */} - {/* {errorContent} */} - {members.map( - ({ - id, - name, - position, - description, - currentProject, - imageSrc, - gmail, - linkedin, - github, - behance, - }) => ( - - ), - )} + {members.map(({ name, role, introduction, affiliation, profileImage, sns }) => ( + + ))} diff --git a/src/views/AboutPage/components/Record/List/index.tsx b/src/views/AboutPage/components/Record/List/index.tsx index 556a33fe..091e5da8 100644 --- a/src/views/AboutPage/components/Record/List/index.tsx +++ b/src/views/AboutPage/components/Record/List/index.tsx @@ -1,31 +1,31 @@ -import { AboutInfoType } from '@src/lib/types/about'; import RecordItem from '../Item'; import * as St from './style'; -type RecordListProps = Pick; - -const RecordList = (props: RecordListProps) => { +const RecordList = ({ + records, +}: { + records: { + memberCount: number; + projectCount: number; + studyCount: number; + }; +}) => { return ( - + ); }; diff --git a/src/views/AboutPage/components/Record/Section/index.tsx b/src/views/AboutPage/components/Record/Section/index.tsx index 83eabb1b..c9112ecf 100644 --- a/src/views/AboutPage/components/Record/Section/index.tsx +++ b/src/views/AboutPage/components/Record/Section/index.tsx @@ -5,11 +5,11 @@ import * as St from './style'; type RecordSectionProps = Pick; -const RecordSection = (props: RecordSectionProps) => { +const RecordSection = ({ generation, records }: RecordSectionProps) => { return ( - - + + ); }; diff --git a/src/views/AboutPage/constant/emptyMembers.ts b/src/views/AboutPage/constant/emptyMembers.ts deleted file mode 100644 index caa2bda4..00000000 --- a/src/views/AboutPage/constant/emptyMembers.ts +++ /dev/null @@ -1,14 +0,0 @@ -import NullImage from '@src/assets/images/null_image.png'; -import { MemberType } from '@src/lib/types/about'; - -export const emptyMembers = (count: number): MemberType[] => { - return Array.from({ length: count }).map((_, idx) => ({ - id: idx, - name: '-', - position: '', - currentProject: '', - description: '-', - part: '-', - imageSrc: NullImage.src, - })); -}; diff --git a/src/views/AboutPage/hooks/queries/useGetMemeber.ts b/src/views/AboutPage/hooks/queries/useGetMemeber.ts deleted file mode 100644 index 70e53942..00000000 --- a/src/views/AboutPage/hooks/queries/useGetMemeber.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { api } from '@src/lib/api'; - -const getMember = async () => { - const response = await api.aboutAPI.getMemberInfo(); - - return response; -}; - -export default function useGetMember() { - const { data, isLoading } = useQuery({ - queryKey: ['member'], - queryFn: getMember, - }); - - return { data, isLoading }; -} diff --git a/src/views/AboutPage/index.tsx b/src/views/AboutPage/index.tsx new file mode 100644 index 00000000..ee46c5aa --- /dev/null +++ b/src/views/AboutPage/index.tsx @@ -0,0 +1,59 @@ +import styled from '@emotion/styled'; +import dynamic from 'next/dynamic'; +import { useQuery } from '@tanstack/react-query'; +import { createContext } from 'react'; +import PageLayout from '@src/components/common/PageLayout'; +import { remoteAdminAPI } from '@src/lib/api/remote/admin'; +import { CoreValueType, GetAboutpageResponse } from '@src/lib/types/admin'; +import { + Banner, + CoreValueSection, + CurriculumSection, + RecordSection, +} from '@src/views/AboutPage/components'; + +const MemberSection = dynamic(() => import('@src/views/AboutPage/components/Member/Section')); + +export const BrandingColorContext = createContext({ + main: '', + low: '', + high: '', + point: '', +}); +const AboutPage = () => { + const { data: adminData } = useQuery({ + queryKey: ['homepage', 'about'], + queryFn: remoteAdminAPI.getAboutpage, + }); + + if (!adminData) return; + return ( + + + + + ({ + title: coreValue.value, + description: coreValue.description, + src: coreValue.image, + }))} + /> + + + {/* */} + + + + ); +}; + +export default AboutPage; + +const Root = styled.main` + display: flex; + flex-direction: column; + justify-content: center; + min-height: 100vh; +`; diff --git a/src/views/MainPage/components/ActivitySection/index.tsx b/src/views/MainPage/components/ActivitySection/index.tsx index 1eafaf73..0993fff9 100644 --- a/src/views/MainPage/components/ActivitySection/index.tsx +++ b/src/views/MainPage/components/ActivitySection/index.tsx @@ -1,15 +1,17 @@ import useInView from '@src/hooks/useInView'; +import { PartIntroType } from '@src/lib/types/admin'; import Activity from '@src/views/MainPage/components/Activity'; import OwnOrganization from '@src/views/MainPage/components/OwnOrganization'; import PartConfig from '@src/views/MainPage/components/PartConfig'; import Comment from '../Comment'; import * as S from './style'; -interface SectionInView { +interface ActivitySectionProps { activityInView: ReturnType; partInView: ReturnType; teamInView: ReturnType; reviewInView: ReturnType; + partIntroduction: PartIntroType[]; } export default function ActivitySection({ @@ -17,11 +19,12 @@ export default function ActivitySection({ partInView, teamInView, reviewInView, -}: SectionInView) { + partIntroduction, +}: ActivitySectionProps) { return ( - + diff --git a/src/views/MainPage/components/Banner/RecruitButton/index.tsx b/src/views/MainPage/components/Banner/RecruitButton/index.tsx index c4863552..5881e1ab 100644 --- a/src/views/MainPage/components/Banner/RecruitButton/index.tsx +++ b/src/views/MainPage/components/Banner/RecruitButton/index.tsx @@ -1,7 +1,12 @@ import { PropsWithChildren, useState } from 'react'; +import { BannerColor } from '..'; import * as S from './style'; -export default function RecruitButton({ children }: PropsWithChildren) { +export default function RecruitButton({ + children, + mainColor, + highColor, +}: PropsWithChildren) { const [blurPosition, setBlurPosition] = useState<{ x: number; y: number }>({ x: 0, y: 0 }); const handleMouseMove = (event: React.MouseEvent) => { @@ -12,8 +17,14 @@ export default function RecruitButton({ children }: PropsWithChildren) { }; return ( - - + +
{children}
diff --git a/src/views/MainPage/components/Banner/RecruitButton/style.ts b/src/views/MainPage/components/Banner/RecruitButton/style.ts index fa2db99e..b3b60479 100644 --- a/src/views/MainPage/components/Banner/RecruitButton/style.ts +++ b/src/views/MainPage/components/Banner/RecruitButton/style.ts @@ -3,14 +3,14 @@ import { colors } from '@sopt-makers/colors'; import Link from 'next/link'; import { BackgroundMove } from '@src/lib/styles/animation'; -export const RecruitButtonWrapper = styled(Link)` +export const RecruitButtonWrapper = styled(Link)<{ mainColor: string; highColor: string }>` margin-top: 41px; display: inline-flex; justify-content: center; align-items: center; border-radius: 99px; - - background: linear-gradient(274deg, #3c92ff, #8fc0ff, #8fc0ff); + background: ${({ mainColor, highColor }) => + `linear-gradient(274deg,${mainColor}, ${highColor}, ${highColor})`}; background-size: 200% 200%; animation: ${BackgroundMove} 1.8s ease-out infinite alternate; @@ -37,11 +37,16 @@ export const RecruitButtonWrapper = styled(Link)` } &:hover { - background: #8fc0ff; + background: ${({ highColor }) => highColor}; } `; -export const MouseTrackerWrapper = styled.div<{ x: number; y: number }>` +export const MouseTrackerWrapper = styled.div<{ + x: number; + y: number; + mainColor: string; + highColor: string; +}>` border-radius: 99px; border: none; width: 289px; @@ -73,16 +78,17 @@ export const MouseTrackerWrapper = styled.div<{ x: number; y: number }>` left: 0; right: 0; bottom: 0; - background-image: radial-gradient( - circle 110px at ${(props) => props.x}px ${(props) => props.y}px, - #3c92ff, + background-image: ${({ x, y, mainColor, highColor }) => ` + radial-gradient( + circle 110px at ${x}px ${y}px, + ${mainColor}, transparent - ), - radial-gradient( - circle 180px at ${(props) => props.x}px ${(props) => props.y}px, - #8fc0ff, + ), radial-gradient( + circle 180px at ${x}px ${y}px, + ${highColor}, transparent - ); + ) + `}; opacity: 0; transition-duration: 0.4s; } diff --git a/src/views/MainPage/components/Banner/index.tsx b/src/views/MainPage/components/Banner/index.tsx index cb41a93a..7f98542c 100644 --- a/src/views/MainPage/components/Banner/index.tsx +++ b/src/views/MainPage/components/Banner/index.tsx @@ -3,8 +3,13 @@ import { checkIsTimeInRange } from '@src/lib/utils/date'; import RecruitButton from './RecruitButton'; import * as S from './style'; -export default function Banner() { - const isValid = checkIsTimeInRange('2024-09-08 10:00:00', '2024-09-13 18:00:00'); // 모집 여부 +export interface BannerColor { + mainColor: string; + highColor: string; +} +export default function Banner({ mainColor, highColor }: BannerColor) { + // TODO: API 필드 추가된 후에 RecruitPage처럼 바뀌어야 함 + const isValid = checkIsTimeInRange('2024-09-08 10:00:00', '2024-09-13 18:00:00'); const onScrollMoveDown = () => { const element = document.getElementById('nextContainer'); @@ -18,7 +23,7 @@ export default function Banner() { 함께라서 외칠 수 있는 열정 오직 이곳 SOPT에서만. - + {isValid ? '36기 YB 지원하기 ' : '모집 알림 신청하기 '}>{' '} diff --git a/src/views/MainPage/components/BottomLayout/index.tsx b/src/views/MainPage/components/BottomLayout/index.tsx index 42b76de5..a3959bd8 100644 --- a/src/views/MainPage/components/BottomLayout/index.tsx +++ b/src/views/MainPage/components/BottomLayout/index.tsx @@ -1,6 +1,7 @@ import { motion, useScroll, useTransform } from 'framer-motion'; import { RefObject, useRef } from 'react'; import useInView from '@src/hooks/useInView'; +import { LatestNewsType, PartIntroType } from '@src/lib/types/admin'; import ActivitySection from '@src/views/MainPage/components/ActivitySection'; import RecentNews from '@src/views/MainPage/components/RecentNews'; import RecruitMessage from '@src/views/MainPage/components/RecruitMessage'; @@ -18,7 +19,13 @@ export type RefHandler = { targetRef: RefObject; }; -function BottomLayout() { +interface BottomLayoutProps { + partIntroduction: PartIntroType[]; + latestNews: LatestNewsType[]; + mainColor: string; + highColor: string; +} +function BottomLayout({ partIntroduction, latestNews, mainColor, highColor }: BottomLayoutProps) { const activity = useInView(); const part = useInView(); const team = useInView(); @@ -57,13 +64,14 @@ function BottomLayout() { partInView={part} teamInView={team} reviewInView={review} + partIntroduction={partIntroduction} />
- - + + ); diff --git a/src/views/MainPage/components/PartConfig/PartSlide/index.tsx b/src/views/MainPage/components/PartConfig/PartSlide/index.tsx index 05ca64d9..7b86a656 100644 --- a/src/views/MainPage/components/PartConfig/PartSlide/index.tsx +++ b/src/views/MainPage/components/PartConfig/PartSlide/index.tsx @@ -5,10 +5,11 @@ import * as S from './style'; interface PartSlideProps { part: Part; + description: string; } -export default function PartSlide({ part }: PartSlideProps) { - const { value, label, description } = partList[part]; +export default function PartSlide({ part, description }: PartSlideProps) { + const { value, label } = partList[part]; const isTablet = useIsTablet('429px', '75rem'); const isMobile = useIsMobile('26.75rem'); const contentDraw = { diff --git a/src/views/MainPage/components/PartConfig/index.tsx b/src/views/MainPage/components/PartConfig/index.tsx index c064044d..e7768205 100644 --- a/src/views/MainPage/components/PartConfig/index.tsx +++ b/src/views/MainPage/components/PartConfig/index.tsx @@ -6,13 +6,18 @@ import { useIsMobile } from '@src/hooks/useDevice'; import useDrag from '@src/hooks/useDrag'; import useInfiniteCarousel from '@src/hooks/useInfiniteCarousel'; import { tabs as carouselList } from '@src/lib/constants/tabs'; +import { PartIntroType } from '@src/lib/types/admin'; import { TabType } from '@src/lib/types/universal'; +import { parseStringToPart } from '@src/lib/utils/parseStringToPart'; import PartButton from '@src/views/MainPage/components/PartConfig/PartButton.tsx'; import PartSlide from '@src/views/MainPage/components/PartConfig/PartSlide'; import Tab from '@src/views/MainPage/components/Tab'; import * as S from './style'; -function PartConfig(_props: unknown, ref: Ref) { +function PartConfig( + { partIntroduction }: { partIntroduction: PartIntroType[] }, + ref: Ref, +) { const { dragRef, handleMouseDown, handleMouseMove, initDragging } = useDrag(); const partRef = useRef([]); const { @@ -27,6 +32,11 @@ function PartConfig(_props: unknown, ref: Ref) { const isMobileSize = useIsMobile('48rem'); const tab = isMobileSize ? 'Part' : ''; + // formatter + const partIntro = Object.fromEntries( + partIntroduction.map((el) => [parseStringToPart(el.part), el.description]), + ); + return ( ) { onTouchEnd={handleTouchEnd} > {infiniteCarouselList.map(({ value }, index) => ( - + ))} diff --git a/src/views/MainPage/components/RecentNews/index.tsx b/src/views/MainPage/components/RecentNews/index.tsx index 6cac18da..47bac9cf 100644 --- a/src/views/MainPage/components/RecentNews/index.tsx +++ b/src/views/MainPage/components/RecentNews/index.tsx @@ -1,8 +1,8 @@ -import { RecentNewsList } from '@src/lib/constants/main'; +import { LatestNewsType } from '@src/lib/types/admin'; import Card from './Card'; import * as S from './style'; -export default function RecentNews() { +export default function RecentNews({ latestNews }: { latestNews: LatestNewsType[] }) { return ( 솝트의 최신 소식이 궁금하다면! @@ -10,10 +10,11 @@ export default function RecentNews() { - {RecentNewsList.concat(RecentNewsList) - .concat(RecentNewsList) - .map(({ title, url, src }, index) => { - return ; + {latestNews + .concat(latestNews) + .concat(latestNews) + .map(({ title, link, image }, idx) => { + return ; })} diff --git a/src/views/MainPage/components/RecruitMessage/index.tsx b/src/views/MainPage/components/RecruitMessage/index.tsx index 08d98660..c30965bc 100644 --- a/src/views/MainPage/components/RecruitMessage/index.tsx +++ b/src/views/MainPage/components/RecruitMessage/index.tsx @@ -3,8 +3,13 @@ import { checkIsTimeInRange } from '@src/lib/utils/date'; import RecruitButton from '../Banner/RecruitButton'; import * as S from './style'; -export default function RecruitMessage() { +interface RecruitMessageProp { + mainColor: string; + highColor: string; +} +export default function RecruitMessage({ mainColor, highColor }: RecruitMessageProp) { const isMobileSize = useIsMobile('48rem'); + // TODO: API 필드 추가된 후에 RecruitPage처럼 바뀌어야 함 const isValid = checkIsTimeInRange('2024-09-08 10:00:00', '2024-09-13 18:00:00'); // 모집 여부 return ( @@ -15,7 +20,9 @@ export default function RecruitMessage() { 아직 모집기간이 아니예요.{isMobileSize &&
}알림 신청을 하시면, 봄에 찾아갈게요! )} - {isValid ? '36기 YB 지원하기 ' : '모집 알림 신청하기 '}> + + {isValid ? '36기 YB 지원하기 ' : '모집 알림 신청하기 '}>{' '} + ); } diff --git a/src/views/MainPage/index.tsx b/src/views/MainPage/index.tsx index ac546ea3..b14b144f 100644 --- a/src/views/MainPage/index.tsx +++ b/src/views/MainPage/index.tsx @@ -1,5 +1,7 @@ -import { useEffect } from 'react'; +import { useQuery } from '@tanstack/react-query'; import PageLayout from '@src/components/common/PageLayout'; +import { remoteAdminAPI } from '@src/lib/api/remote/admin'; +import { GetHomepageResponse } from '@src/lib/types/admin'; import BottomLayout from '@src/views/MainPage/components/BottomLayout'; import IntroSection from '@src/views/MainPage/components/IntroSection'; import TopBanner from '@src/views/MainPage/components/TopBanner'; @@ -10,22 +12,38 @@ import Introduce from './components/Introduce'; import ScrollInteractiveLogo from './components/ScrollInteractiveLogo'; function MainPage() { - const isValid = checkIsTimeInRange('2024-09-08 10:00:00', '2024-09-13 18:00:00'); // 모집 여부 + // TODO: API 필드 추가된 후에 RecruitPage처럼 바뀌어야 함 + const isValid = checkIsTimeInRange('2024-09-08 10:00:00', '2024-09-13 18:00:00'); - const postVisiter = usePostVisitor(); + // const postVisiter = usePostVisitor(); - useEffect(() => { - postVisiter(); - }, [postVisiter]); + // useEffect(() => { + // postVisiter(); + // }, [postVisiter]); + + const { data: adminData } = useQuery({ + queryKey: ['homepage'], + queryFn: remoteAdminAPI.getHomepage, + }); return ( {isValid && } - + - + {adminData && ( + + )} ); } diff --git a/src/views/RecruitPage/components/ActivityReview/index.tsx b/src/views/RecruitPage/components/ActivityReview/index.tsx index a51bcc8f..cd2235a6 100644 --- a/src/views/RecruitPage/components/ActivityReview/index.tsx +++ b/src/views/RecruitPage/components/ActivityReview/index.tsx @@ -1,10 +1,12 @@ import { track } from '@amplitude/analytics-browser'; import Link from 'next/link'; +import { useContext } from 'react'; import { ReactComponent as ArrowLeft } from '@src/assets/icons/arrow_left_28x28.svg'; import { ReactComponent as ArrowRight } from '@src/assets/icons/arrow_right_28x28.svg'; import arrowRightWhite from '@src/assets/icons/arrow_right_white.svg'; import { useHorizontalScroll } from '@src/hooks/useHorizontalScroll'; import { parsePartToKorean } from '@src/lib/utils/parsePartToKorean'; +import { BrandingColorContext } from '../..'; import { SectionTitle, SectionTitleTranslate, SectionTitleWrapper } from '../common/style'; import useGetSampleReviews from './hooks/queries/useGetSampleReviews'; import { @@ -23,7 +25,7 @@ import { export default function ActivityReview() { const { data, isLoading } = useGetSampleReviews(); - + const { main } = useContext(BrandingColorContext); const { scrollableRef, onClickLeftButton, @@ -37,7 +39,7 @@ export default function ActivityReview() { return ( - Activity Review + Activity Review {'회원들의 후기로\nSOPT 활동을 미리 만나보세요.'} diff --git a/src/views/RecruitPage/components/ApplySection/index.tsx b/src/views/RecruitPage/components/ApplySection/index.tsx index f2cd9414..21337d4c 100644 --- a/src/views/RecruitPage/components/ApplySection/index.tsx +++ b/src/views/RecruitPage/components/ApplySection/index.tsx @@ -1,14 +1,16 @@ -import imgRecruitBg from '@src/assets/images/img_recruit_banner.png'; +import { useContext } from 'react'; +import { BrandingColorContext } from '../..'; import * as S from './style'; -const ApplySection = () => { +const ApplySection = ({ headerImg }: { headerImg: string }) => { + const { main } = useContext(BrandingColorContext); return ( - + SOPT의 35번째 열정을  기다리고 있어요! - + 지원하기 diff --git a/src/views/RecruitPage/components/ApplySection/style.ts b/src/views/RecruitPage/components/ApplySection/style.ts index 49829417..494ac92c 100644 --- a/src/views/RecruitPage/components/ApplySection/style.ts +++ b/src/views/RecruitPage/components/ApplySection/style.ts @@ -1,7 +1,6 @@ import styled from '@emotion/styled'; -import { StaticImageData } from 'next/image'; -export const ApplyButton = styled.a` +export const ApplyButton = styled.a<{ main: string }>` width: 220px; padding: 24px 0; border-radius: 50px; @@ -12,7 +11,7 @@ export const ApplyButton = styled.a` font-weight: 700; line-height: 100%; /* 22px */ letter-spacing: -0.44px; - background-color: #5ba3ff; + background-color: ${({ main }) => main}; z-index: 2; /* 태블릿 뷰 */ @media (max-width: 81.1875rem) and (min-width: 47.875rem) { @@ -55,7 +54,7 @@ export const Title = styled.div` } `; -export const Wrapper = styled.div<{ imgRecruitBg: StaticImageData }>` +export const Wrapper = styled.div<{ imgRecruitBg: string }>` width: 100vw; height: 580px; position: relative; @@ -65,7 +64,7 @@ export const Wrapper = styled.div<{ imgRecruitBg: StaticImageData }>` flex-direction: column; margin-top: 80px; - background-image: url(${({ imgRecruitBg }) => imgRecruitBg.src}); + background-image: url(${({ imgRecruitBg }) => imgRecruitBg}); background-repeat: no-repeat; background-size: cover; background-position: top center; diff --git a/src/views/RecruitPage/components/ChapterInfo/index.tsx b/src/views/RecruitPage/components/ChapterInfo/index.tsx index 5898987d..e311c002 100644 --- a/src/views/RecruitPage/components/ChapterInfo/index.tsx +++ b/src/views/RecruitPage/components/ChapterInfo/index.tsx @@ -1,19 +1,28 @@ -import { useState } from 'react'; +import { useContext, useState } from 'react'; import Flex from '@src/components/common/Flex'; +import { PartInfoType, PartType } from '@src/lib/types/admin'; import { Part } from '@src/lib/types/universal'; import { parsePartToKorean } from '@src/lib/utils/parsePartToKorean'; +import { parseStringToPart } from '@src/lib/utils/parseStringToPart'; +import { BrandingColorContext } from '../..'; import TabBar from '../common/Tabs'; import { SectionTitle, SectionTitleTranslate, SectionTitleWrapper } from '../common/style'; -import { infoMap } from './constants'; import * as S from './style'; -const ChapterInfo = () => { +const ChapterInfo = ({ info }: { info: PartInfoType[] }) => { + const { main, point } = useContext(BrandingColorContext); const [selectedTab, setSelectedTab] = useState(Part.PLAN); + const parsedPart = parsePartToKorean(selectedTab); + + const infoMap = info.reduce((acc, { part, introduction }) => { + acc[parseStringToPart(part)] = introduction; + return acc; + }, {} as Record>); return ( - Positions + Positions SOPT 35기는 총 6개의 파트로 이루어져 있어요. @@ -26,19 +35,15 @@ const ChapterInfo = () => { amplitudeTrackingName={'click_recruit_description_part'} /> - {parsePartToKorean(selectedTab)} 파트는 이런 걸 배워요 + {parsedPart} 파트는 이런 걸 배워요 - {infoMap[selectedTab].info} + {infoMap[selectedTab].content} - 이런 분이면 좋아요! + 이런 분이면 좋아요! - - {infoMap[selectedTab].fit.map((fit, idx) => ( -
{fit}
- ))} -
+ {infoMap[selectedTab].preference}
diff --git a/src/views/RecruitPage/components/ChapterInfo/style.ts b/src/views/RecruitPage/components/ChapterInfo/style.ts index ce72d591..b0bad47d 100644 --- a/src/views/RecruitPage/components/ChapterInfo/style.ts +++ b/src/views/RecruitPage/components/ChapterInfo/style.ts @@ -24,8 +24,8 @@ const BlueChip = styled(BaseChip)` } `; -const GreenChip = styled(BaseChip)` - color: #5ba3ff; +const GreenChip = styled(BaseChip)<{ mainColor: string }>` + color: ${({ mainColor }) => mainColor}; padding-left: 20px; &:before { content: '👍'; @@ -91,9 +91,9 @@ const InfoWrapper = styled(BaseText)` } `; -const FitWrapper = styled(BaseText)` +const FitWrapper = styled(BaseText)<{ pointColor: string }>` border-radius: 30px; - background: #1c2837; + background: ${({ pointColor }) => pointColor}; padding: 60px 80px; display: flex; flex-direction: column; diff --git a/src/views/RecruitPage/components/Contact/index.tsx b/src/views/RecruitPage/components/Contact/index.tsx index ff0d4652..0f6bcc9c 100644 --- a/src/views/RecruitPage/components/Contact/index.tsx +++ b/src/views/RecruitPage/components/Contact/index.tsx @@ -1,5 +1,7 @@ import styled from '@emotion/styled'; import Image from 'next/image'; +import { useContext } from 'react'; +import { BrandingColorContext } from '../..'; import { SectionSubTitle, SectionTitle, @@ -9,11 +11,12 @@ import { import { contactInDisplayOrder, contactMap } from './constant'; const Contact = () => { + const { main } = useContext(BrandingColorContext); return ( - Inquiry + Inquiry 문의하기 SOPT 지원에 대해 궁금한 것이 더 있나요? diff --git a/src/views/RecruitPage/components/FAQ/QuestionBox/index.tsx b/src/views/RecruitPage/components/FAQ/QuestionBox/index.tsx index 88570ecf..552fe143 100644 --- a/src/views/RecruitPage/components/FAQ/QuestionBox/index.tsx +++ b/src/views/RecruitPage/components/FAQ/QuestionBox/index.tsx @@ -1,6 +1,8 @@ import styled from '@emotion/styled'; +import { useContext } from 'react'; import { ReactComponent as IcArrow } from '@src/assets/icons/ic_arrow_right_white.svg'; import { FAQType } from '@src/lib/types/faq'; +import { BrandingColorContext } from '@src/views/RecruitPage'; interface QuestionBoxProps { info: FAQType; @@ -12,16 +14,17 @@ function QuestionBox(props: QuestionBoxProps) { info: { question, answer }, status, } = props; + const { main } = useContext(BrandingColorContext); return ( - {question} + {question} - {status && {answer}} + {status && {answer}} ); } @@ -58,14 +61,14 @@ const Styled = { margin-bottom: ${({ isOpened }) => (isOpened ? '16px' : '0')}; } `, - Title: styled.h1` + Title: styled.h1<{ main: string }>` font-weight: 600; font-size: 24rem; line-height: 100%; color: #ffffff; &::before { content: 'Q. '; - color: #5ba3ff; + color: ${({ main }) => main}; } /* 모바일 뷰 */ @@ -91,7 +94,7 @@ const Styled = { transform-origin: center; } `, - Content: styled.div` + Content: styled.div<{ main: string }>` font-weight: 400; font-size: 25rem; line-height: 40px; @@ -101,7 +104,7 @@ const Styled = { &::before { content: 'A. '; - color: #5ba3ff; + color: ${({ main }) => main}; } /* 태블릿 뷰 */ @media (max-width: 119.99375rem) and (min-width: 47.875rem) { diff --git a/src/views/RecruitPage/components/FAQ/index.tsx b/src/views/RecruitPage/components/FAQ/index.tsx index 5cb6e8ba..10245a34 100644 --- a/src/views/RecruitPage/components/FAQ/index.tsx +++ b/src/views/RecruitPage/components/FAQ/index.tsx @@ -1,15 +1,29 @@ import styled from '@emotion/styled'; -import { useState } from 'react'; +import { useContext, useState } from 'react'; +import { questionList } from '@src/lib/constants/faq'; +import { PartQuestionType, QnAType } from '@src/lib/types/admin'; import { ExtraPart, PartExtraType } from '@src/lib/types/universal'; +import { parseStringToPart } from '@src/lib/utils/parseStringToPart'; +import { BrandingColorContext } from '../..'; import TabBar from '../common/Tabs'; import { SectionTitle, SectionTitleTranslate, SectionTitleWrapper } from '../common/style'; import QuestionBox from './QuestionBox'; -import { faqMap } from './constant'; -const FaqInfo = () => { +const FaqInfo = ({ info }: { info: PartQuestionType[] }) => { + const { main } = useContext(BrandingColorContext); const [selectedTab, setSelectedTab] = useState(PartExtraType.ALL); const [status, setStatus] = useState(new Set()); + const infoMap = info.reduce( + (acc, { part, questions }) => { + acc[parseStringToPart(part)] = questions; + return acc; + }, + { + ALL: questionList, + } as Record, + ); + const toggleBox = (index: number) => { const updatedStatus = new Set(status); status.has(index) ? updatedStatus.delete(index) : updatedStatus.add(index); @@ -20,7 +34,7 @@ const FaqInfo = () => { - FAQ + FAQ 자주 묻는 질문 @@ -31,7 +45,7 @@ const FaqInfo = () => { amplitudeTrackingName={'click_recruit_faq_part'} /> - {faqMap[selectedTab].map((info, index) => ( + {infoMap[selectedTab].map((info, index) => (
toggleBox(index)}>
diff --git a/src/views/RecruitPage/components/RecruteeInfo/index.tsx b/src/views/RecruitPage/components/RecruteeInfo/index.tsx index e3fde867..7fc329b4 100644 --- a/src/views/RecruitPage/components/RecruteeInfo/index.tsx +++ b/src/views/RecruitPage/components/RecruteeInfo/index.tsx @@ -1,3 +1,5 @@ +import { useContext } from 'react'; +import { BrandingColorContext } from '../..'; import { SectionSubTitle, SectionTitle, @@ -7,10 +9,11 @@ import { import * as S from './style'; const RecruiteeInfo = () => { + const { main } = useContext(BrandingColorContext); return ( - Recruitment target + Recruitment target 모집 대상 아래 3가지 모두 해당 되는 분이라면, 누구든 지원 가능해요! diff --git a/src/views/RecruitPage/components/Schedule/index.tsx b/src/views/RecruitPage/components/Schedule/index.tsx index 53f8d163..0b591c59 100644 --- a/src/views/RecruitPage/components/Schedule/index.tsx +++ b/src/views/RecruitPage/components/Schedule/index.tsx @@ -1,27 +1,55 @@ +import { useContext } from 'react'; +import { RecruitScheduleType } from '@src/lib/types/admin'; +import { BrandingColorContext } from '../..'; import { SectionTitle, SectionTitleTranslate, SectionTitleWrapper } from '../common/style'; import * as S from './style'; -const Schedule = () => { +const Schedule = ({ info }: { info: RecruitScheduleType }) => { + const { main } = useContext(BrandingColorContext); + const { type, schedule } = info; + const parseDate = (date: Date) => { + const month = date.getMonth() + 1; + const day = date.getDate(); + const hours = date.getHours(); + + const formattedDate = `${month}월 ${day}일`; + const formattedHour = `${hours}시`; + return { date: formattedDate, hour: formattedHour }; + }; + const formattedSchedule = Object.fromEntries( + Object.entries(schedule).map(([key, val]) => [key, parseDate(new Date(val))]), + ); return ( - Schedule + Schedule 모집 일정 - YB 서류 접수 + {type} 서류 접수 - 9월 8일 10시 - 9월 13일 18시 + {`${formattedSchedule.applicationStartTime.date} `} + + {formattedSchedule.applicationStartTime.hour} + {' '} + -{` ${formattedSchedule.applicationEndTime.date} `} + + {formattedSchedule.applicationEndTime.hour} + - YB 서류 결과 발표 + {type} 서류 결과 발표 - 9월 19일 16시 + {`${formattedSchedule.applicationResultTime.date} `} + + {formattedSchedule.applicationResultTime.hour} + - YB 면접 - 9월 21일 - 9월 22일 - YB 최종 결과 발표 + {type} 면접 + {`${formattedSchedule.interviewStartTime.date} - ${formattedSchedule.interviewEndTime.date}`} + {type} 최종 결과 발표 - 9월 25일 16시 + {`${formattedSchedule.finalResultTime.date} `} + {formattedSchedule.finalResultTime.hour} diff --git a/src/views/RecruitPage/components/Schedule/style.ts b/src/views/RecruitPage/components/Schedule/style.ts index 56263ba0..b2147641 100644 --- a/src/views/RecruitPage/components/Schedule/style.ts +++ b/src/views/RecruitPage/components/Schedule/style.ts @@ -65,8 +65,8 @@ export const OddText = styled.div` } `; -export const Highlight = styled.span` - color: #5ba3ff; +export const Highlight = styled.span<{ mainColor: string }>` + color: ${({ mainColor }) => mainColor}; font-size: 24rem; font-weight: 400; letter-spacing: -0.48px; diff --git a/src/views/RecruitPage/components/common/style.ts b/src/views/RecruitPage/components/common/style.ts index a420fb21..667f7f6e 100644 --- a/src/views/RecruitPage/components/common/style.ts +++ b/src/views/RecruitPage/components/common/style.ts @@ -29,8 +29,8 @@ export const SectionTitle = styled.h3` } `; -export const SectionTitleTranslate = styled.h4` - color: #5ba3ff; +export const SectionTitleTranslate = styled.h4<{ mainColor: string }>` + color: ${({ mainColor }) => mainColor}; text-align: center; font-size: 17rem; font-weight: 500; diff --git a/src/views/RecruitPage/index.tsx b/src/views/RecruitPage/index.tsx index 84323b30..c47ce64a 100644 --- a/src/views/RecruitPage/index.tsx +++ b/src/views/RecruitPage/index.tsx @@ -1,35 +1,70 @@ import styled from '@emotion/styled'; -import { lazy } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { Suspense, createContext, lazy } from 'react'; import PageLayout from '@src/components/common/PageLayout'; +import { remoteAdminAPI } from '@src/lib/api/remote/admin'; +import { GetRecruitpageResponse } from '@src/lib/types/admin'; import { checkIsTimeInRange } from '@src/lib/utils/date'; +import ActivityReview from './components/ActivityReview'; import ApplySection from './components/ApplySection'; +import BottomLogo from './components/BottomLogo'; import ChapterInfo from './components/ChapterInfo'; +import Contact from './components/Contact'; +import FaqInfo from './components/FAQ'; import NotificationSection from './components/NotificationSection'; import RecruiteeInfo from './components/RecruteeInfo'; import Schedule from './components/Schedule'; -const FaqInfo = lazy(() => import('./components/FAQ')); -const Contact = lazy(() => import('./components/Contact')); -const ActivityReview = lazy(() => import('./components/ActivityReview')); -const BottomLogo = lazy(() => import('./components/BottomLogo')); +// const FaqInfo = lazy(() => import('./components/FAQ')); +// const Contact = lazy(() => import('./components/Contact')); +// const ActivityReview = lazy(() => import('./components/ActivityReview')); +// const BottomLogo = lazy(() => import('./components/BottomLogo')); +export const BrandingColorContext = createContext({ + main: '', + low: '', + high: '', + point: '', +}); function Recruit() { - const isValid = checkIsTimeInRange('2024-09-08 10:00:00', '2024-09-13 18:00:00'); // 모집 여부 + const { data: adminData } = useQuery({ + queryKey: ['homepage/recruit'], + queryFn: remoteAdminAPI.getRecruitpage, + }); + const isOBRecruiting = checkIsTimeInRange( + adminData?.recruitSchedule[0].schedule.applicationStartTime ?? '', + adminData?.recruitSchedule[0].schedule.applicationEndTime ?? '', + ); + const isYBRecruiting = checkIsTimeInRange( + adminData?.recruitSchedule[1].schedule.applicationStartTime ?? '', + adminData?.recruitSchedule[1].schedule.applicationEndTime ?? '', + ); + const isRecruiting = isOBRecruiting || isYBRecruiting; + + if (!adminData) return; return ( - - {isValid ? : } - - - - - - - - - - + + + {isRecruiting ? ( + + ) : ( + + )} + + + + + + + + + + + {/* */} + + ); }