pl-fe: backups, polls reducers

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-11-06 13:06:41 +01:00
parent 0811af7ad7
commit 542ba9ab78
9 changed files with 64 additions and 41 deletions

View file

@ -2765,8 +2765,6 @@ class PlApiClient {
return response.json as {}; return response.json as {};
}, },
}; };
/** /**

View file

@ -1,5 +1,6 @@
import { getClient } from '../api'; import { getClient } from '../api';
import type { Backup } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store'; import type { AppDispatch, RootState } from 'pl-fe/store';
const BACKUPS_FETCH_REQUEST = 'BACKUPS_FETCH_REQUEST' as const; const BACKUPS_FETCH_REQUEST = 'BACKUPS_FETCH_REQUEST' as const;
@ -10,27 +11,63 @@ const BACKUPS_CREATE_REQUEST = 'BACKUPS_CREATE_REQUEST' as const;
const BACKUPS_CREATE_SUCCESS = 'BACKUPS_CREATE_SUCCESS' as const; const BACKUPS_CREATE_SUCCESS = 'BACKUPS_CREATE_SUCCESS' as const;
const BACKUPS_CREATE_FAIL = 'BACKUPS_CREATE_FAIL' as const; const BACKUPS_CREATE_FAIL = 'BACKUPS_CREATE_FAIL' as const;
interface BackupsFetchRequestAction {
type: typeof BACKUPS_FETCH_REQUEST;
}
interface BackupsFetchSuccessAction {
type: typeof BACKUPS_FETCH_SUCCESS;
backups: Array<Backup>;
}
interface BackupsFetchFailAction {
type: typeof BACKUPS_FETCH_FAIL;
error: unknown;
}
const fetchBackups = () => const fetchBackups = () =>
(dispatch: AppDispatch, getState: () => RootState) => { (dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: BACKUPS_FETCH_REQUEST }); dispatch<BackupsFetchRequestAction>({ type: BACKUPS_FETCH_REQUEST });
return getClient(getState).settings.getBackups().then((backups) => return getClient(getState).settings.getBackups().then((backups) =>
dispatch({ type: BACKUPS_FETCH_SUCCESS, backups }), dispatch<BackupsFetchSuccessAction>({ type: BACKUPS_FETCH_SUCCESS, backups }),
).catch(error => { ).catch(error => {
dispatch({ type: BACKUPS_FETCH_FAIL, error }); dispatch<BackupsFetchFailAction>({ type: BACKUPS_FETCH_FAIL, error });
}); });
}; };
interface BackupsCreateRequestAction {
type: typeof BACKUPS_CREATE_REQUEST;
}
interface BackupsCreateSuccessAction {
type: typeof BACKUPS_CREATE_SUCCESS;
backups: Array<Backup>;
}
interface BackupsCreateFailAction {
type: typeof BACKUPS_CREATE_FAIL;
error: unknown;
}
const createBackup = () => const createBackup = () =>
(dispatch: AppDispatch, getState: () => RootState) => { (dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: BACKUPS_CREATE_REQUEST }); dispatch<BackupsCreateRequestAction>({ type: BACKUPS_CREATE_REQUEST });
return getClient(getState).settings.createBackup().then((backups) => return getClient(getState).settings.createBackup().then((backup) =>
dispatch({ type: BACKUPS_CREATE_SUCCESS, backups }), dispatch<BackupsCreateSuccessAction>({ type: BACKUPS_CREATE_SUCCESS, backups: [backup] }),
).catch(error => { ).catch(error => {
dispatch({ type: BACKUPS_CREATE_FAIL, error }); dispatch<BackupsCreateFailAction>({ type: BACKUPS_CREATE_FAIL, error });
}); });
}; };
type BackupsAction =
| BackupsFetchRequestAction
| BackupsFetchSuccessAction
| BackupsFetchFailAction
| BackupsCreateRequestAction
| BackupsCreateSuccessAction
| BackupsCreateFailAction;
export { export {
BACKUPS_FETCH_REQUEST, BACKUPS_FETCH_REQUEST,
BACKUPS_FETCH_SUCCESS, BACKUPS_FETCH_SUCCESS,
@ -40,4 +77,5 @@ export {
BACKUPS_CREATE_FAIL, BACKUPS_CREATE_FAIL,
fetchBackups, fetchBackups,
createBackup, createBackup,
type BackupsAction,
}; };

View file

@ -76,7 +76,6 @@ const updateNotifications = (notification: BaseNotification) =>
statuses: [getNotificationStatus(notification)], statuses: [getNotificationStatus(notification)],
})); }));
if (showInColumn) { if (showInColumn) {
const normalizedNotification = normalizeNotification(notification); const normalizedNotification = normalizeNotification(notification);

View file

@ -97,7 +97,7 @@ const editStatus = (statusId: string) => (dispatch: AppDispatch, getState: () =>
const state = getState(); const state = getState();
const status = state.statuses[statusId]!; const status = state.statuses[statusId]!;
const poll = status.poll_id ? state.polls.get(status.poll_id) : undefined; const poll = status.poll_id ? state.polls[status.poll_id] : undefined;
dispatch({ type: STATUS_FETCH_SOURCE_REQUEST }); dispatch({ type: STATUS_FETCH_SOURCE_REQUEST });
@ -134,7 +134,7 @@ const deleteStatus = (statusId: string, withRedraft = false) =>
const state = getState(); const state = getState();
const status = state.statuses[statusId]!; const status = state.statuses[statusId]!;
const poll = status.poll_id ? state.polls.get(status.poll_id) : undefined; const poll = status.poll_id ? state.polls[status.poll_id] : undefined;
dispatch({ type: STATUS_DELETE_REQUEST, params: status }); dispatch({ type: STATUS_DELETE_REQUEST, params: status });

View file

@ -30,7 +30,7 @@ const Poll: React.FC<IPoll> = ({ id, status }): JSX.Element | null => {
const intl = useIntl(); const intl = useIntl();
const isLoggedIn = useAppSelector((state) => state.me); const isLoggedIn = useAppSelector((state) => state.me);
const poll = useAppSelector((state) => state.polls.get(id)); const poll = useAppSelector((state) => state.polls[id]);
const [selected, setSelected] = useState({} as Selected); const [selected, setSelected] = useState({} as Selected);

View file

@ -65,7 +65,7 @@ const Backups = () => {
const intl = useIntl(); const intl = useIntl();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const backups = useAppSelector((state) => state.backups.toList().sortBy((backup) => backup.inserted_at)); const backups = useAppSelector((state) => Object.values(state.backups).toSorted((a, b) => a.inserted_at.localeCompare(b.inserted_at)));
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
@ -80,7 +80,7 @@ const Backups = () => {
}).catch(() => {}); }).catch(() => {});
}, []); }, []);
const showLoading = isLoading && backups.count() === 0; const showLoading = isLoading && backups.length === 0;
const emptyMessage = ( const emptyMessage = (
<Card variant='rounded' size='lg'> <Card variant='rounded' size='lg'>
@ -96,11 +96,11 @@ const Backups = () => {
</Card> </Card>
); );
const body = showLoading ? <Spinner /> : backups.isEmpty() ? emptyMessage : ( const body = showLoading ? <Spinner /> : backups.length ? (
<div className='mb-4 grid grid-cols-1 gap-4 sm:grid-cols-2'> <div className='mb-4 grid grid-cols-1 gap-4 sm:grid-cols-2'>
{backups.map((backup) => <Backup key={backup.id} backup={backup} />)} {backups.map((backup) => <Backup key={backup.id} backup={backup} />)}
</div> </div>
); ) : emptyMessage;
return ( return (
<Column label={intl.formatMessage(messages.heading)}> <Column label={intl.formatMessage(messages.heading)}>

View file

@ -1,25 +1,18 @@
import { Map as ImmutableMap } from 'immutable'; import { create } from 'mutative';
import { BACKUPS_FETCH_SUCCESS, BACKUPS_CREATE_SUCCESS } from '../actions/backups'; import { BACKUPS_FETCH_SUCCESS, BACKUPS_CREATE_SUCCESS, type BackupsAction } from '../actions/backups';
import type { Backup } from 'pl-api'; import type { Backup } from 'pl-api';
import type { AnyAction } from 'redux';
type State = ImmutableMap<string, Backup>; type State = Record<string, Backup>;
const initialState: State = ImmutableMap(); const initialState: State = {};
const importBackup = (state: State, backup: Backup) => state.set(backup.inserted_at, backup); const backups = (state = initialState, action: BackupsAction) => {
const importBackups = (state: State, backups: Array<Backup>) => state.withMutations(mutable => {
backups.forEach(backup => importBackup(mutable, backup));
});
const backups = (state = initialState, action: AnyAction) => {
switch (action.type) { switch (action.type) {
case BACKUPS_FETCH_SUCCESS: case BACKUPS_FETCH_SUCCESS:
case BACKUPS_CREATE_SUCCESS: case BACKUPS_CREATE_SUCCESS:
return importBackups(state, action.backups); return create(state, (draft) => action.backups.forEach((backup) => draft[backup.inserted_at] = backup));
default: default:
return state; return state;
} }

View file

@ -1,22 +1,17 @@
import { Map as ImmutableMap } from 'immutable'; import { create } from 'mutative';
import { POLLS_IMPORT, type ImporterAction } from 'pl-fe/actions/importer'; import { POLLS_IMPORT, type ImporterAction } from 'pl-fe/actions/importer';
import type { Poll, Status } from 'pl-api'; import type { Poll } from 'pl-api';
type State = ImmutableMap<string, Poll>; type State = Record<string, Poll>;
const importPolls = (state: State, polls: Array<Exclude<Status['poll'], null>>) => const initialState: State = {};
state.withMutations(map =>
polls.forEach(poll => map.set(poll.id, poll)),
);
const initialState: State = ImmutableMap();
const polls = (state: State = initialState, action: ImporterAction): State => { const polls = (state: State = initialState, action: ImporterAction): State => {
switch (action.type) { switch (action.type) {
case POLLS_IMPORT: case POLLS_IMPORT:
return importPolls(state, action.polls); return create(state, (draft) => action.polls.forEach(poll => draft[poll.id] = poll));
default: default:
return state; return state;
} }

View file

@ -135,7 +135,7 @@ const makeGetStatus = () => createSelector(
if (group) return state.entities[Entities.GROUPS]?.store[group] as Group; if (group) return state.entities[Entities.GROUPS]?.store[group] as Group;
return undefined; return undefined;
}, },
(state: RootState, { id }: APIStatus) => state.polls.get(id) || null, (state: RootState, { id }: APIStatus) => state.polls[id] || null,
(_state: RootState, { username }: APIStatus) => username, (_state: RootState, { username }: APIStatus) => username,
getFilters, getFilters,
(state: RootState) => state.me, (state: RootState) => state.me,