mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-11 22:38:54 +00:00
Show one confirmation toast, not two
Every page wraps itself in AppLayout, so a flashed redirect that lands on a different page component tears the layout down and builds it again -- Toaster with it. The fresh Toaster then reads the flash at mount *and* catches the router success event for the same visit, and every "Client created." arrived twice. Saves that stay on the same component never remount, which is why this survived unnoticed. Deduping on the flash object's identity rather than its text is what keeps the success listener doing its job: two genuine identical messages in a row are separate objects and still both toast. Verified in a real browser rather than by types: create a client, two toasts before, one after, and two consecutive creates over SPA navigation still toast once each. Reported and diagnosed by @denkfabrik-li in #1675.
This commit is contained in:
@@ -15,6 +15,14 @@ a version is cut.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **One confirmation message instead of two.** Saving a new client, system user or role showed the
|
||||
same green "Client created." twice, stacked. So did deleting one. It was only ever cosmetic —
|
||||
nothing happened twice — but it read as though something had, which is the last thing a
|
||||
confirmation should do. Saves that stay on the same screen, such as the email settings, were never
|
||||
affected.
|
||||
([#1675](https://github.com/projectsend/projectsend/issues/1675), reported and diagnosed by
|
||||
[@denkfabrik-li](https://github.com/denkfabrik-li))
|
||||
|
||||
- **Connecting a provider to an account that already has one.** Signing in with Google, Microsoft or
|
||||
a custom provider worked, but attaching one to an existing account did not: the **Connect** button
|
||||
on Settings → Connected accounts appeared to do nothing at all. The button asks the server in the
|
||||
|
||||
@@ -15,11 +15,26 @@ interface Toast {
|
||||
|
||||
const DURATION = 4500;
|
||||
|
||||
/**
|
||||
* The flash object most recently turned into toasts, across remounts.
|
||||
*
|
||||
* Both of the component's sources can hand over the *same* visit's flash:
|
||||
* the layout (and the Toaster inside it) remounts whenever a flashed
|
||||
* redirect lands on a different page component — create → edit, delete →
|
||||
* index — and then the mount-time read and the router `success` event
|
||||
* each fire once for one flash, stacking every "Client created." twice.
|
||||
* Identity comparison is the dedup that cannot over-trigger: a repeat of
|
||||
* the same action produces an identical *message* but never the identical
|
||||
* *object*, so deliberate back-to-back toasts still both show.
|
||||
*/
|
||||
let shownFlash: SharedData['flash'] | null = null;
|
||||
|
||||
/**
|
||||
* App-wide flash toasts. Reads the `flash` shared prop and shows a toast
|
||||
* after any Inertia visit that carried one (created/updated/deleted…).
|
||||
* Uses the router `success` event so two identical messages in a row still
|
||||
* each toast. Mounted once in the app layout.
|
||||
* each toast. Mounted in the app layout, which remounts it — see the note on
|
||||
* `shownFlash` above.
|
||||
*/
|
||||
export function Toaster() {
|
||||
const { t } = useTranslation();
|
||||
@@ -36,16 +51,20 @@ export function Toaster() {
|
||||
|
||||
const dismiss = (id: number) => setToasts((current) => current.filter((toast) => toast.id !== id));
|
||||
|
||||
const pushFlash = (flash: SharedData['flash'] | null | undefined) => {
|
||||
if (!flash || flash === shownFlash) return;
|
||||
shownFlash = flash;
|
||||
push('success', flash.success);
|
||||
push('error', flash.error);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// A flash present on the very first render (e.g. a server redirect on load).
|
||||
push('success', page.props.flash?.success);
|
||||
push('error', page.props.flash?.error);
|
||||
pushFlash(page.props.flash);
|
||||
|
||||
// And every subsequent successful visit.
|
||||
const stop = router.on('success', (event) => {
|
||||
const flash = (event.detail.page.props as unknown as SharedData).flash;
|
||||
push('success', flash?.success);
|
||||
push('error', flash?.error);
|
||||
pushFlash((event.detail.page.props as unknown as SharedData).flash);
|
||||
});
|
||||
|
||||
return () => stop();
|
||||
|
||||
Reference in New Issue
Block a user