import { memo, useCallback, useContext, useMemo, useState } from 'react'; import type { ReactElement } from 'react'; import { toast } from 'react-toastify'; import CommentIcon from '@mui/icons-material/CommentTwoTone'; import DownloadIcon from '@mui/icons-material/GetApp'; import GitHubIcon from '@mui/icons-material/GitHub'; import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone'; import { Avatar, Box, Button, Divider, Link, List, ListItem, ListItemAvatar, ListItemButton, ListItemText, Stack, Typography } from '@mui/material'; import type { SxProps, Theme } from '@mui/material/styles'; import { useRequest } from 'alova/client'; import { SectionContent, useLayoutTitle } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; import { saveFile } from 'utils'; import { API, callAction } from '../../api/app'; import type { APIcall } from './types'; interface HelpLink { href: string; icon: ReactElement; label: () => string; } interface CustomSupport { img_url: string | null; html: string | null; } // Constants moved outside component to prevent recreation const DEFAULT_IMAGE_URL = 'https://docs.emsesp.org/_media/images/installer.jpeg'; const SUPPORT_BOX_STYLES: SxProps = { borderRadius: 3, border: '1px solid lightblue', justifyContent: 'space-evenly', alignItems: 'center' }; const IMAGE_STYLES: SxProps = { maxHeight: { xs: 100, md: 250 } }; const AVATAR_STYLES: SxProps = { bgcolor: '#72caf9' }; const HelpComponent = () => { const { LL } = useI18nContext(); useLayoutTitle(LL.HELP()); const { me } = useContext(AuthenticatedContext); const [customSupport, setCustomSupport] = useState({ img_url: null, html: null }); const [imgError, setImgError] = useState(false); // Memoize the request method to prevent re-creation on every render const getCustomSupportMethod = useMemo( () => callAction({ action: 'getCustomSupport' }), [] ); useRequest(getCustomSupportMethod).onSuccess((event) => { if (event?.data && Object.keys(event.data).length !== 0) { const { Support } = event.data as { Support: { img_url?: string; html?: string[] }; }; setCustomSupport({ img_url: Support.img_url || null, html: Support.html?.join('
') || null }); } }); const { send: sendAPI } = useRequest((data: APIcall) => API(data), { immediate: false }) .onSuccess((event) => { saveFile(event.data, 'system_info', '.json'); toast.info(LL.DOWNLOAD_SUCCESSFUL()); }) .onError((error) => { toast.error(String(error.error?.message || 'An error occurred')); }); // Optimize API call memoization const apiCall = useMemo(() => ({ device: 'system', cmd: 'info', id: 0 }), []); const handleDownloadSystemInfo = useCallback(() => { void sendAPI(apiCall); }, [sendAPI, apiCall]); const handleImageError = useCallback(() => { setImgError(true); }, []); // Memoize help links to prevent recreation on every render const helpLinks: HelpLink[] = useMemo( () => [ { href: 'https://docs.emsesp.org', icon: , label: () => LL.HELP_INFORMATION_1() }, { href: 'https://discord.gg/3J3GgnzpyT', icon: , label: () => LL.HELP_INFORMATION_2() }, { href: 'https://github.com/emsesp/EMS-ESP32/issues/new/choose', icon: , label: () => LL.HELP_INFORMATION_3() } ], [LL] ); const isAdmin = useMemo(() => me?.admin ?? false, [me?.admin]); // Memoize image source computation const imageSrc = useMemo( () => imgError || !customSupport.img_url ? DEFAULT_IMAGE_URL : customSupport.img_url, [imgError, customSupport.img_url] ); return ( {customSupport.html && ( } sx={SUPPORT_BOX_STYLES} >
)} {isAdmin && ( {helpLinks.map(({ href, icon, label }) => ( {icon} ))} )} {LL.HELP_INFORMATION_4()}. ©  emsesp.org ); }; // Memoize the component to prevent unnecessary re-renders const Help = memo(HelpComponent); export default Help;