Skip to content

Commit e9aa802

Browse files
committed
feat: busan.pycon.kr/2026 배포 + 세션 상세 페이지 추가
- GitHub Pages 자동 배포 파이프라인 구축 - Actions 워크플로우 (_site/2026/ + CNAME + 루트 리다이렉트) - vite base 경로를 /2026/로 변경 - /timetable/:code 상세 페이지 신규 - 타임테이블 셀 클릭 → 제목·시간·장소·발표자 프로필·바이오·세션 소개 - 세션 소개는 흰 배경 박스로 가독성 강화 - 한/영 콘텐츠 (en 없으면 ko로 폴백) - 타임테이블 셀에 소형 발표자 아바타 표시 - 세션 데이터 분리 - sessionDetails.js (bio/description ko·en) - speakerAvatars.js (Vite glob 공유 로더) - src/images/speakers/ 11명 사진 포함 - keynote-1, tbd-lee 제목 확정 - Hero의 중복되는 세션 CTA 버튼 제거
1 parent 63871b0 commit e9aa802

24 files changed

Lines changed: 654 additions & 23 deletions

.github/workflows/deploy.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: false
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- uses: actions/setup-node@v4
24+
with:
25+
node-version: 20
26+
cache: npm
27+
28+
- run: npm ci
29+
30+
- run: npm run build
31+
32+
- name: Assemble _site (/2026/ subpath + root redirect + CNAME)
33+
run: |
34+
set -euo pipefail
35+
rm -rf _site
36+
mkdir -p _site/2026
37+
cp -R dist/. _site/2026/
38+
echo "busan.pycon.kr" > _site/CNAME
39+
cat > _site/index.html <<'HTML'
40+
<!doctype html>
41+
<html lang="ko">
42+
<head>
43+
<meta charset="utf-8" />
44+
<title>PyCon Busan 2026</title>
45+
<link rel="canonical" href="https://busan.pycon.kr/2026/" />
46+
<meta http-equiv="refresh" content="0; url=/2026/" />
47+
<script>location.replace('/2026/');</script>
48+
</head>
49+
<body>
50+
<p>Redirecting to <a href="/2026/">/2026/</a>...</p>
51+
</body>
52+
</html>
53+
HTML
54+
55+
- uses: actions/upload-pages-artifact@v3
56+
with:
57+
path: _site
58+
59+
deploy:
60+
needs: build
61+
runs-on: ubuntu-latest
62+
environment:
63+
name: github-pages
64+
url: ${{ steps.deployment.outputs.page_url }}
65+
steps:
66+
- id: deployment
67+
uses: actions/deploy-pages@v4

README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ npm install
3232
npm run dev
3333
```
3434

35-
개발 서버는 `http://localhost:5173`에서 실행됩니다.
35+
개발 서버는 `http://localhost:5173/2026/`에서 실행됩니다. (Vite `base``/2026/`이므로 루트 `/`로 접근하면 404가 납니다.)
3636

3737
### 프로덕션 빌드
3838

@@ -84,9 +84,16 @@ src/
8484

8585
## GitHub Pages 배포
8686

87-
1. 코드를 GitHub 저장소에 푸시합니다
88-
2. GitHub Actions를 사용하거나 수동으로 `dist/` 폴더를 배포합니다
89-
3. 저장소 설정에서 GitHub Pages를 활성화합니다
87+
`main` 브랜치에 푸시되면 `.github/workflows/deploy.yml`이 자동으로 빌드 → `busan.pycon.kr/2026/` 경로에 배포합니다.
88+
89+
최초 1회 설정 (리포 관리자):
90+
91+
1. Repo Settings → Pages → Source: **GitHub Actions**
92+
2. Repo Settings → Actions → General → Workflow permissions: **Read and write permissions**
93+
3. DNS: `busan.pycon.kr` CNAME → `pythonkr.github.io.` (`pycon.kr` zone에 `busan` 서브도메인 CNAME 추가)
94+
4. DNS 전파 후 Pages 설정에서 **Enforce HTTPS** 활성화
95+
96+
루트(`busan.pycon.kr/`) 접근 시 `/2026/`으로 meta-refresh 됩니다.
9097

9198
## 라이선스
9299

