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
149 lines
4.9 KiB
TypeScript
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',
|
|
}
|
|
)
|
|
);
|