mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
694e11c117
When loading the users that can be invited to a fusion or given a calendar
share failed on desktop, the dialog kept showing the list from the last load,
so a user could pick someone who is no longer available and only learn it
from the failed send. The phone sheets already emptied the list and showed
their "no users available" state.
Cause: useVacayUserPicker only emptied the list on a failed load when its
clearOnLoadError option was true; the desktop VacayPersons and
VacaySharedCalendars dialogs passed false, a leftover of the old silent
catch in the desktop dialogs.
Fix: the hook always empties the list when the load fails. The option only
existed to keep the desktop behaviour, so it is removed from the hook and
from all four callers.
Red test: FE-COMP-VACAYPICK-002 (useVacayUserPicker.test.ts) failed on the
unfixed code with "AssertionError: expected [ { id: 2, username: 'bob' },
...(1) ] to deeply equal []".
Changed assertions:
- useVacayUserPicker.test.ts FE-COMP-VACAYPICK-002: was "a failed load keeps
the last list on desktop" expecting available to equal USERS; now expects
an empty list.
- FE-COMP-VACAYPICK-003: was "a failed load empties the list on the phone"
through load() with clearOnLoadError true; now drives the same expectation
(empty list) through a phone sheet reopening, since the option is gone.
98 lines
2.8 KiB
TypeScript
98 lines
2.8 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react';
|
|
|
|
import apiClient from '../../api/client';
|
|
import { useTranslation } from '../../i18n';
|
|
import { getApiErrorMessage } from '../../types';
|
|
import { useToast } from '../shared/Toast';
|
|
|
|
interface VacayUserPickerOptions {
|
|
/** Where the users that can be picked come from. */
|
|
endpoint: '/addons/vacay/available-users' | '/addons/vacay/shares/available-users';
|
|
/** What sending does with the picked user. */
|
|
submit: (userId: number) => Promise<void>;
|
|
successKey: string;
|
|
errorKey: string;
|
|
/**
|
|
* The phone sheets pass their open flag: every opening clears the pick, folds
|
|
* the inline user list and reloads the users. The desktop dialogs leave it out
|
|
* and load on their own.
|
|
*/
|
|
sheetOpen?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Picking another TREK user for a vacay action and sending it: the fusion invite
|
|
* and the read-only calendar share. Each view keeps its own dialog or sheet.
|
|
*/
|
|
export function useVacayUserPicker<U extends { id: number; username: string }>({
|
|
endpoint,
|
|
submit,
|
|
successKey,
|
|
errorKey,
|
|
sheetOpen,
|
|
}: VacayUserPickerOptions) {
|
|
const { t } = useTranslation();
|
|
const toast = useToast();
|
|
const [available, setAvailable] = useState<U[]>([]);
|
|
const [selected, setSelected] = useState<number | null>(null);
|
|
const [sending, setSending] = useState(false);
|
|
/** Whether the inline user list of a phone sheet is unfolded. */
|
|
const [pickerOpen, setPickerOpen] = useState(false);
|
|
|
|
const load = useCallback(async () => {
|
|
try {
|
|
const data = await apiClient.get(endpoint).then((r) => r.data);
|
|
setAvailable(data.users);
|
|
} catch {
|
|
// A failed load empties the list, so nobody picks from a stale one and the
|
|
// dialog or sheet shows its "no users available" state.
|
|
setAvailable([]);
|
|
}
|
|
}, [endpoint]);
|
|
|
|
useEffect(() => {
|
|
if (!sheetOpen) return;
|
|
setSelected(null);
|
|
setPickerOpen(false);
|
|
void load();
|
|
}, [sheetOpen, load]);
|
|
|
|
const togglePicker = () => setPickerOpen((o) => !o);
|
|
|
|
/** Picks a user from the inline list and folds it. */
|
|
const pick = (userId: number) => {
|
|
setSelected(userId);
|
|
setPickerOpen(false);
|
|
};
|
|
|
|
/** Sends to the picked user; `onSent` runs right after the success toast. */
|
|
const send = async (onSent: () => void) => {
|
|
if (!selected) return;
|
|
setSending(true);
|
|
try {
|
|
await submit(selected);
|
|
toast.success(t(successKey));
|
|
onSent();
|
|
} catch (err: unknown) {
|
|
toast.error(getApiErrorMessage(err, t(errorKey)));
|
|
} finally {
|
|
setSending(false);
|
|
}
|
|
};
|
|
|
|
const selectedUser = available.find((u) => u.id === selected);
|
|
|
|
return {
|
|
available,
|
|
selected,
|
|
setSelected,
|
|
selectedUser,
|
|
sending,
|
|
load,
|
|
send,
|
|
pickerOpen,
|
|
togglePicker,
|
|
pick,
|
|
};
|
|
}
|