Skip to content

Commit 20bd691

Browse files
authored
Merge pull request #26 from pythonkr/feature/sponsor-detail
Feat: 후원사 상세 페이지 작업
2 parents df8e0b1 + b6d3020 commit 20bd691

File tree

12 files changed

+10450
-14713
lines changed

12 files changed

+10450
-14713
lines changed

.env.development

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
REACT_APP_PYCONKR_API=https://api-dev.pycon.kr
1+
# REACT_APP_PYCONKR_API=https://api-dev.pycon.kr
2+
REACT_APP_PYCONKR_API=http://localhost:8888

scripts/set_dev.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
cp ./.env.dev ./.env || copy .\\env.dev .\\.env
1+
cp ./.env.development ./.env || copy .\\env.development .\\.env

src/api/sponsor.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,21 @@ import { APISponsor, APISponsorBenefit, APISponsorLevel, APISponsorLevelWithSpon
44
import { Sponsor, SponsorBenefit, SponsorLevel, SponsorLevelWithSponsor } from "models/sponsor"
55

66

7+
export function detailSponsor(id: string): Promise<Sponsor> {
8+
return new Promise((resolve, reject) => {
9+
instance.get<APISponsor>(`http://localhost:8888/2024/sponsors/list/${id}/`).then(response => {
10+
resolve(Sponsor.fromAPI(response.data));
11+
}).catch(error => {
12+
console.error(error);
13+
reject(getErrorMessage(error));
14+
})
15+
return;
16+
})
17+
}
18+
719
export function listSponsorLevels(): Promise<SponsorLevel[]> {
820
return new Promise((resolve, reject) => {
9-
instance.get<APISponsorLevel[]>("/2024/sponsors/levels").then((response) => {
21+
instance.get<APISponsorLevel[]>("http://localhost:8888/2024/sponsors/levels").then((response) => {
1022
resolve(SponsorLevel.fromAPIs(response.data));
1123
}).catch((error) => {
1224
console.error(error);
@@ -19,7 +31,7 @@ export function listSponsorLevels(): Promise<SponsorLevel[]> {
1931
export function listSponsors(): Promise<Sponsor[]> {
2032
return new Promise((resolve, reject) => {
2133
instance
22-
.get<APISponsor[]>("/2024/sponsors/list/")
34+
.get<APISponsor[]>("http://localhost:8888/2024/sponsors/list/")
2335
.then((response) => {
2436
resolve(Sponsor.fromAPIs(response.data));
2537
})
@@ -32,7 +44,7 @@ export function listSponsors(): Promise<Sponsor[]> {
3244
export function listSponsorLevelWithSponsor(): Promise<SponsorLevelWithSponsor[]> {
3345
return new Promise((resolve, reject) => {
3446
instance
35-
.get<APISponsorLevelWithSponsor[]>("/2024/sponsors/levels/with-sponsor/")
47+
.get<APISponsorLevelWithSponsor[]>("http://localhost:8888/2024/sponsors/levels/with-sponsor/")
3648
.then((response) => {
3749
console.log("debug", response);
3850
resolve(SponsorLevelWithSponsor.fromAPIs(response.data));
@@ -46,7 +58,7 @@ export function listSponsorLevelWithSponsor(): Promise<SponsorLevelWithSponsor[]
4658

4759
export function listSponsorBenefits(): Promise<SponsorBenefit[]> {
4860
return new Promise((resolve, reject) => {
49-
instance.get<APISponsorBenefit[]>("/2024/sponsors/benefits/").then(response => {
61+
instance.get<APISponsorBenefit[]>("http://localhost:8888/2024/sponsors/benefits/").then(response => {
5062
resolve(SponsorBenefit.fromAPIs(response.data));
5163
}).catch(error => {
5264
console.error(error);

src/components/Footer/SponsorList.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const Vertical = styled.div`
5858
`;
5959

6060
const SponsorTableList = styled.div`
61+
width: 80%;
6162
& > div + div {
6263
margin-top: 2rem;
6364
}

src/components/Footer/SponsorTable.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Sponsor } from "models/sponsor";
2+
import { Link } from "react-router-dom";
23
import React, { useCallback, useEffect, useRef, useState } from "react";
34
import styled from "styled-components";
45

@@ -14,7 +15,9 @@ function SponsorTable({ max, levelName, sponsors, ...rest }: Props) {
1415
<h3>{levelName}</h3>
1516
<div style={{ gridTemplateColumns: `repeat(${max}, 1fr)` }}>
1617
{sponsors.map((sponsor) => (
17-
<img src={sponsor.logo_image} alt={sponsor.name} />
18+
<Link to={`/sponsoring/sponsor/${sponsor.id}`} relative="path">
19+
<img src={sponsor.logo_image} alt={sponsor.name} />
20+
</Link>
1821
))}
1922
</div>
2023
</SponsorCard>
@@ -42,7 +45,6 @@ const SponsorCard = styled.div`
4245
}
4346
4447
@media only screen and (max-width: 810px) {
45-
display: block;
4648
margin: 1rem;
4749
4850
& > div {

src/models/api/sponsor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export type APISponsorBenefit = {
1313
unit: string;
1414
is_countable: boolean;
1515
offer: Number;
16+
uncountable_offer: string;
1617
}
1718

1819
export type APISponsorLevel = {

src/models/sponsor.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export class SponsorBenefit {
77
offer: Number;
88
unit: string;
99
is_countable: boolean;
10+
uncountable_offer: string;
1011

1112
private constructor(p: SponsorBenefit) {
1213
this.id = p.id;
@@ -15,6 +16,7 @@ export class SponsorBenefit {
1516
this.offer = p.offer;
1617
this.unit = p.unit;
1718
this.is_countable = p.is_countable;
19+
this.uncountable_offer = p.uncountable_offer;
1820
}
1921

2022
static fromAPI(d: APISponsorBenefit): SponsorBenefit {
@@ -25,6 +27,7 @@ export class SponsorBenefit {
2527
offer: d.offer,
2628
unit: d.unit,
2729
is_countable: d.is_countable,
30+
uncountable_offer: d.uncountable_offer,
2831
});
2932
}
3033
static fromAPIs(data: APISponsorBenefit[]): SponsorBenefit[] {

src/pages/Sponsor/SponsorDetail.tsx

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { SponsorAPI } from "api";
2+
import * as R from "remeda";
3+
import { wrap } from "@suspensive/react";
4+
import React, { useEffect, useState } from "react";
5+
import useTranslation from "utils/hooks/useTranslation";
6+
import { APISponsor } from "models/api/sponsor";
7+
import { useNavigate, useParams } from "react-router";
8+
import styled from "styled-components";
9+
import { useRetrieveSponsorQuery } from "utils/hooks/useAPI";
10+
11+
const SponsorDetail: React.FC<{ sponsor: APISponsor }> = ({ sponsor }) => {
12+
return (
13+
<Vertical>
14+
<H1>{sponsor.name}</H1>
15+
<a href={sponsor.url}>
16+
<img src={sponsor.logo_image} alt={sponsor.name} />
17+
</a>
18+
<H3 dangerouslySetInnerHTML={{ __html: sponsor.desc }}></H3>
19+
</Vertical>
20+
);
21+
};
22+
23+
const SponsorDetailPage = () => {
24+
const t = useTranslation();
25+
const navigate = useNavigate();
26+
const { id } = useParams<{ id: string }>();
27+
28+
if (!(R.isString(id) && !R.isEmpty(id))) {
29+
navigate("/session");
30+
return null;
31+
}
32+
33+
const SponsorDetailWrapper = wrap
34+
.ErrorBoundary({ fallback: <h4>{t("후원사 정보를 불러오는 중 에러가 발생했습니다.")}</h4> })
35+
.Suspense({ fallback: <h4>{t("후원사 정보를 불러오는 중 입니다.")}</h4> })
36+
.on(() => {
37+
// eslint-disable-next-line react-hooks/rules-of-hooks
38+
const { data } = useRetrieveSponsorQuery(id);
39+
return <SponsorDetail sponsor={data} />;
40+
});
41+
42+
return (
43+
<Container>
44+
<SponsorDetailWrapper />
45+
</Container>
46+
);
47+
};
48+
49+
export default SponsorDetailPage;
50+
51+
const Container = styled.div`
52+
display: flex;
53+
justify-content: center;
54+
align-items: center;
55+
padding: 2rem 0;
56+
width: 100%;
57+
58+
&:nth-child(even) {
59+
background-color: #141414;
60+
}
61+
`;
62+
63+
const Vertical = styled.div`
64+
display: flex;
65+
flex-direction: column;
66+
justify-content: center;
67+
align-items: center;
68+
text-align: center;
69+
margin-bottom: 2rem;
70+
width: 100%;
71+
`;
72+
73+
const H1 = styled.h1`
74+
margin-top: 3rem;
75+
font-size: 40px;
76+
color: #b0a8fe;
77+
78+
@media only screen and (max-width: 810px) {
79+
padding: 0 1rem;
80+
font-size: 24px;
81+
}
82+
`;
83+
84+
const H3 = styled.h3`
85+
margin-top: 1.5rem;
86+
font-size: 24px;
87+
color: #b0a8fe;
88+
89+
@media only screen and (max-width: 810px) {
90+
padding: 0 1rem;
91+
font-size: 16px;
92+
}
93+
`;

src/pages/Sponsor/SponsorLevelList.tsx

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,24 @@ const SponsorLevelList = () => {
99
const [listOfSponsorLevel, setListOfSponsorLevel] = useState<SponsorLevel[][]>([]);
1010
const [listOfSponsorBenefit, setListOfSponsorBenefit] = useState<SponsorBenefit[]>([]);
1111

12+
const getBenefitDescription = (benefit: SponsorBenefit | undefined) => {
13+
if (benefit === undefined) return "-";
14+
if (benefit.is_countable) {
15+
return benefit.offer ? `${benefit.offer}${benefit.unit}` : benefit.uncountable_offer;
16+
}
17+
return benefit.uncountable_offer;
18+
};
19+
1220
useEffect(() => {
1321
SponsorAPI.listSponsorLevels().then((levels) => {
14-
if (levels.length > 4) {
15-
const half_length = Math.ceil(levels.length / 2);
16-
const firstSide = levels.slice(0, half_length);
17-
const secondSide = levels.slice(half_length);
22+
const onlyVisible = levels.filter((level) => level.visible);
23+
if (onlyVisible.length > 4) {
24+
const half_length = Math.ceil(onlyVisible.length / 2);
25+
const firstSide = onlyVisible.slice(0, half_length);
26+
const secondSide = onlyVisible.slice(half_length);
1827
setListOfSponsorLevel([firstSide, secondSide]);
1928
} else {
20-
setListOfSponsorLevel([levels]);
29+
setListOfSponsorLevel([onlyVisible]);
2130
}
2231
});
2332
SponsorAPI.listSponsorBenefits().then((benefits) => {
@@ -47,13 +56,11 @@ const SponsorLevelList = () => {
4756
(benefitByLevel) => benefitByLevel.id === benefit.id
4857
);
4958
return (
50-
<td>
51-
{benefitAboutLevel?.is_countable ? `` : ``}
52-
{benefitAboutLevel?.offer === 0
53-
? "-"
54-
: benefitAboutLevel?.offer.toString()}
55-
{benefitAboutLevel?.is_countable && benefitAboutLevel?.unit}
56-
</td>
59+
<td
60+
dangerouslySetInnerHTML={{
61+
__html: getBenefitDescription(benefitAboutLevel),
62+
}}
63+
></td>
5764
);
5865
})}
5966
</tr>
@@ -201,6 +208,11 @@ const SponsorRatingTable = styled.div`
201208
font-size: 10px;
202209
}
203210
211+
& > * {
212+
color: #b0a8fe;
213+
margin: 0;
214+
}
215+
204216
border-bottom: 1px solid #b0a8fe;
205217
padding: 1rem 0;
206218
text-align: center;

src/routes.tsx

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1-
import React from "react"
2-
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"
1+
import React from "react";
2+
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
33

4-
import Footer from "components/Footer"
5-
import Nav from "components/Nav"
6-
import { DialogCollection } from "dialogs"
7-
import Coc from "pages/About/coc"
8-
import Pyconkr2024 from "pages/About/pyconkr2024"
9-
import Cfp from "pages/Contribution/cfp"
10-
import Home from "pages/Home"
11-
import NotFound from "pages/NotFound"
12-
import PrivacyPolicy from "pages/PrivacyPolicy"
13-
import { SessionDetailPage } from "pages/Session/detail"
14-
import { SessionListPage } from "pages/Session/list"
15-
import { SessionTimeTablePage } from "pages/Session/timetable"
16-
import SponsorPage from "pages/Sponsor"
17-
import TermsOfService from "pages/TermsOfService"
18-
import Health from "./pages/About/health"
4+
import Footer from "components/Footer";
5+
import Nav from "components/Nav";
6+
import { DialogCollection } from "dialogs";
7+
import Coc from "pages/About/coc";
8+
import Pyconkr2024 from "pages/About/pyconkr2024";
9+
import Cfp from "pages/Contribution/cfp";
10+
import Home from "pages/Home";
11+
import NotFound from "pages/NotFound";
12+
import PrivacyPolicy from "pages/PrivacyPolicy";
13+
import { SessionDetailPage } from "pages/Session/detail";
14+
import { SessionListPage } from "pages/Session/list";
15+
import { SessionTimeTablePage } from "pages/Session/timetable";
16+
import SponsorPage from "pages/Sponsor";
17+
import TermsOfService from "pages/TermsOfService";
18+
import Health from "./pages/About/health";
19+
import SponsorDetailPage from "pages/Sponsor/SponsorDetail";
1920

2021
const Router = () => {
2122
return (
@@ -27,6 +28,7 @@ const Router = () => {
2728
<Route path="/about/coc" element={<Coc />} />
2829
<Route path="/about/health" element={<Health />} />
2930
<Route path="/sponsoring/sponsor/prospectus" element={<SponsorPage />} />
31+
<Route path="/sponsoring/sponsor/:id" element={<SponsorDetailPage />} />
3032
<Route path="/session" element={<SessionListPage />} />
3133
<Route path="/session/:code" element={<SessionDetailPage />} />
3234
<Route path="/session/timetable" element={<SessionTimeTablePage />} />

0 commit comments

Comments
 (0)