Skip to content

Commit e9e6b28

Browse files
committed
feat: 후원사 API 연동
1 parent c64b2ea commit e9e6b28

File tree

2 files changed

+99
-122
lines changed

2 files changed

+99
-122
lines changed
Lines changed: 98 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,98 @@
1-
import styled from "@emotion/styled";
2-
import { useEffect, useState } from "react";
3-
4-
import SponsorExample from "../../../assets/sponsorExample.svg?react";
5-
6-
interface Sponsor {
7-
id: number;
8-
name: string;
9-
Logo: React.ComponentType;
10-
}
11-
12-
export default function Sponsor() {
13-
const [sponsors, setSponsors] = useState<Sponsor[]>([]);
14-
15-
// 16개의 임시 스폰서 데이터 생성
16-
useEffect(() => {
17-
const fetchSponsors = () => {
18-
const dummySponsors = Array(16)
19-
.fill(null)
20-
.map((_, index) => ({
21-
id: index + 1,
22-
name: `후원사 ${index + 1}`,
23-
Logo: SponsorExample,
24-
}));
25-
26-
setSponsors(dummySponsors);
27-
};
28-
29-
fetchSponsors();
30-
}, []);
31-
32-
return (
33-
<SponsorSection aria-label="후원사 섹션">
34-
<SponsorTitle as="h4" role="heading" aria-level={4}>
35-
후원사 목록
36-
</SponsorTitle>
37-
<SponsorGrid role="list" aria-label="후원사 목록 그리드">
38-
{sponsors.map((sponsor) => (
39-
<SponsorItem key={sponsor.id} role="listitem">
40-
<SponsorButton type="button" aria-label={`${sponsor.name} 상세 정보 보기`}>
41-
<span className="sr-only">{sponsor.name}</span>
42-
<sponsor.Logo aria-hidden="true" />
43-
</SponsorButton>
44-
</SponsorItem>
45-
))}
46-
</SponsorGrid>
47-
</SponsorSection>
48-
);
49-
}
50-
51-
const SponsorSection = styled.section`
52-
width: 1067px;
53-
margin: 0 auto;
54-
margin-bottom: 140px;
55-
`;
56-
57-
const SponsorTitle = styled.h4`
58-
font-weight: 600;
59-
font-size: 37px;
60-
text-align: center;
61-
margin: 0;
62-
`;
63-
64-
const SponsorGrid = styled.div`
65-
margin-top: 101px;
66-
display: grid;
67-
grid-template-columns: repeat(4, 1fr);
68-
column-gap: 35px;
69-
row-gap: 75px;
70-
justify-items: center;
71-
`;
72-
73-
const SponsorItem = styled.div`
74-
width: 240px;
75-
height: 75px;
76-
`;
77-
78-
const SponsorButton = styled.button`
79-
width: 100%;
80-
height: 100%;
81-
display: flex;
82-
align-items: center;
83-
justify-content: center;
84-
background: none;
85-
border: none;
86-
padding: 0;
87-
cursor: pointer;
88-
transition: transform 0.2s ease;
89-
90-
&:focus {
91-
outline: 2px solid #007aff;
92-
outline-offset: 4px;
93-
border-radius: 4px;
94-
}
95-
96-
&:focus:not(:focus-visible) {
97-
outline: none;
98-
}
99-
100-
&:focus-visible {
101-
outline: 2px solid #007aff;
102-
outline-offset: 4px;
103-
border-radius: 4px;
104-
}
105-
106-
&:hover {
107-
// transform: scale(1.05);
108-
}
109-
110-
.sr-only {
111-
position: absolute;
112-
width: 1px;
113-
height: 1px;
114-
padding: 0;
115-
margin: -1px;
116-
overflow: hidden;
117-
clip: rect(0, 0, 0, 0);
118-
white-space: nowrap;
119-
border: 0;
120-
}
121-
`;
1+
import * as Common from "@frontend/common";
2+
import { CircularProgress, Divider, Stack, Typography, styled } from "@mui/material";
3+
import { ErrorBoundary, Suspense } from "@suspensive/react";
4+
import { Link } from "react-router-dom";
5+
6+
import { useAppContext } from "../../../contexts/app_context";
7+
8+
const LogoHeight: React.CSSProperties["height"] = "8rem";
9+
const LogoWidth: React.CSSProperties["width"] = "15rem";
10+
11+
const SponsorContainer = styled(Stack)({
12+
width: "100%",
13+
alignItems: "center",
14+
justifyContent: "center",
15+
});
16+
17+
const SponsorSection = styled(Stack)({
18+
margin: "8rem 8rem 4rem 8rem",
19+
width: "100%",
20+
maxWidth: "1300px",
21+
});
22+
23+
const SponsorStack = styled(Stack)({
24+
flexDirection: "row",
25+
flexWrap: "wrap",
26+
justifyContent: "center",
27+
alignItems: "center",
28+
padding: "0 1rem",
29+
gap: "2rem",
30+
});
31+
32+
const LogoImageContainer = styled(Stack)({
33+
alignItems: "center",
34+
justifyContent: "center",
35+
alignContent: "stretch",
36+
height: LogoHeight,
37+
maxHeight: LogoHeight,
38+
maxWidth: LogoWidth,
39+
});
40+
41+
const LogoImage = styled("img")({
42+
height: `calc(${LogoHeight} * 0.9)`, // 90% of LogoHeight
43+
minHeight: LogoHeight,
44+
minWidth: `calc(${LogoWidth} * 0.9)`, // 80% of LogoWidth
45+
maxWidth: "100%",
46+
maxHeight: "100%",
47+
objectFit: "contain",
48+
});
49+
50+
export const Sponsor: React.FC = ErrorBoundary.with(
51+
{
52+
fallback: (
53+
<Typography variant="h6" color="error">
54+
후원사 정보를 불러오는 중 문제가 발생했습니다,
55+
<br />
56+
잠시 후 다시 시도해 주세요.
57+
</Typography>
58+
),
59+
},
60+
Suspense.with({ fallback: <CircularProgress /> }, () => {
61+
const { siteMapNode } = useAppContext();
62+
const backendAPIClient = Common.Hooks.BackendAPI.useBackendClient();
63+
const { data: sponsorData } = Common.Hooks.BackendAPI.useSponsorQuery(backendAPIClient);
64+
65+
if (!siteMapNode) return <CircularProgress />;
66+
67+
const flatSiteMap = Common.Utils.buildFlatSiteMap(siteMapNode);
68+
const flatSiteMapObj = flatSiteMap.reduce((a, i) => ({ ...a, [i.id]: i }), {} as Record<string, { route: string }>);
69+
70+
return (
71+
<SponsorContainer>
72+
<SponsorSection aria-label="후원사 섹션">
73+
<Typography variant="h4" textAlign="center" children="후원사 목록" fontWeight="bold" area-level={4} />
74+
<Stack spacing={4} sx={{ my: 4 }} aria-label="후원사 목록 그리드">
75+
{sponsorData
76+
.filter((t) => t.sponsors.length)
77+
.map((sponsorTier, i, a) => (
78+
<Stack spacing={6} key={sponsorTier.id} aria-label={`후원사 티어: ${sponsorTier.name}`}>
79+
<Typography variant="h5" key={sponsorTier.id} textAlign="center" fontWeight="bold" children={sponsorTier.name} area-level={5} />
80+
<SponsorStack>
81+
{sponsorTier.sponsors.map((sponsor) => {
82+
const sponsorImg = (
83+
<LogoImageContainer>
84+
<LogoImage src={sponsor.logo} alt={sponsor.name} loading="lazy" />
85+
</LogoImageContainer>
86+
);
87+
return sponsor.sitemap_id ? <Link to={flatSiteMapObj[sponsor.sitemap_id].route} children={sponsorImg} /> : sponsorImg;
88+
})}
89+
</SponsorStack>
90+
{i !== a.length - 1 && <Divider sx={{ m: "2rem" }} />}
91+
</Stack>
92+
))}
93+
</Stack>
94+
</SponsorSection>
95+
</SponsorContainer>
96+
);
97+
})
98+
);

apps/pyconkr/src/components/layout/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Outlet } from "react-router-dom";
44

55
import Footer from "./Footer";
66
import Header from "./Header";
7-
import Sponsor from "./Sponsor";
7+
import { Sponsor } from "./Sponsor";
88
import { useAppContext } from "../../contexts/app_context";
99

1010
export default function MainLayout() {

0 commit comments

Comments
 (0)