fix: RFC 9553 compliance for contacts (birthday, addresses)

This commit is contained in:
Linus Rath
2026-03-19 13:33:53 +01:00
parent 2edf2fab89
commit 77514bd054
5 changed files with 178 additions and 36 deletions
+21 -8
View File
@@ -6,7 +6,7 @@ import { Mail, Phone, Building, MapPin, StickyNote, Pencil, Trash2, BookUser, Co
import { Avatar } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import type { ContactCard } from "@/lib/jmap/types";
import type { ContactCard, AnniversaryDate, PartialDate } from "@/lib/jmap/types";
import { getContactDisplayName, getContactPrimaryEmail } from "@/stores/contact-store";
import { useSmimeStore } from "@/stores/smime-store";
import { parseCertificatePemOrDer, extractCertificateInfo } from "@/lib/smime/certificate-utils";
@@ -26,12 +26,23 @@ function formatPhoneFeatures(features?: Record<string, boolean>): string {
return Object.keys(features).filter(k => features[k]).join(", ");
}
function formatDate(dateInput: string | Record<string, unknown>): string {
function formatDate(dateInput: AnniversaryDate): string {
// Handle RFC 9553 PartialDate objects: { year?, month?, day?, calendarScale? }
// Handle RFC 9553 Timestamp objects: { "@type": "Timestamp", utc: "..." }
if (typeof dateInput === 'object' && dateInput !== null) {
const year = dateInput.year as number | undefined;
const month = dateInput.month as number | undefined;
const day = dateInput.day as number | undefined;
if (dateInput['@type'] === 'Timestamp' && typeof dateInput.utc === 'string') {
try {
const d = new Date(dateInput.utc as string);
if (!isNaN(d.getTime())) {
return d.toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" });
}
} catch { /* fallback */ }
return String(dateInput.utc);
}
const pd = dateInput as PartialDate;
const year = pd.year;
const month = pd.month;
const day = pd.day;
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const parts: string[] = [];
if (month && monthNames[month - 1]) parts.push(monthNames[month - 1]);
@@ -282,9 +293,11 @@ export function ContactDetail({ contact, onEdit, onDelete, isMobile, className }
{addresses.map((a, i) => (
<div key={i} className="text-sm space-y-0.5 rounded-md border border-border/60 bg-muted/30 p-3">
<div>
{a.fullAddress
? a.fullAddress
: [a.street, a.locality, a.region, a.postcode, a.country].filter(Boolean).join(", ")}
{a.full || a.fullAddress
? (a.full || a.fullAddress)
: a.components && a.components.length > 0
? a.components.filter(c => c.kind !== 'separator').map(c => c.value).filter(Boolean).join(", ")
: [a.street, a.locality, a.region, a.postcode, a.country].filter(Boolean).join(", ")}
{a.contexts && <ContextBadge contexts={a.contexts} />}
</div>
{a.timeZone && (