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>
This commit is contained in:
2026-07-29 20:28:06 +08:00
parent fc55965555
commit 4085393db1
11 changed files with 254 additions and 128 deletions

View File

@@ -1,6 +1,17 @@
import { ChromeOutlined, SoundOutlined } from '@ant-design/icons';
import { useBreakpoint } from '@ant-design/pro-components';
import { getLocale, Icon } from '@umijs/max';
import { Alert, Divider, Image, Modal, Space, Tabs, Typography } from 'antd';
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';
@@ -8,9 +19,9 @@ import bus from '@/utils/eventBus';
import { $t } from '@/utils/i18n';
const { Paragraph } = Typography;
const ChangePassTips: React.FC = () => {
const VoiceCheck: React.FC = () => {
const { richFormat } = useRichI18n();
const screens = useBreakpoint();
const [visible, setVisible] = useState(false);
const [hasAudioPermission, setHasAudioPermission] = useState(true);
@@ -69,25 +80,46 @@ const ChangePassTips: React.FC = () => {
),
},
];
const tipsNode = (
<>
<Alert
message={$t('pages.voiceCheck.alertMessage')}
type="warning"
showIcon
/>
</>
);
return (
<>
<Space align="center" onClick={() => setVisible(true)}>
{!hasAudioPermission && (
<>
<Alert
message={$t('pages.voiceCheck.alertMessage')}
type="warning"
showIcon
/>
<Icon
icon="local:silence"
height="20px"
fill="#f59e0b"
className="flex align-center"
/>
</>
)}
<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"
@@ -103,7 +135,7 @@ const ChangePassTips: React.FC = () => {
className="flex align-center"
/>
)} */}
</Space>
</Flex>
<Modal
title={
@@ -154,4 +186,4 @@ const ChangePassTips: React.FC = () => {
</>
);
};
export default ChangePassTips;
export default VoiceCheck;