src/App.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { HashRouter as Router, Routes, Route } from 'react-router-dom';
33
import Navbar from './components/Navbar';
44
import Home from './pages/Home';
55
import Timetable from './pages/Timetable';
6+
import TimetableDetail from './pages/TimetableDetail';
67
import Team from './pages/Team';
78
import Sprint from './pages/Sprint';
89
import LightTalk from './pages/LightTalk';
@@ -36,6 +37,7 @@ function App() {
3637
<Route path="/volunteer" element={<Volunteer />} />
3738
<Route path="/team" element={<Team />} />
3839
<Route path="/timetable" element={<Timetable />} />
40+
<Route path="/timetable/:code" element={<TimetableDetail />} />
3941
<Route path="/sprint" element={<Sprint />} />
4042
<Route path="/lighttalk" element={<LightTalk />} />
4143
<Route path="/schedule" element={<Schedule />} />

src/components/Hero.jsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import logo from "../images/pycon-logo.png";
33
import { FaMapMarkerAlt } from "react-icons/fa";
44
import { SiNaver } from "react-icons/si";
55
import { useTranslation } from "react-i18next";
6-
import { Link } from "react-router-dom";
76

87
function Hero() {
98
const { t } = useTranslation();
@@ -26,9 +25,6 @@ function Hero() {
2625
<div className="hero-divider" />
2726

2827
<div className="hero-cta-buttons">
29-
<Link to="/session" className="hero-btn hero-btn-primary">
30-
{t("sessionList")}
31-
</Link>
3228
<a
3329
href="https://event-us.kr/pythonkorea/event/122855"
3430
target="_blank"

src/components/TimetableList.css

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,25 @@
9999
font-size: 0.85rem;
100100
}
101101

102+
/* Link-wrapped session cell */
103+
.tt-session-link {
104+
display: block;
105+
color: inherit;
106+
text-decoration: none;
107+
margin: -0.7rem -0.9rem;
108+
padding: 0.7rem 0.9rem;
109+
border-radius: 4px;
110+
transition: background 0.15s;
111+
}
112+
113+
.tt-session-link:hover {
114+
background: rgba(102, 126, 234, 0.1);
115+
}
116+
117+
.tt-session-link:hover .tt-session-title {
118+
text-decoration: underline;
119+
}
120+
102121
/* Session content */
103122
.tt-session {
104123
display: flex;
@@ -116,10 +135,24 @@
116135
.tt-session-speaker {
117136
font-size: 0.82rem;
118137
color: #666;
138+
display: inline-flex;
139+
align-items: center;
140+
gap: 0.35rem;
141+
line-height: 1.2;
119142
}
120143

121-
.tt-session-speaker::before {
122-
content: '👤 ';
144+
/* 아바타가 없는 경우에만 사람 이모지 표시 */
145+
.tt-session-speaker:not(.has-avatar)::before {
146+
content: '👤';
147+
}
148+
149+
.tt-session-avatar {
150+
width: 1.25rem;
151+
height: 1.25rem;
152+
border-radius: 50%;
153+
object-fit: cover;
154+
flex-shrink: 0;
155+
border: 1px solid #e0e0e8;
123156
}
124157

125158
/* Mobile */

src/components/TimetableList.jsx

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,37 @@
11
import { useTranslation } from 'react-i18next';
2+
import { Link } from 'react-router-dom';
3+
import { speakerAvatars } from '../data/speakerAvatars';
24
import './TimetableList.css';
35

4-
function SessionCell({ title, speaker }) {
5-
return (
6+
function SessionCell({ title, speaker, code }) {
7+
const avatarUrl = code ? speakerAvatars[code] : null;
8+
9+
const body = (
610
<div className="tt-session">
711
<div className="tt-session-title">{title}</div>
8-
{speaker && <div className="tt-session-speaker">{speaker}</div>}
12+
{speaker && (
13+
<div className={`tt-session-speaker ${avatarUrl ? 'has-avatar' : ''}`}>
14+
{avatarUrl && (
15+
<img
16+
className="tt-session-avatar"
17+
src={avatarUrl}
18+
alt={speaker}
19+
loading="lazy"
20+
/>
21+
)}
22+
<span>{speaker}</span>
23+
</div>
24+
)}
925
</div>
1026
);
27+
28+
if (!code) return body;
29+
30+
return (
31+
<Link to={`/timetable/${code}`} className="tt-session-link">
32+
{body}
33+
</Link>
34+
);
1135
}
1236

1337
function TimeRange({ start, end }) {
@@ -51,7 +75,7 @@ function TimetableList({ sessions }) {
5175
<TimeRange start={row.time} end={row.endTime} />
5276
</td>
5377
<td className="tt-td tt-cell-full" colSpan={3}>
54-
<SessionCell title={title(row)} speaker={row.speaker} />
78+
<SessionCell title={title(row)} speaker={row.speaker} code={row.code} />
5579
</td>
5680
</tr>
5781
);
@@ -65,10 +89,10 @@ function TimetableList({ sessions }) {
6589
</td>
6690
<td className="tt-td tt-cell-empty"></td>
6791
<td className="tt-td tt-cell-session">
68-
<SessionCell title={roomTitle(row.room1)} speaker={row.room1.speaker} />
92+
<SessionCell title={roomTitle(row.room1)} speaker={row.room1.speaker} code={row.room1.code} />
6993
</td>
7094
<td className="tt-td tt-cell-session">
71-
<SessionCell title={roomTitle(row.room2)} speaker={row.room2.speaker} />
95+
<SessionCell title={roomTitle(row.room2)} speaker={row.room2.speaker} code={row.room2.code} />
7296
</td>
7397
</tr>
7498
);

0 commit comments

Comments
 (0)