diff --git a/.env.dev b/.env.dev index c080381..9fe9142 100644 --- a/.env.dev +++ b/.env.dev @@ -1 +1 @@ -REACT_APP_PYCONKR_API=https://payment-dev.pycon.kr \ No newline at end of file +REACT_APP_PYCONKR_API=https://api-dev.pycon.kr \ No newline at end of file diff --git a/public/images/sponsor/CertificateOfSponsorship.png b/public/images/sponsor/CertificateOfSponsorship.png new file mode 100644 index 0000000..540781f Binary files /dev/null and b/public/images/sponsor/CertificateOfSponsorship.png differ diff --git a/public/images/sponsor/OpenSpace.png b/public/images/sponsor/OpenSpace.png new file mode 100644 index 0000000..4dd5eb9 Binary files /dev/null and b/public/images/sponsor/OpenSpace.png differ diff --git a/public/logo192.png b/public/logo192.png new file mode 100644 index 0000000..6f76588 Binary files /dev/null and b/public/logo192.png differ diff --git a/public/logo512.png b/public/logo512.png new file mode 100644 index 0000000..6f76588 Binary files /dev/null and b/public/logo512.png differ diff --git a/src/api/sponsor.ts b/src/api/sponsor.ts index 0fa3eb0..c2fd4ef 100644 --- a/src/api/sponsor.ts +++ b/src/api/sponsor.ts @@ -1,33 +1,26 @@ -import { Sponsor } from "models/sponsor"; +import { Sponsor, SponsorBenefit, SponsorLevelWithSponsor } from "models/sponsor"; import axios from "lib/axios"; -import { APISponsor } from "models/api/sponsor"; +import { APISponsor, APISponsorBenefit, APISponsorLevel, APISponsorLevelWithSponsor } from "models/api/sponsor"; +import { SponsorLevel } from "models/sponsor"; import { getErrorMessage } from "api"; -import SponsorLevels from "enums/sponsorLevels"; -export function listSponsors(): Promise { + +export function listSponsorLevels(): Promise { return new Promise((resolve, reject) => { - resolve([ - { - id: "1", - name: "후원사1", - level: SponsorLevels.keystone, - }, - { - id: "2", - name: "후원사2", - level: SponsorLevels.gold, - }, - { - id: "3", - name: "후원사3", - level: SponsorLevels.ruby, - }, - ]); + axios.get("/2023/sponsors/levels").then((response) => { + resolve(SponsorLevel.fromAPIs(response.data)); + }).catch((error) => { + console.error(error); + reject(getErrorMessage(error)); + }) return; + }); +} - // eslint-disable-next-line no-unreachable +export function listSponsors(): Promise { + return new Promise((resolve, reject) => { axios - .get("/sponsor") + .get("/2023/sponsors/list/") .then((response) => { resolve(Sponsor.fromAPIs(response.data)); }) @@ -37,3 +30,28 @@ export function listSponsors(): Promise { }); }); } +export function listSponsorLevelWithSponsor(): Promise { + return new Promise((resolve, reject) => { + axios + .get("/2023/sponsors/levels/with-sponsor/") + .then((response) => { + console.log("debug", response); + resolve(SponsorLevelWithSponsor.fromAPIs(response.data)); + }) + .catch((error) => { + console.error(error); + reject(getErrorMessage(error)); + }); + }); +} + +export function listSponsorBenefits(): Promise { + return new Promise((resolve, reject) => { + axios.get("/2023/sponsors/benefits/").then(response => { + resolve(SponsorBenefit.fromAPIs(response.data)); + }).catch(error => { + console.error(error); + reject(getErrorMessage(error)); + }) + }) +} diff --git a/src/components/Footer/SponsorList.tsx b/src/components/Footer/SponsorList.tsx new file mode 100644 index 0000000..1a9bc55 --- /dev/null +++ b/src/components/Footer/SponsorList.tsx @@ -0,0 +1,75 @@ +import { SponsorAPI } from "api"; +import React, { useEffect, useState } from "react"; +import useTranslation from "utils/hooks/useTranslation"; +import { SponsorLevelWithSponsor } from "models/sponsor"; +import styled from "styled-components"; +import SponsorTable from "./SponsorTable"; + +const SponsorList = () => { + const t = useTranslation(); + const [listOfSponsorLevel, setListOfSponsorLevel] = useState([]); + + useEffect(() => { + SponsorAPI.listSponsorLevelWithSponsor().then((levels) => { + setListOfSponsorLevel(levels); + }); + }, []); + + return ( + + +

{t("후원사 목록")}

+ + {listOfSponsorLevel.map((level) => ( + + ))} + +
+
+ ); +}; + +export default SponsorList; + +const Container = styled.div` + display: flex; + justify-content: center; + align-items: center; + padding: 2rem 0; + width: 100%; + + &:nth-child(even) { + background-color: #141414; + } +`; + +const Vertical = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + margin-bottom: 2rem; + width: 100%; +`; + +const SponsorTableList = styled.div` + & > div + div { + margin-top: 2rem; + } +`; + +const H1 = styled.h1` + margin-top: 3rem; + font-size: 40px; + color: #b0a8fe; + + @media only screen and (max-width: 810px) { + padding: 0 1rem; + font-size: 24px; + } +`; diff --git a/src/pages/Sponsor/SponsorTable.tsx b/src/components/Footer/SponsorTable.tsx similarity index 88% rename from src/pages/Sponsor/SponsorTable.tsx rename to src/components/Footer/SponsorTable.tsx index deceaa6..5e620bf 100644 --- a/src/pages/Sponsor/SponsorTable.tsx +++ b/src/components/Footer/SponsorTable.tsx @@ -1,10 +1,11 @@ +import { Sponsor } from "models/sponsor"; import React, { useCallback, useEffect, useRef, useState } from "react"; import styled from "styled-components"; type Props = React.HTMLAttributes & { max: Number; levelName: string; - sponsors: Array<{ name: string; image: string }>; + sponsors: Sponsor[]; }; function SponsorTable({ max, levelName, sponsors, ...rest }: Props) { @@ -13,7 +14,7 @@ function SponsorTable({ max, levelName, sponsors, ...rest }: Props) {

