Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Refactored metadata for contacts, administration, and Ulticards pages to utilize dynamic app names and descriptions. - Introduced new product pages for Ultiai, Ultical, Ulticards, Ultidrive, Ultimail, and Ultimeet with appropriate metadata. - Enhanced layout components to ensure consistent styling and functionality across new product sections. - Updated various components to replace hardcoded labels with dynamic references to improve maintainability and consistency.
215 lines
6.6 KiB
TypeScript
215 lines
6.6 KiB
TypeScript
import type { Metadata } from "next"
|
|
import { displayFileName } from "@/lib/drive/display-file-name"
|
|
import { parseDriveSegments } from "@/lib/drive/drive-url"
|
|
|
|
export type SuiteApp = "mail" | "drive" | "contacts" | "agenda" | "meet" | "admin" | "compte" | "suite"
|
|
|
|
/** Separator between page segment and product name in document titles. */
|
|
export const SUITE_TITLE_SEP = " - "
|
|
|
|
/** Display name of the calendar app (`/agenda`). */
|
|
export const ULTICAL_APP_NAME = "UltiCal"
|
|
|
|
/** Display name of the contacts app (`/contacts`). */
|
|
export const ULTICARDS_APP_NAME = "UltiCards"
|
|
|
|
export const MAIL_INBOX_DOCUMENT_TITLE = `Boîte mail${SUITE_TITLE_SEP}Ultimail`
|
|
|
|
const DESCRIPTIONS: Record<SuiteApp, string> = {
|
|
mail: "Client mail Ultimail — suite souveraine",
|
|
drive: "Stockage de fichiers UltiDrive — suite UltiSuite",
|
|
contacts: `Carnet d'adresses — ${ULTICARDS_APP_NAME}`,
|
|
agenda: "Calendrier partagé, invitations et disponibilités — UltiSuite",
|
|
meet: "Visioconférence chiffrée intégrée à la suite — UltiMeet",
|
|
admin: "Console d'administration — UltiSuite",
|
|
compte: "Réglages du compte — UltiSuite",
|
|
suite: "Ultimail, UltiDrive et contacts — interface suite unifiée",
|
|
}
|
|
|
|
const APP_LABELS: Record<SuiteApp, string> = {
|
|
mail: "Ultimail",
|
|
drive: "UltiDrive",
|
|
contacts: ULTICARDS_APP_NAME,
|
|
agenda: ULTICAL_APP_NAME,
|
|
meet: "UltiMeet",
|
|
admin: "Administration",
|
|
compte: "Compte Ulti",
|
|
suite: "UltiSuite",
|
|
}
|
|
|
|
const ICONS: Record<SuiteApp, Metadata["icons"]> = {
|
|
suite: {
|
|
icon: [
|
|
{ url: "/ultisuite-mark.svg", type: "image/svg+xml" },
|
|
{ url: "/ultisuite-32.png", sizes: "32x32", type: "image/png" },
|
|
],
|
|
apple: [{ url: "/ultisuite-180.png", sizes: "180x180", type: "image/png" }],
|
|
shortcut: "/ultisuite-mark.svg",
|
|
},
|
|
admin: {
|
|
icon: [{ url: "/admin-mark.svg", type: "image/svg+xml" }],
|
|
apple: [{ url: "/admin-mark.svg", type: "image/svg+xml" }],
|
|
shortcut: "/admin-mark.svg",
|
|
},
|
|
mail: {
|
|
icon: [
|
|
{ url: "/ultimail-mark.svg", type: "image/svg+xml" },
|
|
{ url: "/icon.png", sizes: "32x32", type: "image/png" },
|
|
],
|
|
apple: [{ url: "/apple-icon.png", sizes: "180x180", type: "image/png" }],
|
|
shortcut: "/ultimail-mark.svg",
|
|
},
|
|
drive: {
|
|
icon: [
|
|
{ url: "/ultidrive-mark.svg", type: "image/svg+xml" },
|
|
{ url: "/drive/favicon-32.png", sizes: "32x32", type: "image/png" },
|
|
{ url: "/drive/favicon-16.png", sizes: "16x16", type: "image/png" },
|
|
],
|
|
apple: [{ url: "/drive/apple-touch-icon.png", sizes: "180x180", type: "image/png" }],
|
|
shortcut: "/ultidrive-mark.svg",
|
|
},
|
|
contacts: {
|
|
icon: [
|
|
{ url: "/contacts-mark.svg", type: "image/svg+xml" },
|
|
{ url: "/icon.png", sizes: "32x32", type: "image/png" },
|
|
],
|
|
apple: [{ url: "/contacts-mark.svg", type: "image/svg+xml" }],
|
|
shortcut: "/contacts-mark.svg",
|
|
},
|
|
compte: {
|
|
icon: [{ url: "/compte-mark.svg", type: "image/svg+xml" }],
|
|
apple: [{ url: "/compte-mark.svg", type: "image/svg+xml" }],
|
|
shortcut: "/compte-mark.svg",
|
|
},
|
|
agenda: {
|
|
icon: [
|
|
{ url: "/agenda-mark.svg", type: "image/svg+xml" },
|
|
{ url: "/icon.png", sizes: "32x32", type: "image/png" },
|
|
],
|
|
apple: [{ url: "/agenda-mark.svg", type: "image/svg+xml" }],
|
|
shortcut: "/agenda-mark.svg",
|
|
},
|
|
meet: {
|
|
icon: [
|
|
{ url: "/ultimeet-mark.svg", type: "image/svg+xml" },
|
|
{ url: "/icon.png", sizes: "32x32", type: "image/png" },
|
|
],
|
|
apple: [{ url: "/ultimeet-mark.svg", type: "image/svg+xml" }],
|
|
shortcut: "/ultimeet-mark.svg",
|
|
},
|
|
}
|
|
|
|
type PageMetadataOptions = {
|
|
app: SuiteApp
|
|
/** Full document title (no suffix added). */
|
|
title?: string
|
|
/** Short segment only — parent layout `title.template` adds the product suffix. */
|
|
titleSegment?: string
|
|
description?: string
|
|
/** When true with `title`, use `title` as the full document title. */
|
|
absoluteTitle?: boolean
|
|
}
|
|
|
|
export function formatSuiteDocumentTitle(segment: string, app: SuiteApp): string {
|
|
return `${segment}${SUITE_TITLE_SEP}${APP_LABELS[app]}`
|
|
}
|
|
|
|
export function truncateSubjectForTitle(subject: string, maxLen = 48): string {
|
|
const clean = subject.replace(/\s+/g, " ").trim()
|
|
if (!clean) return ""
|
|
if (clean.length <= maxLen) return clean
|
|
return `${clean.slice(0, maxLen - 1).trimEnd()}…`
|
|
}
|
|
|
|
export function mailDocumentTitle(subject: string | null | undefined): string {
|
|
const trimmed = subject?.replace(/\s+/g, " ").trim()
|
|
if (trimmed) {
|
|
return formatSuiteDocumentTitle(truncateSubjectForTitle(trimmed), "mail")
|
|
}
|
|
return MAIL_INBOX_DOCUMENT_TITLE
|
|
}
|
|
|
|
export function suiteRootMetadata(): Metadata {
|
|
return {
|
|
title: APP_LABELS.suite,
|
|
description: DESCRIPTIONS.suite,
|
|
icons: ICONS.suite,
|
|
}
|
|
}
|
|
|
|
export function suitePageMetadata({
|
|
app,
|
|
title,
|
|
titleSegment,
|
|
description,
|
|
absoluteTitle = false,
|
|
}: PageMetadataOptions): Metadata {
|
|
const label = APP_LABELS[app]
|
|
|
|
let resolvedTitle: Metadata["title"]
|
|
if (titleSegment !== undefined) {
|
|
// Short segment — nearest ancestor `title.template` adds the product suffix once.
|
|
resolvedTitle = titleSegment
|
|
} else if (title === undefined) {
|
|
resolvedTitle = {
|
|
default: label,
|
|
template: `%s${SUITE_TITLE_SEP}${label}`,
|
|
}
|
|
} else if (absoluteTitle) {
|
|
resolvedTitle = { absolute: title }
|
|
} else {
|
|
// Full title already includes suffix — absolute so root/ancestor templates do not re-apply.
|
|
resolvedTitle = { absolute: formatSuiteDocumentTitle(title, app) }
|
|
}
|
|
|
|
const icons = ICONS[app]
|
|
|
|
const meta: Metadata = {
|
|
title: resolvedTitle,
|
|
description: description ?? DESCRIPTIONS[app],
|
|
icons,
|
|
}
|
|
|
|
if (app === "drive") {
|
|
meta.manifest = "/drive/manifest.webmanifest"
|
|
meta.appleWebApp = {
|
|
capable: true,
|
|
title: "UltiDrive",
|
|
statusBarStyle: "default",
|
|
}
|
|
}
|
|
|
|
return meta
|
|
}
|
|
|
|
export function driveDocumentTitle(segments?: string[]): string {
|
|
const route = parseDriveSegments(segments)
|
|
switch (route.view) {
|
|
case "recent":
|
|
return "Récents"
|
|
case "starred":
|
|
return "Favoris"
|
|
case "trash":
|
|
return "Corbeille"
|
|
case "shared":
|
|
return route.pathSegments.length > 0
|
|
? displayFileName(route.pathSegments.at(-1)!)
|
|
: "Partagés avec moi"
|
|
case "search":
|
|
return "Recherche"
|
|
case "org":
|
|
return route.pathSegments.length > 0
|
|
? displayFileName(route.pathSegments.at(-1)!)
|
|
: "Dossier d'organisation"
|
|
case "mount":
|
|
return route.pathSegments.length > 0
|
|
? displayFileName(route.pathSegments.at(-1)!)
|
|
: "Volume monté"
|
|
default:
|
|
if (route.pathSegments.length > 0) {
|
|
return displayFileName(route.pathSegments.at(-1)!)
|
|
}
|
|
return "Mon Drive"
|
|
}
|
|
}
|