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

+43 -34
View File
@@ -13,6 +13,7 @@ import {
Gift,
Images,
ScissorsSquare,
Smartphone,
Stamp,
Star,
TrendingUp,
@@ -24,7 +25,7 @@ import { auth } from "@/auth";
import { Card } from "@/components/ui/card";
import { TopNav } from "@/components/navigation/top-nav";
import { prisma } from "@/lib/db/prisma";
import { hasPermission } from "@/lib/auth/permissions";
import { hasPermission, isSuperAdmin } from "@/lib/auth/permissions";
import { Permission } from "@/generated/prisma/enums";
import { getActingBusinessContext } from "@/lib/business/get-acting-business-context";
import { todayIn } from "@/lib/time/business-time";
@@ -48,38 +49,44 @@ function StatTile({ icon: Icon, label, value, hint }: { icon: typeof Calendar; l
);
}
const MANAGE_SECTIONS = [
{
title: "Business",
links: [
{ href: "/owner/settings/business", label: "Business Details", icon: Building2 },
{ href: "/owner/settings/hours", label: "Opening Hours", icon: CalendarClock },
{ href: "/owner/settings/gallery", label: "Gallery", icon: Images },
],
},
{
title: "Booking",
links: [
{ href: "/owner/services", label: "Services", icon: ScissorsSquare },
{ href: "/owner/settings/booking-rules", label: "Booking Rules", icon: ClipboardList },
{ href: "/owner/settings/loyalty", label: "Loyalty Programme", icon: Crown },
],
},
{
title: "Customers",
links: [{ href: "/owner/settings/reviews", label: "Reviews", icon: Star }],
},
{
title: "Team",
links: [{ href: "/owner/team", label: "Team Management", icon: Users }],
},
{
title: "Reports",
links: [{ href: "/owner/reports", label: "Reports", icon: BarChart3 }],
},
] as const;
/** App Icon & Home Screen is Super Administrator only (see /owner/settings/app) — everything else here is any Owner. */
function getManageSections(isSuper: boolean) {
return [
{
title: "Business",
links: [
{ href: "/owner/settings/business", label: "Business Details", icon: Building2 },
{ href: "/owner/settings/hours", label: "Opening Hours", icon: CalendarClock },
{ href: "/owner/settings/gallery", label: "Gallery", icon: Images },
...(isSuper
? [{ href: "/owner/settings/app", label: "App Icon & Home Screen", icon: Smartphone }]
: []),
],
},
{
title: "Booking",
links: [
{ href: "/owner/services", label: "Services", icon: ScissorsSquare },
{ href: "/owner/settings/booking-rules", label: "Booking Rules", icon: ClipboardList },
{ href: "/owner/settings/loyalty", label: "Loyalty Programme", icon: Crown },
],
},
{
title: "Customers",
links: [{ href: "/owner/settings/reviews", label: "Reviews", icon: Star }],
},
{
title: "Team",
links: [{ href: "/owner/team", label: "Team Management", icon: Users }],
},
{
title: "Reports",
links: [{ href: "/owner/reports", label: "Reports", icon: BarChart3 }],
},
];
}
function ManageSection({ title, links }: (typeof MANAGE_SECTIONS)[number]) {
function ManageSection({ title, links }: ReturnType<typeof getManageSections>[number]) {
return (
<div>
<p className="text-muted mb-2 px-1 text-[10px] font-bold tracking-widest uppercase">{title}</p>
@@ -108,14 +115,16 @@ export default async function OwnerDashboardPage() {
if (!context) redirect("/staff");
const today = todayIn(context.businessTimezone);
const [summary, unreadNotificationCount] = await Promise.all([
const [summary, unreadNotificationCount, isSuper] = await Promise.all([
getDashboardSummary(prisma, {
businessId: context.businessId,
businessTimezone: context.businessTimezone,
date: today,
}),
getUnreadNotificationCount(prisma, session.user.id),
isSuperAdmin(session.user.id),
]);
const manageSections = getManageSections(isSuper);
return (
<div className="pb-12">
@@ -237,7 +246,7 @@ export default async function OwnerDashboardPage() {
</Card>
<div className="space-y-5">
{MANAGE_SECTIONS.map((section) => (
{manageSections.map((section) => (
<ManageSection key={section.title} {...section} />
))}
</div>
@@ -0,0 +1,46 @@
import type { Metadata } from "next";
import { redirect } from "next/navigation";
import { auth } from "@/auth";
import { SettingsPageShell } from "@/components/settings/settings-page-shell";
import { prisma } from "@/lib/db/prisma";
import { isSuperAdmin } from "@/lib/auth/permissions";
import { getActingBusinessContext } from "@/lib/business/get-acting-business-context";
import { parseSplashScreens } from "@/lib/business/branding";
import { getBusinessProfile } from "@/services/business/business.service";
import { AppIconSettingsForm } from "@/features/owner/components/app-icon-settings-form";
export const metadata: Metadata = { title: "App Icon & Home Screen" };
export const dynamic = "force-dynamic";
/** Super Administrator only — not Owner, unlike every other /owner/settings/* page. See requireSuperAdminBusinessManager in features/owner/actions/business.ts for the matching action-layer check. */
export default async function OwnerAppIconSettingsPage() {
const session = await auth();
if (!session?.user) redirect("/sign-in");
if (!(await isSuperAdmin(session.user.id))) redirect("/owner");
const context = await getActingBusinessContext();
if (!context) redirect("/staff");
const business = await getBusinessProfile(prisma, context.businessId);
if (!business) redirect("/owner");
return (
<SettingsPageShell title="App Icon & Home Screen">
<AppIconSettingsForm
initial={{
shortName: business.shortName?.trim() || business.name,
appIconUrl: business.appIconUrl,
themeColor: business.themeColor,
backgroundColor: business.backgroundColor,
icon192Url: business.icon192Url,
icon512Url: business.icon512Url,
maskableIcon192Url: business.maskableIcon192Url,
maskableIcon512Url: business.maskableIcon512Url,
splashScreens: parseSplashScreens(business.splashScreens),
bottomNavShowLabels: business.bottomNavShowLabels,
}}
/>
</SettingsPageShell>
);
}
@@ -38,9 +38,6 @@ export default async function OwnerBusinessDetailsPage() {
welcomeBackgroundDesktopUrl: business.welcomeBackgroundDesktopUrl,
welcomeBackgroundMobileUrl: business.welcomeBackgroundMobileUrl,
faviconUrl: business.faviconUrl,
appIconUrl: business.appIconUrl,
themeColor: business.themeColor,
backgroundColor: business.backgroundColor,
phone: business.phone ?? "",
email: business.email ?? "",
website: business.website ?? "",