From 7a08546da6deed0b3ef6a021e7080726ea2fa83a Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 18 Jul 2026 02:20:04 -0500 Subject: [PATCH] =?UTF-8?q?feat(brand):=20BRAND=5F*=20env=20scheme=20?= =?UTF-8?q?=E2=80=94=20instance=20branding=20without=20a=20rebuild?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace baked-in UOM/MysticMoon/UOMysticmoon branding with a BRAND_* env scheme so one prebuilt image runs as any shard; UOMysticmoon becomes the first tenant that sets these vars rather than a special case in the code. Architecture (chosen because the app ships as a prebuilt image): - server/src/config/brand.js + bot/src/brand.js read BRAND_* once at boot, with Runic Gateway defaults. - Text/colors reach the SPA at RUNTIME through the existing public settings API (settings.model.getPublic -> SiteContext), so no client rebuild. The admin-editable site title + contact email still override BRAND_NAME/email. - SiteContext applies BRAND_ACCENT_COLOR to the --accent CSS var at runtime. - Express templates the built index.html /description/OG/favicon at serve time from BRAND_* (renderIndexHtml in app.js). - Server-side consumers read brand directly: emails, TOTP issuer, API docs, boot logs, HTML error page. Bot uses it for embed color + logs. Assets: logo/hero/favicon delivered from a ./brand:/app/brand bind-mount (BRAND_LOGO/HERO/FAVICON), with neutral defaults baked in; hero falls back to a built-in image when unset. Scope: also genericized package.json names (uomysticmoon-* -> runic-gateway-*) and the DB_NAME/DB_USER/COOKIE_NAME code defaults (runic_gateway/runic/ rg_token). Production keeps its real values by pinning them in .env — see .env.uomysticmoon.example, which reproduces the exact UOMysticmoon identity (proof the substitution works). Changing a deployed COOKIE_NAME invalidates existing sessions, so UOMysticmoon pins uomm_token. Verified: 193 server tests pass, client builds, app.js loads + templates the built index.html, brand transform injects title/description/OG/favicon. --- .env.example | 35 ++++++++++-- .env.uomysticmoon.example | 30 ++++++++++ README.md | 48 +++++++++++++--- bot/.env.example | 6 +- bot/package-lock.json | 4 +- bot/package.json | 4 +- bot/src/brand.js | 13 +++++ bot/src/db.js | 2 +- bot/src/discord/commands/rolemenu.command.js | 3 +- bot/src/discord/newsAnnounce.js | 3 +- bot/src/internal/requireInternalKey.js | 2 +- bot/src/server.js | 3 +- brand/README.md | 15 +++++ client/index.html | 4 +- client/package-lock.json | 4 +- client/package.json | 2 +- client/src/components/SiteFooter.jsx | 4 +- client/src/components/SiteHeader.jsx | 4 +- client/src/contexts/SiteContext.jsx | 15 ++++- client/src/lib/heroLayout.js | 32 +++++++---- client/src/routes/admin/AdminLayout.jsx | 4 +- client/src/routes/admin/AdminLogin.jsx | 8 +-- .../src/routes/admin/views/EmailDelivery.jsx | 4 +- .../src/routes/player/PlayerPortalLayout.jsx | 4 +- client/src/routes/player/PlayerShell.jsx | 10 ++-- client/src/routes/public/About.jsx | 6 +- client/src/routes/public/Maintenance.jsx | 10 ++-- client/src/routes/public/News.jsx | 4 +- client/src/routes/public/Portal.jsx | 8 +-- client/src/routes/public/Website.jsx | 6 +- client/src/routes/wiki/Wiki.jsx | 4 +- client/src/styles/theme.css | 2 +- docker-compose.yml | 5 ++ package.json | 4 +- server/.env.example | 11 ++-- server/db/schema.sql | 2 +- server/db/seed.js | 11 ++-- server/package-lock.json | 4 +- server/package.json | 4 +- server/src/app.js | 57 +++++++++++++++++-- server/src/auth/token.js | 2 +- server/src/config/brand.js | 46 +++++++++++++++ server/src/model/settings/settings.model.js | 16 ++++++ server/src/server.js | 5 +- server/src/utils/db.js | 2 +- server/src/utils/mailer.js | 9 +-- server/src/utils/totp.js | 4 +- server/swagger/swagger-output.json | 8 +-- server/swagger/swagger.js | 15 +++-- 49 files changed, 389 insertions(+), 119 deletions(-) create mode 100644 .env.uomysticmoon.example create mode 100644 bot/src/brand.js create mode 100644 brand/README.md create mode 100644 server/src/config/brand.js diff --git a/.env.example b/.env.example index 9ba8c93..c932e62 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,7 @@ -# ─── UOMysticmoon — root environment (used by docker-compose) ─── +# ─── Runic Gateway — root environment (used by docker-compose) ─── # Copy to .env and fill in. NEVER commit the real .env. +# To run this as an existing branded instance (e.g. UOMysticmoon), see +# .env.uomysticmoon.example for the exact BRAND_*/DB pinning to copy in. # Container image tag pulled by docker-compose (app + bot). Published by the # Gitea Actions workflow on every merge to main as `latest` and `sha-<7>`. @@ -23,12 +25,32 @@ LOG_TO_FILE=true # set false for console-only LOG_DIR=/app/logs # log directory inside the container (bind-mounted to ./logs) LOG_FILE=app.log +# ─── Branding (BRAND_*) ─────────────────────────────────────────────────── +# Instance identity. Defaults render as "Runic Gateway"; set these to rebrand +# without a rebuild. Text + colors reach the SPA through the settings API at +# runtime; the server templates index.html <title>/meta/OG/favicon at boot. The +# admin-editable "site title" and "contact email" settings, if set, override +# BRAND_NAME / BRAND_CONTACT_EMAIL. +BRAND_NAME=Runic Gateway +BRAND_SHORT_NAME=Runic Gateway +BRAND_TAGLINE=an independent private Ultima Online shard +BRAND_DESCRIPTION=Runic Gateway — an independent private Ultima Online shard. News, screenshots, guides, and community notes. +BRAND_CONTACT_EMAIL= +BRAND_URL= +# Accent color — drives the web theme's --accent and the Discord embed color. +BRAND_ACCENT_COLOR=#7f99bd +# Image assets: paths under the /brand mount (see docker-compose.yml) or absolute +# URLs. Blank = built-in defaults (hero falls back to a neutral built-in image). +BRAND_LOGO= +BRAND_HERO= +BRAND_FAVICON= + # Database (the values here are shared by the `db`, `app`, and `bot` containers — # the bot only ever touches its own tables: guild_config, mod_actions, warnings) DB_HOST=db DB_PORT=3306 -DB_NAME=uomysticmoon -DB_USER=uomm +DB_NAME=runic_gateway +DB_USER=runic DB_PASSWORD=change-me-db-password DB_ROOT_PASSWORD=change-me-root-password @@ -38,7 +60,8 @@ JWT_EXPIRES_IN=1d # auto = Secure cookie only when the request arrives over HTTPS (Pangolin). # Leave as auto so login works both via the LAN IP (HTTP) and the proxy (HTTPS). COOKIE_SECURE=auto -COOKIE_NAME=uomm_token +# Changing this on a live instance invalidates existing sessions (users re-login). +COOKIE_NAME=rg_token # Reverse-proxy trust (req.ip / req.secure for rate limiting, backoff, bot-ban). # Path: client -> Pangolin -> newt agent "ptero" (separate VM) -> app. Pin this @@ -51,8 +74,8 @@ TRUST_PROXY=1 # request (to verify/refresh ptero's IP without redeploying). Noisy; keep off. DEBUG_TRUST_PROXY=0 -# Optional TOTP two-factor (opt-in per user). -TOTP_ISSUER=UOMysticmoon +# Optional TOTP two-factor (opt-in per user). Defaults to BRAND_NAME when unset. +# TOTP_ISSUER=Runic Gateway TOTP_CHALLENGE_TTL=5m # First admin bootstrap — created only if no users exist yet. diff --git a/.env.uomysticmoon.example b/.env.uomysticmoon.example new file mode 100644 index 0000000..e1a5d58 --- /dev/null +++ b/.env.uomysticmoon.example @@ -0,0 +1,30 @@ +# ─── UOMysticmoon instance — BRAND_* / identity overrides ─── +# +# Runic Gateway's first "tenant". Copy these into the deploy .env (on top of +# .env.example) to run RunicGateway/website as UOMysticmoon. This is the proof +# that branding is data, not code: the same image renders as UOMysticmoon purely +# from these vars. +# +# Only the values that differ from the Runic Gateway defaults are shown. + +# Identity +BRAND_NAME=UOMysticmoon +BRAND_SHORT_NAME=Mysticmoon +BRAND_TAGLINE=an independent private Ultima Online shard +BRAND_DESCRIPTION=UOMysticmoon — an independent private Ultima Online shard. News, screenshots, guides, and community notes. +BRAND_CONTACT_EMAIL=UOMysticmoon@gmail.com +# BRAND_URL=https://<your public url> + +# Visual — the existing UOM accent + hero image (baked into the image already). +BRAND_ACCENT_COLOR=#7f99bd +BRAND_HERO=/assets/img/uomysticmoon-main-hero.png + +# TOTP label (defaults to BRAND_NAME, so optional — shown for clarity). +TOTP_ISSUER=UOMysticmoon + +# ── Infrastructure identifiers — PIN to the existing production values so the +# ── app keeps talking to the same database and existing sessions stay valid. +# ── (These are NOT branding; they must match what production already uses.) +DB_NAME=uomysticmoon +DB_USER=uomm +COOKIE_NAME=uomm_token diff --git a/README.md b/README.md index e794493..89be6f0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ -# UOMysticmoon Website +# Runic Gateway Website -Public site, wiki, and protected admin panel for the **UOMysticmoon** private Ultima Online -shard — a full-stack app in one repo: +Public site, wiki, and protected admin panel for a private Ultima Online shard — a +full-stack app in one repo. Branding is instance-configurable via `BRAND_*` (see +[Branding](#branding)); **UOMysticmoon** is the first instance. + +A full-stack app in one repo: - **Backend** — Node.js + Express REST API (layered `router → controller → model → db`), MariaDB, a provider-agnostic session layer (JWT cookie for web, bearer tokens for mobile, pluggable SSO). - **Frontend** — React + Vite single-page app (public site, wiki, and the admin panel), dark "gothic" theme (Cinzel + Georgia). @@ -133,14 +136,14 @@ to the backend, so the SPA stays same-origin (cookies work). **1. Start a MariaDB the backend can reach** (published on `localhost:3306`): ```bash -docker run -d --name uomm-db -p 3306:3306 -e MARIADB_DATABASE=uomysticmoon -e MARIADB_USER=uomm -e MARIADB_PASSWORD=devpass -e MARIADB_ROOT_PASSWORD=rootpass mariadb:11 +docker run -d --name rg-db -p 3306:3306 -e MARIADB_DATABASE=runic_gateway -e MARIADB_USER=runic -e MARIADB_PASSWORD=devpass -e MARIADB_ROOT_PASSWORD=rootpass mariadb:11 ``` **2. Configure + start the backend** (terminal 1): ```bash cp server/.env.example server/.env -# Set DB_HOST=127.0.0.1, DB_PORT=3306, DB_USER=uomm, DB_PASSWORD=devpass, +# Set DB_HOST=127.0.0.1, DB_PORT=3306, DB_USER=runic, DB_PASSWORD=devpass, # JWT_SECRET=<anything>, ADMIN_USERNAME=admin, ADMIN_PASSWORD=<your password> npm run install-server npm run server # nodemon → http://localhost:3000 @@ -249,7 +252,7 @@ actually returns (`400` validation, `401`/`403` auth, `404`, `409` conflicts, `4 **Authentication in the UI** — click **Authorize** and provide either: -- `cookieAuth` — the `uomm_token` session cookie (set automatically in the browser after +- `cookieAuth` — the session cookie (name `rg_token`, configurable via `COOKIE_NAME`; set automatically in the browser after `POST /api/v1/auth/login`), or - `bearerAuth` — a mobile access token from `POST /api/v1/auth/mobile/login` (sent as `Authorization: Bearer <token>`). @@ -348,19 +351,20 @@ Copy `.env.example` (Compose) or `server/.env.example` (local) and fill in. **`. | `PORT` | `3000` | server listens on `0.0.0.0:PORT` | | `UPLOAD_DIR` | `<server>/uploads` | where post images are written (`/app/uploads`, volume-mounted, in Compose) | | `DB_HOST` / `DB_PORT` | `db` / `3306` | `db` in Compose; `127.0.0.1` for local dev | -| `DB_NAME` / `DB_USER` / `DB_PASSWORD` | `uomysticmoon` / `uomm` / — | app database credentials | +| `DB_NAME` / `DB_USER` / `DB_PASSWORD` | `runic_gateway` / `runic` / — | app database credentials | | `DB_ROOT_PASSWORD` | — | MariaDB root (Compose only) | | `JWT_SECRET` | — | **required** — long random string; signs session, mobile, and SSO-flow tokens | | `JWT_EXPIRES_IN` | `1d` | web session token + cookie lifetime | | `COOKIE_SECURE` | `auto` | `auto` = Secure only over HTTPS (works on LAN HTTP + Pangolin HTTPS) | -| `COOKIE_NAME` | `uomm_token` | | +| `COOKIE_NAME` | `rg_token` | changing it on a live instance invalidates existing sessions | +| `BRAND_*` | Runic Gateway | instance branding (name, tagline, colors, logo/hero/favicon) — see [Branding](#branding) | | `SECRET_ENC_KEY` | — | **required in prod** — key for AES-256-GCM encryption of stored OAuth client secrets. Dev falls back to a key derived from `JWT_SECRET` (with a warning) | | `APP_BASE_URL` | — | public base URL, used to build the SSO OAuth `redirect_uri` (`${APP_BASE_URL}/api/v1/auth/sso/:provider/callback`). Set in prod to match what you register with Google/Discord; if unset it is derived from the request (fine for local dev) | | `MOBILE_ACCESS_TTL` | `15m` | mobile bearer **access** token lifetime (short-lived) | | `MOBILE_REFRESH_TTL_DAYS` | `30` | mobile **refresh** token lifetime (long-lived, rotated on use) | | `TRUST_PROXY` | `1` | reverse-proxy trust for correct `req.ip` / `req.secure` (rate limiting, backoff, bot-ban). Pin to the proxy hop's LAN IP in prod. A blanket `true` is rejected (coerced to `1`) to block `X-Forwarded-For` spoofing | | `DEBUG_TRUST_PROXY` | `0` | `1` logs raw peer address + `X-Forwarded-For` + resolved `req.ip` per request (to verify/refresh the proxy IP). Noisy — leave off | -| `TOTP_ISSUER` | `UOMysticmoon` | label shown in authenticator apps for optional per-user 2FA | +| `TOTP_ISSUER` | `BRAND_NAME` | label shown in authenticator apps for optional per-user 2FA | | `TOTP_CHALLENGE_TTL` | `5m` | lifetime of the short-lived post-password "awaiting code" step | | `ADMIN_USERNAME` / `ADMIN_PASSWORD` | — | first-admin bootstrap (first boot only) | | _Email_ | — | configured in Admin → Settings → Email (Gmail OAuth2), not via env; recipient = `contact_email` setting | @@ -372,6 +376,32 @@ Copy `.env.example` (Compose) or `server/.env.example` (local) and fill in. **`. --- +## Branding + +Instance identity is data, not code — set via `BRAND_*` env vars, so one prebuilt +image can run as any shard. With none set, everything renders as **Runic Gateway**. + +| Var | What | +|---|---| +| `BRAND_NAME` / `BRAND_SHORT_NAME` | display name (full / short-in-prose) | +| `BRAND_TAGLINE` / `BRAND_DESCRIPTION` | tagline + meta/OG description | +| `BRAND_CONTACT_EMAIL` / `BRAND_URL` | contact + canonical URL (for OG/absolute links) | +| `BRAND_ACCENT_COLOR` | theme `--accent` (web) + Discord embed color | +| `BRAND_LOGO` / `BRAND_HERO` / `BRAND_FAVICON` | image paths under the `/brand` mount, or absolute URLs | + +**How it flows:** text/colors reach the SPA at runtime through the public settings +API (`SiteContext`), so no rebuild is needed; the server templates `index.html` +`<title>`/meta/OG/favicon at boot; emails, TOTP issuer, and the Discord bot read +`BRAND_*` directly. The admin-editable **site title** and **contact email** +settings override `BRAND_NAME` / `BRAND_CONTACT_EMAIL` when set. Image assets are +delivered from the `./brand` bind-mount (see `brand/README.md`). + +**UOMysticmoon** is the first instance — [`.env.uomysticmoon.example`](.env.uomysticmoon.example) +holds the exact `BRAND_*` + infra (`DB_NAME`/`DB_USER`/`COOKIE_NAME`) pinning to +run this repo as UOMysticmoon. + +--- + ## Security **Session & authorization** diff --git a/bot/.env.example b/bot/.env.example index 8cd4a01..3f9f590 100644 --- a/bot/.env.example +++ b/bot/.env.example @@ -1,4 +1,4 @@ -# ─── UOMysticmoon Discord bot — local dev environment ─── +# ─── Runic Gateway Discord bot — local dev environment ─── # Copy to bot/.env for running `npm run dev` outside Docker. # (In Docker, the root .env / docker-compose provides these instead.) # @@ -40,6 +40,6 @@ SITE_PUBLIC_URL=http://localhost:3000/api/v1/public # etc.) directly. Point this at the same DB the server/ uses. DB_HOST=127.0.0.1 DB_PORT=3306 -DB_NAME=uomysticmoon -DB_USER=uomm +DB_NAME=runic_gateway +DB_USER=runic DB_PASSWORD=change-me-db-password diff --git a/bot/package-lock.json b/bot/package-lock.json index 1c1c9d4..b2f3935 100644 --- a/bot/package-lock.json +++ b/bot/package-lock.json @@ -1,11 +1,11 @@ { - "name": "uomysticmoon-bot", + "name": "runic-gateway-bot", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "uomysticmoon-bot", + "name": "runic-gateway-bot", "version": "1.0.0", "license": "ISC", "dependencies": { diff --git a/bot/package.json b/bot/package.json index 3796135..7e97402 100644 --- a/bot/package.json +++ b/bot/package.json @@ -1,7 +1,7 @@ { - "name": "uomysticmoon-bot", + "name": "runic-gateway-bot", "version": "1.0.0", - "description": "Discord bot for the UOMysticmoon community server", + "description": "Discord bot for the Runic Gateway community server", "private": true, "main": "src/server.js", "scripts": { diff --git a/bot/src/brand.js b/bot/src/brand.js new file mode 100644 index 0000000..bfa72e3 --- /dev/null +++ b/bot/src/brand.js @@ -0,0 +1,13 @@ +// Branding for the Discord bot. Mirrors the server's BRAND_* scheme so embeds and +// logs carry the instance identity. Kept minimal — the bot only needs the name +// and the accent color (as an int for discord.js embeds). +require('dotenv').config() + +const name = process.env.BRAND_NAME || 'Runic Gateway' +const accentHex = process.env.BRAND_ACCENT_COLOR || '#7f99bd' +const accentInt = (() => { + const n = parseInt(String(accentHex).replace('#', ''), 16) + return Number.isNaN(n) ? 0x7f99bd : n +})() + +module.exports = { name, accentHex, accentInt } diff --git a/bot/src/db.js b/bot/src/db.js index 5d01a08..a796a45 100644 --- a/bot/src/db.js +++ b/bot/src/db.js @@ -12,7 +12,7 @@ const pool = mariadb.createPool({ port: Number(process.env.DB_PORT) || 3306, user: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD || '', - database: process.env.DB_NAME || 'uomysticmoon', + database: process.env.DB_NAME || 'runic_gateway', connectionLimit: 5, insertIdAsNumber: true, bigIntAsNumber: true, diff --git a/bot/src/discord/commands/rolemenu.command.js b/bot/src/discord/commands/rolemenu.command.js index e073f17..97736b7 100644 --- a/bot/src/discord/commands/rolemenu.command.js +++ b/bot/src/discord/commands/rolemenu.command.js @@ -9,6 +9,7 @@ const { } = require('discord.js') const roleMenus = require('../../model/roleMenus') +const brand = require('../../brand') // Capped at 5 roles per menu — a single Discord action row holds at most 5 // buttons, and one row keeps this a single simple slash command instead of @@ -62,7 +63,7 @@ module.exports = { return } - const embed = new EmbedBuilder().setTitle(title).setColor(0x6a8fc2) + const embed = new EmbedBuilder().setTitle(title).setColor(brand.accentInt) if (description) embed.setDescription(description) const row = new ActionRowBuilder().addComponents( diff --git a/bot/src/discord/newsAnnounce.js b/bot/src/discord/newsAnnounce.js index 7f8cad3..0d09232 100644 --- a/bot/src/discord/newsAnnounce.js +++ b/bot/src/discord/newsAnnounce.js @@ -4,6 +4,7 @@ const { EmbedBuilder } = require('discord.js') const guildConfig = require('../model/guildConfig') +const brand = require('../brand') const createLogger = require('../utils/logger') const log = createLogger('news') @@ -15,7 +16,7 @@ async function postAnnounce(client, guildId, { title, excerpt, url, imageUrl }) const channel = await client.channels.fetch(channelId) if (!channel || !channel.isTextBased()) throw new Error('Configured news channel is missing or not text-based.') - const embed = new EmbedBuilder().setColor(0x6a8fc2).setTitle(title).setURL(url) + const embed = new EmbedBuilder().setColor(brand.accentInt).setTitle(title).setURL(url) if (excerpt) embed.setDescription(excerpt) if (imageUrl) embed.setImage(imageUrl) diff --git a/bot/src/internal/requireInternalKey.js b/bot/src/internal/requireInternalKey.js index efb20c7..3202456 100644 --- a/bot/src/internal/requireInternalKey.js +++ b/bot/src/internal/requireInternalKey.js @@ -1,4 +1,4 @@ -// Gate for the bot's /internal/* API. The only caller is the main UOMysticmoon +// Gate for the bot's /internal/* API. The only caller is the main Runic Gateway // server, over the private compose network — never expose this route through // the public reverse proxy. Timing-safe compare so response time can't be used // to brute-force the shared secret one byte at a time. diff --git a/bot/src/server.js b/bot/src/server.js index 70cdc7d..582826b 100644 --- a/bot/src/server.js +++ b/bot/src/server.js @@ -4,6 +4,7 @@ const app = require('./app') const bootstrap = require('./bootstrap') const createLogger = require('./utils/logger') const discordManager = require('./discord/discordManager') +const brand = require('./brand') const pkg = require('../package.json') const log = createLogger('server') @@ -11,7 +12,7 @@ const PORT = Number(process.env.PORT) || 4100 const HOST = '0.0.0.0' async function start() { - log.info(`starting UOMysticmoon bot v${pkg.version}`, { + log.info(`starting ${brand.name} bot v${pkg.version}`, { node: process.version, logFile: createLogger.logFilePath || 'disabled (console only)', }) diff --git a/brand/README.md b/brand/README.md new file mode 100644 index 0000000..a5d8ef7 --- /dev/null +++ b/brand/README.md @@ -0,0 +1,15 @@ +# Brand assets (per-instance) + +This directory is bind-mounted into the container at `/app/brand` (see +`docker-compose.yml`). Drop instance branding images here and point the matching +`BRAND_*` env vars at them, e.g.: + +``` +BRAND_LOGO=/brand/logo.png +BRAND_HERO=/brand/hero.png +BRAND_FAVICON=/brand/favicon.ico +``` + +Leave the vars blank to use the built-in defaults (the hero falls back to a +neutral built-in image; no logo/favicon is injected). Nothing here is required +for the app to run — it renders cleanly with an empty `brand/`. diff --git a/client/index.html b/client/index.html index f7d3f29..d92425b 100644 --- a/client/index.html +++ b/client/index.html @@ -3,8 +3,8 @@ <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <title>UOMysticmoon - + Runic Gateway + diff --git a/client/package-lock.json b/client/package-lock.json index 834e8bf..7118c69 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,11 +1,11 @@ { - "name": "uomysticmoon-client", + "name": "runic-gateway-client", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "uomysticmoon-client", + "name": "runic-gateway-client", "version": "1.0.0", "dependencies": { "@tiptap/extension-image": "^2.27.2", diff --git a/client/package.json b/client/package.json index 250c40d..72480e3 100644 --- a/client/package.json +++ b/client/package.json @@ -1,5 +1,5 @@ { - "name": "uomysticmoon-client", + "name": "runic-gateway-client", "private": true, "version": "1.0.0", "type": "module", diff --git a/client/src/components/SiteFooter.jsx b/client/src/components/SiteFooter.jsx index 7ca30d1..526fd8e 100644 --- a/client/src/components/SiteFooter.jsx +++ b/client/src/components/SiteFooter.jsx @@ -2,7 +2,7 @@ import { Link } from 'react-router-dom' import { useSite } from '../contexts/SiteContext.jsx' export default function SiteFooter() { - const { contactEmail } = useSite() + const { contactEmail, siteTitle } = useSite() return (