@@ -4,15 +4,38 @@ import { ErrorBoundary, Suspense } from "@suspensive/react";
44import * as React from "react" ;
55import { Navigate , useParams } from "react-router-dom" ;
66
7+ import PyCon2025Logo from "../../assets/pyconkr2025_logo.png" ;
78import { useAppContext } from "../../contexts/app_context" ;
89import { PageLayout } from "../layout/PageLayout" ;
910
11+ const PROFILE_IMAGE_SIZE = "7rem" ;
12+
13+ type SimplifiedSpeakerSchema = {
14+ id : string ;
15+ nickname : string ;
16+ image : string | null ;
17+ biography : string ;
18+ } ;
19+
1020const CenteredLoadingPage : React . FC = ( ) => (
1121 < Common . Components . CenteredPage >
1222 < CircularProgress />
1323 </ Common . Components . CenteredPage >
1424) ;
1525
26+ const StyledPresentationImage = styled ( Common . Components . FallbackImage ) ( ( { theme } ) => ( {
27+ maxWidth : "75%" ,
28+ maxHeight : "480px" ,
29+ aspectRatio : "1" ,
30+ margin : theme . spacing ( 4 , 0 ) ,
31+ borderRadius : "2rem" ,
32+ border : `1px solid ${ theme . palette . divider } ` ,
33+
34+ [ theme . breakpoints . down ( "lg" ) ] : {
35+ maxWidth : "100%" ,
36+ } ,
37+ } ) ) ;
38+
1639const DescriptionBox = styled ( Box ) ( ( { theme } ) => ( {
1740 width : "100%" ,
1841 padding : theme . spacing ( 2 , 4 ) ,
@@ -30,6 +53,84 @@ const DescriptionBox = styled(Box)(({ theme }) => ({
3053 } ,
3154} ) ) ;
3255
56+ const BiographyBox = styled ( Box ) ( ( { theme } ) => ( {
57+ width : "100%" ,
58+
59+ "& .markdown-body" : {
60+ width : "100%" ,
61+ p : { margin : theme . spacing ( 2 , 0 ) } ,
62+ } ,
63+ } ) ) ;
64+
65+ const ProfileImageContainer = styled ( Stack ) ( {
66+ minWidth : PROFILE_IMAGE_SIZE ,
67+ width : PROFILE_IMAGE_SIZE ,
68+ maxWidth : PROFILE_IMAGE_SIZE ,
69+ minHeight : PROFILE_IMAGE_SIZE ,
70+ height : PROFILE_IMAGE_SIZE ,
71+ maxHeight : PROFILE_IMAGE_SIZE ,
72+ overflow : "hidden" ,
73+ borderRadius : "50%" ,
74+ border : `1px solid rgba(0, 0, 0, 0.12)` ,
75+ } ) ;
76+
77+ const ProfileImageStyle : React . CSSProperties = {
78+ width : "100%" ,
79+ height : "100%" ,
80+ objectFit : "cover" ,
81+ } ;
82+
83+ const ProfileImage = styled ( Common . Components . FallbackImage ) ( ProfileImageStyle ) ;
84+
85+ const ProfileImageErrorFallback : React . FC = ( ) => (
86+ < Stack alignItems = "center" justifyContent = "center" sx = { { ...ProfileImageStyle } } >
87+ < img src = { PyCon2025Logo } alt = "PyCon 2025 Logo" style = { { width : "100%" , height : "100%" , objectFit : "cover" , borderRadius : "50%" } } />
88+ </ Stack >
89+ ) ;
90+
91+ const PresentationSpeakerItem : React . FC < { speaker : SimplifiedSpeakerSchema } > = ( { speaker } ) => {
92+ return (
93+ < >
94+ < Stack direction = "row" spacing = { 4 } sx = { { px : 2 , py : 1 } } >
95+ < ProfileImageContainer sx = { { flexGrow : 0 } } >
96+ < ProfileImage alt = "Speaker Image" src = { speaker . image || "" } errorFallback = { < ProfileImageErrorFallback /> } />
97+ </ ProfileImageContainer >
98+ < Stack alignItems = "flex-start" justifyContent = "center" sx = { { flexGrow : 1 } } >
99+ < Typography variant = "h4" fontWeight = "700" fontSize = "2rem" children = { speaker . nickname } />
100+ { speaker . biography ? (
101+ < BiographyBox children = { < Common . Components . MDXRenderer text = { speaker . biography || "" } format = "md" /> } />
102+ ) : (
103+ < >
104+ < br />
105+ < br />
106+ </ >
107+ ) }
108+ </ Stack >
109+ </ Stack >
110+ < Divider flexItem />
111+ </ >
112+ ) ;
113+ } ;
114+
115+ const PresentationImageFallback : React . FC < { language : "ko" | "en" } > = ( { language } ) => {
116+ const message =
117+ language === "ko" ? (
118+ < >
119+ 지금은 발표 사진을 불러올 수 없어요
120+ < br />
121+ 잠시 후 다시 시도해 주세요.
122+ </ >
123+ ) : (
124+ < >
125+ Unable to load the presentation image at the moment.
126+ < br />
127+ Please try again later.
128+ </ >
129+ ) ;
130+
131+ return < Typography variant = "caption" color = "textSecondary" children = { message } /> ;
132+ } ;
133+
33134export const PresentationDetailPage : React . FC = ErrorBoundary . with (
34135 { fallback : Common . Components . ErrorFallback } ,
35136 Suspense . with ( { fallback : < CenteredLoadingPage /> } , ( ) => {
@@ -42,6 +143,7 @@ export const PresentationDetailPage: React.FC = ErrorBoundary.with(
42143
43144 const descriptionFallback = language === "ko" ? "해당 발표의 설명은 준비 중이에요!" : "Description of the presentation is under preparation!" ;
44145 const categoriesStr = language === "ko" ? "카테고리" : "Categories" ;
146+ const speakersStr = language === "ko" ? "발표자" : "Speakers" ;
45147
46148 React . useEffect ( ( ) => {
47149 setAppContext ( ( prev ) => ( {
@@ -53,10 +155,11 @@ export const PresentationDetailPage: React.FC = ErrorBoundary.with(
53155 } , [ language , presentation , setAppContext ] ) ;
54156
55157 return (
56- < PageLayout sx = { { maxWidth : "960px" } } >
57- < Typography variant = "h4" fontWeight = "700" textAlign = "start" sx = { { width : "100%" , p : 2 } } >
58- { presentation . title }
59- </ Typography >
158+ < PageLayout >
159+ < Typography variant = "h4" fontWeight = "700" textAlign = "start" sx = { { width : "100%" , px : 2 , pt : 0 , pb : 1 } } children = { presentation . title } />
160+ { presentation . summary && (
161+ < Typography variant = "h6" fontWeight = "700" textAlign = "start" sx = { { width : "100%" , px : 2 , pt : 1 , pb : 3 } } children = { presentation . summary } />
162+ ) }
60163 < Divider flexItem />
61164 { presentation . categories . length ? (
62165 < >
@@ -71,9 +174,27 @@ export const PresentationDetailPage: React.FC = ErrorBoundary.with(
71174 < Divider flexItem />
72175 </ >
73176 ) : null }
177+ { presentation . image && (
178+ < StyledPresentationImage
179+ alt = "Presentation Image"
180+ src = { presentation . image }
181+ errorFallback = { < PresentationImageFallback language = { language } /> }
182+ />
183+ ) }
74184 < DescriptionBox >
75185 < Common . Components . MDXRenderer text = { presentation . description || descriptionFallback } format = "md" />
76186 </ DescriptionBox >
187+ < Divider flexItem />
188+ { presentation . speakers && (
189+ < >
190+ < Typography variant = "h5" fontWeight = "bold" sx = { { width : "100%" , px : 2 , py : 4 } } children = { speakersStr } />
191+ < Stack spacing = { 2 } sx = { { width : "100%" , px : 3 } } >
192+ { presentation . speakers . map ( ( speaker ) => (
193+ < PresentationSpeakerItem key = { speaker . id } speaker = { speaker as SimplifiedSpeakerSchema } />
194+ ) ) }
195+ </ Stack >
196+ </ >
197+ ) }
77198 </ PageLayout >
78199 ) ;
79200 } )
0 commit comments