714f731ac9
Neue Felder loginHintDirectory/loginHintLocal (Platzhalter, Pflicht, max. 1000), Migration 20260930170000 (nullable, leer = Standard aus @tessera/shared). Knopf Passwort festlegen und Gueltigkeitshinweis bleiben fest; local-no-link wird nie erzeugt und bleibt fest. Vorschau springt beim Bearbeiten auf die passende Kontoart. Lokal im Browser nachgewiesen. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
102 lines
3.0 KiB
TypeScript
102 lines
3.0 KiB
TypeScript
/**
|
|
* Willkommensmail-Vorlage — API-Client (Administrator → Willkommensmail).
|
|
* Konsumiert `/welcome-mail-template`. Muster `custom-modules-api.ts`:
|
|
* `credentials: 'include'` fuer Cookie-Auth, `NEXT_PUBLIC_API_URL` als Basis.
|
|
*/
|
|
|
|
import type { WelcomeMailTexts } from '@tessera/shared';
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
|
|
|
/** Antwort von GET/PUT/DELETE /welcome-mail-template. */
|
|
export interface WelcomeMailTemplateState {
|
|
/** `true` = eigene Vorlage gespeichert, `false` = Standardtexte. */
|
|
custom: boolean;
|
|
texts: WelcomeMailTexts;
|
|
defaults: WelcomeMailTexts;
|
|
updatedAt: string | null;
|
|
updatedBy: string | null;
|
|
}
|
|
|
|
export interface WelcomeMailPreview {
|
|
subject: string;
|
|
html: string;
|
|
text: string;
|
|
}
|
|
|
|
/** Fehler mit HTTP-Status und den Servermeldungen (400: je Feld eine). */
|
|
export class WelcomeMailTemplateRequestError extends Error {
|
|
constructor(
|
|
readonly status: number,
|
|
readonly messages: string[],
|
|
) {
|
|
super(messages.join(' ') || `HTTP ${status}`);
|
|
this.name = 'WelcomeMailTemplateRequestError';
|
|
}
|
|
}
|
|
|
|
async function readMessages(res: Response): Promise<string[]> {
|
|
try {
|
|
const body = (await res.json()) as { message?: unknown };
|
|
if (Array.isArray(body.message)) return body.message.map(String);
|
|
if (typeof body.message === 'string' && body.message) return [body.message];
|
|
} catch {
|
|
// kein JSON — Status allein
|
|
}
|
|
return [];
|
|
}
|
|
|
|
async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
const res = await fetch(`${API_URL}/welcome-mail-template${path}`, {
|
|
credentials: 'include',
|
|
...init,
|
|
headers: init.body ? { 'Content-Type': 'application/json' } : undefined,
|
|
});
|
|
if (!res.ok) throw new WelcomeMailTemplateRequestError(res.status, await readMessages(res));
|
|
return (await res.json()) as T;
|
|
}
|
|
|
|
/** Nur die sechs Texte senden — nie weitere Felder aus dem Formularzustand. */
|
|
function textsOf(texts: WelcomeMailTexts): WelcomeMailTexts {
|
|
return {
|
|
subject: texts.subject,
|
|
heading: texts.heading,
|
|
intro: texts.intro,
|
|
loginHintDirectory: texts.loginHintDirectory,
|
|
loginHintLocal: texts.loginHintLocal,
|
|
closing: texts.closing,
|
|
};
|
|
}
|
|
|
|
export function fetchWelcomeMailTemplate(): Promise<WelcomeMailTemplateState> {
|
|
return request('');
|
|
}
|
|
|
|
export function saveWelcomeMailTemplate(
|
|
texts: WelcomeMailTexts,
|
|
): Promise<WelcomeMailTemplateState> {
|
|
return request('', { method: 'PUT', body: JSON.stringify(textsOf(texts)) });
|
|
}
|
|
|
|
export function resetWelcomeMailTemplate(): Promise<WelcomeMailTemplateState> {
|
|
return request('', { method: 'DELETE' });
|
|
}
|
|
|
|
export function previewWelcomeMail(
|
|
texts: WelcomeMailTexts,
|
|
account: 'directory' | 'local',
|
|
signal?: AbortSignal,
|
|
): Promise<WelcomeMailPreview> {
|
|
return request('/preview', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ ...textsOf(texts), account }),
|
|
signal,
|
|
});
|
|
}
|
|
|
|
export function sendWelcomeMailTest(
|
|
texts: WelcomeMailTexts,
|
|
): Promise<{ success: true; to: string }> {
|
|
return request('/test', { method: 'POST', body: JSON.stringify(textsOf(texts)) });
|
|
}
|