Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Raidy is a raid scheduling project for Discord statics: the web console is where everyone plans, and Discord carries the announcements. One Go process runs the Discord bot, web API, scheduler, and production web assets, backed by PostgreSQL.

**Status:** `v0.0.18` is an early development snapshot.
**Status:** `v0.0.19` is an early development snapshot.

```text
backend/ Go service, Discord interactions, web API, PostgreSQL migrations
Expand Down Expand Up @@ -60,8 +60,8 @@ The Go process applies pinned, embedded Goose migrations before opening the appl
`web/product/` is the public product page, and the demo is the real app running against in-browser sample data. Neither needs the Go backend. From `web/`:

```sh
npm run dev:product # product page with live reload
npm run dev:demo # demo at /demo/ with live reload
npm run dev:product # product page and demo together, with live reload
npm run dev:demo # the demo alone, at /demo/
npm run screenshots # capture the app screenshots the page shows
npm run build:pages # build both into dist-pages/
npm run preview:pages # serve dist-pages/ as GitHub Pages does
Expand Down
13 changes: 13 additions & 0 deletions web/e2e/product.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ test("the product page leads to the live demo and the code", async ({ page }) =>
);
});

// Regression: the demo link was relative to the current page, and the
// product server answered every path with the product page, so each click
// added another /demo/ to the URL instead of opening the demo.
test("the demo opens at /demo/ and leads back to the product page", async ({ page }) => {
await page.goto("/");
await page.getByRole("link", { name: "Try the demo" }).click();
await expect(page.getByRole("table", { name: /availability/i })).toBeVisible({ timeout: 30_000 });
expect(new URL(page.url()).pathname).toBe("/demo/");
await page.getByRole("link", { name: "About Raidy" }).click();
await expect(page.getByRole("heading", { level: 1 })).toHaveText("See which nights your whole static can raid.");
expect(new URL(page.url()).pathname).toBe("/");
});

