Skip to content

Commit

Permalink
refactor: split useStore into several small hooks based on zustand st…
Browse files Browse the repository at this point in the history
…ores
  • Loading branch information
felixmosh committed Aug 9, 2023
1 parent 98aad9f commit 76cd2b8
Show file tree
Hide file tree
Showing 22 changed files with 635 additions and 807 deletions.
4 changes: 2 additions & 2 deletions packages/api/src/handlers/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ async function getJobState(
_req: BullBoardRequest,
job: QueueJob
): Promise<ControllerHandlerReturnType> {
const state = await job.getState();
const status = await job.getState();

return {
status: 200,
body: {
job,
state,
status,
},
};
}
Expand Down
7 changes: 6 additions & 1 deletion packages/api/typings/responses.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { AppQueue } from './app';
import { AppJob, AppQueue, Status } from './app';

export interface GetQueuesResponse {
queues: AppQueue[];
}

export interface GetJobResponse {
job: AppJob;
status: Status;
}
8 changes: 4 additions & 4 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@
"@babel/preset-typescript": "^7.13.0",
"@babel/runtime": "^7.17.9",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
"@radix-ui/react-alert-dialog": "^1.0.0",
"@radix-ui/react-dialog": "^1.0.0",
"@radix-ui/react-dropdown-menu": "^1.0.0",
"@radix-ui/react-switch": "^1.0.0",
"@radix-ui/react-alert-dialog": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-switch": "^1.0.3",
"@types/react": "^17.0.14",
"@types/react-dom": "^17.0.14",
"@types/react-paginate": "^7.1.1",
Expand Down
63 changes: 22 additions & 41 deletions packages/ui/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Suspense } from 'react';
import React, { Suspense, useEffect } from 'react';
import { Route, Switch } from 'react-router-dom';
import { ToastContainer } from 'react-toastify';
import { ConfirmModal } from './components/ConfirmModal/ConfirmModal';
Expand All @@ -8,8 +8,9 @@ import { Loader } from './components/Loader/Loader';
import { Menu } from './components/Menu/Menu';
import { Title } from './components/Title/Title';
import { useActiveQueue } from './hooks/useActiveQueue';
import { useConfirm } from './hooks/useConfirm';
import { useQueues } from './hooks/useQueues';
import { useScrollTopOnNav } from './hooks/useScrollTopOnNav';
import { useStore } from './hooks/useStore';

