feat: Erinnerungs-Checkboxen, Anbieterlogos via Logo.dev, Kontakte in Einstellungen
This commit is contained in:
1 parent
11bef8748f
commit
a1195223e0
18 files changed
+381
-50
No files matched your search
+56
@@ -0,0 +1,56 @@
|
||||
export type LogoCandidate = { name: string; domain: string; logoUrl: string | null };
|
||||
|
||||
const DOMAIN_RE = /^[a-z0-9.-]+\.[a-z]{2,}$/i;
|
||||
|
||||
export function normalizeDomain(domain: string | null | undefined): string | null {
|
||||
if (!domain) return null;
|
||||
const d = domain.trim().toLowerCase();
|
||||
if (!DOMAIN_RE.test(d)) return null;
|
||||
return d;
|
||||
}
|
||||
|
||||
/** Baut die öffentliche Logo-URL für eine gespeicherte Domain. Key bleibt serverseitig. */
|
||||
export function logoImageUrl(domain: string | null | undefined): string | null {
|
||||
const d = normalizeDomain(domain);
|
||||
const token = process.env.LOGO_DEV_PUBLISHABLE_KEY;
|
||||
if (!d || !token) return null;
|
||||
return `https://img.logo.dev/${d}?token=${token}&size=128&format=png`;
|
||||
}
|
||||
|
||||
/** Kürzel aus dem Anbieternamen als Fallback, z. B. "Therme Erding" → "TE". */
|
||||
export function monogram(name: string): string {
|
||||
const parts = name.trim().split(/\s+/).filter(Boolean);
|
||||
if (parts.length === 0) return "?";
|
||||
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
||||
return (parts[0][0] + parts[1][0]).toUpperCase();
|
||||
}
|
||||
|
||||
/** Reichert ein Voucher-Objekt um die öffentliche logoUrl an (serverseitig). */
|
||||
export function withLogoUrl<T extends { providerDomain: string | null }>(v: T): T & { logoUrl: string | null } {
|
||||
return { ...v, logoUrl: logoImageUrl(v.providerDomain) };
|
||||
}
|
||||
|
||||
/** Sucht Anbieter über Logo.dev (serverseitig, Secret-Key). Gibt max. 8 Treffer zurück. */
|
||||
export async function searchLogos(query: string): Promise<LogoCandidate[]> {
|
||||
const key = process.env.LOGO_DEV_SECRET_KEY;
|
||||
if (!key) throw new Error("LOGO_DEV_SECRET_KEY fehlt");
|
||||
const q = query.trim();
|
||||
if (!q) return [];
|
||||
const res = await fetch(`https://api.logo.dev/search?q=${encodeURIComponent(q)}`, {
|
||||
headers: { Authorization: `Bearer ${key}` },
|
||||
});
|
||||
if (!res.ok) throw new Error(`Logosuche ${res.status}`);
|
||||
const data: unknown = await res.json();
|
||||
if (!Array.isArray(data)) return [];
|
||||
const out: LogoCandidate[] = [];
|
||||
for (const item of data) {
|
||||
if (typeof item !== "object" || item === null) continue;
|
||||
const rec = item as Record<string, unknown>;
|
||||
const domain = normalizeDomain(typeof rec.domain === "string" ? rec.domain : null);
|
||||
if (!domain) continue;
|
||||
const name = typeof rec.name === "string" && rec.name.trim() ? rec.name.trim() : domain;
|
||||
out.push({ name, domain, logoUrl: logoImageUrl(domain) });
|
||||
if (out.length >= 8) break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
Reference in new issue
Block a user