From 90b53d49fdbfe24cdd3535a855c121e8aac62894 Mon Sep 17 00:00:00 2001 From: plebbitor Date: Mon, 20 Mar 2023 17:08:05 +0100 Subject: [PATCH] moved some repeated code --- src/App.js | 88 +++++++++- src/components/CaptchaModal.jsx | 6 +- src/components/CatalogLoader.jsx | 4 +- src/components/PostLoader.jsx | 4 +- src/components/ReplyModal.jsx | 4 +- src/components/SettingsModal.jsx | 4 +- src/components/views/Board.jsx | 200 ++-------------------- src/components/views/Catalog.jsx | 200 ++-------------------- src/components/views/Home.jsx | 4 +- src/components/views/NotFound.jsx | 4 +- src/components/views/Thread.jsx | 205 ++--------------------- src/hooks/useClickForm.js | 32 ++++ src/{useBoardStore.js => useAppStore.js} | 36 ++-- src/utils/handleStyleChange.js | 94 +++++++++++ 14 files changed, 299 insertions(+), 586 deletions(-) create mode 100644 src/hooks/useClickForm.js rename src/{useBoardStore.js => useAppStore.js} (67%) create mode 100644 src/utils/handleStyleChange.js diff --git a/src/App.js b/src/App.js index fdeb09c9..0606f34b 100644 --- a/src/App.js +++ b/src/App.js @@ -1,7 +1,7 @@ import React, { useEffect } from 'react'; -import { Route, Routes } from 'react-router-dom'; +import { Route, Routes, useLocation } from 'react-router-dom'; import { Helmet } from 'react-helmet-async'; -import useBoardStore from './useBoardStore'; +import useAppStore from './useAppStore'; import Home from './components/views/Home'; import Board from './components/views/Board'; import Thread from './components/views/Thread'; @@ -48,8 +48,19 @@ const StyledContainer = styled(ToastContainer)` export default function App() { - const { bodyStyle, setBodyStyle } = useBoardStore(state => state); + const { + bodyStyle, setBodyStyle, + setDefaultSubplebbits, + setIsSettingsOpen, + setSelectedStyle, + setShowPostForm, + setShowPostFormLink, + } = useAppStore(state => state); + + const location = useLocation(); + + // preload images const imageUrls = Array.from({ length: 14 }, (_, index) => `/assets/banners/banner-${index + 1}.jpg`); useEffect(() => { @@ -57,6 +68,77 @@ export default function App() { }, []); + // automatic dark mode without interefering with user's selected style + useEffect(() => { + const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const isDarkMode = darkModeMediaQuery.matches; + + if (isDarkMode) { + setSelectedStyle('Tomorrow'); + setBodyStyle({ + background: '#1d1f21 none', + color: '#c5c8c6', + fontFamily: 'Arial, Helvetica, sans-serif' + }); + localStorage.setItem('selectedStyle', 'Tomorrow'); + } + + const darkModeListener = (e) => { + if (e.matches) { + setSelectedStyle('Tomorrow'); + setBodyStyle({ + background: '#1d1f21 none', + color: '#c5c8c6', + fontFamily: 'Arial, Helvetica, sans-serif' + }); + localStorage.setItem('selectedStyle', 'Tomorrow'); + } + }; + + darkModeMediaQuery.addEventListener('change', darkModeListener); + + return () => { + darkModeMediaQuery.removeEventListener('change', darkModeListener); + }; + }, []); + + // fetch default subplebbits + useEffect(() => { + let didCancel = false; + fetch( + "https://raw.githubusercontent.com/plebbit/temporary-default-subplebbits/master/subplebbits.json", + { cache: "no-cache" } + ) + .then((res) => res.json()) + .then(res => { + if (!didCancel) { + setDefaultSubplebbits(res); + } + }); + return () => { + didCancel = true; + }; + }, []); + + // handle nested routes + useEffect(() => { + const path = location.pathname; + if (path.endsWith('/post')) { + setShowPostFormLink(false); + setShowPostForm(true); + } else { + setShowPostFormLink(true); + setShowPostForm(false); + } + + if (path.endsWith('/settings')) { + setIsSettingsOpen(true); + } else { + setIsSettingsOpen(false); + } + + }, [location.pathname]); + return (
diff --git a/src/components/CaptchaModal.jsx b/src/components/CaptchaModal.jsx index a8f1b46b..99de849e 100644 --- a/src/components/CaptchaModal.jsx +++ b/src/components/CaptchaModal.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import useBoardStore from '../useBoardStore'; +import useAppStore from '../useAppStore'; import Modal from 'react-modal'; import styled from 'styled-components'; import Draggable from 'react-draggable'; @@ -246,8 +246,8 @@ const StyledModal = styled(Modal)` const CaptchaModal = ({ isOpen, closeModal, captchaImage }) => { - const { captchaResponse, setCaptchaResponse } = useBoardStore(state => state); - const selectedStyle = useBoardStore(state => state.selectedStyle); + const { captchaResponse, setCaptchaResponse } = useAppStore(state => state); + const selectedStyle = useAppStore(state => state.selectedStyle); const handleCloseModal = () => { closeModal(); diff --git a/src/components/CatalogLoader.jsx b/src/components/CatalogLoader.jsx index f302866a..6a66d35c 100644 --- a/src/components/CatalogLoader.jsx +++ b/src/components/CatalogLoader.jsx @@ -1,10 +1,10 @@ import React from 'react'; import ContentLoader from 'react-content-loader'; -import useBoardStore from '../useBoardStore'; +import useAppStore from '../useAppStore'; const SingleRectLoader = () => { - const selectedStyle = useBoardStore((state) => state.selectedStyle); + const selectedStyle = useAppStore((state) => state.selectedStyle); const backgroundColor = selectedStyle === 'Tomorrow' ? '#333' : '#f3f3f3'; const foregroundColor = selectedStyle === 'Tomorrow' ? '#555' : '#ecebeb'; diff --git a/src/components/PostLoader.jsx b/src/components/PostLoader.jsx index 1980b19c..fcb70d9b 100644 --- a/src/components/PostLoader.jsx +++ b/src/components/PostLoader.jsx @@ -1,9 +1,9 @@ import React from 'react'; import ContentLoader from 'react-content-loader'; -import useBoardStore from '../useBoardStore'; +import useAppStore from '../useAppStore'; const SinglePostLoader = () => { - const selectedStyle = useBoardStore((state) => state.selectedStyle); + const selectedStyle = useAppStore((state) => state.selectedStyle); const backgroundColor = selectedStyle === 'Tomorrow' ? '#333' : '#f3f3f3'; const foregroundColor = selectedStyle === 'Tomorrow' ? '#555' : '#ecebeb'; diff --git a/src/components/ReplyModal.jsx b/src/components/ReplyModal.jsx index 43f6dc15..cea0bea3 100644 --- a/src/components/ReplyModal.jsx +++ b/src/components/ReplyModal.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import useBoardStore from '../useBoardStore'; +import useAppStore from '../useAppStore'; import Modal from 'react-modal'; import styled from 'styled-components'; import Draggable from 'react-draggable'; @@ -248,7 +248,7 @@ const StyledModal = styled(Modal)` const ReplyModal = ({ isOpen, closeModal }) => { - const selectedStyle = useBoardStore(state => state.selectedStyle); + const selectedStyle = useAppStore(state => state.selectedStyle); const handleCloseModal = () => { closeModal(); diff --git a/src/components/SettingsModal.jsx b/src/components/SettingsModal.jsx index 7f5f0107..4aefe5da 100644 --- a/src/components/SettingsModal.jsx +++ b/src/components/SettingsModal.jsx @@ -2,7 +2,7 @@ import React from "react"; import { Link, useLocation, useNavigate } from "react-router-dom"; import Modal from "react-modal"; import styled from "styled-components"; -import useBoardStore from "../useBoardStore"; +import useAppStore from "../useAppStore"; const StyledModal = styled(Modal)` .panel { @@ -140,7 +140,7 @@ const customOverlayStyles = { }; const SettingsModal = ({ isOpen, closeModal }) => { - const selectedStyle = useBoardStore(state => state.selectedStyle); + const selectedStyle = useAppStore(state => state.selectedStyle); const navigate = useNavigate(); const location = useLocation(); diff --git a/src/components/views/Board.jsx b/src/components/views/Board.jsx index 1373042b..9ec60fac 100644 --- a/src/components/views/Board.jsx +++ b/src/components/views/Board.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, Fragment } from 'react'; -import { Link, useNavigate, useParams, useLocation } from 'react-router-dom'; -import useBoardStore from '../../useBoardStore'; +import { Link, useNavigate, useParams } from 'react-router-dom'; +import useAppStore from '../../useAppStore'; import { Container, NavBar, Header, Break, PostFormLink, PostFormTable, PostForm, TopBar, BoardForm } from './styles/Board.styled'; import CaptchaModal from '../CaptchaModal'; import ImageBanner from '../ImageBanner'; @@ -10,39 +10,35 @@ import SettingsModal from '../SettingsModal'; import { useFeed, useAccountsActions } from '@plebbit/plebbit-react-hooks'; import InfiniteScroll from 'react-infinite-scroller'; import { Tooltip } from 'react-tooltip'; +import handleStyleChange from '../../utils/handleStyleChange'; import onError from '../../utils/onError'; import onSuccess from '../../utils/onSuccess'; import getDate from '../../utils/getDate'; import renderComments from '../../utils/renderComments'; +import useClickForm from '../../hooks/useClickForm'; -const Board = ({ setBodyStyle }) => { - +const Board = () => { const { - selectedTitle, - setSelectedTitle, - selectedAddress, - setSelectedAddress, - setSelectedThread, + captchaResponse, setCaptchaResponse, + defaultSubplebbits, + isSettingsOpen, setIsSettingsOpen, + selectedAddress, setSelectedAddress, selectedStyle, - setSelectedStyle, - captchaResponse, - setCaptchaResponse - } = useBoardStore(state => state); + setSelectedThread, + selectedTitle, setSelectedTitle, + showPostForm, + showPostFormLink + } = useAppStore(state => state); - const [defaultSubplebbits, setDefaultSubplebbits] = useState([]); - const [showPostFormLink, setShowPostFormLink] = useState(true); - const [showPostForm, setShowPostForm] = useState(false); const [name, setName] = useState(''); const [subject, setSubject] = useState(''); const [comment, setComment] = useState(''); const { publishComment } = useAccountsActions(); const [isCaptchaOpen, setIsCaptchaOpen] = useState(false); const [isReplyOpen, setIsReplyOpen] = useState(false); - const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [captchaImage, setCaptchaImage] = useState(''); const navigate = useNavigate(); - const location = useLocation(); const [prevScrollPos, setPrevScrollPos] = useState(0); const [visible, setVisible] = useState(true); const [endIndex, setEndIndex] = useState(2); @@ -50,6 +46,7 @@ const Board = ({ setBodyStyle }) => { const [selectedFeed, setSelectedFeed] = useState(feed); const renderedFeed = selectedFeed.slice(0, endIndex); const { subplebbitAddress } = useParams(); + const handleClickForm = useClickForm(); // temporary title from JSON, gets subplebbitAddress from URL @@ -66,24 +63,6 @@ const Board = ({ setBodyStyle }) => { setSelectedFeed(feed); }, [feed]); - // fetches default subplebbits from JSON - useEffect(() => { - let didCancel = false; - fetch( - "https://raw.githubusercontent.com/plebbit/temporary-default-subplebbits/master/subplebbits.json", - { cache: "no-cache" } - ) - .then((res) => res.json()) - .then(res => { - if (!didCancel) { - setDefaultSubplebbits(res); - } - }); - return () => { - didCancel = true; - }; - }, []); - // mobile navbar scroll effect useEffect(() => { const handleScroll = () => { @@ -103,60 +82,6 @@ const Board = ({ setBodyStyle }) => { setEndIndex(2); }, [selectedAddress]); - // nested routes handling - useEffect(() => { - const path = location.pathname; - - if (path.endsWith('/post')) { - setShowPostFormLink(false); - setShowPostForm(true); - } else { - setShowPostFormLink(true); - setShowPostForm(false); - } - - if (path.endsWith('/settings')) { - setIsSettingsOpen(true); - } else { - setIsSettingsOpen(false); - } - - }, [location.pathname]); - - // automatic dark mode without interefering with user's selected style - useEffect(() => { - const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - const isDarkMode = darkModeMediaQuery.matches; - - if (isDarkMode) { - setSelectedStyle('Tomorrow'); - setBodyStyle({ - background: '#1d1f21 none', - color: '#c5c8c6', - fontFamily: 'Arial, Helvetica, sans-serif' - }); - localStorage.setItem('selectedStyle', 'Tomorrow'); - } - - const darkModeListener = (e) => { - if (e.matches) { - setSelectedStyle('Tomorrow'); - setBodyStyle({ - background: '#1d1f21 none', - color: '#c5c8c6', - fontFamily: 'Arial, Helvetica, sans-serif' - }); - localStorage.setItem('selectedStyle', 'Tomorrow'); - } - }; - - darkModeMediaQuery.addEventListener('change', darkModeListener); - - return () => { - darkModeMediaQuery.removeEventListener('change', darkModeListener); - }; - }, []); - const tryLoadMore = async () => { @@ -260,13 +185,6 @@ const Board = ({ setBodyStyle }) => { }; - const handleClickForm = () => { - setShowPostFormLink(false); - setShowPostForm(true); - navigate(`/${selectedAddress}/post`); - }; - - const handleClickThread = (thread) => { setSelectedThread(thread); }; @@ -323,94 +241,6 @@ const Board = ({ setBodyStyle }) => { } - const handleStyleChange = (event) => { - switch (event.target.value) { - case "Yotsuba": - const yotsubaBodyStyle = { - background: "#ffe url(/assets/fade.png) top repeat-x", - color: "maroon", - fontFamily: "Arial, Helvetica, sans-serif" - }; - setBodyStyle(yotsubaBodyStyle); - setSelectedStyle("Yotsuba"); - localStorage.setItem("selectedStyle", "Yotsuba"); - localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBodyStyle)); - break; - - case "Yotsuba-B": - const yotsubaBBodyStyle = { - background: "#eef2ff url(/assets/fade-blue.png) top center repeat-x", - color: "#000", - fontFamily: "Arial, Helvetica, sans-serif" - }; - setBodyStyle(yotsubaBBodyStyle); - setSelectedStyle("Yotsuba-B"); - localStorage.setItem("selectedStyle", "Yotsuba-B"); - localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBBodyStyle)); - break; - - case "Futaba": - const futabaBodyStyle = { - background: "#ffe", - color: "maroon", - fontFamily: "times new roman, serif" - }; - setBodyStyle(futabaBodyStyle); - setSelectedStyle("Futaba"); - localStorage.setItem("selectedStyle", "Futaba"); - localStorage.setItem("bodyStyle", JSON.stringify(futabaBodyStyle)); - break; - - case "Burichan": - const burichanBodyStyle = { - background: "#eef2ff", - color: "#000", - fontFamily: "times new roman, serif" - }; - setBodyStyle(burichanBodyStyle); - setSelectedStyle("Burichan"); - localStorage.setItem("selectedStyle", "Burichan"); - localStorage.setItem("bodyStyle", JSON.stringify(burichanBodyStyle)); - break; - - case "Tomorrow": - const tomorrowBodyStyle = { - background: "#1d1f21 none", - color: "#c5c8c6", - fontFamily: "Arial, Helvetica, sans-serif" - }; - setBodyStyle(tomorrowBodyStyle); - setSelectedStyle("Tomorrow"); - localStorage.setItem("selectedStyle", "Tomorrow"); - localStorage.setItem("bodyStyle", JSON.stringify(tomorrowBodyStyle)); - break; - - case "Photon": - const photonBodyStyle = { - background: "#eee none", - color: "#333", - fontFamily: "Arial, Helvetica, sans-serif" - }; - setBodyStyle(photonBodyStyle); - setSelectedStyle("Photon"); - localStorage.setItem("selectedStyle", "Photon"); - localStorage.setItem("bodyStyle", JSON.stringify(photonBodyStyle)); - break; - - default: - const defaultBodyStyle = { - background: "#ffe url(/assets/fade.png) top repeat-x", - color: "maroon", - fontFamily: "Arial, Helvetica, sans-serif" - }; - setBodyStyle(defaultBodyStyle); - setSelectedStyle("Yotsuba"); - localStorage.setItem("selectedStyle", "Yotsuba"); - localStorage.setItem("bodyStyle", JSON.stringify(defaultBodyStyle)); - } - } - - return ( diff --git a/src/components/views/Catalog.jsx b/src/components/views/Catalog.jsx index 8c602ab6..d8fbe4f5 100644 --- a/src/components/views/Catalog.jsx +++ b/src/components/views/Catalog.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { Link, useNavigate, useParams, useLocation } from 'react-router-dom'; -import useBoardStore from '../../useBoardStore'; +import { Link, useNavigate, useParams } from 'react-router-dom'; +import useAppStore from '../../useAppStore'; import { Container, NavBar, Header, Break, PostForm, PostFormLink, PostFormTable } from './styles/Board.styled'; import { TopBar } from './styles/Thread.styled'; import { Threads } from './styles/Catalog.styled'; @@ -9,41 +9,38 @@ import ImageBanner from '../ImageBanner'; import CaptchaModal from '../CaptchaModal'; import CatalogLoader from '../CatalogLoader'; import SettingsModal from '../SettingsModal'; +import handleStyleChange from '../../utils/handleStyleChange'; import onError from '../../utils/onError'; import onSuccess from '../../utils/onSuccess'; import InfiniteScroll from 'react-infinite-scroller'; +import useClickForm from '../../hooks/useClickForm'; -const Catalog = ({ setBodyStyle }) => { - +const Catalog = () => { const { - selectedTitle, - setSelectedTitle, - selectedAddress, - setSelectedAddress, - setSelectedThread, + captchaResponse, setCaptchaResponse, + defaultSubplebbits, + isSettingsOpen, setIsSettingsOpen, + selectedAddress, setSelectedAddress, selectedStyle, - setSelectedStyle, - captchaResponse, - setCaptchaResponse - } = useBoardStore(state => state); + setSelectedThread, + selectedTitle, setSelectedTitle, + showPostForm, + showPostFormLink + } = useAppStore(state => state); - const [defaultSubplebbits, setDefaultSubplebbits] = useState([]); - const [showPostFormLink, setShowPostFormLink] = useState(true); - const [showPostForm, setShowPostForm] = useState(false); const [name, setName] = useState(''); const [subject, setSubject] = useState(''); const [comment, setComment] = useState(''); const { publishComment } = useAccountsActions(); const [isCaptchaOpen, setIsCaptchaOpen] = useState(false); - const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [captchaImage, setCaptchaImage] = useState(''); const navigate = useNavigate(); - const location = useLocation(); const [prevScrollPos, setPrevScrollPos] = useState(0); const [visible, setVisible] = useState(true); const { feed, hasMore, loadMore } = useFeed([`${selectedAddress}`], 'new'); const { subplebbitAddress } = useParams(); + const handleClickForm = useClickForm(); @@ -56,24 +53,6 @@ const Catalog = ({ setBodyStyle }) => { }, [subplebbitAddress, setSelectedAddress, setSelectedTitle, defaultSubplebbits]); - useEffect(() => { - let didCancel = false; - fetch( - "https://raw.githubusercontent.com/plebbit/temporary-default-subplebbits/master/subplebbits.json", - { cache: "no-cache" } - ) - .then((res) => res.json()) - .then(res => { - if (!didCancel) { - setDefaultSubplebbits(res); - } - }); - return () => { - didCancel = true; - }; - }, []); - - useEffect(() => { const handleScroll = () => { const currentScrollPos = window.pageYOffset; @@ -87,59 +66,6 @@ const Catalog = ({ setBodyStyle }) => { return () => window.removeEventListener('scroll', handleScroll); }, [prevScrollPos, visible]); - // nested routes handling - useEffect(() => { - const path = location.pathname; - if (path.endsWith('/post')) { - setShowPostFormLink(false); - setShowPostForm(true); - } else { - setShowPostFormLink(true); - setShowPostForm(false); - } - - if (path.endsWith('/settings')) { - setIsSettingsOpen(true); - } else { - setIsSettingsOpen(false); - } - - }, [location.pathname]); - - // automatic dark mode without interefering with user's selected style - useEffect(() => { - const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - const isDarkMode = darkModeMediaQuery.matches; - - if (isDarkMode) { - setSelectedStyle('Tomorrow'); - setBodyStyle({ - background: '#1d1f21 none', - color: '#c5c8c6', - fontFamily: 'Arial, Helvetica, sans-serif' - }); - localStorage.setItem('selectedStyle', 'Tomorrow'); - } - - const darkModeListener = (e) => { - if (e.matches) { - setSelectedStyle('Tomorrow'); - setBodyStyle({ - background: '#1d1f21 none', - color: '#c5c8c6', - fontFamily: 'Arial, Helvetica, sans-serif' - }); - localStorage.setItem('selectedStyle', 'Tomorrow'); - } - }; - - darkModeMediaQuery.addEventListener('change', darkModeListener); - - return () => { - darkModeMediaQuery.removeEventListener('change', darkModeListener); - }; - }, []); - const tryLoadMore = async () => { @@ -225,14 +151,6 @@ const Catalog = ({ setBodyStyle }) => { alert("- Embedding media is optional, posts can be text-only. \n- A CAPTCHA challenge will appear after posting. \n- The CAPTCHA is case-sensitive."); }; - - const handleClickForm = () => { - setShowPostFormLink(false); - setShowPostForm(true); - navigate(`/${selectedAddress}/catalog/post`); - }; - - const handleClickThread = (thread) => { setSelectedThread(thread); } @@ -271,94 +189,6 @@ const Catalog = ({ setBodyStyle }) => { } - const handleStyleChange = (event) => { - switch (event.target.value) { - case "Yotsuba": - const yotsubaBodyStyle = { - background: "#ffe url(/assets/fade.png) top repeat-x", - color: "maroon", - fontFamily: "Arial, Helvetica, sans-serif" - }; - setBodyStyle(yotsubaBodyStyle); - setSelectedStyle("Yotsuba"); - localStorage.setItem("selectedStyle", "Yotsuba"); - localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBodyStyle)); - break; - - case "Yotsuba-B": - const yotsubaBBodyStyle = { - background: "#eef2ff url(/assets/fade-blue.png) top center repeat-x", - color: "#000", - fontFamily: "Arial, Helvetica, sans-serif" - }; - setBodyStyle(yotsubaBBodyStyle); - setSelectedStyle("Yotsuba-B"); - localStorage.setItem("selectedStyle", "Yotsuba-B"); - localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBBodyStyle)); - break; - - case "Futaba": - const futabaBodyStyle = { - background: "#ffe", - color: "maroon", - fontFamily: "times new roman, serif" - }; - setBodyStyle(futabaBodyStyle); - setSelectedStyle("Futaba"); - localStorage.setItem("selectedStyle", "Futaba"); - localStorage.setItem("bodyStyle", JSON.stringify(futabaBodyStyle)); - break; - - case "Burichan": - const burichanBodyStyle = { - background: "#eef2ff", - color: "#000", - fontFamily: "times new roman, serif" - }; - setBodyStyle(burichanBodyStyle); - setSelectedStyle("Burichan"); - localStorage.setItem("selectedStyle", "Burichan"); - localStorage.setItem("bodyStyle", JSON.stringify(burichanBodyStyle)); - break; - - case "Tomorrow": - const tomorrowBodyStyle = { - background: "#1d1f21 none", - color: "#c5c8c6", - fontFamily: "Arial, Helvetica, sans-serif" - }; - setBodyStyle(tomorrowBodyStyle); - setSelectedStyle("Tomorrow"); - localStorage.setItem("selectedStyle", "Tomorrow"); - localStorage.setItem("bodyStyle", JSON.stringify(tomorrowBodyStyle)); - break; - - case "Photon": - const photonBodyStyle = { - background: "#eee none", - color: "#333", - fontFamily: "Arial, Helvetica, sans-serif" - }; - setBodyStyle(photonBodyStyle); - setSelectedStyle("Photon"); - localStorage.setItem("selectedStyle", "Photon"); - localStorage.setItem("bodyStyle", JSON.stringify(photonBodyStyle)); - break; - - default: - const defaultBodyStyle = { - background: "#ffe url(/assets/fade.png) top repeat-x", - color: "maroon", - fontFamily: "Arial, Helvetica, sans-serif" - }; - setBodyStyle(defaultBodyStyle); - setSelectedStyle("Yotsuba"); - localStorage.setItem("selectedStyle", "Yotsuba"); - localStorage.setItem("bodyStyle", JSON.stringify(defaultBodyStyle)); - } - } - - return ( diff --git a/src/components/views/Home.jsx b/src/components/views/Home.jsx index 91dbbd80..5b77e538 100644 --- a/src/components/views/Home.jsx +++ b/src/components/views/Home.jsx @@ -1,12 +1,12 @@ import React, { useState, useEffect } from 'react'; -import useBoardStore from '../../useBoardStore'; +import useAppStore from '../../useAppStore'; import { Link } from 'react-router-dom'; import { Container, Header, Logo, Page, Search, About, AboutTitle, AboutContent, Boards, BoardsTitle, BoardsContent, Footer } from './styles/Home.styled'; const Home = ({ setBodyStyle }) => { const [defaultSubplebbits, setDefaultSubplebbits] = useState([]); - const { setSelectedTitle, setSelectedAddress, setSelectedStyle } = useBoardStore(state => state); + const { setSelectedTitle, setSelectedAddress, setSelectedStyle } = useAppStore(state => state); useEffect(() => { setBodyStyle({ diff --git a/src/components/views/NotFound.jsx b/src/components/views/NotFound.jsx index 36641cb3..67024bb8 100644 --- a/src/components/views/NotFound.jsx +++ b/src/components/views/NotFound.jsx @@ -1,12 +1,12 @@ import React, { useEffect } from 'react'; -import useBoardStore from '../../useBoardStore'; +import useAppStore from '../../useAppStore'; import { Link } from "react-router-dom"; import { Container, Header, Logo, Page, Boards, BoardsTitle } from './styles/Home.styled'; const NotFound = ({ setBodyStyle }) => { - const { setSelectedStyle } = useBoardStore(state => state); + const { setSelectedStyle } = useAppStore(state => state); useEffect(() => { setBodyStyle({ diff --git a/src/components/views/Thread.jsx b/src/components/views/Thread.jsx index 9304096f..9aea0347 100644 --- a/src/components/views/Thread.jsx +++ b/src/components/views/Thread.jsx @@ -1,53 +1,50 @@ import React, { useState, useEffect } from 'react'; -import { Link, useNavigate, useParams, useLocation } from 'react-router-dom'; -import useBoardStore from '../../useBoardStore'; +import { Link, useNavigate, useParams } from 'react-router-dom'; +import useAppStore from '../../useAppStore'; import { Container, NavBar, Header, Break, PostForm, PostFormTable, BoardForm } from './styles/Board.styled'; import { ReplyFormLink, TopBar, BottomBar } from './styles/Thread.styled'; import { useComment, useAccountsActions } from '@plebbit/plebbit-react-hooks'; +import { Tooltip } from 'react-tooltip'; import CaptchaModal from '../CaptchaModal'; import ImageBanner from '../ImageBanner'; import PostLoader from '../PostLoader'; import ReplyModal from '../ReplyModal'; import SettingsModal from '../SettingsModal'; -import { Tooltip } from 'react-tooltip'; +import handleStyleChange from '../../utils/handleStyleChange'; import onError from '../../utils/onError'; import onSuccess from '../../utils/onSuccess'; import getDate from '../../utils/getDate'; import renderThreadComments from '../../utils/renderThreadComments'; +import useClickForm from '../../hooks/useClickForm'; -const Thread = ({ setBodyStyle }) => { - +const Thread = () => { const { - selectedTitle, - setSelectedTitle, - selectedAddress, - setSelectedAddress, - selectedThread, - setSelectedThread, + captchaResponse, setCaptchaResponse, + defaultSubplebbits, + isSettingsOpen, setIsSettingsOpen, + selectedAddress, setSelectedAddress, selectedStyle, - setSelectedStyle, - captchaResponse, - setCaptchaResponse - } = useBoardStore(state => state); + selectedThread, setSelectedThread, + selectedTitle, setSelectedTitle, + showPostForm, + showPostFormLink + } = useAppStore(state => state); - const [defaultSubplebbits, setDefaultSubplebbits] = useState([]); - const [showPostFormLink, setShowPostFormLink] = useState(true); - const [showPostForm, setShowPostForm] = useState(false); + const [name, setName] = useState(''); const [subject, setSubject] = useState(''); const [commentContent, setCommentContent] = useState(''); const { publishComment } = useAccountsActions(); const [isCaptchaOpen, setIsCaptchaOpen] = useState(false); const [isReplyOpen, setIsReplyOpen] = useState(false); - const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [captchaImage, setCaptchaImage] = useState(''); const navigate = useNavigate(); - const location = useLocation(); const [prevScrollPos, setPrevScrollPos] = useState(0); const [visible, setVisible] = useState(true); const comment = useComment(`${selectedThread}`); const { subplebbitAddress, threadCid } = useParams(); + const handleClickForm = useClickForm(); // temporary title from JSON, gets subplebbitAddress and threadCid from URL @@ -59,24 +56,6 @@ const Thread = ({ setBodyStyle }) => { setSelectedTitle(selectedSubplebbit.title); } }, [subplebbitAddress, setSelectedAddress, setSelectedTitle, defaultSubplebbits]); - - // fetches default subplebbits from JSON - useEffect(() => { - let didCancel = false; - fetch( - "https://raw.githubusercontent.com/plebbit/temporary-default-subplebbits/master/subplebbits.json", - { cache: "no-cache" } - ) - .then((res) => res.json()) - .then(res => { - if (!didCancel) { - setDefaultSubplebbits(res); - } - }); - return () => { - didCancel = true; - }; - }, []); // mobile navbar scroll effect useEffect(() => { @@ -91,62 +70,9 @@ const Thread = ({ setBodyStyle }) => { return () => window.removeEventListener('scroll', handleScroll); }, [prevScrollPos, visible]); - - // nested routes handling - useEffect(() => { - const path = location.pathname; - if (path.endsWith('/post')) { - setShowPostFormLink(false); - setShowPostForm(true); - } else { - setShowPostFormLink(true); - setShowPostForm(false); - } - - if (path.endsWith('/settings')) { - setIsSettingsOpen(true); - } else { - setIsSettingsOpen(false); - } - - }, [location.pathname]); - - // automatic dark mode without interefering with user's selected style - useEffect(() => { - const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - const isDarkMode = darkModeMediaQuery.matches; - - if (isDarkMode) { - setSelectedStyle('Tomorrow'); - setBodyStyle({ - background: '#1d1f21 none', - color: '#c5c8c6', - fontFamily: 'Arial, Helvetica, sans-serif' - }); - localStorage.setItem('selectedStyle', 'Tomorrow'); - } - - const darkModeListener = (e) => { - if (e.matches) { - setSelectedStyle('Tomorrow'); - setBodyStyle({ - background: '#1d1f21 none', - color: '#c5c8c6', - fontFamily: 'Arial, Helvetica, sans-serif' - }); - localStorage.setItem('selectedStyle', 'Tomorrow'); - } - }; - - darkModeMediaQuery.addEventListener('change', darkModeListener); - - return () => { - darkModeMediaQuery.removeEventListener('change', darkModeListener); - }; - }, []); - + const onChallengeVerification = (challengeVerification) => { if (challengeVerification.challengeSuccess === true) { onSuccess('challenge success', {publishedCid: challengeVerification.publication.cid}) @@ -227,13 +153,6 @@ const Thread = ({ setBodyStyle }) => { }; - const handleClickForm = () => { - setShowPostFormLink(false); - setShowPostForm(true); - navigate(`/${selectedAddress}/thread/${selectedThread}/post`); - }; - - const handleClickTop = () => { window.scrollTo(0, 0); } @@ -293,94 +212,6 @@ const Thread = ({ setBodyStyle }) => { const handleSettingsOpen = () => { setIsSettingsOpen(true); } - - - const handleStyleChange = (event) => { - switch (event.target.value) { - case "Yotsuba": - const yotsubaBodyStyle = { - background: "#ffe url(/assets/fade.png) top repeat-x", - color: "maroon", - fontFamily: "Arial, Helvetica, sans-serif" - }; - setBodyStyle(yotsubaBodyStyle); - setSelectedStyle("Yotsuba"); - localStorage.setItem("selectedStyle", "Yotsuba"); - localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBodyStyle)); - break; - - case "Yotsuba-B": - const yotsubaBBodyStyle = { - background: "#eef2ff url(/assets/fade-blue.png) top center repeat-x", - color: "#000", - fontFamily: "Arial, Helvetica, sans-serif" - }; - setBodyStyle(yotsubaBBodyStyle); - setSelectedStyle("Yotsuba-B"); - localStorage.setItem("selectedStyle", "Yotsuba-B"); - localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBBodyStyle)); - break; - - case "Futaba": - const futabaBodyStyle = { - background: "#ffe", - color: "maroon", - fontFamily: "times new roman, serif" - }; - setBodyStyle(futabaBodyStyle); - setSelectedStyle("Futaba"); - localStorage.setItem("selectedStyle", "Futaba"); - localStorage.setItem("bodyStyle", JSON.stringify(futabaBodyStyle)); - break; - - case "Burichan": - const burichanBodyStyle = { - background: "#eef2ff", - color: "#000", - fontFamily: "times new roman, serif" - }; - setBodyStyle(burichanBodyStyle); - setSelectedStyle("Burichan"); - localStorage.setItem("selectedStyle", "Burichan"); - localStorage.setItem("bodyStyle", JSON.stringify(burichanBodyStyle)); - break; - - case "Tomorrow": - const tomorrowBodyStyle = { - background: "#1d1f21 none", - color: "#c5c8c6", - fontFamily: "Arial, Helvetica, sans-serif" - }; - setBodyStyle(tomorrowBodyStyle); - setSelectedStyle("Tomorrow"); - localStorage.setItem("selectedStyle", "Tomorrow"); - localStorage.setItem("bodyStyle", JSON.stringify(tomorrowBodyStyle)); - break; - - case "Photon": - const photonBodyStyle = { - background: "#eee none", - color: "#333", - fontFamily: "Arial, Helvetica, sans-serif" - }; - setBodyStyle(photonBodyStyle); - setSelectedStyle("Photon"); - localStorage.setItem("selectedStyle", "Photon"); - localStorage.setItem("bodyStyle", JSON.stringify(photonBodyStyle)); - break; - - default: - const defaultBodyStyle = { - background: "#ffe url(/assets/fade.png) top repeat-x", - color: "maroon", - fontFamily: "Arial, Helvetica, sans-serif" - }; - setBodyStyle(defaultBodyStyle); - setSelectedStyle("Yotsuba"); - localStorage.setItem("selectedStyle", "Yotsuba"); - localStorage.setItem("bodyStyle", JSON.stringify(defaultBodyStyle)); - } - } diff --git a/src/hooks/useClickForm.js b/src/hooks/useClickForm.js new file mode 100644 index 00000000..a69857ca --- /dev/null +++ b/src/hooks/useClickForm.js @@ -0,0 +1,32 @@ +import { useNavigate, useLocation } from "react-router-dom"; +import useAppStore from "../useAppStore"; + +const useClickForm = () => { + const navigate = useNavigate(); + const location = useLocation(); + const { setShowPostForm, setShowPostFormLink } = useAppStore.getState(); + + const handleClickForm = () => { + setShowPostForm(true); + setShowPostFormLink(false); + + const subplebbitAddress = location.pathname.split("/")[1]; + const threadCid = location.pathname.split("/")[3]; + + if (location.pathname === "/") { + navigate("/post"); + } else if (location.pathname.endsWith("/catalog")) { + navigate(`/${subplebbitAddress}/catalog/post`); + } else if (location.pathname.endsWith("/thread")) { + navigate(`/${subplebbitAddress}/thread/post`); + } else if (threadCid) { + navigate(`/${subplebbitAddress}/thread/${threadCid}/post`); + } else { + navigate(`/${subplebbitAddress}/post`); + } + }; + + return handleClickForm; +}; + +export default useClickForm; diff --git a/src/useBoardStore.js b/src/useAppStore.js similarity index 67% rename from src/useBoardStore.js rename to src/useAppStore.js index bd76d9df..7aea5d5a 100644 --- a/src/useBoardStore.js +++ b/src/useAppStore.js @@ -1,6 +1,9 @@ import { create } from 'zustand'; -const useBoardStore = create((set) => ({ +const useAppStore = create((set) => ({ + captchaResponse: '', + setCaptchaResponse: (response) => set({ captchaResponse: response }), + bodyStyle: JSON.parse(localStorage.getItem('bodyStyle')) || { background: '#ffe url(/assets/fade.png) top repeat-x', color: 'maroon', @@ -10,24 +13,35 @@ const useBoardStore = create((set) => ({ localStorage.setItem('bodyStyle', JSON.stringify(bodyStyle)); set(() => ({ bodyStyle })); }, - - selectedTitle: '', - setSelectedTitle: (title) => set({ selectedTitle: title }), + defaultSubplebbits: [], + setDefaultSubplebbits: (subplebbits) => set({ defaultSubplebbits: subplebbits }), + + isSettingsOpen: false, + setIsSettingsOpen: (isOpen) => set({ isSettingsOpen: isOpen }), + selectedAddress: '', setSelectedAddress: (address) => set({ selectedAddress: address }), - - selectedThread: '', - setSelectedThread: (thread) => set({ selectedThread: thread }), - + + selectedStyle: localStorage.getItem('selectedStyle') || 'Yotsuba', setSelectedStyle: (style) => { localStorage.setItem('selectedStyle', style); set({ selectedStyle: style }); }, + + selectedThread: '', + setSelectedThread: (thread) => set({ selectedThread: thread }), + + selectedTitle: '', + setSelectedTitle: (title) => set({ selectedTitle: title }), + + showPostForm: false, + setShowPostForm: (show) => set({ showPostForm: show }), + + showPostFormLink: true, + setShowPostFormLink: (show) => set({ showPostFormLink: show }), - captchaResponse: '', - setCaptchaResponse: (response) => set({ captchaResponse: response }), })); -export default useBoardStore; \ No newline at end of file +export default useAppStore; \ No newline at end of file diff --git a/src/utils/handleStyleChange.js b/src/utils/handleStyleChange.js new file mode 100644 index 00000000..9bd1053f --- /dev/null +++ b/src/utils/handleStyleChange.js @@ -0,0 +1,94 @@ +import useAppStore from "../useAppStore"; + +const handleStyleChange = (event) => { + const { + setBodyStyle, setSelectedStyle + } = useAppStore.getState(); + + switch (event.target.value) { + case "Yotsuba": + const yotsubaBodyStyle = { + background: "#ffe url(/assets/fade.png) top repeat-x", + color: "maroon", + fontFamily: "Arial, Helvetica, sans-serif" + }; + setBodyStyle(yotsubaBodyStyle); + setSelectedStyle("Yotsuba"); + localStorage.setItem("selectedStyle", "Yotsuba"); + localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBodyStyle)); + break; + + case "Yotsuba-B": + const yotsubaBBodyStyle = { + background: "#eef2ff url(/assets/fade-blue.png) top center repeat-x", + color: "#000", + fontFamily: "Arial, Helvetica, sans-serif" + }; + setBodyStyle(yotsubaBBodyStyle); + setSelectedStyle("Yotsuba-B"); + localStorage.setItem("selectedStyle", "Yotsuba-B"); + localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBBodyStyle)); + break; + + case "Futaba": + const futabaBodyStyle = { + background: "#ffe", + color: "maroon", + fontFamily: "times new roman, serif" + }; + setBodyStyle(futabaBodyStyle); + setSelectedStyle("Futaba"); + localStorage.setItem("selectedStyle", "Futaba"); + localStorage.setItem("bodyStyle", JSON.stringify(futabaBodyStyle)); + break; + + case "Burichan": + const burichanBodyStyle = { + background: "#eef2ff", + color: "#000", + fontFamily: "times new roman, serif" + }; + setBodyStyle(burichanBodyStyle); + setSelectedStyle("Burichan"); + localStorage.setItem("selectedStyle", "Burichan"); + localStorage.setItem("bodyStyle", JSON.stringify(burichanBodyStyle)); + break; + + case "Tomorrow": + const tomorrowBodyStyle = { + background: "#1d1f21 none", + color: "#c5c8c6", + fontFamily: "Arial, Helvetica, sans-serif" + }; + setBodyStyle(tomorrowBodyStyle); + setSelectedStyle("Tomorrow"); + localStorage.setItem("selectedStyle", "Tomorrow"); + localStorage.setItem("bodyStyle", JSON.stringify(tomorrowBodyStyle)); + break; + + case "Photon": + const photonBodyStyle = { + background: "#eee none", + color: "#333", + fontFamily: "Arial, Helvetica, sans-serif" + }; + setBodyStyle(photonBodyStyle); + setSelectedStyle("Photon"); + localStorage.setItem("selectedStyle", "Photon"); + localStorage.setItem("bodyStyle", JSON.stringify(photonBodyStyle)); + break; + + default: + const defaultBodyStyle = { + background: "#ffe url(/assets/fade.png) top repeat-x", + color: "maroon", + fontFamily: "Arial, Helvetica, sans-serif" + }; + setBodyStyle(defaultBodyStyle); + setSelectedStyle("Yotsuba"); + localStorage.setItem("selectedStyle", "Yotsuba"); + localStorage.setItem("bodyStyle", JSON.stringify(defaultBodyStyle)); + } +} + +export default handleStyleChange; \ No newline at end of file