const JobPageLazy = React.lazy(() =>
import('./pages/JobPage/JobPage').then(({ JobPage }) => ({ default: JobPage }))
Expand All @@ -27,8 +28,13 @@ const OverviewPageLazy = React.lazy(() =>

export const App = () => {
useScrollTopOnNav();
const { state, actions, selectedStatuses, confirmProps } = useStore();
const activeQueue = useActiveQueue(state.data);
const { queues, actions: queueActions } = useQueues();
const activeQueue = useActiveQueue({ queues });
const { confirmProps } = useConfirm();

useEffect(() => {
queueActions.updateQueues();
}, []);

return (
<>
Expand All @@ -38,46 +44,21 @@ export const App = () => {
</Header>
<main>
<div>
{state.loading ? (
<Loader />
) : (
<>
<Suspense fallback={<Loader />}>
<Switch>
<Route
path="/queue/:name/:jobId"
render={() => (
<JobPageLazy
queue={activeQueue || null}
actions={actions}
selectedStatus={selectedStatuses}
/>
)}
/>
<Route
path="/queue/:name"
render={() => (
<QueuePageLazy
queue={activeQueue || null}
actions={actions}
selectedStatus={selectedStatuses}
/>
)}
/>
<Suspense fallback={<Loader />}>
<Switch>
<Route
path="/queue/:name/:jobId"
render={() => <JobPageLazy queue={activeQueue || null} />}
/>
<Route path="/queue/:name" render={() => <QueuePageLazy />} />

<Route
path="/"
exact
render={() => <OverviewPageLazy queues={state.data?.queues} />}
/>
</Switch>
</Suspense>
<ConfirmModal {...confirmProps} />
</>
)}
<Route path="/" exact render={() => <OverviewPageLazy />} />
</Switch>
</Suspense>
<ConfirmModal {...confirmProps} />
</div>
</main>
<Menu queues={state.data?.queues} selectedStatuses={selectedStatuses} />
<Menu queues={queues} />
<ToastContainer />
</>
);
Expand Down
16 changes: 7 additions & 9 deletions packages/ui/src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,14 @@ import cn from 'clsx';
import React, { useState } from 'react';
import { NavLink } from 'react-router-dom';
import { STATUS_LIST } from '../../constants/status-list';
import { Store } from '../../hooks/useStore';
import { useSelectedStatuses } from '../../hooks/useSelectedStatuses';
import { SearchIcon } from '../Icons/Search';
import s from './Menu.module.css';

export const Menu = ({
queues,
selectedStatuses,
}: {
queues: AppQueue[] | undefined;
selectedStatuses: Store['selectedStatuses'];
}) => {
export const Menu = ({ queues }: { queues: AppQueue[] | null }) => {
const selectedStatuses = useSelectedStatuses();
const [searchTerm, setSearchTerm] = useState('');

return (
<aside className={s.aside}>
<div className={s.secondary}>QUEUES</div>
Expand All @@ -36,7 +32,9 @@ export const Menu = ({
{!!queues && (
<ul className={s.menu}>
{queues
.filter(({ name }) => name?.toLowerCase().includes(searchTerm?.toLowerCase()))
.filter(({ name }) =>
name?.toLowerCase().includes(searchTerm?.toLowerCase() as string)
)
.map(({ name: queueName, isPaused }) => (
<li key={queueName}>
<NavLink
Expand Down
24 changes: 12 additions & 12 deletions packages/ui/src/components/QueueActions/QueueActions.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { STATUSES } from '@bull-board/api/src/constants/statuses';
import { AppQueue, JobCleanStatus, JobRetryStatus, Status } from '@bull-board/api/typings/app';
import React from 'react';
import { Store } from '../../hooks/useStore';
import { QueueActions as QueueActionsType } from '../../../typings/app';
import { Button } from '../Button/Button';
import { PromoteIcon } from '../Icons/Promote';
import { RetryIcon } from '../Icons/Retry';
import { TrashIcon } from '../Icons/Trash';
import { Button } from '../Button/Button';
import s from './QueueActions.module.css';
import { PromoteIcon } from '../Icons/Promote';

interface QueueActionProps {
queue: AppQueue;
actions: Store['actions'];
actions: QueueActionsType;
status: Status;
allowRetries: boolean;
}
Expand Down Expand Up @@ -46,14 +46,6 @@ export const QueueActions = ({ status, actions, queue, allowRetries }: QueueActi
</Button>
</li>
)}
{isCleanAllStatus(status) && (
<li>
<Button onClick={actions.cleanAll(queue.name, status)} className={s.button}>
<TrashIcon />
Clean all
</Button>
</li>
)}
{isPromoteAllStatus(status) && (
<li>
<Button onClick={actions.promoteAll(queue.name)} className={s.button}>
Expand All @@ -62,6 +54,14 @@ export const QueueActions = ({ status, actions, queue, allowRetries }: QueueActi
</Button>
</li>
)}
{isCleanAllStatus(status) && (
<li>
<Button onClick={actions.cleanAll(queue.name, status)} className={s.button}>
<TrashIcon />
Clean all
</Button>
</li>
)}
</ul>
);
};
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { AppQueue } from '@bull-board/api/typings/app';
import { Item, Portal, Root, Trigger } from '@radix-ui/react-dropdown-menu';
import React from 'react';
import { Store } from '../../hooks/useStore';
import { QueueActions } from '../../../typings/app';
import { Button } from '../Button/Button';
import { DropdownContent } from '../DropdownContent/DropdownContent';
import { EllipsisVerticalIcon } from '../Icons/EllipsisVertical';
import { PauseIcon } from '../Icons/Pause';
import { PlayIcon } from '../Icons/Play';
import { TrashIcon } from '../Icons/Trash';
import { Button } from '../Button/Button';
import s from './QueueDropdownActions.module.css';

export const QueueDropdownActions = ({
queue,
actions,
}: {
queue: AppQueue;
actions: Store['actions'];
actions: QueueActions;
}) => (
<Root>
<Trigger asChild>
Expand Down
9 changes: 4 additions & 5 deletions packages/ui/src/components/StatusMenu/StatusMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { STATUSES } from '@bull-board/api/src/constants/statuses';
import { AppQueue } from '@bull-board/api/typings/app';
import React from 'react';
import { NavLink, useRouteMatch } from 'react-router-dom';
import s from './StatusMenu.module.css';
import { AppQueue } from '@bull-board/api/typings/app';
import { STATUS_LIST } from '../../constants/status-list';
import { STATUSES } from '@bull-board/api/src/constants/statuses';
import { Store } from '../../hooks/useStore';
import { QueueDropdownActions } from '../QueueDropdownActions/QueueDropdownActions';
import s from './StatusMenu.module.css';

export const StatusMenu = ({ queue, actions }: { queue: AppQueue; actions: Store['actions'] }) => {
export const StatusMenu = ({ queue, actions }: { queue: AppQueue; actions: any }) => {
const { url } = useRouteMatch();

return (
Expand Down
14 changes: 14 additions & 0 deletions packages/ui/src/hooks/useActiveJobId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { matchPath } from 'react-router';
import { useLocation } from 'react-router-dom';

export function useActiveJobId(): string {
const { pathname } = useLocation();

const match = matchPath<{ name: string; jobId: string }>(pathname, {
path: ['/queue/:name/:jobId'],
exact: false,
strict: false,
});

return decodeURIComponent(match?.params.jobId || '');
}
8 changes: 4 additions & 4 deletions packages/ui/src/hooks/useActiveQueue.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { AppQueue } from '@bull-board/api/dist/typings/app';
import { useActiveQueueName } from './useActiveQueueName';
import { Store } from './useStore';
import { QueuesState } from './useQueues';

export function useActiveQueue(data: Store['state']['data']): AppQueue | null {
export function useActiveQueue(data: Pick<QueuesState, 'queues'>): AppQueue | null {
const activeQueueName = useActiveQueueName();

if (!data) {
if (!data.queues) {
return null;
}

const activeQueue = data.queues?.find((q) => q.name === activeQueueName);
const activeQueue = data.queues.find((q) => q.name === activeQueueName);

return activeQueue || null;
}
47 changes: 26 additions & 21 deletions packages/ui/src/hooks/useConfirm.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,50 @@
import { useState } from 'react';
import { create } from 'zustand';
import { ConfirmProps } from '../components/ConfirmModal/ConfirmModal';

interface ConfirmState {
promise: { resolve: (value: unknown) => void; reject: () => void } | null;
opts: { title?: string; description?: string };
opts: { title?: string; description?: string } | null;
setState(state: Omit<ConfirmState, 'setState'>): void;
}

export interface ConfirmApi {
confirmProps: ConfirmProps;
openConfirm: (opts?: ConfirmState['opts']) => Promise<unknown>;
}

export function useConfirm(): ConfirmApi {
const [confirmData, setConfirmData] = useState<ConfirmState | null>(null);
const useConfirmStore = create<ConfirmState>((set) => ({
opts: null,
promise: null,
setState: (state) => set(() => ({ ...state })),
}));

function openConfirm(opts: ConfirmState['opts'] = {}) {
return new Promise((resolve, reject) => {
setConfirmData({ promise: { resolve, reject }, opts });
});
}
export function useConfirm(): ConfirmApi {
const { promise, opts, setState } = useConfirmStore((state) => state);

return {
confirmProps: {
open: !!confirmData?.promise,
title: confirmData?.opts.title || 'Are you sure?',
description: confirmData?.opts.description || '',
onCancel: () => {
setConfirmData({
opts: { title: confirmData?.opts.title, description: confirmData?.opts.description },
open: !!promise,
title: opts?.title || 'Are you sure?',
description: opts?.description || '',
onCancel: function onCancel() {
setState({
opts: { title: opts?.title, description: opts?.description },
promise: null,
});
confirmData?.promise?.reject();
promise?.reject();
},
onConfirm: () => {
setConfirmData({
opts: { title: confirmData?.opts.title, description: confirmData?.opts.description },
onConfirm: function onConfirm() {
setState({
opts: { title: opts?.title, description: opts?.description },
promise: null,
});
confirmData?.promise?.resolve(undefined);
promise?.resolve(undefined);
},
},
openConfirm,
openConfirm: function openConfirm(opts: ConfirmState['opts'] = {}) {
return new Promise((resolve, reject) => {
setState({ promise: { resolve, reject }, opts });
});
},
};
}
Loading

0 comments on commit 76cd2b8

Please sign in to comment.