;
+ resolve: () => void;
+}
+
+let modals: ModalData[] = [];
+let listeners: (() => void)[] = [];
+
+const emitChange = () => {
+ listeners.forEach((listener) => listener());
+};
+
+const SERVER_SNAPSHOT: ModalData[] = [];
+
+export const modalStore = {
+ subscribe: (listener: () => void) => {
+ listeners = [...listeners, listener];
+ return () => {
+ listeners = listeners.filter((l) => l !== listener);
+ };
+ },
+ getSnapshot: () => modals,
+ getServerSnapshot: () => SERVER_SNAPSHOT,
+ addModal: (modal: ModalData) => {
+ modals = [...modals, modal];
+ emitChange();
+ },
+ removeModal: (id: string) => {
+ modals = modals.filter((m) => m.id !== id);
+ emitChange();
+ },
+ removeByScope: (scopeId: string) => {
+ modals = modals.filter((m) => m.scopeId !== scopeId);
+ emitChange();
+ },
+};
+
+export type { ModalData };
diff --git a/web-app/app/shared/components/modal/useModal.ts b/web-app/app/shared/components/modal/useModal.ts
new file mode 100644
index 0000000..d637246
--- /dev/null
+++ b/web-app/app/shared/components/modal/useModal.ts
@@ -0,0 +1,50 @@
+import type { ComponentType } from 'react';
+import { useCallback, useEffect, useId } from 'react';
+
+import { modalStore } from './store';
+
+interface ModalControlProps {
+ isOpen?: boolean;
+ onOpenChange?: (isOpen: boolean) => void;
+}
+
+export const useModal = () => {
+ const scopeId = useId();
+
+ useEffect(() => {
+ return () => {
+ modalStore
+ .getSnapshot()
+ .filter((m) => m.scopeId === scopeId)
+ .forEach((m) => m.resolve());
+ modalStore.removeByScope(scopeId);
+ };
+ }, [scopeId]);
+
+ const show = useCallback(
+ (
+ Component: ComponentType
,
+ props?: Omit
+ ): Promise =>
+ new Promise((resolve) => {
+ modalStore.addModal({
+ id: crypto.randomUUID(),
+ scopeId,
+ Component,
+ props: props ?? {},
+ resolve,
+ });
+ }),
+ [scopeId]
+ );
+
+ const hide = useCallback(() => {
+ modalStore
+ .getSnapshot()
+ .filter((m) => m.scopeId === scopeId)
+ .forEach((m) => m.resolve());
+ modalStore.removeByScope(scopeId);
+ }, [scopeId]);
+
+ return { show, hide };
+};
diff --git a/web-app/app/shared/components/pagination/Pagination.tsx b/web-app/app/shared/components/pagination/Pagination.tsx
index 294555f..1037bd7 100644
--- a/web-app/app/shared/components/pagination/Pagination.tsx
+++ b/web-app/app/shared/components/pagination/Pagination.tsx
@@ -49,7 +49,7 @@ export const Pagination = ({ totalPages, currentPage, pageCount = 10, onPageChan
const noNext = start + pageCount >= totalPages;
return (
-