Files
SRCmail/stores/signature-store.ts
Bernd Rodler b98ab59f0d fix: Phase 2 QA — all 25 remaining HIGH/MEDIUM/LOW issues
HIGH fixes (7):
- H1: VNCdirectory admin i18n — 30+ translation keys added
- H2: handleSave try/catch with error toast
- H3: Free/busy accountId scoping
- H4: cancelEventBookings filter by eventId
- H5: Resource picker static apiFetch import
- H6: Sharing-store toast messages via lastMessage state
- H7: roleLabel for all resource types

MEDIUM fixes (11):
- M1: identitySignatureMap cleanup on delete
- M2: Now-line relative positioning
- M3: Radial menu disabled item keyboard nav
- M4: Radial menu stable event listener via refs
- M5: cancelBooking error on missing booking
- M6: PasswordRow isMasked state flag
- M7: Extract shared rights into lib/sharing-rights.ts
- M8: VNCtalk client server-side guard
- M9: Collabora configManager instead of process.env
- M10: CONFIG_ENV_MAP VNCdirectory fields
- M11: SENSITIVE_CONFIG_KEYS field name unification

LOW fixes (7):
- L1-L3: Unused imports removed
- L4: aria-labels on close, clear, search, spinner
- L5-L7: Comments for intentional patterns, null guard
2026-08-07 14:21:07 +02:00

149 lines
4.9 KiB
TypeScript

import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { generateUUID } from '@/lib/utils';
export interface Signature {
id: string;
name: string;
body: string;
plainText: string;
createdAt: string;
updatedAt: string;
}
interface SignatureState {
signatures: Signature[];
defaultSignatureId: string | null;
replySignatureId: string | null;
identitySignatureMap: Record<string, { defaultId?: string; replyId?: string }>;
addSignature: (sig: Omit<Signature, 'id' | 'createdAt' | 'updatedAt'>) => Signature;
updateSignature: (id: string, updates: Partial<Pick<Signature, 'name' | 'body' | 'plainText'>>) => void;
deleteSignature: (id: string) => void;
duplicateSignature: (id: string) => Signature;
setDefaultSignatureId: (id: string | null) => void;
setReplySignatureId: (id: string | null) => void;
getSignatureById: (id: string) => Signature | undefined;
setIdentitySignature: (identityId: string, type: 'default' | 'reply', signatureId: string | null) => void;
getIdentityDefaultSignatureId: (identityId: string) => string | null;
getIdentityReplySignatureId: (identityId: string) => string | null;
}
export const useSignatureStore = create<SignatureState>()(
persist(
(set, get) => ({
signatures: [],
defaultSignatureId: null,
replySignatureId: null,
identitySignatureMap: {},
addSignature: (sig) => {
const now = new Date().toISOString();
const newSig: Signature = {
...sig,
id: generateUUID(),
createdAt: now,
updatedAt: now,
};
set((state) => ({
signatures: [...state.signatures, newSig],
}));
return newSig;
},
updateSignature: (id, updates) => {
set((state) => ({
signatures: state.signatures.map((s) =>
s.id === id
? { ...s, ...updates, updatedAt: new Date().toISOString() }
: s
),
}));
},
deleteSignature: (id) => {
set((state) => {
const nextMap = { ...state.identitySignatureMap };
for (const identityId of Object.keys(nextMap)) {
const entry = nextMap[identityId];
if (entry.defaultId === id || entry.replyId === id) {
const updated = { ...entry };
if (updated.defaultId === id) delete updated.defaultId;
if (updated.replyId === id) delete updated.replyId;
if (Object.keys(updated).length === 0) {
delete nextMap[identityId];
} else {
nextMap[identityId] = updated;
}
}
}
return {
signatures: state.signatures.filter((s) => s.id !== id),
defaultSignatureId: state.defaultSignatureId === id ? null : state.defaultSignatureId,
replySignatureId: state.replySignatureId === id ? null : state.replySignatureId,
identitySignatureMap: nextMap,
};
});
},
duplicateSignature: (id) => {
const original = get().signatures.find((s) => s.id === id);
if (!original) {
throw new Error(`Signature with id ${id} not found`);
}
const now = new Date().toISOString();
const duplicate: Signature = {
...original,
id: generateUUID(),
name: `${original.name} (copy)`,
createdAt: now,
updatedAt: now,
};
set((state) => ({
signatures: [...state.signatures, duplicate],
}));
return duplicate;
},
setDefaultSignatureId: (id) => {
set({ defaultSignatureId: id });
},
setReplySignatureId: (id) => {
set({ replySignatureId: id });
},
getSignatureById: (id) => {
return get().signatures.find((s) => s.id === id);
},
setIdentitySignature: (identityId, type, signatureId) => {
set((state) => {
const current = state.identitySignatureMap[identityId] ?? {};
const updated = {
...current,
[type === 'default' ? 'defaultId' : 'replyId']: signatureId ?? undefined,
};
if (updated.defaultId === undefined && updated.replyId === undefined) {
const { [identityId]: _, ...rest } = state.identitySignatureMap;
return { identitySignatureMap: rest };
}
return { identitySignatureMap: { ...state.identitySignatureMap, [identityId]: updated } };
});
},
getIdentityDefaultSignatureId: (identityId) => {
const entry = get().identitySignatureMap[identityId];
return entry?.defaultId ?? get().defaultSignatureId;
},
getIdentityReplySignatureId: (identityId) => {
const entry = get().identitySignatureMap[identityId];
return entry?.replyId ?? get().replySignatureId ?? get().defaultSignatureId;
},
}),
{
name: 'signature-storage',
}
)
);