From 5a455951b79fd529eb1748aa305d333ce0a19274 Mon Sep 17 00:00:00 2001 From: Ash Keel Date: Mon, 17 May 2021 09:32:57 +0200 Subject: [PATCH] Fix minor UI issues after update --- frontend/src/lib/react-utils.ts | 32 ++++++++++++++++++++-- frontend/src/store/api/reducer.ts | 28 ++++++++++++++++++- frontend/src/ui/pages/loyalty/Goals.tsx | 12 ++++---- frontend/src/ui/pages/loyalty/UserList.tsx | 13 ++------- 4 files changed, 66 insertions(+), 19 deletions(-) diff --git a/frontend/src/lib/react-utils.ts b/frontend/src/lib/react-utils.ts index 90d185c..e2f6be4 100644 --- a/frontend/src/lib/react-utils.ts +++ b/frontend/src/lib/react-utils.ts @@ -1,9 +1,16 @@ import { ActionCreatorWithOptionalPayload, AsyncThunk } from '@reduxjs/toolkit'; import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { KilovoltMessage } from '@strimertul/kilovolt-client'; +import { + KilovoltMessage, + SubscriptionHandler, +} from '@strimertul/kilovolt-client'; import { RootState } from '../store'; -import { APIState } from '../store/api/reducer'; +import apiReducer, { + APIState, + getUserPoints, + LoyaltyStorage, +} from '../store/api/reducer'; export function useModule({ key, @@ -37,6 +44,27 @@ export function useModule({ return [data, setter]; } +export function useUserPoints(): LoyaltyStorage { + const prefix = 'loyalty/points/'; + const client = useSelector((state: RootState) => state.api.client); + const data = useSelector((state: RootState) => state.api.loyalty.users); + const dispatch = useDispatch(); + useEffect(() => { + dispatch(getUserPoints()); + const subscriber: SubscriptionHandler = (newValue, key) => { + const user = key.substring(prefix.length); + const entry = JSON.parse(newValue); + dispatch(apiReducer.actions.loyaltyUserPointsChanged({ user, entry })); + }; + client.subscribePrefix(prefix, subscriber); + return () => { + client.subscribePrefix(prefix, subscriber); + }; + }, []); + return data; +} + export default { useModule, + useUserPoints, }; diff --git a/frontend/src/store/api/reducer.ts b/frontend/src/store/api/reducer.ts index 88bb29a..127ea6d 100644 --- a/frontend/src/store/api/reducer.ts +++ b/frontend/src/store/api/reducer.ts @@ -208,7 +208,6 @@ export const getUserPoints = createAsyncThunk( async (_: void, { getState }) => { const { api } = getState() as { api: APIState }; const keys = await api.client.getKeysByPrefix(loyaltyPointsPrefix); - console.log(keys); const userpoints: LoyaltyStorage = {}; Object.entries(keys).forEach(([k, v]) => { userpoints[k.substr(loyaltyPointsPrefix.length)] = JSON.parse(v); @@ -217,6 +216,25 @@ export const getUserPoints = createAsyncThunk( }, ); +export const setUserPoints = createAsyncThunk( + 'api/setUserPoints', + async ( + { + user, + points, + relative, + }: { user: string; points: number; relative: boolean }, + { getState }, + ) => { + const { api } = getState() as { api: APIState }; + const entry: LoyaltyPointsEntry = { points }; + if (relative) { + entry.points += api.loyalty.users[user].points ?? 0; + } + return api.client.putJSON(loyaltyPointsPrefix + user, entry); + }, +); + export const modules = { moduleConfig: makeModule( moduleConfigKey, @@ -333,6 +351,14 @@ const apiReducer = createSlice({ loyaltyGoalsChanged(state, { payload }: PayloadAction) { state.loyalty.goals = payload; }, + loyaltyUserPointsChanged( + state, + { + payload: { user, entry }, + }: PayloadAction<{ user: string; entry: LoyaltyPointsEntry }>, + ) { + state.loyalty.users[user] = entry; + }, }, extraReducers: (builder) => { builder.addCase(createWSClient.fulfilled, (state, { payload }) => { diff --git a/frontend/src/ui/pages/loyalty/Goals.tsx b/frontend/src/ui/pages/loyalty/Goals.tsx index e062d06..428e814 100644 --- a/frontend/src/ui/pages/loyalty/Goals.tsx +++ b/frontend/src/ui/pages/loyalty/Goals.tsx @@ -141,7 +141,7 @@ function GoalModal({ setID(newID.toLowerCase().replace(/[^a-zA-Z0-9]/gi, '-')); const slug = id || name?.toLowerCase().replace(/[^a-zA-Z0-9]/gi, '-') || ''; - const idExists = goals?.some((reward) => reward.id === slug) ?? false; + const idExists = goals?.some((goal) => goal.id === slug) ?? false; const idInvalid = slug !== initialData?.id && idExists; const validForm = idInvalid === false && name !== '' && total >= 0; @@ -175,7 +175,7 @@ function GoalModal({ >
- +
@@ -183,20 +183,20 @@ function GoalModal({ setIDex(ev.target.value)} />

{idInvalid ? (

- There is already a reward with this ID! Please choose a - different one. + There is already a goal with this ID! Please choose a different + one.

) : (

Choose a simple name that can be referenced by other software. - It will be auto-generated from the reward name if you leave it + It will be auto-generated from the goal name if you leave it blank.

)} diff --git a/frontend/src/ui/pages/loyalty/UserList.tsx b/frontend/src/ui/pages/loyalty/UserList.tsx index cf993ef..fc2e20c 100644 --- a/frontend/src/ui/pages/loyalty/UserList.tsx +++ b/frontend/src/ui/pages/loyalty/UserList.tsx @@ -1,9 +1,7 @@ -import React, { useEffect, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import React, { useState } from 'react'; import { RouteComponentProps } from '@reach/router'; import PageList from '../../components/PageList'; -import { RootState } from '../../../store'; -import { getUserPoints } from '../../../store/api/reducer'; +import { useUserPoints } from '../../../lib/react-utils'; interface SortingOrder { key: 'user' | 'points'; @@ -13,17 +11,12 @@ export default function LoyaltyUserListPage( // eslint-disable-next-line @typescript-eslint/no-unused-vars props: RouteComponentProps, ): React.ReactElement { - const users = useSelector((state: RootState) => state.api.loyalty.users); - const dispatch = useDispatch(); + const users = useUserPoints(); const [sorting, setSorting] = useState({ key: 'points', order: 'desc', }); - useEffect(() => { - dispatch(getUserPoints()); - }, []); - const [entriesPerPage, setEntriesPerPage] = useState(15); const [page, setPage] = useState(0); const [usernameFilter, setUsernameFilter] = useState('');