From 79dcc7355692ce5295aebbf28e00ded9ce4e29fb Mon Sep 17 00:00:00 2001 From: plebeius Date: Thu, 6 Nov 2025 22:20:46 +0100 Subject: [PATCH] refactor(modals): split directory modal into separate create board modal Separate DirectoryModal (for home view placeholders) and CreateBoardModal (for board view create button) into distinct components with their own stores, allowing independent styling and cleaner separation of concerns. --- src/app.tsx | 18 +-- .../create-board-modal.module.css | 109 ++++++++++++++++++ .../create-board-modal/create-board-modal.tsx | 80 +++++++++++++ src/components/create-board-modal/index.ts | 1 + .../directory-modal/directory-modal.tsx | 50 ++++---- src/components/topbar/topbar.tsx | 6 +- src/stores/use-create-board-modal-store.ts | 25 ++++ src/stores/use-directory-modal-store.ts | 9 +- src/views/home/boards-list/boards-list.tsx | 2 +- src/views/home/home.tsx | 11 ++ 10 files changed, 262 insertions(+), 49 deletions(-) create mode 100644 src/components/create-board-modal/create-board-modal.module.css create mode 100644 src/components/create-board-modal/create-board-modal.tsx create mode 100644 src/components/create-board-modal/index.ts create mode 100644 src/stores/use-create-board-modal-store.ts diff --git a/src/app.tsx b/src/app.tsx index d4e83417..5225d275 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -4,7 +4,7 @@ import { useAccountComment } from '@plebbit/plebbit-react-hooks'; import { initSnow, removeSnow } from './lib/snow'; import { isAllView, isModView, isSubscriptionsView } from './lib/utils/view-utils'; import useReplyModalStore from './stores/use-reply-modal-store'; -import useDirectoryModalStore from './stores/use-directory-modal-store'; +import useCreateBoardModalStore from './stores/use-create-board-modal-store'; import useSpecialThemeStore from './stores/use-special-theme-store'; import useIsMobile from './hooks/use-is-mobile'; import useTheme from './hooks/use-theme'; @@ -19,7 +19,7 @@ import Post from './views/post'; import { DesktopBoardButtons, MobileBoardButtons } from './components/board-buttons'; import BoardHeader from './components/board-header'; import ChallengeModal from './components/challenge-modal'; -import DirectoryModal from './components/directory-modal'; +import CreateBoardModal from './components/create-board-modal'; import ReplyModal from './components/reply-modal'; import PostForm from './components/post-form'; import SubplebbitStats from './components/subplebbit-stats'; @@ -34,6 +34,7 @@ const BoardLayout = () => { const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams()); const isInModView = isModView(location.pathname); const pendingPost = useAccountComment({ commentIndex: accountCommentIndex ? parseInt(accountCommentIndex) : undefined }); + const { closeCreateBoardModal } = useCreateBoardModalStore(); // Christmas theme const { isEnabled: isSpecialEnabled } = useSpecialThemeStore(); @@ -46,6 +47,11 @@ const BoardLayout = () => { }; }, [isSpecialEnabled, isMobile]); + // Close create board modal when navigating to a different page + useEffect(() => { + closeCreateBoardModal(); + }, [location.pathname, closeCreateBoardModal]); + // force rerender of post form when navigating between pages, except when opening settings modal in current view const key = location.pathname.endsWith('/settings') ? `${subplebbitAddress}-${location.pathname.replace(/\/settings$/, '')}` @@ -54,6 +60,7 @@ const BoardLayout = () => { return (
+ {isMobile ? (subplebbitAddress || isInAllView || isInModView || isInSubscriptionsView || pendingPost?.subplebbitAddress) && ( @@ -95,20 +102,13 @@ const GlobalLayout = () => { }, [theme]); const { activeCid, threadCid, subplebbitAddress, closeModal, showReplyModal, scrollY } = useReplyModalStore(); - const { closeDirectoryModal } = useDirectoryModalStore(); const location = useLocation(); const isInSettingsView = location.pathname.endsWith('/settings'); - // Close directory modal when navigating to a different page - useEffect(() => { - closeDirectoryModal(); - }, [location.pathname, closeDirectoryModal]); - return ( <> - {activeCid && threadCid && subplebbitAddress && ( { + const { showModal, closeCreateBoardModal } = useCreateBoardModalStore(); + + if (!showModal) { + return null; + } + + const handleBackdropClick = (e: React.MouseEvent) => { + if (e.target === e.currentTarget) { + closeCreateBoardModal(); + } + }; + + return ( +
+
+
+

Create a Board

+
+
+
+

Creating Your Board

+

+ Create a board using the{' '} + + Seedit GUI client + {' '} + or{' '} + + plebbit-cli + + . Build a following: Users can subscribe to your board via the "[Subscribe]" button, which adds it to their top bar. You can gain + subscribers through direct links, word of mouth, or search—no directory assignment or dev approval needed. +

+
+ +
+

Submitting to a Directory

+

+ Open a PR editing{' '} + + 5chan-multisub.json + {' '} + with your board's title, address, and NSFW status. Devs will review and merge if approved. 99% uptime is required. +

+
+ +
+

Future: DAO Voting

+

+ Directory assignments will use gasless pubsub voting—communities vote, highest-voted board wins the slot. Voting pages = discovery: Each + directory's voting page lists all competing boards (even low-voted ones), giving visibility without winning or dev approval. See{' '} + + design draft + + . +

+
+ +
+

Decentralization

+

+ Devs can change directories via commits in the open-source repo. No centralized servers—anyone can fork, modify, and redeploy to their own domain. 5chan is + adminless with no central authority. +

+
+
+
+ +
+
+
+ ); +}; + +export default CreateBoardModal; diff --git a/src/components/create-board-modal/index.ts b/src/components/create-board-modal/index.ts new file mode 100644 index 00000000..ee6571e8 --- /dev/null +++ b/src/components/create-board-modal/index.ts @@ -0,0 +1 @@ +export { default } from './create-board-modal'; diff --git a/src/components/directory-modal/directory-modal.tsx b/src/components/directory-modal/directory-modal.tsx index 2239877f..29b81933 100644 --- a/src/components/directory-modal/directory-modal.tsx +++ b/src/components/directory-modal/directory-modal.tsx @@ -2,7 +2,7 @@ import useDirectoryModalStore from '../../stores/use-directory-modal-store'; import styles from './directory-modal.module.css'; const DirectoryModal = () => { - const { showModal, modalContext, closeDirectoryModal } = useDirectoryModalStore(); + const { showModal, closeDirectoryModal } = useDirectoryModalStore(); if (!showModal) { return null; @@ -14,33 +14,27 @@ const DirectoryModal = () => { } }; - const isPlaceholderContext = modalContext === 'placeholder'; - return (
-

{isPlaceholderContext ? 'Submit a Board to a Directory' : 'Create a Board'}

+

Submit a Board to a Directory

- {isPlaceholderContext && ( -

- The board you clicked on doesn't exist yet, but it can be yours! -

- )} +

+ The board you clicked on doesn't exist yet, but it can be yours! +

- {isPlaceholderContext && ( -
-

Directory vs Regular Board

-

- A "directory board" is assigned to a category on the homepage (like "Anime & Manga" or "Video Games"). Directory assignments - are temporary and handpicked by devs until DAO curation is implemented.{' '} - Anyone can create a board and users can access it anytime using its address—use the search bar, direct links, or the - "[Subscribe]" button to access boards regardless of directory assignment. -

-
- )} +
+

Directory vs Regular Board

+

+ A "directory board" is assigned to a category on the homepage (like "Anime & Manga" or "Video Games"). Directory assignments + are temporary and handpicked by devs until DAO curation is implemented.{' '} + Anyone can create a board and users can access it anytime using its address—use the search bar, direct links, or the + "[Subscribe]" button to access boards regardless of directory assignment. +

+

Creating Your Board

@@ -58,15 +52,13 @@ const DirectoryModal = () => {

- {isPlaceholderContext && ( -
-

Directory Requirements

-

- Boards need 99% uptime (they're P2P nodes), plus be active, well-moderated, and relevant to the category. Devs review and approve/reject based on - these criteria. -

-
- )} +
+

Directory Requirements

+

+ Boards need 99% uptime (they're P2P nodes), plus be active, well-moderated, and relevant to the category. Devs review and approve/reject based on these + criteria. +

+

Submitting to a Directory

diff --git a/src/components/topbar/topbar.tsx b/src/components/topbar/topbar.tsx index 5842ce4d..b5c0596e 100644 --- a/src/components/topbar/topbar.tsx +++ b/src/components/topbar/topbar.tsx @@ -6,7 +6,7 @@ import { useAccount, useAccountComment, useAccountSubplebbits } from '@plebbit/p import { isAllView, isCatalogView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits'; import { TimeFilter } from '../board-buttons'; -import useDirectoryModalStore from '../../stores/use-directory-modal-store'; +import useCreateBoardModalStore from '../../stores/use-create-board-modal-store'; import styles from './topbar.module.css'; import _, { debounce } from 'lodash'; @@ -76,7 +76,7 @@ const TopBarDesktop = () => { const params = useParams(); const isInCatalogView = isCatalogView(location.pathname, params); const [showSearchBar, setShowSearchBar] = useState(false); - const { openDirectoryModal } = useDirectoryModalStore(); + const { openCreateBoardModal } = useCreateBoardModalStore(); const subscriptions = account?.subscriptions; @@ -110,7 +110,7 @@ const TopBarDesktop = () => { )} [ - openDirectoryModal('create-button')} style={{ cursor: 'pointer' }}> + openCreateBoardModal()} style={{ cursor: 'pointer' }}> {t('create_board')} ] diff --git a/src/stores/use-create-board-modal-store.ts b/src/stores/use-create-board-modal-store.ts new file mode 100644 index 00000000..163ee893 --- /dev/null +++ b/src/stores/use-create-board-modal-store.ts @@ -0,0 +1,25 @@ +import { create } from 'zustand'; + +interface CreateBoardModalState { + showModal: boolean; + openCreateBoardModal: () => void; + closeCreateBoardModal: () => void; +} + +const useCreateBoardModalStore = create((set) => ({ + showModal: false, + + openCreateBoardModal: () => { + set({ + showModal: true, + }); + }, + + closeCreateBoardModal: () => { + set({ + showModal: false, + }); + }, +})); + +export default useCreateBoardModalStore; diff --git a/src/stores/use-directory-modal-store.ts b/src/stores/use-directory-modal-store.ts index 3ceed28a..873aae9c 100644 --- a/src/stores/use-directory-modal-store.ts +++ b/src/stores/use-directory-modal-store.ts @@ -1,22 +1,17 @@ import { create } from 'zustand'; -type ModalContext = 'placeholder' | 'create-button'; - interface DirectoryModalState { showModal: boolean; - modalContext: ModalContext; - openDirectoryModal: (context?: ModalContext) => void; + openDirectoryModal: () => void; closeDirectoryModal: () => void; } const useDirectoryModalStore = create((set) => ({ showModal: false, - modalContext: 'placeholder', - openDirectoryModal: (context: ModalContext = 'placeholder') => { + openDirectoryModal: () => { set({ showModal: true, - modalContext: context, }); }, diff --git a/src/views/home/boards-list/boards-list.tsx b/src/views/home/boards-list/boards-list.tsx index 3c3ecb2b..121b9c7a 100644 --- a/src/views/home/boards-list/boards-list.tsx +++ b/src/views/home/boards-list/boards-list.tsx @@ -49,7 +49,7 @@ const BoardsList = ({ multisub }: { multisub: MultisubSubplebbit[] }) => { // Handler for placeholder board links const handlePlaceholderClick = (e: React.MouseEvent) => { e.preventDefault(); - openDirectoryModal('placeholder'); + openDirectoryModal(); }; if (loading) { diff --git a/src/views/home/home.tsx b/src/views/home/home.tsx index 093233f3..219aac6b 100644 --- a/src/views/home/home.tsx +++ b/src/views/home/home.tsx @@ -9,7 +9,9 @@ import PopularThreadsBox from './popular-threads-box'; import BoardsList from './boards-list'; import Version from '../../components/version'; import useDisclaimerModalStore from '../../stores/use-disclaimer-modal-store'; +import useDirectoryModalStore from '../../stores/use-directory-modal-store'; import DisclaimerModal from '../../components/disclaimer-modal'; +import DirectoryModal from '../../components/directory-modal'; import _ from 'lodash'; // https://github.com/plebbit/lists/blob/master/5chan-multisub.json @@ -178,14 +180,23 @@ const Home = () => { const defaultSubplebbits = useDefaultSubplebbits(); const subplebbitAddresses = useDefaultSubplebbitAddresses(); const { subplebbits } = useSubplebbits({ subplebbitAddresses }); + const { closeDirectoryModal } = useDirectoryModalStore(); useEffect(() => { document.title = '5chan'; }, []); + // Close directory modal when navigating away from home + useEffect(() => { + return () => { + closeDirectoryModal(); + }; + }, [closeDirectoryModal]); + return ( <> +