Skip to content

Commit 5e6a689

Browse files
committed
Add GitHub Pages staging preview
1 parent 4b680e7 commit 5e6a689

13 files changed

Lines changed: 165 additions & 21 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Deploy dev staging site
2+
3+
on:
4+
push:
5+
branches: ["dev"]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: staging-pages
15+
cancel-in-progress: true
16+
17+
jobs:
18+
deploy:
19+
if: github.repository == 'Hilo-Hilo/compbioatberkeley.github.io'
20+
environment:
21+
name: github-pages
22+
url: ${{ steps.deployment.outputs.page_url }}
23+
runs-on: ubuntu-latest
24+
steps:
25+
- name: Checkout
26+
uses: actions/checkout@v5
27+
28+
- name: Set up Node
29+
uses: actions/setup-node@v4
30+
with:
31+
node-version: lts/*
32+
cache: npm
33+
34+
- name: Install dependencies
35+
run: npm ci
36+
37+
- name: Mirror public officer data
38+
run: node scripts/sync-preview-data.js
39+
40+
- name: Build staging site
41+
run: npm run build:preview
42+
env:
43+
VITE_BASE_PATH: /compbioatberkeley.github.io/
44+
45+
- name: Configure Pages
46+
uses: actions/configure-pages@v5
47+
48+
- name: Upload Pages artifact
49+
uses: actions/upload-pages-artifact@v4
50+
with:
51+
path: ./dist
52+
53+
- name: Deploy to GitHub Pages
54+
id: deployment
55+
uses: actions/deploy-pages@v4

‎public/404.html‎

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
<!DOCTYPE html>
22
<html lang="en">
3-
<head>
4-
<meta charset="utf-8">
5-
<title>Computational Biology at Berkeley</title>
6-
<script>
7-
// GitHub Pages SPA redirect hack
8-
// Store the current path and redirect to index with the path in sessionStorage
9-
sessionStorage.redirect = location.href;
10-
</script>
11-
<meta http-equiv="refresh" content="0;URL='/'">
12-
</head>
13-
<body>
14-
</body>
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Computational Biology at Berkeley</title>
6+
<script>
7+
(function () {
8+
var projectName = "compbioatberkeley.github.io";
9+
var pathSegments = window.location.pathname.split("/").filter(Boolean);
10+
var isProjectPage =
11+
window.location.hostname.endsWith(".github.io") &&
12+
pathSegments[0] === projectName;
13+
var basePath = isProjectPage ? "/" + projectName + "/" : "/";
14+
15+
sessionStorage.redirect = window.location.href;
16+
window.location.replace(basePath);
17+
})();
18+
</script>
19+
</head>
20+
<body></body>
1521
</html>

‎scripts/sync-preview-data.js‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env node
2+
3+
import fs from "fs/promises";
4+
import path from "path";
5+
import { fileURLToPath } from "url";
6+
7+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
8+
const publicRoot = path.resolve(__dirname, "../public");
9+
const sourceOrigin =
10+
process.env.PREVIEW_DATA_ORIGIN || "https://compbioatberkeley.github.io";
11+
12+
const currentSemester = () => {
13+
const now = new Date();
14+
const prefix = now.getMonth() <= 6 ? "sp" : "fa";
15+
return `${prefix}${String(now.getFullYear()).slice(-2)}`;
16+
};
17+
18+
const destinationFor = (urlPath) => {
19+
const destination = path.resolve(publicRoot, urlPath.replace(/^\/+/, ""));
20+
if (
21+
destination !== publicRoot &&
22+
!destination.startsWith(`${publicRoot}${path.sep}`)
23+
) {
24+
throw new Error(`Refusing to write outside public/: ${urlPath}`);
25+
}
26+
return destination;
27+
};
28+
29+
const download = async (urlPath) => {
30+
const response = await fetch(new URL(urlPath, sourceOrigin));
31+
if (!response.ok) {
32+
throw new Error(`Failed to fetch ${urlPath}: ${response.status}`);
33+
}
34+
35+
const destination = destinationFor(urlPath);
36+
await fs.mkdir(path.dirname(destination), { recursive: true });
37+
const bytes = Buffer.from(await response.arrayBuffer());
38+
await fs.writeFile(destination, bytes);
39+
return bytes;
40+
};
41+
42+
const syncSemester = async (semester) => {
43+
const jsonPath = `/fetched/officers/${semester}/officers-${semester}.json`;
44+
const jsonBytes = await download(jsonPath);
45+
const officers = JSON.parse(jsonBytes.toString("utf8"));
46+
const imagePaths = [
47+
...new Set(
48+
officers
49+
.map((officer) => officer.image)
50+
.filter(
51+
(image) =>
52+
typeof image === "string" && image.startsWith("/fetched/"),
53+
),
54+
),
55+
];
56+
57+
await Promise.all(imagePaths.map(download));
58+
console.log(
59+
`Synced ${officers.length} ${semester} officers and ${imagePaths.length} images`,
60+
);
61+
};
62+
63+
for (const semester of [...new Set(["fa25", currentSemester()])]) {
64+
await syncSemester(semester);
65+
}

‎src/App.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const BioNetwork = lazy(
3030
);
3131

3232
const queryClient = new QueryClient();
33+
const routerBasename = import.meta.env.BASE_URL.replace(/\/$/, "") || "/";
3334

3435
const ScrollToTop = () => {
3536
const { pathname } = useLocation();
@@ -129,6 +130,7 @@ const App = () => (
129130
<Toaster />
130131
<Sonner />
131132
<BrowserRouter
133+
basename={routerBasename}
132134
future={{ v7_startTransition: true, v7_relativeSplatPath: true }}
133135
>
134136
<ScrollToTop />

‎src/components/Footer.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Link as RouterLink } from "react-router-dom";
2+
import { publicAssetPath } from "@/lib/publicAsset";
23

3-
const LOGO_SRC = "/comp-bio-logo-berkeley.svg";
4+
const LOGO_SRC = publicAssetPath("comp-bio-logo-berkeley.svg");
45

56
const quickLinks = [
67
{ name: "home", path: "/" },

‎src/components/Navigation.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { useEffect, useState } from "react";
22
import { Link, useLocation } from "react-router-dom";
33
import { Menu, Moon, Sun, X } from "lucide-react";
44
import { useTheme } from "next-themes";
5+
import { publicAssetPath } from "@/lib/publicAsset";
56

6-
const LOGO_SRC = "/comp-bio-logo-berkeley.svg";
7+
const LOGO_SRC = publicAssetPath("comp-bio-logo-berkeley.svg");
78

89
const navItems = [
910
{ name: "home", path: "/" },

‎src/components/OfficerCard.tsx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import { Info } from "lucide-react";
88
import { Officer } from "@/types/officers";
99
import { ReactNode } from "react";
10+
import { publicAssetPath } from "@/lib/publicAsset";
1011

1112
interface OfficerCardProps {
1213
officer: Officer;
@@ -23,13 +24,13 @@ interface SocialLink {
2324
export const OfficerCard = ({ officer }: OfficerCardProps) => {
2425
const name = officer.name || "";
2526
const role = officer.role || "";
26-
const image = officer.image || "";
27+
const image = officer.image ? publicAssetPath(officer.image) : "";
2728
const personalWebsite = officer["personal website"] || "";
2829
const linkedin = officer.linkedin || "";
2930
const github = officer.github || "";
3031
const orcid = officer.orcid || "";
3132
const bio = officer.bio || "";
32-
const defaultImage = "/placeholder.svg";
33+
const defaultImage = publicAssetPath("placeholder.svg");
3334

3435
const socialLinks: SocialLink[] = [
3536
{

‎src/hooks/useOfficers.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState, useEffect } from 'react';
22
import { Officer } from '@/types/officers';
3+
import { publicAssetPath } from '@/lib/publicAsset';
34

45
// Helper function to get current semester based on date
56
function getCurrentSemester(): string {
@@ -27,7 +28,9 @@ export const useOfficers = () => {
2728

2829
// Get current semester and fetch from the semester-specific directory
2930
const semester = getCurrentSemester();
30-
const response = await fetch(`/fetched/officers/${semester}/officers-${semester}.json`);
31+
const response = await fetch(
32+
publicAssetPath(`fetched/officers/${semester}/officers-${semester}.json`),
33+
);
3134

3235
if (!response.ok) {
3336
throw new Error(`Failed to fetch officers data: ${response.status}`);

‎src/hooks/useOfficersFa25.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState, useEffect } from 'react';
22
import { Officer } from '@/types/officers';
3+
import { publicAssetPath } from '@/lib/publicAsset';
34

45
export const useOfficersFa25 = () => {
56
const [officers, setOfficers] = useState<Officer[]>([]);
@@ -13,7 +14,9 @@ export const useOfficersFa25 = () => {
1314
setError(null);
1415

1516
// Fetch from the pre-built JSON file for FA25
16-
const response = await fetch('/fetched/officers/fa25/officers-fa25.json');
17+
const response = await fetch(
18+
publicAssetPath('fetched/officers/fa25/officers-fa25.json'),
19+
);
1720

1821
if (!response.ok) {
1922
throw new Error(`Failed to fetch FA25 officers data: ${response.status}`);

‎src/index.css‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
/*
66
* Computational Biology at Berkeley
7-
* Signal Light theme from the supplied design direction.
7+
* Berkeley blue and California gold visual system.
88
* Berkeley blue carries structure; California gold marks action and emphasis.
99
*/
1010

0 commit comments

Comments
 (0)