AcadeDoc/apps/client/src/lib/config.ts
Corentin b53ab5043f feat(acadedoc): add AcadeDoc branding, Brevo SMTP preset, UI customization — R4.4
- Rebranding: BRAND_NAME env var (default AcadeDoc) replaces hardcoded "DocAdenice"
  in index.html title/meta, PWA manifest, app-header logo text, email footer/body
- lib/config.ts: getAppName() reads BRAND_NAME; new getBrandLogoUrl/PrimaryColor/AccentColor helpers
- vite.config.ts: BRAND_* vars exposed via define block to client
- brand-theme.ts: getBrandTheme() generates 10-shade MantineColorsTuple from hex
  (no @mantine/colors-generator dep); merged into MantineProvider at boot
- theme/__tests__/brand-theme.test.ts: 11 vitest tests (generateColorTuple + getBrandTheme)
- Workspace branding: migration adds primary_color/accent_color to workspaces table
  WorkspaceBrandingService + WorkspaceBrandingController (POST /workspace/branding,
  POST /workspace/branding/update — admin only) + DTO hex validation
- Settings: /settings/branding page (WorkspaceBranding) + sidebar entry (admin-only)
- workspace-branding.spec.ts: 13 vitest tests (service + controller + DTO validation)
- SMTP Brevo: .env.example preset block + transactional/README.md ops guide
  (key gen, port 587 STARTTLS, 300/day free limit, swaks/curl test)
- environment.service.ts: getMailFromName() falls back to BRAND_NAME if MAIL_FROM_NAME unset
- vitest.config.ts server: include pattern extended to src/core/workspace/spec/**
- i18n: 11 branding keys added to en-US and fr-FR translations
- 0 TypeScript errors client + server, 11 client + 13 server new tests all green

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 11:36:38 +02:00

120 lines
3 KiB
TypeScript

import bytes from "bytes";
import { castToBoolean } from "@/lib/utils.tsx";
import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts";
import { sanitizeUrl } from "@docmost/editor-ext";
declare global {
interface Window {
CONFIG?: Record<string, string>;
}
}
export function getAppName(): string {
return getConfigValue("BRAND_NAME", "AcadeDoc");
}
export function getBrandLogoUrl(): string | null {
return getConfigValue("BRAND_LOGO_URL") || null;
}
export function getBrandPrimaryColor(): string {
return getConfigValue("BRAND_PRIMARY_COLOR", "#2563eb");
}
export function getBrandAccentColor(): string {
return getConfigValue("BRAND_ACCENT_COLOR", "#7c3aed");
}
export function getAppUrl(): string {
return `${window.location.protocol}//${window.location.host}`;
}
export function getServerAppUrl(): string {
return getConfigValue("APP_URL");
}
export function getBackendUrl(): string {
return getAppUrl() + "/api";
}
export function getCollaborationUrl(): string {
const baseUrl =
getConfigValue("COLLAB_URL") ||
(import.meta.env.DEV ? process.env.APP_URL : getAppUrl());
const collabUrl = new URL("/collab", baseUrl);
collabUrl.protocol = collabUrl.protocol === "https:" ? "wss:" : "ws:";
return collabUrl.toString();
}
export function getSubdomainHost(): string {
return getConfigValue("SUBDOMAIN_HOST");
}
export function isCloud(): boolean {
return castToBoolean(getConfigValue("CLOUD"));
}
export function getAvatarUrl(
avatarUrl: string,
type: AvatarIconType = AvatarIconType.AVATAR,
) {
if (!avatarUrl) return null;
if (avatarUrl?.startsWith("http")) return avatarUrl;
return getBackendUrl() + `/attachments/img/${type}/` + encodeURI(avatarUrl);
}
export function getSpaceUrl(spaceSlug: string) {
return "/s/" + spaceSlug;
}
export function getFileUrl(src: string) {
if (!src) return src;
if (src.startsWith("http")) return src;
if (src.startsWith("/api/")) {
// Remove the '/api' prefix
return getBackendUrl() + src.substring(4);
}
if (src.startsWith("/files/")) {
return getBackendUrl() + src;
}
return sanitizeUrl(src);
}
export function getFileUploadSizeLimit() {
const limit = getConfigValue("FILE_UPLOAD_SIZE_LIMIT", "50mb");
return bytes(limit);
}
export function getFileImportSizeLimit() {
const limit = getConfigValue("FILE_IMPORT_SIZE_LIMIT", "200mb");
return bytes(limit);
}
export function getDrawioUrl() {
return getConfigValue("DRAWIO_URL", "https://embed.diagrams.net");
}
export function getBillingTrialDays() {
return getConfigValue("BILLING_TRIAL_DAYS");
}
export function getPostHogHost() {
return getConfigValue("POSTHOG_HOST");
}
export function isPostHogEnabled(): boolean {
return Boolean(getPostHogHost() && getPostHogKey());
}
export function getPostHogKey() {
return getConfigValue("POSTHOG_KEY");
}
function getConfigValue(key: string, defaultValue: string = undefined): string {
const rawValue = import.meta.env.DEV
? process?.env?.[key]
: window?.CONFIG?.[key];
return rawValue ?? defaultValue;
}