PWA Settings

Fixed webmanifest generation and pwa icon.

Made nav bar text optional.
This commit is contained in:
Stefan Lazic committed 2026-08-07 13:38:11 +01:00
1 parent a702320d4e
commit 769d3f9ebc
23 files changed
+1065 -91

No files matched your search

+46
View File
@@ -25,6 +25,12 @@ import { prisma } from "@/lib/db/prisma";
* Business row exists yet, so it must never throw.
*/
/** One iOS `apple-touch-startup-image` entry — an exact per-device media query paired with its splash image, matching the shape a generator like progressier.com's ZIP bundle produces. See importAppIconZipAction. */
export interface SplashScreenEntry {
media: string;
url: string;
}
export interface BrandingInfo {
configured: boolean;
name: string;
@@ -33,9 +39,19 @@ export interface BrandingInfo {
logoUrl: string | null;
coverImageUrl: string | null;
faviconUrl: string | null;
/** Apple touch icon (iOS "Add to Home Screen") — distinct from the manifest icon fields below, which Android/Chrome installs read instead. */
appIconUrl: string | null;
themeColor: string;
backgroundColor: string;
/** PWA manifest `icons` array — see src/app/manifest.ts. Each falls back independently to the app's own generated icon when unset. */
icon192Url: string | null;
icon512Url: string | null;
maskableIcon192Url: string | null;
maskableIcon512Url: string | null;
/** iOS `apple-touch-startup-image` splash screens — see src/app/layout.tsx. */
splashScreens: SplashScreenEntry[];
/** Icon-only vs icon+label bottom navigation — see components/navigation/bottom-nav.tsx. */
bottomNavShowLabels: boolean;
/** Welcome page (/) background — see src/app/page.tsx. Independent images, not one image cropped two ways. */
welcomeBackgroundDesktopUrl: string | null;
welcomeBackgroundMobileUrl: string | null;
@@ -43,6 +59,18 @@ export interface BrandingInfo {
phone: string | null;
}
/** Defensive parse of the `Business.splashScreens` JSON column — never trusts its shape at compile time. Drops any entry missing a string `media`/`url` rather than throwing. */
export function parseSplashScreens(value: unknown): SplashScreenEntry[] {
if (!Array.isArray(value)) return [];
return value.filter(
(entry): entry is SplashScreenEntry =>
typeof entry === "object" &&
entry !== null &&
typeof (entry as Record<string, unknown>).media === "string" &&
typeof (entry as Record<string, unknown>).url === "string",
);
}
const DEFAULT_THEME_COLOR = "#111b15";
const DEFAULT_BACKGROUND_COLOR = "#f4f6f4";
@@ -58,6 +86,12 @@ const UNCONFIGURED_BRANDING: BrandingInfo = {
appIconUrl: null,
themeColor: DEFAULT_THEME_COLOR,
backgroundColor: DEFAULT_BACKGROUND_COLOR,
icon192Url: null,
icon512Url: null,
maskableIcon192Url: null,
maskableIcon512Url: null,
splashScreens: [],
bottomNavShowLabels: true,
welcomeBackgroundDesktopUrl: null,
welcomeBackgroundMobileUrl: null,
email: null,
@@ -85,6 +119,12 @@ export const getBranding = cache(async (): Promise<BrandingInfo> => {
appIconUrl: true,
themeColor: true,
backgroundColor: true,
icon192Url: true,
icon512Url: true,
maskableIcon192Url: true,
maskableIcon512Url: true,
splashScreens: true,
bottomNavShowLabels: true,
welcomeBackgroundDesktopUrl: true,
welcomeBackgroundMobileUrl: true,
email: true,
@@ -107,6 +147,12 @@ export const getBranding = cache(async (): Promise<BrandingInfo> => {
appIconUrl: business.appIconUrl,
themeColor: business.themeColor || DEFAULT_THEME_COLOR,
backgroundColor: business.backgroundColor || DEFAULT_BACKGROUND_COLOR,
icon192Url: business.icon192Url,
icon512Url: business.icon512Url,
maskableIcon192Url: business.maskableIcon192Url,
maskableIcon512Url: business.maskableIcon512Url,
splashScreens: parseSplashScreens(business.splashScreens),
bottomNavShowLabels: business.bottomNavShowLabels,
welcomeBackgroundDesktopUrl: business.welcomeBackgroundDesktopUrl,
welcomeBackgroundMobileUrl: business.welcomeBackgroundMobileUrl,
email: business.email,