Feature: configurable PWA install screenshots (per-domain)
Admins can upload custom mobile/desktop screenshots shown in the browser's PWA install dialog, replacing the hardcoded Bulwark ones. Two new config keys (pwaScreenshotMobileUrl/DesktopUrl), upload widgets in the admin Branding tab, a sharp-based /api/pwa-screenshot/[variant] resize route, and manifest.ts picks the custom screenshots when configured. Like the other branding fields, screenshots are per-domain: they are BRANDING_OVERRIDE_KEYS, the manifest and the /api/pwa-screenshot route resolve them from the request host (domain override -> global -> Bulwark default), and the admin Branding tab + upload/delete route handle them in a per-domain scope, mirroring pwaIconUrl/faviconUrl.
This commit is contained in:
@@ -26,14 +26,18 @@ const ALLOWED_MIME_TYPES = new Set([
|
||||
'image/vnd.microsoft.icon',
|
||||
]);
|
||||
|
||||
type UploadSlot = BrandingOverrideKey;
|
||||
|
||||
/** Slots that correspond to branding config keys */
|
||||
const VALID_SLOTS = new Set<BrandingOverrideKey>([
|
||||
const VALID_SLOTS = new Set<UploadSlot>([
|
||||
'faviconUrl',
|
||||
'pwaIconUrl',
|
||||
'appLogoLightUrl',
|
||||
'appLogoDarkUrl',
|
||||
'loginLogoLightUrl',
|
||||
'loginLogoDarkUrl',
|
||||
'pwaScreenshotMobileUrl',
|
||||
'pwaScreenshotDesktopUrl',
|
||||
]);
|
||||
|
||||
const EXT_BY_MIME: Record<string, string> = {
|
||||
@@ -131,7 +135,7 @@ export async function POST(request: NextRequest) {
|
||||
return NextResponse.json({ error: 'Missing file or slot' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (!VALID_SLOTS.has(slot as BrandingOverrideKey)) {
|
||||
if (!VALID_SLOTS.has(slot as UploadSlot)) {
|
||||
return NextResponse.json({ error: `Invalid slot: ${slot}` }, { status: 400 });
|
||||
}
|
||||
|
||||
@@ -226,7 +230,7 @@ export async function DELETE(request: NextRequest) {
|
||||
const slot = body.slot;
|
||||
const rawHost = body.host ?? '';
|
||||
|
||||
if (!slot || !VALID_SLOTS.has(slot as BrandingOverrideKey)) {
|
||||
if (!slot || !VALID_SLOTS.has(slot as UploadSlot)) {
|
||||
return NextResponse.json({ error: 'Invalid or missing slot' }, { status: 400 });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user