import React, { useEffect, useState } from 'react'; import { Route, Routes, useLocation } from 'react-router-dom'; import { Helmet } from 'react-helmet-async'; import 'react-tooltip/dist/react-tooltip.css'; import 'react-toastify/dist/ReactToastify.css'; import useGeneralStore from './hooks/stores/useGeneralStore'; import { GlobalStyle } from './components/styled/GlobalStyle.styled'; import { Toast } from './components/styled/Toast.styled'; import All from './components/views/All'; import AllCatalog from './components/views/AllCatalog'; import Board from './components/views/Board'; import Catalog from './components/views/Catalog'; import Description from './components/views/Description'; import Home from './components/views/Home'; import NotFound from './components/views/NotFound'; import Pending from './components/views/Pending'; import Rules from './components/views/Rules'; import Subscriptions from './components/views/Subscriptions'; import SubscriptionsCatalog from './components/views/SubscriptionsCatalog'; import Thread from './components/views/Thread'; import CaptchaModal from './components/modals/CaptchaModal'; import { importAll } from './components/ImageBanner'; import preloadImages from './utils/preloadImages'; import useError from './hooks/useError'; import useInfo from './hooks/useInfo'; import useSuccess from './hooks/useSuccess'; import packageJson from '../package.json'; const commitRef = process?.env?.REACT_APP_COMMIT_REF || ''; export default function App() { const { bodyStyle, setBodyStyle, setDefaultSubplebbits, isCaptchaOpen, setIsCaptchaOpen, setIsSettingsOpen, selectedStyle, setSelectedStyle, setShowPostForm, setShowPostFormLink, } = useGeneralStore((state) => state); const location = useLocation(); const isHomeRoute = location.pathname === '/'; const isElectron = window.electron && window.electron.isElectron; const [, setNewErrorMessage] = useError(); const [, setNewSuccessMessage] = useSuccess(); const [, setNewInfoMessage] = useInfo(); const [infoMessageShown, setInfoMessageShown] = useState(false); useEffect(() => { const successToast = localStorage.getItem('successToast'); const errorToast = localStorage.getItem('errorToast'); if (successToast) { setNewSuccessMessage(successToast); localStorage.removeItem('successToast'); } else if (errorToast) { setNewErrorMessage(errorToast); localStorage.removeItem('errorToast'); } else { return; } }, [setNewErrorMessage, setNewSuccessMessage]); // check for new version // TODO: delete this code and toast when v1.0.0 is released useEffect(() => { const fetchVersionInfo = async () => { try { const packageRes = await fetch('https://raw.githubusercontent.com/plebbit/plebchan/master/package.json', { cache: 'no-cache' }); const packageData = await packageRes.json(); if (packageJson.version !== packageData.version && !infoMessageShown) { const newVersionInfo = isElectron ? `New version available, plebchan v${packageData.version}. You are using v${packageJson.version}. Download the latest version here: https://github.com/plebbit/plebchan/releases/latest` : `New version available, plebchan v${packageData.version}. You are using v${packageJson.version}. Refresh to update.`; setNewInfoMessage(newVersionInfo); setInfoMessageShown(true); } if (commitRef.length > 0) { const commitRes = await fetch('https://api.github.com/repos/plebbit/plebchan/commits?per_page=1&sha=development', { cache: 'no-cache' }); const commitData = await commitRes.json(); const latestCommitHash = commitData[0].sha; if (latestCommitHash.trim() !== commitRef.trim() && !infoMessageShown) { const newVersionInfo = `New dev version available, commit ${latestCommitHash.slice(0, 7)}. You are using commit ${commitRef.slice(0, 7)}. Refresh to update.`; setNewInfoMessage(newVersionInfo); setInfoMessageShown(true); } } } catch (error) { console.error('Failed to fetch latest version info:', error); } }; fetchVersionInfo(); }, [setNewInfoMessage, isElectron, infoMessageShown]); // preload banners useEffect(() => { const loadImages = async () => { const images = await importAll(require.context('../public/assets', true, /\.(png|jpe?g|svg)$/)); const imageUrls = images.map((image) => image.default); preloadImages(imageUrls); }; loadImages(); }, []); // automatic dark mode without interefering with user's selected style useEffect(() => { if (isHomeRoute) return; const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const isDarkMode = darkModeMediaQuery.matches; if (isDarkMode && !isHomeRoute) { setSelectedStyle('Tomorrow'); setBodyStyle({ background: '#1d1f21 none', color: '#c5c8c6', fontFamily: 'Arial, Helvetica, sans-serif', }); localStorage.setItem('selectedStyle', 'Tomorrow'); } const darkModeListener = (e) => { if (e.matches && !isHomeRoute) { 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); }; }, [isHomeRoute, setBodyStyle, setSelectedStyle]); // 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; }; }, [setDefaultSubplebbits]); // 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, setIsSettingsOpen, setShowPostForm, setShowPostFormLink]); return (
} /> }> } /> } /> }> } /> } /> }> } /> } /> }> } /> }> } /> }> } /> }> } /> }> } /> }> } /> }> } /> } /> setIsCaptchaOpen(false)} />
); }