test("tapping a night in the hero updates the Discord post", async ({ page }) => {
await page.clock.setFixedTime(monday);
await page.goto("/");
Expand Down
4 changes: 2 additions & 2 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions web/package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "raidy-web",
"private": true,
"version": "0.0.18",
"version": "0.0.19",
"type": "module",
"scripts": {
"dev": "vite",
"dev:product": "vite --config vite.product.config.ts",
"dev:product": "node scripts/dev-site.mjs",
"dev:demo": "vite --mode demo --base /demo/",
"build": "tsc -b && vite build",
"check": "tsc -b --pretty false",
Expand All @@ -14,7 +14,7 @@
"test:e2e": "playwright test",
"build:demo": "tsc -b && vite build --mode demo --base /demo/ --outDir dist-demo",
"screenshots": "playwright test --config playwright.screenshots.config.ts",
"build:pages": "rm -rf dist-pages && tsc -b && vite build --config vite.product.config.ts && vite build --mode demo --base /demo/ --outDir dist-pages/demo",
"build:pages": "rm -rf dist-pages && tsc -b && vite build --config vite.product.config.ts && vite build --mode demo --base ./ --outDir dist-pages/demo",
"preview:pages": "vite preview --config vite.product.config.ts"
},
"dependencies": {
Expand Down
3 changes: 2 additions & 1 deletion web/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ export default defineConfig({
reuseExistingServer: !process.env.CI
},
{
command: `npx vite --config vite.product.config.ts --host 127.0.0.1 --port ${productPort} --strictPort`,
// The same command as `npm run dev:product`: the page plus the demo at /demo/.
command: `node scripts/dev-site.mjs --host 127.0.0.1 --port ${productPort} --strictPort`,
url: `http://127.0.0.1:${productPort}/`,
reuseExistingServer: !process.env.CI
}
Expand Down
16 changes: 16 additions & 0 deletions web/scripts/dev-site.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Runs the product page and the demo together, so links to /demo/ work:
// the product dev server forwards /demo/ to the demo dev server.
import { spawn } from "node:child_process";

const demo = ["--mode", "demo", "--base", "/demo/", "--host", "127.0.0.1", "--port", "5174", "--strictPort"];
const product = ["--config", "vite.product.config.ts", ...process.argv.slice(2)];

// Run vite directly rather than through npx, so stopping this script stops both servers.
const servers = [demo, product].map((args) =>
spawn(process.execPath, ["node_modules/vite/bin/vite.js", ...args], { stdio: "inherit" })
);

const stop = () => servers.forEach((server) => server.kill());
process.on("SIGINT", stop);
process.on("SIGTERM", stop);
servers.forEach((server) => server.on("exit", (code) => code && (stop(), process.exit(code))));
6 changes: 3 additions & 3 deletions web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Overview } from "@/features/overview/overview";
import { SchedulePanel } from "@/features/schedule/schedule-panel";
import { TeamPanel } from "@/features/team/team-panel";
import { message } from "@/lib/format";
import { isDemo, teamURL } from "@/lib/paths";
import { isDemo, productURL, teamURL } from "@/lib/paths";
import { applyTheme, type Theme, type ThemePreference } from "@/lib/theme";
import { cn } from "@/lib/utils";

Expand Down Expand Up @@ -97,7 +97,7 @@ export default function App() {

async function logout() {
await api("/api/auth/logout", { method: "POST" });
location.assign("/");
location.assign(isDemo ? productURL : "/");
}

function chooseTheme(next: ThemePreference) {
Expand Down Expand Up @@ -232,7 +232,7 @@ function DemoBanner() {
<span className="font-medium">Demo</span>
<span className="text-muted-foreground"> · Sample data. Changes reset when you reload.</span>
</span>
<a href="/" className="font-medium text-primary underline-offset-4 hover:underline">
<a href={productURL} className="font-medium text-primary underline-offset-4 hover:underline">
About Raidy
</a>
</div>
Expand Down
3 changes: 3 additions & 0 deletions web/src/lib/paths.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/** True in the demo build, which runs the app against in-browser sample data. */
export const isDemo = import.meta.env.MODE === "demo";

/** The product page, which sits one level above the demo wherever the site is hosted. */
export const productURL = "../";

/** The app URL for a team: /app?team=N normally, /demo/?team=N in the demo. */
export function teamURL(teamID: number) {
return `${import.meta.env.BASE_URL}${isDemo ? "" : "app"}?team=${teamID}`;
Expand Down
2 changes: 1 addition & 1 deletion web/src/product/product-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function ProductPage() {
return (
<div className="min-h-screen bg-background text-foreground">
<header className="mx-auto flex h-16 max-w-6xl items-center gap-6 px-4 sm:px-6">
<a href="/" aria-label="Raidy home">
<a href="./" aria-label="Raidy home">
<Wordmark className="text-xl" />
</a>
<nav className="hidden gap-6 text-sm text-muted-foreground sm:flex" aria-label="Sections">
Expand Down
7 changes: 5 additions & 2 deletions web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
export default defineConfig(({ mode }) => ({
plugins: [react(), tailwindcss()],
// Each mode keeps its own dependency cache, so the app and the demo can run
// side by side without re-optimizing each other's dependencies.
cacheDir: mode === "demo" ? "node_modules/.vite-demo" : "node_modules/.vite",
resolve: {
alias: {
"@": path.resolve(__dirname, "./src")
Expand All @@ -17,4 +20,4 @@ export default defineConfig({
"/readyz": "http://127.0.0.1:8080"
}
}
});
}));
12 changes: 11 additions & 1 deletion web/vite.product.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,25 @@ import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";

// The product page at the site root; the demo build is added under /demo/.
// The product page at the site root; the demo build is added under demo/.
// Paths are relative so the site works on a custom domain or under
// joshchen-dev.github.io/raidy/ alike.
export default defineConfig({
root: path.resolve(__dirname, "product"),
base: "./",
cacheDir: path.resolve(__dirname, "node_modules/.vite-product"),
// No single-page fallback: an unknown path is a 404, not the product page again.
appType: "mpa",
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src")
}
},
server: {
// `npm run dev:product` also starts the demo; serve it at /demo/ here.
proxy: { "/demo": { target: "http://127.0.0.1:5174", ws: true } }
},
build: {
outDir: path.resolve(__dirname, "dist-pages"),
emptyOutDir: false
Expand Down
Loading