| 1 | import { Atom } from "effect/reactivity"; |
| 2 | |
| 3 | import { appAtomRegistry } from "./atom-registry"; |
| 4 | |
| 5 | interface ThreadComposerError { |
| 6 | readonly message: string; |
| 7 | /** The queued message this error is about, when it is about one. */ |
| 8 | readonly messageId: string | null; |
| 9 | } |
| 10 | |
| 11 | /** |
| 12 | * Why a thread's last message did not go out, shown above that thread's |
| 13 | * composer. The outbox drain can reject a message after the user has left the |
| 14 | * thread, so the reason is kept per thread until they dismiss it, send again, |
| 15 | * or the message it describes is delivered after all. Keyed by `scopedThreadKey`. |
| 16 | */ |
| 17 | export const threadComposerErrorsAtom = Atom.make<Readonly<Record<string, ThreadComposerError>>>( |
| 18 | {}, |
| 19 | ).pipe(Atom.keepAlive, Atom.withLabel("mobile:thread-composer-errors")); |
| 20 | |
| 21 | export function setThreadComposerError( |
| 22 | threadKey: string, |
| 23 | message: string, |
| 24 | messageId: string | null = null, |
| 25 | ): void { |
| 26 | appAtomRegistry.set(threadComposerErrorsAtom, { |
| 27 | ...appAtomRegistry.get(threadComposerErrorsAtom), |
| 28 | [threadKey]: { message, messageId }, |
| 29 | }); |
| 30 | } |
| 31 | |
| 32 | /** With `messageId`, clears only an error that describes that message. */ |
| 33 | export function clearThreadComposerError(threadKey: string, messageId?: string): void { |
| 34 | const current = appAtomRegistry.get(threadComposerErrorsAtom); |
| 35 | const error = current[threadKey]; |
| 36 | if (!error || (messageId !== undefined && error.messageId !== messageId)) { |
| 37 | return; |
| 38 | } |
| 39 | const next = { ...current }; |
| 40 | delete next[threadKey]; |
| 41 | appAtomRegistry.set(threadComposerErrorsAtom, next); |
| 42 | } |
| 43 | |
| 44 | export function clearThreadComposerErrorsForEnvironment(environmentId: string): void { |
| 45 | const current = appAtomRegistry.get(threadComposerErrorsAtom); |
| 46 | const prefix = `${environmentId}:`; |
| 47 | const keys = Object.keys(current).filter((key) => key.startsWith(prefix)); |
| 48 | if (keys.length === 0) { |
| 49 | return; |
| 50 | } |
| 51 | const next = { ...current }; |
| 52 | for (const key of keys) { |
| 53 | delete next[key]; |
| 54 | } |
| 55 | appAtomRegistry.set(threadComposerErrorsAtom, next); |
| 56 | } |