diff --git a/src/components/BoardSettings.jsx b/src/components/BoardSettings.jsx new file mode 100644 index 00000000..49ceb085 --- /dev/null +++ b/src/components/BoardSettings.jsx @@ -0,0 +1,161 @@ +import React, { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; +import { StyledModal } from './styled/modals/ModerationModal.styled'; +import useError from "../hooks/useError"; +import useSuccess from "../hooks/useSuccess"; +import useGeneralStore from '../hooks/stores/useGeneralStore'; + +const BoardSettings = ({ subplebbit }) => { + const generateSettingsFromSubplebbit = (subplebbitData) => ({ + address: subplebbitData.address, + apiUrl: subplebbitData.apiUrl, + description: subplebbitData.description, + pubsubTopic: subplebbitData.pubsubTopic, + settings: { + fetchThumbnailUrls: subplebbitData.settings?.fetchThumbnailUrls, + fetchThumbnailUrlsProxyUrl: subplebbitData.settings?.fetchThumbnailUrlsProxyUrl, + }, + roles: subplebbitData.roles, + rules: subplebbitData.rules, + suggested: { + avatarUrl: subplebbitData.suggested?.avatarUrl, + backgroundUrl: subplebbitData.suggested?.backgroundUrl, + bannerUrl: subplebbitData.suggested?.bannerUrl, + language: subplebbitData.suggested?.language, + primaryColor: subplebbitData.suggested?.primaryColor, + secondaryColor: subplebbitData.suggested?.secondaryColor, + }, + title: subplebbitData.title, + }); + + const initialSettings = generateSettingsFromSubplebbit(subplebbit); + const { selectedStyle } = useGeneralStore(state => state); + const [isModalOpen, setIsModalOpen] = useState(false); + const [boardSettingsJson, setBoardSettingsJson] = useState(JSON.stringify(initialSettings, null, 2)); + + const [, setNewErrorMessage] = useError(); + const [, setNewSuccessMessage] = useSuccess(); + + + useEffect(() => { + setBoardSettingsJson(JSON.stringify(generateSettingsFromSubplebbit(subplebbit), null, 2)); + }, [subplebbit]); + + + function validateSettings(updatedSettings, allowedSettings) { + for (let key in updatedSettings) { + if (!allowedSettings.hasOwnProperty(key)) { + throw new Error(`Unexpected setting: ${key}`); + } + + if (typeof updatedSettings[key] === 'object' && updatedSettings[key] !== null) { + validateSettings(updatedSettings[key], allowedSettings[key]); + } + } + } + + // TODO: add API logic + const handleSaveChanges = async () => { + try { + const updatedSettings = JSON.parse(boardSettingsJson); + validateSettings(updatedSettings, initialSettings); + + + + setNewSuccessMessage("Changes saved successfully"); + } catch (error) { + setNewErrorMessage(`Error saving changes: `, error) + } + }; + + + const handleResetChanges = () => { + setBoardSettingsJson(JSON.stringify(initialSettings, null, 2)); + }; + // TODO: add API logic + + + function generateSettingsList(settingsObj, parentKey = '') { + let result = []; + + for (let key in settingsObj) { + if (typeof settingsObj[key] === 'object' && settingsObj[key] !== null) { + const nestedItems = generateSettingsList(settingsObj[key], `${parentKey}${key}.`); + if (nestedItems.length > 1) { + result.push(`${parentKey}${key}: { ${nestedItems.join(', ')} }`); + } else { + result.push(...nestedItems); + } + } else { + result.push(`${parentKey}${key}`); + } + } + + return result; + } + + + const possibleSettingsList = generateSettingsList(initialSettings); + + + const handleCloseModal = () => { + setIsModalOpen(false); + }; + + + return ( + <> + + + + Board Settings + + + + + + + Allowed settings: + + {`{ ${possibleSettingsList.join(', ')} }`} + + + API docs: https://github.com/plebbit/plebbit-js#readme + + setBoardSettingsJson(e.target.value)} + className="board-settings" + /> + + Reset + Save Changes + + + + [ + + { + window.electron && window.electron.isElectron + ? setIsModalOpen(true) + : alert( + 'To edit this board you must be using the plebchan desktop app, which is a plebbit full node that seeds the board automatically.\n\nDownload plebchan here:\n\nhttps://github.com/plebbit/plebchan/releases/latest' + ); + }} + > + Board Settings + + + ] + > + ); +}; + +export default BoardSettings; \ No newline at end of file diff --git a/src/components/styled/modals/ModerationModal.styled.jsx b/src/components/styled/modals/ModerationModal.styled.jsx index 952f143b..6cc9655c 100644 --- a/src/components/styled/modals/ModerationModal.styled.jsx +++ b/src/components/styled/modals/ModerationModal.styled.jsx @@ -22,6 +22,31 @@ export const StyledModal = styled(Modal)` } } + .panel-board { + top: 120px; + width: 700px; + height: auto; + max-height: 80%; + left: calc(50% - 350px); + overflow-y: auto; + position: absolute; + padding: 2px 5px 5px; + font-size: 14px; + box-shadow: 0 0 5px rgba(0, 0, 0, .25); + + @media (max-width: 800px) { + width: 80%; + max-height: 60%; + left: calc(50% - 40%); + } + + @media (max-width: 480px) { + width: 320px; + max-height: 60%; + left: calc(50% - 160px); + } + } + .panel-header { font-size: 16px; font-weight: 700; @@ -124,6 +149,13 @@ export const StyledModal = styled(Modal)` min-height: 50px; } + .board-settings { + width: 83%; + margin-bottom: 20px; + margin-top: 20px; + height: 400px; + } + .save-button { margin: auto; margin-bottom: 10px; @@ -132,11 +164,34 @@ export const StyledModal = styled(Modal)` text-align: center; } + .button-group { + display: flex; + justify-content: center; + gap: 10px; + margin-bottom: 10px; + } + + #save-board-settings, #reset-board-settings { + text-align: center; + } + + .settings-info { + overflow-x: auto; + max-width: 100%; + margin: 10px 10px 0 10px; + font-size: 0.9em; + line-break: anywhere; + } + + .settings-info strong { + margin-bottom: 10px; + } + ${({ selectedStyle }) => { switch (selectedStyle) { case 'Yotsuba': return ` - .panel { + .panel, .panel-board { background-color: #f0e0d6; } @@ -166,7 +221,7 @@ export const StyledModal = styled(Modal)` case 'Yotsuba-B': return ` - .panel { + .panel, .panel-board { background-color: #d6daf0; } @@ -196,7 +251,7 @@ export const StyledModal = styled(Modal)` case 'Futaba': return ` - .panel { + .panel, .panel-board { background-color: #f0e0d6; } @@ -226,7 +281,7 @@ export const StyledModal = styled(Modal)` case 'Burichan': return ` - .panel { + .panel, .panel-board { background-color: #d6daf0; } @@ -256,7 +311,7 @@ export const StyledModal = styled(Modal)` case 'Tomorrow': return ` - .panel { + .panel, .panel-board { background-color: #282a2e; } @@ -296,7 +351,7 @@ export const StyledModal = styled(Modal)` case 'Photon': return ` - .panel { + .panel, .panel-board { background-color: #ddd; } diff --git a/src/components/views/Board.jsx b/src/components/views/Board.jsx index 15e82c62..8aecbdc0 100755 --- a/src/components/views/Board.jsx +++ b/src/components/views/Board.jsx @@ -17,6 +17,7 @@ import EditLabel from '../EditLabel'; import ImageBanner from '../ImageBanner'; import AdminListModal from '../modals/AdminListModal'; import ModerationModal from '../modals/ModerationModal'; +import BoardSettings from '../BoardSettings'; import OfflineIndicator from '../OfflineIndicator'; import PendingLabel from '../PendingLabel'; import Post from '../Post'; @@ -906,6 +907,9 @@ const Board = () => { {subplebbit?.state === "succeeded" ? ( <> + {subplebbit?.roles[account?.author?.address]?.role === "admin" ? ( + + ) : null} [ handleSubscribe()}> diff --git a/src/components/views/Catalog.jsx b/src/components/views/Catalog.jsx index b65d4730..1b7bb878 100755 --- a/src/components/views/Catalog.jsx +++ b/src/components/views/Catalog.jsx @@ -17,6 +17,7 @@ import ImageBanner from '../ImageBanner'; import VerifiedAuthor from '../VerifiedAuthor'; import CreateBoardModal from '../modals/CreateBoardModal'; import ModerationModal from '../modals/ModerationModal'; +import BoardSettings from '../BoardSettings'; import OfflineIndicator from '../OfflineIndicator'; import SettingsModal from '../modals/SettingsModal'; import countLinks from '../../utils/countLinks'; @@ -1263,6 +1264,9 @@ const Catalog = () => { {subplebbit.state === "succeeded" ? ( <> + {subplebbit?.roles[account?.author?.address]?.role === "admin" ? ( + + ) : null} [ handleSubscribe()}>