mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-09 17:29:50 +03:00
224 lines
5.8 KiB
TypeScript
224 lines
5.8 KiB
TypeScript
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<Theme> = {
|
|
borderRadius: 3,
|
|
border: '1px solid lightblue',
|
|
justifyContent: 'space-evenly',
|
|
alignItems: 'center'
|
|
};
|
|
|
|
const IMAGE_STYLES: SxProps<Theme> = {
|
|
maxHeight: { xs: 100, md: 250 }
|
|
};
|
|
|
|
const AVATAR_STYLES: SxProps<Theme> = {
|
|
bgcolor: '#72caf9'
|
|
};
|
|
|
|
const HelpComponent = () => {
|
|
const { LL } = useI18nContext();
|
|
useLayoutTitle(LL.HELP());
|
|
|
|
const { me } = useContext(AuthenticatedContext);
|
|
|
|
const [customSupport, setCustomSupport] = useState<CustomSupport>({
|
|
img_url: null,
|
|
html: null
|
|
});
|
|
const [imgError, setImgError] = useState<boolean>(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('<br/>') || 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: <MenuBookIcon />,
|
|
label: () => LL.HELP_INFORMATION_1()
|
|
},
|
|
{
|
|
href: 'https://discord.gg/3J3GgnzpyT',
|
|
icon: <CommentIcon />,
|
|
label: () => LL.HELP_INFORMATION_2()
|
|
},
|
|
{
|
|
href: 'https://github.com/emsesp/EMS-ESP32/issues/new/choose',
|
|
icon: <GitHubIcon />,
|
|
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 (
|
|
<SectionContent>
|
|
{customSupport.html && (
|
|
<Stack
|
|
padding={1}
|
|
mb={2}
|
|
direction="row"
|
|
divider={<Divider orientation="vertical" flexItem />}
|
|
sx={SUPPORT_BOX_STYLES}
|
|
>
|
|
<Typography variant="subtitle1">
|
|
<div dangerouslySetInnerHTML={{ __html: customSupport.html }} />
|
|
</Typography>
|
|
<Box
|
|
component="img"
|
|
referrerPolicy="no-referrer"
|
|
sx={IMAGE_STYLES}
|
|
onError={handleImageError}
|
|
src={imageSrc}
|
|
/>
|
|
</Stack>
|
|
)}
|
|
|
|
{isAdmin && (
|
|
<List>
|
|
{helpLinks.map(({ href, icon, label }) => (
|
|
<ListItem key={href}>
|
|
<ListItemButton
|
|
component="a"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
href={href}
|
|
>
|
|
<ListItemAvatar>
|
|
<Avatar sx={AVATAR_STYLES}>{icon}</Avatar>
|
|
</ListItemAvatar>
|
|
<ListItemText primary={label()} />
|
|
</ListItemButton>
|
|
</ListItem>
|
|
))}
|
|
</List>
|
|
)}
|
|
|
|
<Box p={2} color="warning.main">
|
|
<Typography mb={1} variant="body1">
|
|
{LL.HELP_INFORMATION_4()}.
|
|
</Typography>
|
|
<Button
|
|
startIcon={<DownloadIcon />}
|
|
variant="outlined"
|
|
color="primary"
|
|
onClick={handleDownloadSystemInfo}
|
|
>
|
|
{LL.SUPPORT_INFORMATION(0)}
|
|
</Button>
|
|
</Box>
|
|
|
|
<Divider sx={{ mt: 4 }} />
|
|
|
|
<Typography color="white" variant="subtitle1" align="center" mt={1}>
|
|
©
|
|
<Link
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
href="https://emsesp.org"
|
|
color="primary"
|
|
>
|
|
emsesp.org
|
|
</Link>
|
|
</Typography>
|
|
</SectionContent>
|
|
);
|
|
};
|
|
|
|
// Memoize the component to prevent unnecessary re-renders
|
|
const Help = memo(HelpComponent);
|
|
|
|
export default Help;
|