{levelName}

{sponsors.map((sponsor) => ( - {sponsor.name} + {sponsor.name} ))}
diff --git a/src/components/Footer/index.tsx b/src/components/Footer/index.tsx index a9d7520..b6ad866 100644 --- a/src/components/Footer/index.tsx +++ b/src/components/Footer/index.tsx @@ -1,5 +1,4 @@ -import { Sponsor } from "models/sponsor"; -import React, { useEffect, useState } from "react"; +import React from "react"; import styled from "styled-components"; import { SponsorAPI } from "api"; @@ -15,69 +14,16 @@ import { Youtube, } from "assets/icons"; import useTranslation from "utils/hooks/useTranslation"; -import SponsorLevels, { SponsorLevel, SponsorLevelCode } from "enums/sponsorLevels"; import { Link } from "react-router-dom"; +import SponsorList from "./SponsorList"; const Footer = () => { - const [sponsors, setSponsors] = useState< - | { - level: SponsorLevel; - sponsors: Sponsor[]; - }[] - | undefined - >([]); const t = useTranslation(); - useEffect(() => { - SponsorAPI.listSponsors() - .then((res) => { - setSponsors( - Object.entries( - res.reduce( - (acc, cur) => { - if (cur.level.code === "unknown") return acc; - if (acc[cur.level.code] === undefined) acc[cur.level.code] = [cur]; - else acc[cur.level.code].push(cur); - return acc; - }, - {} as { [l: string]: Sponsor[] } - ) - ) - .reduce( - (acc, [levelCode, sponsorList]) => { - acc.push({ - level: SponsorLevels[levelCode as SponsorLevelCode], - sponsors: sponsorList, - }); - return acc; - }, - [] as NonNullable - ) - .sort((a, b) => a.level.priority - b.level.priority) - ); - }) - .catch((e) => { - console.error(e); - setSponsors(undefined); - }); - }, []); - return ( - {/* - {sponsors === undefined ? ( - 후원사 목록을 가져오는데 실패했습니다. - ) : ( - sponsors.map((s) => ( -
-
{s.level.name}
- {s.sponsors.map((sponsor) => ( -
{sponsor.name}
- ))} -
- )) - )} -
*/} + +
{/*
*/} {/* */} diff --git a/src/components/common/Collapse/index.tsx b/src/components/common/Collapse/index.tsx index 61fecc4..dc1250f 100644 --- a/src/components/common/Collapse/index.tsx +++ b/src/components/common/Collapse/index.tsx @@ -1,5 +1,4 @@ -import React, { useCallback, useEffect, useRef, useState } from "react"; -import { isEscKeyPressed } from "utils"; +import React, { useRef, useState } from "react"; import styled from "styled-components"; type Props = React.HTMLAttributes & { diff --git a/src/locale/English/translation.ts b/src/locale/English/translation.ts index 81a3ea4..85bb75f 100644 --- a/src/locale/English/translation.ts +++ b/src/locale/English/translation.ts @@ -10,6 +10,10 @@ const EnglishTranslation = { 프로그램: "Program", 기여하기: "Contribution", 후원하기: "Sponsoring", + "파이콘 한국은?": "About PyCon Korea", + "후원사 가이드 다운로드": "Download PDF of sponsor guide", + "후원 관련 문의": "Inquiries about sponsorship", + "파이콘 한국 후원의 의미": "Sponsored by PyCon Korea", "파이콘 한국 2024": "PyCon Korea 2024", "파이콘 한국 준비위원회": "Organizing Team", "파이콘 한국 행동 강령": "Code of Conduct", @@ -97,6 +101,99 @@ const EnglishTranslation = { "What's the difference between PyCon and other developer's conferences?", "파이콘 한국은 커뮤니티 주관으로 이뤄지는 비영리 개발자 대상 행사로, 타 기업 및 기관에서 개최하는 개발자 행사와는 성격이 다릅니다. 발표자와 튜토리얼 진행자를 포함하여, 자원봉사자와 준비위원회 담당자 등 모든 인원이 금전적 이득 없이 순수히 오픈소스 프로그래밍 언어인 파이썬의 저변 확대와 커뮤니티 활성화를 위해 진행하는 행사입니다.": "PyCon Korea is a non-profitable, developer-oriented event organized by community members, and has different characteristics from conferences organized companies or organizations. All attendees, including speakers, tutorial hosts, voluntary workers, and organizers, participate in PyCon Korea without any profitable purpose, and only to broaden the usage of Python, an open source programming language, and activate its community.", -}; + // About Sponsor translations + "후원하기2": "Become a Sponsor", + "파이콘 한국은 커뮤니티 주관으로 이뤄지는 비영리 개발자 대상 행사로 오픈소스 프로그래밍 언어인 파이썬의 저변 확대와 커뮤니티 활성화를 위해 진행하는 행사입니다.": "PyCon Korea is a non-profit, community-organized event for developers that aims to expand the reach and community of Python, an open source programming language.", + "비영리 행사": "Non-profit Conference", + "파이콘 한국의 발표자 및 튜토리얼 진행자를 포함, 자원봉사자와 준비위원회 담당자 등 모든 인원이 금전적 이득 없이 행사를 준비하고 운영해 나갑니다.": "PyCon Korea is organized and run by volunteers and organizing committee members, including speakers and tutorial facilitators, without financial gain.", + "커뮤니티에 기여": "Contributing to the community", + "이에 파이콘 한국에의 후원은 국내 오픈소스 커뮤니티와 파이썬 커뮤니티에 대한 가장 좋은 기여 방법이며 여러 우수한 개발자들과의 만남을 가지실 수 있는 기회입니다.": "Sponsoring PyCon Korea is the best way to contribute to the local open source community and Python community, and it's a great way to meet some of the best developers in the country.", + "2014년, 한국에서 첫 파이콘이 열린 이후로 파이써니스타들은 파이콘이라는 만남의 장에 파이썬이라는 하나의 공통점으로 뭉쳐 각자의 순간들을 나누고, 새로운 순간들을 함께 만들어왔습니다.": "Since the first PyCon was held in South Korea in 2014, Python enthusiasts have been gathering at PyCon to share their moments and create new ones together.", + "파이썬과 행복했던 순간들, 파이썬이기에 가능했던 순간들, 여러분이 소중한 순간순간들을 가지고 모여 함께 새로운 순간들을 만들어 내길 바랍니다.": "We hope you'll take the moments you've had with Python, the moments you've been happy with, and the moments you've made possible because of Python, and that you'll come together to create new ones.", + "지난 파이콘 보러가기": "See all previous PyCons", + "후원사 신청 절차": "Process to sign up for sponsor", + "후원사 신청": "Sign up for sponsor", + "본 페이지 상단의 ‘후원사로 참여하기' 버튼을 통해 후원에 필요한 정보를 입력해주세요.": "Please fill out the information required for sponsorship via the \"Become a Sponsor\" button at the top of this page.", + "입력해주신 정보는 내부 검토를 거치며, 일부 항목의 경우수정을 요청드릴 수 있습니다.": "The information you provide is subject to internal review, and in some cases, we may ask you to make corrections.", + "전자 계약서 서명": "Sign the contract", + "후원사 신청서에 대한 검토가 완료되면 후원사 계약을 위한 전자 계약서가 발송됩니다.": "Once your sponsor application has been reviewed, you'll be sent an electronic contract for your sponsorship agreement.", + "후원금 입금": "Deposit donations", + "계약서 서명을 완료하신 이후 2주 이내로 후원금 입금을 요청드립니다.": "We'll ask you to deposit your pledge within two weeks of signing the contract.", + "하단에 표기된 후원 금액은 부가세가 포함되지 않은 금액으로, 부가세 10%를 가산하여 입금해주셔야 합니다.": "The pledge amounts shown below are exclusive of VAT, and you will need to add 10% VAT to your pledge.", + "후원사 확정": "Confirm sponsor", + "후원 금액이 정상적으로 입금된 것이 확인된 즉시, 파이콘 한국 2024의 후원사로 확정됩니다.": "As soon as we confirm that your pledge has been successfully deposited, you will be confirmed as a sponsor of PyCon Korea 2024.", + "후원사 혜택": "Benefits of sponsor", + "후원사 부스": "Booth for sponsor", + "후원사만의 공간에서 개발자 채용, 회사 또는 서비스 홍보, 코딩 챌린지, 제비 뽑기 등 다양한 행사를 진행할 수 있습니다.": "Sponsors have their own space to hire developers, promote their company or service, host coding challenges, sweepstakes, and more.", + "후원사 세션": "Session for sponsor", + "파이콘 한국에서 후원사 로고를 걸고 파이썬 또는 회사/단체 내의 개발 문화에 대해서 이야기 할 수 있습니다.": "At PyCon Korea, you can display your sponsor's logo and talk about Python or the development culture within your company or organization.", + "로고 노출": "Display company logo", + "파이콘 한국 행사 전체와 홈페이지를 통해서 로고가 노출되며, 지난 홈페이지도 계속 보관, 유지되어 지속적으로 로고가 노출됩니다.": "The logo will be displayed throughout the PyCon Korea event and on the homepage, and past homepages will be archived and maintained for continued exposure.", + "홍보 영상": "Broadcast promotional videos", + "파이콘 한국에서 발표 세션 중간데 후원 등급별 노출 횟수에 따라 후원사 홍보 영상을 송출합니다.": "At PyCon Korea, we'll run a sponsored promotional video between presentations based on the number of impressions per sponsorship level.", + "티켓 지원": "Support ticket for sponsor", + "파이콘 한국을 즐길 수 있는 컨퍼런스 티켓을 지원합니다.": "We support conference tickets to enjoy PyCon Korea.", + "티켓 개수는 후원 등급 별 상이합니다.": "The number of tickets varies by sponsor level.", + "증정품 지급": "Give away merchandise of sponsor", + "파이콘 한국에서 후원사의 굿즈 등 소정의 증정품을 전달할 수 있습니다.": "At PyCon Korea, you'll be able to hand out small giveaways, such as merchandise from sponsors.", + "후원 증서": "Certificate of Sponsorship", + "후원에 대한 감사의 마음을 담아 파이콘 한국 후원인증서를 드립니다.": "As a thank you for your support, you will receive a PyCon Korea Sponsorship Certificate.", + "네트워킹 공간": "Space for networking", + "참가자들과 자유롭게 네트워킹 할 수 있는 공간을 제공합니다.": "Provide a space where participants can freely network with each other.", + "후원사 등급 안내": "About sponsor levels", + "전 등급 공통": "To all levels", + "SNS 홍보": "Promote on social media", + "표기된 금액은 부가세가 포함되지 않은 금액이며, 부가세는 10% 입니다.": "The amounts shown do not include VAT, which is 10%.", + "추후 일부 내용이 변경될 수 있습니다.": "Some of these may change in the future.", + "스타트업 스폰서십은 사내에서 파이썬을 사용하고, 설립 3년 이하, 사내 인원 30인 이하인 곳에 한합니다.": "Startup sponsorships are limited to companies that use Python in-house, are 3 years old or less, and have 30 or fewer employees.", + "커뮤니티 스폰서십은 비영리 단체에 한해 후원이 가능합니다.": "Community sponsorships are available for non-profit organizations only.", + "출판사 후원의 경우, 파이썬 관련 도서 출판 기록이 필요합니다.": "For publisher sponsorship, we require a record of publication of Python-related books.", + "후원 가능 여부나 기타 문의사항은 언제든지": "please feel free to contact us at", + "로 문의 주시기 바랍니다.": "for sponsorship availability or other questions.", + "후원사 신청시 여러 후원 등급에 중복 신청도 가능한가요?": "Can I apply for multiple sponsorship levels when applying for a sponsorship?", + "아니요, 중복 신청은 불가능합니다.": "No, you can't apply multiple times.", + "후원사 선정은 입금순으로 이루어지기 때문에 후원하고자 하시는 등급에 빠르게 신청하시는 걸 추천드립니다.": "We recommend applying early for the tier you'd like to sponsor, as sponsors are selected in order of deposit.", + "일부 후원 등급의 경우에는 후원사의 수가 정해져있기 때문에 조기 마감될 수 있습니다.": "Some sponsorship levels have a limited number of sponsors and may close early.", + "해당 후원 등급의 잔여 후원사 수는 추후 후원사 페이지에서 확인하실 수 있습니다.": "The number of remaining sponsorships in that level will be available on the sponsoring page at a later date.", + "후원사 선정 방법이 궁금합니다. 선착순인가요?": "I'm curious about how sponsors are selected. Is it first come, first served?", + "후원사 등록 과정이 정상적으로 등록되었다는 가정 하에 선착순으로 이뤄집니다.": "Yes, the sponsor signup process is first-come, first-served, assuming you're successfully registered.", + "후원사 등록이 정상적으로 진행되었는지 확인 방법이 있나요?": "Is there a way to verify that my sponsor registration went through?", + "입금이 완료되면 입금 확인 메일과 함께 정식 후원사 확정 메일을 보내드립니다.": "Once your deposit is complete, we'll send you an email confirming your deposit and official sponsor confirmation.", + "후원금": "Donations", + "후원금은 어디에 쓰이나요?": "Where do my donations go?", + "행사 운영비로 사용되게 됩니다.": "This will be used to fund the conference.", + "장소대여비, 부스 운영비, 각종 프로그램 진행비, 해외 스피커 항공료 및 호텔, 스피커와 운영팀 식사, 비디오녹화, 기념 티셔츠 등의 제작에 사용됩니다.": "Venue rental, booth fees, program fees, international speaker airfare and hotels, speaker and management team meals, video recording, commemorative t-shirts, and more.", + "세금 계산서 발행이 가능한가요?": "Can I issue tax invoices?", + "네, 사단법인 파이썬사용자모임 명의로 세금계산서 발행이 가능합니다.": "Yes, you can issue tax invoices in the name of the Python User Group.", + "해당 금액은 VAT가 포함된 금액인가요?": "Is that amount inclusive of VAT?", + "해당 금액은 부가세가 포함되지 않은 금액이며, 해당 항목들에 대해서도 전자 세금계산서 발급될 예정입니다.": "These amounts do not include VAT, and you will be issued an electronic tax invoice for those items as well.", + "등급 & 부스": "Level & Booth", + "부스 위치는 어떻게 결정되나요?": "How are booth locations determined?", + "준비위에서 신청순으로 배정합니다.": "Assign them in order of application from ready.", + "이후 후원사들 간에 협의하여 바꾸시는 것은 가능합니다.": "Subsequent changes can be made by agreement between sponsors.", + "바꾸신 경우에는 준비위에 알려주시기 바랍니다.": "If you make any changes, please let us know.", + "부스 운영 인력은 컨퍼런스 티켓이 필요한가요?": "Do booth staff need conference tickets?", + "부스 운영 인력은 티켓이 필요하지 않으며 별도의 식별 가능한 표식을 제공해 드릴 예정입니다.": "Booth staff do not require a ticket and will provide you with a separate identifiable sign.", + "다만 부스 운영 인력은 컨퍼런스와 세션 등을 참가할 수 없습니다.": "However, booth staffing cannot attend conferences, sessions, etc.", + "부스 운영 인력에도 제한이 있나요?": "Is there a limit to the number of people who can run a booth?", + "아니요, 별도의 인원 제한은 없습니다.": "No, there are no specific headcount limits.", + "다만 동시에 부스에 있는 운영 인력의 수를 준비위와 미리 협의하셔야 합니다.": "However, the number of operational personnel in the booth at the same time should be discussed with the organizers in advance.", + "부스에서 제공할 수 있는 물품에 제한이 있나요?": "Are there any restrictions on what I can offer at my booth?", + "아니요, 행동강령을 위반하지 않는 물품이라면 제한이 없습니다.": "No, as long as the item doesn't violate the Code of Conduct, there are no restrictions.", + "일반적으로는 티셔츠, 리딤코드, 문구류, 뱃지, 스티커 등을 제공합니다.": "Typically, we offer t-shirts, redemption codes, stationery, badges, stickers, and more.", + "후원사 세션의 발표자도 컨퍼런스 티켓을 구매해야 하나요?": "Do presenters of sponsored sessions need to purchase a conference ticket?", + "네, 모든 참가자는 컨퍼런스 티켓을 소지하셔야 합니다.": "Yes, all participants are required to have a conference ticket.", + "후원사에게 혜택으로 제공되는 컨퍼런스 티켓을 활용하실 수 있습니다.": "Take advantage of conference tickets provided as a benefit to our sponsors.", + "홍보 영상은 어느 시점에 노출되는 건가요?": "At what point will the promotional video be shown?", + "후원사 홍보 영상은 세션 중간 중간 여유 시간에 노출 됩니다.": "Sponsor promotional videos are shown during free time between sessions.", + "후원사 목록": "List of sponsors", + "키스톤": "Keystone", + "다이아몬드": "Diamond", + "플래티넘": "Platinum", + "골드": "Gold", + "스타트업": "Startup", + "커뮤니티": "Community", + "출판사": "Publisher", +} export default EnglishTranslation; diff --git a/src/models/api/sponsor.ts b/src/models/api/sponsor.ts index 3762f9b..cd5395f 100644 --- a/src/models/api/sponsor.ts +++ b/src/models/api/sponsor.ts @@ -1,5 +1,46 @@ export type APISponsor = { id: string; name: string; - level: string; + desc: string; + logo_image: string; + url: string; }; + +export type APISponsorBenefit = { + id: Number; + name: string; + desc: string; + unit: string; + is_countable: boolean; + offer: Number; +} + +export type APISponsorLevel = { + id: Number; + name: string; + desc: string; + visible: boolean; + price: Number; + limit: Number; + order: Number; + benefits: APISponsorBenefit[]; +} + +export type APISponsorLevelOnly = { + id: Number; + name: string; + desc: string; + visible: boolean; + price: Number; + limit: Number; + order: Number; +} + +export type APISponsorLevelWithSponsor = { + id: Number; + name: string; + desc: string; + visible: boolean; + order: Number; + sponsor: APISponsor[]; +} \ No newline at end of file diff --git a/src/models/sponsor.ts b/src/models/sponsor.ts index ce7bb15..6fac25a 100644 --- a/src/models/sponsor.ts +++ b/src/models/sponsor.ts @@ -1,25 +1,166 @@ -import SponsorLevels, { SponsorLevel } from "enums/sponsorLevels"; -import { APISponsor } from "./api/sponsor"; +import { APISponsorLevel, APISponsorBenefit, APISponsor, APISponsorLevelOnly, APISponsorLevelWithSponsor } from "./api/sponsor"; + +export class SponsorBenefit { + id: Number; + name: string; + desc: string; + offer: Number; + unit: string; + is_countable: boolean; + + private constructor(p: SponsorBenefit) { + this.id = p.id; + this.name = p.name; + this.desc = p.desc; + this.offer = p.offer; + this.unit = p.unit; + this.is_countable = p.is_countable; + } + + static fromAPI(d: APISponsorBenefit): SponsorBenefit { + return new SponsorBenefit({ + id: d.id, + name: d.name, + desc: d.desc, + offer: d.offer, + unit: d.unit, + is_countable: d.is_countable, + }); + } + static fromAPIs(data: APISponsorBenefit[]): SponsorBenefit[] { + return data.map((d) => SponsorBenefit.fromAPI(d)); + } +} + +export class SponsorLevel { + id: Number; + name: string; + desc: string; + visible: boolean; + price: Number; + limit: Number; + order: Number; + benefits: SponsorBenefit[] + + private constructor(p: SponsorLevel) { + this.id = p.id; + this.name = p.name; + this.desc = p.desc; + this.visible = p.visible; + this.price = p.price; + this.limit = p.limit; + this.order = p.order; + this.benefits = SponsorBenefit.fromAPIs(p.benefits); + } + + static fromAPI(d: APISponsorLevel): SponsorLevel { + return new SponsorLevel({ + id: d.id, + name: d.name, + desc: d.desc, + visible: d.visible, + price: d.price, + limit: d.limit, + order: d.order, + benefits: d.benefits + }); + } + static fromAPIs(data: APISponsorLevel[]): SponsorLevel[] { + return data.map((d) => SponsorLevel.fromAPI(d)); + } +} + +export class SponsorLevelWithSponsor { + id: Number; + name: string; + desc: string; + visible: boolean; + order: Number; + sponsor: Sponsor[]; + + private constructor(p: SponsorLevelWithSponsor) { + this.id = p.id; + this.name = p.name; + this.desc = p.desc; + this.visible = p.visible; + this.order = p.order; + this.sponsor = Sponsor.fromAPIs(p.sponsor); + } + + static fromAPI(d: APISponsorLevelWithSponsor): SponsorLevelWithSponsor { + return new SponsorLevelWithSponsor({ + id: d.id, + name: d.name, + desc: d.desc, + visible: d.visible, + order: d.order, + sponsor: d.sponsor, + }); + } + static fromAPIs(data: APISponsorLevelWithSponsor[]): SponsorLevelWithSponsor[] { + return data.map((d) => SponsorLevelWithSponsor.fromAPI(d)); + } +} + +class SponsorLevelOnly { + id: Number; + name: string; + desc: string; + visible: boolean; + price: Number; + limit: Number; + order: Number; + + private constructor(p: SponsorLevelOnly) { + this.id = p.id; + this.name = p.name; + this.desc = p.desc; + this.visible = p.visible; + this.price = p.price; + this.limit = p.limit; + this.order = p.order; + } + + static fromAPI(d: APISponsorLevelOnly): SponsorLevelOnly { + return new SponsorLevelOnly({ + id: d.id, + name: d.name, + desc: d.desc, + visible: d.visible, + price: d.price, + limit: d.limit, + order: d.order, + }); + } +} + export class Sponsor { id: string; name: string; - level: SponsorLevel; + desc: string; + logo_image: string; + url: string; private constructor(p: Sponsor) { this.id = p.id; this.name = p.name; - this.level = p.level; + this.desc = p.desc; + this.logo_image = p.logo_image; + this.url = p.url; } static fromAPI(d: APISponsor): Sponsor { return new Sponsor({ id: d.id, name: d.name, - level: SponsorLevels.fromCode(d.level), + desc: d.desc, + logo_image: d.logo_image, + url: d.url, }); } static fromAPIs(data: APISponsor[]): Sponsor[] { return data.map((d) => Sponsor.fromAPI(d)); } } + diff --git a/src/pages/Sponsor/SponsorLevelList.tsx b/src/pages/Sponsor/SponsorLevelList.tsx new file mode 100644 index 0000000..63bae07 --- /dev/null +++ b/src/pages/Sponsor/SponsorLevelList.tsx @@ -0,0 +1,233 @@ +import { SponsorAPI } from "api"; +import React, { useEffect, useState } from "react"; +import useTranslation from "utils/hooks/useTranslation"; +import { SponsorLevel, SponsorBenefit } from "models/sponsor"; +import styled from "styled-components"; + +const SponsorLevelList = () => { + const t = useTranslation(); + const [listOfSponsorLevel, setListOfSponsorLevel] = useState([]); + const [listOfSponsorBenefit, setListOfSponsorBenefit] = useState([]); + + useEffect(() => { + SponsorAPI.listSponsorLevels().then((levels) => { + if (levels.length > 4) { + const half_length = Math.ceil(levels.length / 2); + const firstSide = levels.slice(0, half_length); + const secondSide = levels.slice(half_length); + setListOfSponsorLevel([firstSide, secondSide]); + } else { + setListOfSponsorLevel([levels]); + } + }); + SponsorAPI.listSponsorBenefits().then((benefits) => { + setListOfSponsorBenefit(benefits); + }); + }, []); + + return ( + + +

{t("후원사 등급 안내")}

+ {listOfSponsorLevel.map((sponsorLevel) => ( + +
+ + + {sponsorLevel.map((level) => ( + + ))} + + + {listOfSponsorBenefit.map((benefit) => ( + + + {sponsorLevel.map((level) => { + const benefitAboutLevel = level.benefits.find( + (benefitByLevel) => benefitByLevel.id === benefit.id + ); + return ( + + ); + })} + + ))} + +
{level.name}
{benefit.name} + {benefitAboutLevel?.is_countable ? `` : ``} + {benefitAboutLevel?.offer === 0 + ? "-" + : benefitAboutLevel?.offer.toString()} + {benefitAboutLevel?.is_countable && benefitAboutLevel?.unit} +
+ + ))} + +

{t("전 등급 공통")}

+
    +
  • {t("SNS 홍보")}
  • +
  • {t("증정품 지급")}
  • +
  • {t("후원 증서")}
  • +
+
+ +
    +
  • {t("표기된 금액은 부가세가 포함되지 않은 금액이며, 부가세는 10% 입니다.")}
  • +
  • {t("추후 일부 내용이 변경될 수 있습니다.")}
  • +
  • + {t( + "스타트업 스폰서십은 사내에서 파이썬을 사용하고, 설립 3년 이하, 사내 인원 30인 이하인 곳에 한합니다." + )} +
  • +
  • {t("커뮤니티 스폰서십은 비영리 단체에 한해 후원이 가능합니다.")}
  • +
  • {t("출판사 후원의 경우, 파이썬 관련 도서 출판 기록이 필요합니다.")}
  • +
  • + {t("후원 가능 여부나 기타 문의사항은 언제든지")}{" "} + + sponsor@pycon.kr + {" "} + {t("로 문의 주시기 바랍니다.")} +
  • +
+
+ + + ); +}; + +export default SponsorLevelList; + +const Container = styled.div` + display: flex; + justify-content: center; + align-items: center; + padding: 2rem 0; + width: 100%; + + &:nth-child(even) { + background-color: #141414; + } +`; + +const Vertical = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + margin-bottom: 2rem; + width: 100%; +`; + +const Caution = styled.div` + width: 100%; + + & > ul { + margin-left: 1.5rem; + display: block; + padding-top: 1rem; + + @media only screen and (max-width: 810px) { + width: 85%; + margin-left: 2rem; + } + + & > li { + list-style: disc; + text-align: start; + font-size: 16px; + + @media only screen and (max-width: 810px) { + font-size: 12px; + } + } + } +`; + +const SponsorRatingTable = styled.div` + width: 100%; + height: 100%; + border: 2px solid #b0a8fe; + border-radius: 1rem; + margin-top: 2rem; + + @media only screen and (max-width: 810px) { + width: 90%; + margin: 2rem 1rem 0 1rem; + } + + & > h3 { + text-align: start; + padding-left: 2rem; + } + + & > ul { + padding-left: 1.5rem; + padding-bottom: 0.5rem; + + & > li { + list-style: disc; + text-align: start; + color: #b0a8fe; + font-size: 16px; + } + } + + & > table { + background: tansparent; + border-collapse: collapse; + border-style: hidden; + + & > thead > td { + text-align: center; + background: #b0a8fe; + color: #141414; + + @media only screen and (max-width: 810px) { + font-size: 12px; + } + + &:first-child { + border-top-left-radius: 0.5rem; + } + + &:last-child { + border-top-right-radius: 0.5rem; + } + } + + & > tbody > tr > td { + @media only screen and (max-width: 810px) { + font-size: 10px; + } + + border-bottom: 1px solid #b0a8fe; + padding: 1rem 0; + text-align: center; + background: #141414; + color: #b0a8fe; + } + } +`; + +const H1 = styled.h1` + margin-top: 3rem; + font-size: 40px; + color: #b0a8fe; + + @media only screen and (max-width: 810px) { + padding: 0 1rem; + font-size: 24px; + } +`; + +const H3 = styled.h3` + margin-top: 1.5rem; + font-size: 24px; + color: #b0a8fe; + + @media only screen and (max-width: 810px) { + padding: 0 1rem; + font-size: 16px; + } +`; diff --git a/src/pages/Sponsor/index.tsx b/src/pages/Sponsor/index.tsx index 1d72202..d80a180 100644 --- a/src/pages/Sponsor/index.tsx +++ b/src/pages/Sponsor/index.tsx @@ -7,21 +7,36 @@ import styled from "styled-components"; import Page from "components/common/Page"; import useIsMobile from "utils/hooks/useIsMobile"; import Collapse from "components/common/Collapse"; -import SponsorTable from "./SponsorTable"; +import { Link } from "react-router-dom"; +import useTranslation from "utils/hooks/useTranslation"; +import SponsorLevelList from "./SponsorLevelList"; const SponsorPage = () => { + const t = useTranslation(); const isMobile = useIsMobile(); return ( - sponsor_logo - 2024.05.27 - 2024.07.31 + sponsor_logo + 2024.05.27 - 2024.08.31 - - + + + + + + - 후원 관련 문의:{" "} + {t("후원 관련 문의")}:{" "} sponsor@pycon.kr @@ -31,22 +46,29 @@ const SponsorPage = () => { - finale2023_lg -

파이콘 한국 후원의 의미

-

파이콘 한국은?

+ finale2023_lg +

{t("파이콘 한국 후원의 의미")}

+

{t("파이콘 한국은?")}

- 파이콘 한국은 커뮤니티 주관으로 이뤄지는 비영리 개발자 대상 행사로 오픈소스 프로그래밍 - 언어인 파이썬의 저변 확대와 커뮤니티 활성화를 위해 진행하는 행사입니다. + {t( + "파이콘 한국은 커뮤니티 주관으로 이뤄지는 비영리 개발자 대상 행사로 오픈소스 프로그래밍 언어인 파이썬의 저변 확대와 커뮤니티 활성화를 위해 진행하는 행사입니다." + )}
-

비영리 행사

+

{t("비영리 행사")}

- 파이콘 한국의 발표자 및 튜토리얼 진행자를 포함, 자원봉사자와 준비위원회 담당자 등 모든 - 인원이 금전적 이득 없이 행사를 준비하고 운영해 나갑니다. + {t( + "파이콘 한국의 발표자 및 튜토리얼 진행자를 포함, 자원봉사자와 준비위원회 담당자 등 모든 인원이 금전적 이득 없이 행사를 준비하고 운영해 나갑니다." + )}
-

커뮤니티에 기여

+

{t("커뮤니티에 기여")}

- 이에 파이콘 한국에의 후원은 국내 오픈소스 커뮤니티와 파이썬 커뮤니티에 대한 가장 좋은 - 기여 방법이며 여러 우수한 개발자들과의 만남을 가지실 수 있는 기회입니다. + {t( + "이에 파이콘 한국에의 후원은 국내 오픈소스 커뮤니티와 파이썬 커뮤니티에 대한 가장 좋은 기여 방법이며 여러 우수한 개발자들과의 만남을 가지실 수 있는 기회입니다." + )}
@@ -54,55 +76,70 @@ const SponsorPage = () => { introduceSlogan.png -

2024 파이콘 한국

+

{t("파이콘 한국 2024")}

- 2014년, 한국에서 첫 파이콘이 열린 이후로 파이써니스타들은 파이콘이라는 만남의 장에 - 파이썬이라는 하나의 공통점으로 뭉쳐 각자의 순간들을 나누고, 새로운 순간들을 함께 - 만들어왔습니다. 여러분의 소중한 순간이 모여 파이콘은 점점 성장해올 수 있었고, 어느덧 - 10번째 파이콘 한국을 앞두고 있습니다. 파이썬과 행복했던 순간들, 파이썬이기에 가능했던 - 순간들, 여러분이 소중한 순간순간들을 가지고 모여 함께 새로운 순간들을 만들어 내길 - 바랍니다. + {t( + "2014년, 한국에서 첫 파이콘이 열린 이후로 파이써니스타들은 파이콘이라는 만남의 장에 파이썬이라는 하나의 공통점으로 뭉쳐 각자의 순간들을 나누고, 새로운 순간들을 함께 만들어왔습니다." + )} +
+ {t( + "여러분의 소중한 순간이 모여 파이콘은 점점 성장해올 수 있었고, 어느덧 10번째 파이콘 한국을 앞두고 있습니다." + )} +
+ {t( + "파이썬과 행복했던 순간들, 파이썬이기에 가능했던 순간들, 여러분이 소중한 순간순간들을 가지고 모여 함께 새로운 순간들을 만들어 내길 바랍니다." + )}
- + + +
-

후원사 신청 절차

+

{t("후원사 신청 절차")}

01

-

후원사 신청

+

{t("후원사 신청")}

- 본 페이지 상단의 ‘후원사로 참여하기' 버튼을 통해 후원에 필요한 정보를 입력해주세요. - 입력해주신 정보는 내부 검토를 거치며, 일부 항목의 경우수정을 요청드릴 수 있습니다. + {t( + "본 페이지 상단의 ‘후원사로 참여하기' 버튼을 통해 후원에 필요한 정보를 입력해주세요." + )}{" "} + {t( + "입력해주신 정보는 내부 검토를 거치며, 일부 항목의 경우수정을 요청드릴 수 있습니다." + )}

02

-

전자 계약서 서명

+

{t("전자 계약서 서명")}

- 후원사 신청서에 대한 검토가 완료되면 후원사 계약을 위한 전자 계약서가 발송됩니다. + {t( + "후원사 신청서에 대한 검토가 완료되면 후원사 계약을 위한 전자 계약서가 발송됩니다." + )}

03

-

후원금 입금

+

{t("후원금 입금")}

- 계약서 서명을 완료하신 이후 2주 이내로 후원금 입금을 요청드립니다. 하단에 표기된 - 후원 금액은 부가세가 포함되지 않은 금액으로, 부가세 10%를 가산하여 입금해주셔야 - 합니다. + {t("계약서 서명을 완료하신 이후 2주 이내로 후원금 입금을 요청드립니다.")}{" "} + {t( + "하단에 표기된 후원 금액은 부가세가 포함되지 않은 금액으로, 부가세 10%를 가산하여 입금해주셔야 합니다." + )}

04

-

후원사 확정

+

{t("후원사 확정")}

- 후원 금액이 정상적으로 입금된 것이 확인된 즉시, 파이콘 한국 2024의 후원사로 - 확정됩니다. + {t( + "후원 금액이 정상적으로 입금된 것이 확인된 즉시, 파이콘 한국 2024의 후원사로 확정됩니다." + )}
@@ -110,7 +147,7 @@ const SponsorPage = () => {
-

후원사 혜택

+

{t("후원사 혜택")}

{ > -

후원사 부스

+

{t("후원사 부스")}

- 후원사만의 공간에서 개발자 채용, 회사 또는 서비스 홍보, 코딩 챌린지, 제비 뽑기 등 - 다양한 행사를 진행할 수 있습니다. + {t( + "후원사만의 공간에서 개발자 채용, 회사 또는 서비스 홍보, 코딩 챌린지, 제비 뽑기 등 다양한 행사를 진행할 수 있습니다." + )}
- +
-

후원사 세션

+

{t("후원사 세션")}

- 파이콘 한국에서 후원사 로고를 걸고 파이썬 또는 회사/단체 내의 개발 문화에 대해서 - 이야기 할 수 있습니다. + {t( + "파이콘 한국에서 후원사 로고를 걸고 파이썬 또는 회사/단체 내의 개발 문화에 대해서 이야기 할 수 있습니다." + )}
- +
-

로고 노출

+

{t("로고 노출")}

- 파이콘 한국 행사 전체와 홈페이지를 통해서 로고가 노출되며, 지난 홈페이지도 계속 - 보관, 유지되어 지속적으로 로고가 노출됩니다. + {t( + "파이콘 한국 행사 전체와 홈페이지를 통해서 로고가 노출되며, 지난 홈페이지도 계속 보관, 유지되어 지속적으로 로고가 노출됩니다." + )}
- +
-

홍보 영상

+

{t("홍보 영상")}

- 파이콘 한국에서 발표 세션 중간데 후원 등급별 노출 횟수에 따라 후원사 홍보 영상을 - 송출합니다. + {t( + "파이콘 한국에서 발표 세션 중간데 후원 등급별 노출 횟수에 따라 후원사 홍보 영상을 송출합니다." + )}
- +
-

티켓 지원

+

{t("티켓 지원")}

- 파이콘 한국을 즐길 수 있는 컨퍼런스 티켓을 지원합니다. 티켓 개수는 후원 등급 별 - 상이합니다. + {t("파이콘 한국을 즐길 수 있는 컨퍼런스 티켓을 지원합니다.")}{" "} + {t("티켓 개수는 후원 등급 별 상이합니다.")}
- +
-

증정품 지급

-
파이콘 한국에서 후원사의 굿즈 등 소정의 증정품을 전달할 수 있습니다.
- +

{t("증정품 지급")}

+
{t("파이콘 한국에서 후원사의 굿즈 등 소정의 증정품을 전달할 수 있습니다.")}
+
-

후원 증서

-
후원에 대한 감사의 마음을 담아 파이콘 한국 후원인증서를 드립니다.
- +

{t("후원 증서")}

+
{t("후원에 대한 감사의 마음을 담아 파이콘 한국 후원인증서를 드립니다.")}
+
-

네트워킹 공간

-
참가자들과 자유롭게 네트워킹 할 수 있는 공간을 제공합니다.
- +

{t("네트워킹 공간")}

+
{t("참가자들과 자유롭게 네트워킹 할 수 있는 공간을 제공합니다.")}
+
- - -

후원사 등급 안내

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
키스톤다이아몬드플래티넘
후원금20,000,000원10,000,000원6,000,000원
티켓 지원20매15매5매
부스5칸3칸1칸
테이블---
후원사 세션1세션1세션-
홍보 영상2회1회2회
로고 노출 위치 - 네임택(티켓) 스트랩
- 현수막 -
- 스탠딩 배너 -
- 웹사이트 -
- 현수막 -
- 스탠딩 배너 -
- 웹사이트 -
- 현수막 -
- 스탠딩 배너 -
- 웹사이트 -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
골드스타트업커뮤니티출판사
후원금3,000,000원1,000,000원300,000원도서 60권 이상
티켓 지원3매---
부스----
테이블1칸1칸1칸1칸
후원사 세션----
홍보 영상1회---
로고 노출 위치 - 현수막 -
- 스탠딩 배너 -
- 웹사이트 -
- 현수막 -
- 스탠딩 배너 -
- 웹사이트 -
- 현수막 -
- 웹사이트 -
- 현수막 -
- 스탠딩 배너 -
- 웹사이트 -
-
- -

전 등급 공통

-
    -
  • SNS 홍보
  • -
  • 증정품 지급
  • -
  • 후원 증서
  • -
-
- -
    -
  • 표기된 금액은 부가세가 포함되지 않은 금액이며, 부가세는 10% 입니다.
  • -
  • 추후 일부 내용이 변경될 수 있습니다.
  • -
  • - 스타트업 스폰서십은 사내에서 파이썬을 사용하고, 설립 3년 이하, 사내 인원 30인 이하인 - 곳에 한합니다. -
  • -
  • 커뮤니티 스폰서십은 비영리 단체에 한해 후원이 가능합니다.
  • -
  • 출판사 후원의 경우, 파이썬 관련 도서 출판 기록이 필요합니다.
  • -
  • - 후원 가능 여부나 기타 문의사항은 언제든지{" "} - - sponsor@pycon.kr - - 로 문의 주시기 바랍니다. -
  • -
-
-
-
+

FAQ

-

후원사 신청

+

{t("후원사 신청")}

-

후원금

+

{t("후원금")}

-

등급 & 부스

+

{t("등급 & 부스")}

- - -

후원사 목록

- - - - - - - - - -
-
); }; @@ -556,12 +438,6 @@ const Button = styled.button` font-size: 16px; `; -const SponsorList = styled.div` - & > div + div { - margin-top: 2rem; - } -`; - const Faq = styled.table` border: 1px solid #b0a8fe; border-right: none; @@ -605,72 +481,6 @@ const GuideCard = styled.div` } `; -const SponsorRatingTable = styled.div` - width: 100%; - height: 100%; - border: 2px solid #b0a8fe; - border-radius: 1rem; - margin-top: 2rem; - - @media only screen and (max-width: 810px) { - width: 90%; - margin: 2rem 1rem 0 1rem; - } - - & > h3 { - text-align: start; - padding-left: 2rem; - } - - & > ul { - padding-left: 1.5rem; - padding-bottom: 0.5rem; - - & > li { - list-style: disc; - text-align: start; - color: #b0a8fe; - font-size: 16px; - } - } - - & > table { - background: tansparent; - border-collapse: collapse; - border-style: hidden; - - & > thead > td { - text-align: center; - background: #b0a8fe; - color: #141414; - - @media only screen and (max-width: 810px) { - font-size: 12px; - } - - &:first-child { - border-top-left-radius: 0.5rem; - } - - &:last-child { - border-top-right-radius: 0.5rem; - } - } - - & > tbody > tr > td { - @media only screen and (max-width: 810px) { - font-size: 10px; - } - - border-bottom: 1px solid #b0a8fe; - padding: 1rem 0; - text-align: center; - background: #141414; - color: #b0a8fe; - } - } -`; - const BenefitCard = styled.div` border: 1px solid #b0a8fe; border-radius: 1rem; @@ -699,31 +509,6 @@ const BenefitImage = styled.img` border-bottom-right-radius: 1rem !important; `; -const Caution = styled.div` - width: 100%; - - & > ul { - margin-left: 1.5rem; - display: block; - padding-top: 1rem; - - @media only screen and (max-width: 810px) { - width: 85%; - margin-left: 2rem; - } - - & > li { - list-style: disc; - text-align: start; - font-size: 16px; - - @media only screen and (max-width: 810px) { - font-size: 12px; - } - } - } -`; - const Vertical = styled.div` display: flex; flex-direction: column; diff --git a/src/utils/hooks/useTranslation.ts b/src/utils/hooks/useTranslation.ts index 958718c..43deafa 100644 --- a/src/utils/hooks/useTranslation.ts +++ b/src/utils/hooks/useTranslation.ts @@ -10,7 +10,7 @@ const useTranslation = () => { (state) => state.core.language ); const t = useCallback( - (key: string) => { + (key: string, suffix: string = "") => { let json; switch (language) { case "ENG": @@ -22,8 +22,9 @@ const useTranslation = () => { json = EngTranslationKey; break; } + const sentence = `${key}${suffix}` - return key in json ? json[key as keyof typeof json] : key; + return key in json ? json[sentence as keyof typeof json] : key; }, [language] );