Files
alert/src/components/VoiceCheck/index.tsx
Johnton Chen 4085393db1 feat(company): add expiry warning banner, route wrapper, and conditional UI for expired companies
- Add CompanyExpireWarning component to layout header for expiring subscription alerts
- Add CompanyExpire wrapper to guard rules and data-source routes
- Refactor CompanyExpireGuard to support optional fullscreen mode via prop
- Add responsive Popover support to VoiceCheck for mobile breakpoints
- Hide rule CRUD action buttons when company validity is expired
- Update profile page to display expiry status alongside validity date
- Update i18n strings for clearer expiry messaging

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-29 20:28:06 +08:00

190 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ChromeOutlined, SoundOutlined } from '@ant-design/icons';
import { useBreakpoint } from '@ant-design/pro-components';
import { getLocale, Icon } from '@umijs/max';
import {
Alert,
Divider,
Flex,
Image,
Modal,
Popover,
Space,
Tabs,
Typography,
} from 'antd';
import { useCallback, useEffect, useState } from 'react';
import useRichI18n from '@/hooks/useRichI18n';
import { checkAudioPermission } from '@/utils';
import bus from '@/utils/eventBus';
import { $t } from '@/utils/i18n';
const { Paragraph } = Typography;
const VoiceCheck: React.FC = () => {
const { richFormat } = useRichI18n();
const screens = useBreakpoint();
const [visible, setVisible] = useState(false);
const [hasAudioPermission, setHasAudioPermission] = useState(true);
const checkAudio = useCallback((showPopup?: boolean) => {
checkAudioPermission().then((hasPermission) => {
setHasAudioPermission(hasPermission);
});
showPopup && setVisible(true);
}, []);
const openPopup = useCallback(() => setVisible(true), []);
useEffect(() => {
checkAudio();
bus.on('check-voice-permission', checkAudio);
bus.on('open-voice-popup', openPopup);
// document.addEventListener('click', () => checkAudio());
return () => {
bus.off('check-voice-permission', checkAudio);
bus.off('open-voice-popup', openPopup);
// document.removeEventListener('click', () => checkAudio());
};
}, []);
const browserGuides = [
{
key: 'chrome',
label: (
<span>
<ChromeOutlined /> Chrome
</span>
),
children: (
<div>
<Paragraph>1. {richFormat('pages.voiceCheck.chrome.tips')}</Paragraph>
<Paragraph>
2. {richFormat('pages.voiceCheck.chrome.tips2')}
</Paragraph>
<Paragraph>
3. {richFormat('pages.voiceCheck.chrome.tips3')}
</Paragraph>
<Paragraph>
4. {richFormat('pages.voiceCheck.chrome.tips4')}
</Paragraph>
<Paragraph>
5. {richFormat('pages.voiceCheck.chrome.tips5')}
</Paragraph>
<Image
width="100%"
src={require(
`@/assets/imgs/voice_permission_tips_${getLocale()}.png`,
)}
/>
</div>
),
},
];
const tipsNode = (
<>
<Alert
message={$t('pages.voiceCheck.alertMessage')}
type="warning"
showIcon
/>
</>
);
return (
<>
<Flex align="center" onClick={() => setVisible(true)}>
{!hasAudioPermission &&
(['xs', 'sm'].includes(screens || '') ? (
<Popover
content={tipsNode}
styles={{
body: { padding: 0 },
}}
zIndex={999}
defaultOpen={true}
>
<Icon
icon="local:silence"
height="20px"
fill="#f59e0b"
className="flex align-center"
/>
</Popover>
) : (
<>
{tipsNode}
<Icon
icon="local:silence"
height="20px"
fill="#f59e0b"
className="flex align-center"
/>
</>
))}
{/* {hasAudioPermission ? (
<Icon
icon="local:voice"
height="20px"
fill="#34d399"
className="flex align-center"
/>
) : (
<Icon
icon="local:silence"
height="20px"
fill="#f59e0b"
className="flex align-center"
/>
)} */}
</Flex>
<Modal
title={
<Space>
<SoundOutlined /> {$t('pages.voiceCheck.title')}
</Space>
}
open={visible}
onCancel={() => setVisible(false)}
width={800}
footer={null}
centered
styles={{
body: {
maxHeight: '85vh',
overflowY: 'auto',
overflowX: 'hidden',
marginRight: -20,
paddingRight: 8,
},
}}
>
<Space direction="vertical" size="middle">
<Alert
message={$t('pages.voiceCheck.alertMessage2')}
description={$t('pages.voiceCheck.alertMessage3')}
type="warning"
banner
/>
{/* <div>
<Space style={{ marginBottom: 10 }} align="center">
<InfoCircleOutlined />
<Text strong>{$t('pages.voiceCheck.tips')}</Text>
</Space>
<div>{$t('pages.voiceCheck.tips2')}</div>
</div> */}
<Divider style={{ margin: 0 }}>
{$t('pages.voiceCheck.tips3')}
</Divider>
<Tabs defaultActiveKey="chrome" items={browserGuides} size="small" />
<Alert type="info" message={$t('pages.voiceCheck.tips4')} banner />
</Space>
</Modal>
</>
);
};
export default VoiceCheck;