Skip to content

Commit

Permalink
✨ Refactor event list state into a reducer
Browse files Browse the repository at this point in the history
  • Loading branch information
foysalit committed Feb 2, 2024
1 parent da9e7a7 commit 0a21335
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 77 deletions.
57 changes: 42 additions & 15 deletions components/mod-event/EventList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ export const ModEventList = (
) => {
const {
types,
setTypes,
includeAllUserRecords,
setIncludeAllUserRecords,
modEvents,
fetchMoreModEvents,
hasMoreModEvents,
Expand All @@ -76,15 +74,12 @@ export const ModEventList = (
toggleCommentFilter,
setCommentFilterKeyword,
createdBy,
setCreatedBy,
subject,
setSubject,
oldestFirst,
setOldestFirst,
createdAfter,
setCreatedAfter,
createdBefore,
setCreatedBefore,
changeListFilter,
resetListFilters
} = useModEventList(props)

const [showFiltersPanel, setShowFiltersPanel] = useState(false)
Expand All @@ -100,7 +95,11 @@ export const ModEventList = (
{...{
subjectTitle,
includeAllUserRecords,
setIncludeAllUserRecords,
setIncludeAllUserRecords: (value) =>
changeListFilter({
field: 'includeAllUserRecords',
value,
}),
isShowingEventsByCreator,
}}
/>
Expand All @@ -126,7 +125,9 @@ export const ModEventList = (
<div className="mr-4">
<TypeFilterCheckbox
selectedTypes={types}
setSelectedTypes={setTypes}
setSelectedTypes={(value) =>
changeListFilter({ field: 'types', value })
}
/>
</div>
<div>
Expand Down Expand Up @@ -188,7 +189,12 @@ export const ModEventList = (
className="block w-full"
disabled={!!props.createdBy}
value={createdBy || ''}
onChange={(ev) => setCreatedBy(ev.target.value)}
onChange={(ev) =>
changeListFilter({
field: 'createdBy',
value: ev.target.value,
})
}
autoComplete="off"
/>
</FormLabel>
Expand All @@ -206,7 +212,12 @@ export const ModEventList = (
placeholder="DID or AT-URI"
className="block w-full"
value={subject || ''}
onChange={(ev) => setSubject(ev.target.value)}
onChange={(ev) =>
changeListFilter({
field: 'subject',
value: ev.target.value,
})
}
autoComplete="off"
/>
</FormLabel>
Expand All @@ -222,7 +233,12 @@ export const ModEventList = (
name="createdAfter"
className="block w-full"
value={createdAfter}
onChange={(ev) => setCreatedAfter(ev.target.value)}
onChange={(ev) =>
changeListFilter({
field: 'createdAfter',
value: ev.target.value,
})
}
autoComplete="off"
min={FIRST_EVENT_TIMESTAMP}
max={new Date().toISOString().split('.')[0]}
Expand All @@ -240,7 +256,12 @@ export const ModEventList = (
name="createdBefore"
className="block w-full"
value={createdBefore}
onChange={(ev) => setCreatedBefore(ev.target.value)}
onChange={(ev) =>
changeListFilter({
field: 'createdBefore',
value: ev.target.value,
})
}
autoComplete="off"
min={FIRST_EVENT_TIMESTAMP}
max={new Date().toISOString().split('.')[0]}
Expand All @@ -256,7 +277,9 @@ export const ModEventList = (
name="sortDirection"
className="flex items-center"
checked={oldestFirst}
onChange={() => setOldestFirst((current) => !current)}
onChange={() =>
changeListFilter({ field: 'oldestFirst', value: !oldestFirst })
}
label="Show oldest events first (default: newest first)"
/>
</div>
Expand All @@ -269,7 +292,11 @@ export const ModEventList = (
No moderation events found.
{!!types.length && (
<p className="text-xs italic pt-2">
<a className="underline" href="#" onClick={() => setTypes([])}>
<a
className="underline"
href="#"
onClick={() => resetListFilters()}
>
Clear all filters
</a>{' '}
to see all events
Expand Down
155 changes: 93 additions & 62 deletions components/mod-event/useModEventList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useInfiniteQuery } from '@tanstack/react-query'
import client from '@/lib/client'
import { useContext, useEffect, useState } from 'react'
import { useContext, useEffect, useReducer, useState } from 'react'
import { AuthContext } from '@/shell/AuthContext'
import { ComAtprotoAdminQueryModerationEvents } from '@atproto/api'
import { MOD_EVENT_TITLES } from './constants'
Expand All @@ -18,58 +18,95 @@ type CommentFilter = {

export const FIRST_EVENT_TIMESTAMP = '2022-11-01T00:00'
const allTypes = Object.keys(MOD_EVENT_TITLES)
const initialListState = {
types: allTypes,
includeAllUserRecords: false,
commentFilter: { enabled: false, keyword: '' },
createdBy: undefined,
subject: undefined,
oldestFirst: false,
createdBefore: new Date().toISOString().split('.')[0],
createdAfter: FIRST_EVENT_TIMESTAMP,
}

// The 2 fields need overriding because in the initialState, they are set as undefined so the alternative string type is not accepted without override
type EventListState = Omit<typeof initialListState, 'subject' | 'createdBy'> & {
subject?: string
createdBy?: string
}

type EventListFilterPayload =
| { field: 'types'; value: string[] }
| { field: 'includeAllUserRecords'; value: boolean }
| { field: 'commentFilter'; value: CommentFilter }
| { field: 'createdBy'; value: string | undefined }
| { field: 'subject'; value: string | undefined }
| { field: 'oldestFirst'; value: boolean }
| { field: 'createdBefore'; value: string }
| { field: 'createdAfter'; value: string }

type EventListAction =
| {
type: 'SET_FILTER'
payload: EventListFilterPayload
}
| {
type: 'RESET'
}

const eventListReducer = (state: EventListState, action: EventListAction) => {
switch (action.type) {
case 'SET_FILTER':
return { ...state, [action.payload.field]: action.payload.value }
case 'RESET':
return initialListState
default:
return state
}
}

export const useModEventList = (
props: { subject?: string; createdBy?: string } & ModEventListQueryOptions,
) => {
const { isLoggedIn } = useContext(AuthContext)
const [createdAfter, setCreatedAfter] = useState<string>(
FIRST_EVENT_TIMESTAMP,
)
const [createdBefore, setCreatedBefore] = useState<string>(
new Date().toISOString().split('.')[0],
)
const [subject, setSubject] = useState<string | undefined>(props.subject)
const [createdBy, setCreatedBy] = useState<string | undefined>(
props.createdBy,
)
const [types, setTypes] = useState<string[]>(allTypes)
const [oldestFirst, setOldestFirst] = useState<boolean>(false)
const [commentFilter, setCommentFilter] = useState<CommentFilter>({
enabled: false,
keyword: '',
})
const [includeAllUserRecords, setIncludeAllUserRecords] =
useState<boolean>(false)
const [listState, dispatch] = useReducer(eventListReducer, initialListState)

const setCommentFilter = (value: CommentFilter) => {
dispatch({ type: 'SET_FILTER', payload: { field: 'commentFilter', value } })
}

useEffect(() => {
if (props.subject !== subject) {
setSubject(props.subject)
if (props.subject !== listState.subject) {
dispatch({
type: 'SET_FILTER',
payload: { field: 'subject', value: props.subject },
})
}
}, [props.subject])

useEffect(() => {
if (props.createdBy !== createdBy) {
setCreatedBy(props.createdBy)
if (props.createdBy !== listState.createdBy) {
dispatch({
type: 'SET_FILTER',
payload: { field: 'createdBy', value: props.createdBy },
})
}
}, [props.createdBy])

const results = useInfiniteQuery({
enabled: isLoggedIn,
queryKey: [
'modEventList',
{
createdBy,
subject,
queryKey: ['modEventList', { listState }],
queryFn: async ({ pageParam }) => {
const {
types,
includeAllUserRecords,
commentFilter,
createdBy,
subject,
oldestFirst,
createdAfter,
createdBefore,
},
],
queryFn: async ({ pageParam }) => {
createdAfter,
} = listState
const queryParams: ComAtprotoAdminQueryModerationEvents.QueryParams = {
cursor: pageParam,
includeAllUserRecords,
Expand All @@ -93,7 +130,7 @@ export const useModEventList = (

const filterTypes = types.filter(Boolean)
if (filterTypes.length < allTypes.length && filterTypes.length > 0) {
queryParams.types = allTypes
queryParams.types = filterTypes
}

if (oldestFirst) {
Expand All @@ -115,47 +152,41 @@ export const useModEventList = (
})

const hasFilter =
(types.length > 0 &&
types.length !== Object.keys(MOD_EVENT_TITLES).length) ||
includeAllUserRecords ||
commentFilter.enabled ||
createdBy ||
subject ||
oldestFirst
(listState.types.length > 0 &&
listState.types.length !== allTypes.length) ||
listState.includeAllUserRecords ||
listState.commentFilter.enabled ||
listState.createdBy ||
listState.subject ||
listState.oldestFirst

return {
types,
setTypes,
includeAllUserRecords,
setIncludeAllUserRecords,
// Data from react-query
modEvents: results.data?.pages.map((page) => page.events).flat() || [],
fetchMoreModEvents: results.fetchNextPage,
hasMoreModEvents: results.hasNextPage,
refetchModEvents: results.refetch,
isInitialLoadingModEvents: results.isInitialLoading,
hasFilter,
commentFilter,

// Helper functions to mutate state
toggleCommentFilter: () => {
setCommentFilter((prev) => {
if (prev.enabled) {
return { enabled: false, keyword: '' }
}
return { enabled: true, keyword: '' }
})
if (listState.commentFilter.enabled) {
return setCommentFilter({ enabled: false, keyword: '' })
}
return setCommentFilter({ enabled: true, keyword: '' })
},
setCommentFilterKeyword: (keyword: string) => {
setCommentFilter({ enabled: true, keyword })
},
createdBy,
setCreatedBy,
subject,
setSubject,
setOldestFirst,
oldestFirst,
createdBefore,
setCreatedBefore,
createdAfter,
setCreatedAfter,
changeListFilter: (payload: EventListFilterPayload) =>
dispatch({ type: 'SET_FILTER', payload: payload }),
resetListFilters: () => dispatch({ type: 'RESET' }),

// State data
...listState,

// Derived data from state
hasFilter,
}
}

Expand Down

0 comments on commit 0a21335

Please sign in to comment.