53 lines
2.0 KiB
TypeScript
53 lines
2.0 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect } from 'react';
|
|
import { toast } from 'sonner';
|
|
import { useI18n } from '@/lib/hooks/use-i18n';
|
|
import { acknowledgePersistLoss, subscribeToPersistHealth } from '@/lib/store/persist-health';
|
|
|
|
/**
|
|
* Surfaces the persistence failures the user cannot otherwise notice.
|
|
*
|
|
* When the storage seam refuses to write — its key never hydrated, so writing
|
|
* would replace stored data with defaults — the app keeps working, the store
|
|
* keeps updating in memory, and every change is lost on reload. Renders nothing
|
|
* until that happens.
|
|
*/
|
|
export function StorageHealthNotice() {
|
|
const { t } = useI18n();
|
|
|
|
useEffect(
|
|
() =>
|
|
subscribeToPersistHealth(({ name, status }) => {
|
|
// Two toasts per key, deliberately not one. "Storage is down" is a
|
|
// condition that recovery retracts; "your edits were lost" is a fact
|
|
// that recovery does not undo, so they cannot share an id — dismissing
|
|
// the first would take the second with it. Both are sticky and
|
|
// de-duplicated: neither resolves on its own, and repeating them per
|
|
// refused write would bury the app.
|
|
if (status === 'changes-lost') {
|
|
toast.error(t('settings.persistChangesLost'), {
|
|
id: `persist-changes-lost:${name}`,
|
|
duration: Infinity,
|
|
// The only way out is the user acknowledging it — and the
|
|
// acknowledgement has to reach the channel, or the same store
|
|
// losing changes again is swallowed as a duplicate and a later
|
|
// subscriber resurrects a toast that was already dealt with.
|
|
closeButton: true,
|
|
onDismiss: () => acknowledgePersistLoss(name),
|
|
});
|
|
return;
|
|
}
|
|
const id = `persist-unavailable:${name}`;
|
|
if (status === 'recovered') {
|
|
toast.dismiss(id);
|
|
return;
|
|
}
|
|
toast.error(t('settings.persistUnavailable'), { id, duration: Infinity });
|
|
}),
|
|
[t],
|
|
);
|
|
|
|
return null;
|
|
}
|