refactor: remove hotel-name fallback, fail fast when unconfigured
CI / check (push) Failing after 39s
CI / release (push) Skipped
CI / deploy (push) Skipped

Drop the hardcoded FALLBACK_HOTEL_NAME ("Atom") preset and the brand.ts
module. HOTEL_NAME is now a required env var: if it (and the CMS hotel_name
setting) is missing the site fails validation at startup/build with a clear
message instead of silently rendering a placeholder hotel name.

resolveHotelName() resolves CMS hotel_name -> required HOTEL_NAME only.
Callers that used the preset (api/home route catch branch, CMS settings form
default, mobile-nav/logo-generator prop defaults) now use the configured name
or an empty default; the real name is already passed in by server parents.
This commit is contained in:
openhands committed 2026-08-27 16:35:00 +02:00
1 parent 555c783d55
commit 164a4f4ef6
8 files changed
+19 -30

No files matched your search

@@ -1,4 +1,3 @@
import { FALLBACK_HOTEL_NAME } from "@/lib/brand";
import { HABBO_GAMEDATA_HOTELS } from "@/lib/habbo-gamedata-hotel"; import { HABBO_GAMEDATA_HOTELS } from "@/lib/habbo-gamedata-hotel";
export type FieldType = export type FieldType =
@@ -50,7 +49,7 @@ export const SETTINGS_GROUPS: SettingsGroup[] = [
label: "Hotel name", label: "Hotel name",
type: "text", type: "text",
placeholder: "Epicnabbo", placeholder: "Epicnabbo",
defaultValue: FALLBACK_HOTEL_NAME, defaultValue: "",
}, },
{ {
key: "cms_logo", key: "cms_logo",
+2 -2
View File
@@ -1,6 +1,6 @@
import { count, desc, eq } from "drizzle-orm"; import { count, desc, eq } from "drizzle-orm";
import { env } from "@/env";
import { apiJson } from "@/lib/api"; import { apiJson } from "@/lib/api";
import { FALLBACK_HOTEL_NAME } from "@/lib/brand";
import { db, User, WebsiteArticles } from "@/lib/db"; import { db, User, WebsiteArticles } from "@/lib/db";
import { resolveHotelName } from "@/lib/hotel-name"; import { resolveHotelName } from "@/lib/hotel-name";
import { apiCacheKey, cacheSafe, redisCache } from "@/lib/redis-cache"; import { apiCacheKey, cacheSafe, redisCache } from "@/lib/redis-cache";
@@ -38,7 +38,7 @@ export async function GET(_req: Request) {
return apiJson(data); return apiJson(data);
} catch { } catch {
return apiJson( return apiJson(
{ articles: [], online: 0, hotelName: FALLBACK_HOTEL_NAME }, { articles: [], online: 0, hotelName: env.HOTEL_NAME },
{ status: 200 }, { status: 200 },
); );
} }
+1 -2
View File
@@ -3,7 +3,6 @@
import { AnimatePresence, motion } from "motion/react"; import { AnimatePresence, motion } from "motion/react";
import Image from "next/image"; import Image from "next/image";
import { type ReactNode, useRef, useState } from "react"; import { type ReactNode, useRef, useState } from "react";
import { FALLBACK_HOTEL_NAME } from "@/lib/brand";
import { mobileMenuVariants } from "@/lib/motion"; import { mobileMenuVariants } from "@/lib/motion";
interface MobileNavProps { interface MobileNavProps {
@@ -17,7 +16,7 @@ export function MobileNav({
children, children,
menuLabel = "Open menu", menuLabel = "Open menu",
closeLabel = "Close menu", closeLabel = "Close menu",
brandLabel = FALLBACK_HOTEL_NAME, brandLabel = "",
}: MobileNavProps) { }: MobileNavProps) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const detailsRef = useRef<HTMLDivElement>(null); const detailsRef = useRef<HTMLDivElement>(null);
+1 -2
View File
@@ -16,10 +16,9 @@ import {
renderToCanvas, renderToCanvas,
} from "@/components/public/sprite-font"; } from "@/components/public/sprite-font";
import { ContentCard } from "@/components/public/ui"; import { ContentCard } from "@/components/public/ui";
import { FALLBACK_HOTEL_NAME } from "@/lib/brand";
export default function LogoGenerator({ export default function LogoGenerator({
initialText = FALLBACK_HOTEL_NAME, initialText = "",
}: { }: {
initialText?: string; initialText?: string;
}) { }) {
+6 -2
View File
@@ -1,5 +1,4 @@
import { z } from "zod"; import { z } from "zod";
import { FALLBACK_HOTEL_NAME } from "@/lib/brand";
// Minimal validated env for the foundation. When the Next.js app is added this // Minimal validated env for the foundation. When the Next.js app is added this
// will move to @t3-oss/env-nextjs (the habbo-next pattern), but the data layer // will move to @t3-oss/env-nextjs (the habbo-next pattern), but the data layer
@@ -25,7 +24,12 @@ const schema = z
.int() .int()
.positive() .positive()
.default(10_000), .default(10_000),
HOTEL_NAME: z.string().default(FALLBACK_HOTEL_NAME), HOTEL_NAME: z
.string()
.min(
1,
"HOTEL_NAME is not set — the site has not been configured/built yet.",
),
APP_URL: z.string().url().default("http://localhost:3000"), APP_URL: z.string().url().default("http://localhost:3000"),
NEXT_PUBLIC_APP_URL: z.string().url().default("http://localhost:3000"), NEXT_PUBLIC_APP_URL: z.string().url().default("http://localhost:3000"),
// Public imager URL — overrides the default /imaging relative path. // Public imager URL — overrides the default /imaging relative path.
-10
View File
@@ -1,10 +0,0 @@
/**
* Single hardcoded fallback hotel brand.
* Override order at runtime:
* 1. website_settings.hotel_name
* 2. HOTEL_NAME env
* 3. this constant
*
* Safe for client components (no env / DB imports).
*/
export const FALLBACK_HOTEL_NAME = "Atom";
+7 -8
View File
@@ -1,17 +1,16 @@
import "server-only"; import "server-only";
import { env } from "@/env"; import { env } from "@/env";
import { FALLBACK_HOTEL_NAME } from "@/lib/brand";
import { siteSettings } from "@/lib/services/site-settings"; import { siteSettings } from "@/lib/services/site-settings";
/** /**
* Resolve the public hotel name: CMS setting → HOTEL_NAME env → FALLBACK_HOTEL_NAME. * Resolve the public hotel name: CMS `hotel_name` setting, falling back to the
* required HOTEL_NAME env var. There is no hardcoded preset — the site must be
* configured, otherwise HOTEL_NAME fails validation at startup.
*/ */
export async function resolveHotelName(): Promise<string> { export async function resolveHotelName(): Promise<string> {
const fromSettings = await siteSettings.get("hotel_name", env.HOTEL_NAME); const fromSettings = (
const trimmed = fromSettings?.trim(); await siteSettings.get("hotel_name", env.HOTEL_NAME)
if (trimmed) return trimmed; )?.trim();
const fromEnv = env.HOTEL_NAME?.trim(); return fromSettings || env.HOTEL_NAME;
if (fromEnv) return fromEnv;
return FALLBACK_HOTEL_NAME;
} }
+1 -2
View File
@@ -1,12 +1,11 @@
import "server-only"; import "server-only";
import { FALLBACK_HOTEL_NAME } from "@/lib/brand";
import { db, WebsiteSetting } from "@/lib/db"; import { db, WebsiteSetting } from "@/lib/db";
import { logger } from "@/lib/logger"; import { logger } from "@/lib/logger";
import { redis } from "@/lib/redis"; import { redis } from "@/lib/redis";
const DEFAULTS: Record<string, string> = { const DEFAULTS: Record<string, string> = {
hotel_name: FALLBACK_HOTEL_NAME, hotel_name: "",
habbo_imaging_url: "/imaging", habbo_imaging_url: "/imaging",
logo_url: "", logo_url: "",
nitro_client_url: "", nitro_client_url: "",