feat: add localized high-risk status remap guard with optimized modal UX

This commit is contained in:
Seefs
2026-02-22 20:14:56 +08:00
parent 4831bb7b5b
commit c4c4e5eda6
2 changed files with 89 additions and 25 deletions

View File

@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import React, { useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
Modal,
Button,
@@ -32,7 +32,30 @@ import MarkdownRenderer from '../markdown/MarkdownRenderer';
const { Text } = Typography;
const RiskAcknowledgementModal = ({
const RiskMarkdownBlock = React.memo(function RiskMarkdownBlock({
markdownContent,
}) {
if (!markdownContent) {
return null;
}
return (
<div
className='rounded-lg'
style={{
border: '1px solid var(--semi-color-warning-light-hover)',
background:
'linear-gradient(180deg, var(--semi-color-warning-light-default) 0%, var(--semi-color-fill-0) 100%)',
padding: '12px',
contentVisibility: 'auto',
}}
>
<MarkdownRenderer content={markdownContent} />
</div>
);
});
const RiskAcknowledgementModal = React.memo(function RiskAcknowledgementModal({
visible,
title,
markdownContent = '',
@@ -47,7 +70,7 @@ const RiskAcknowledgementModal = ({
confirmText = '',
onCancel,
onConfirm,
}) => {
}) {
const isMobile = useIsMobile();
const [checkedItems, setCheckedItems] = useState([]);
const [typedText, setTypedText] = useState('');
@@ -68,6 +91,17 @@ const RiskAcknowledgementModal = ({
return typedText.trim() === requiredText.trim();
}, [typedText, requiredText]);
const detailText = useMemo(() => detailItems.join(', '), [detailItems]);
const canConfirm = allChecked && typedMatched;
const handleChecklistChange = useCallback((index, checked) => {
setCheckedItems((previous) => {
const next = [...previous];
next[index] = checked;
return next;
});
}, []);
return (
<Modal
visible={visible}
@@ -85,7 +119,7 @@ const RiskAcknowledgementModal = ({
bodyStyle={{
maxHeight: isMobile ? '70vh' : '72vh',
overflowY: 'auto',
padding: isMobile ? '12px 16px' : '16px 20px',
padding: isMobile ? '12px 16px' : '18px 22px',
}}
footer={
<Space>
@@ -93,7 +127,7 @@ const RiskAcknowledgementModal = ({
<Button
theme='solid'
type='danger'
disabled={!allChecked || !typedMatched}
disabled={!canConfirm}
onClick={onConfirm}
>
{confirmText}
@@ -102,31 +136,49 @@ const RiskAcknowledgementModal = ({
}
>
<div className='flex flex-col gap-4'>
{markdownContent ? (
<div className='border border-warning-200 bg-warning-50 rounded-md px-3 py-2'>
<MarkdownRenderer content={markdownContent} />
</div>
) : null}
<div
className='rounded-lg'
style={{
border: '1px solid var(--semi-color-warning-light-hover)',
background: 'var(--semi-color-warning-light-default)',
padding: isMobile ? '10px 12px' : '12px 14px',
}}
>
</div>
<RiskMarkdownBlock markdownContent={markdownContent} />
{detailItems.length > 0 ? (
<div className='flex flex-col gap-2'>
<div
className='flex flex-col gap-2 rounded-lg'
style={{
border: '1px solid var(--semi-color-warning-light-hover)',
background: 'var(--semi-color-fill-0)',
padding: isMobile ? '10px 12px' : '12px 14px',
}}
>
{detailTitle ? <Text strong>{detailTitle}</Text> : null}
<div className='font-mono text-xs break-all bg-orange-50 border border-orange-200 rounded-md p-2'>
{detailItems.join(', ')}
{detailText}
</div>
</div>
) : null}
{checklist.length > 0 ? (
<div className='flex flex-col gap-2'>
<div
className='flex flex-col gap-2 rounded-lg'
style={{
border: '1px solid var(--semi-color-border)',
background: 'var(--semi-color-fill-0)',
padding: isMobile ? '10px 12px' : '12px 14px',
}}
>
{checklist.map((item, index) => (
<Checkbox
key={`risk-check-${index}`}
checked={!!checkedItems[index]}
onChange={(event) => {
const next = [...checkedItems];
next[index] = event.target.checked;
setCheckedItems(next);
handleChecklistChange(index, event.target.checked);
}}
>
{item}
@@ -136,15 +188,23 @@ const RiskAcknowledgementModal = ({
) : null}
{requiredText ? (
<div className='flex flex-col gap-2'>
<div
className='flex flex-col gap-2 rounded-lg'
style={{
border: '1px solid var(--semi-color-danger-light-hover)',
background: 'var(--semi-color-danger-light-default)',
padding: isMobile ? '10px 12px' : '12px 14px',
}}
>
{inputPrompt ? <Text strong>{inputPrompt}</Text> : null}
<div className='font-mono text-xs break-all bg-gray-50 border border-gray-200 rounded-md p-2'>
<div className='font-mono text-xs break-all rounded-md p-2 bg-gray-50 border border-gray-200'>
{requiredText}
</div>
<Input
value={typedText}
onChange={setTypedText}
placeholder={inputPlaceholder}
autoFocus={visible}
onCopy={(event) => event.preventDefault()}
onCut={(event) => event.preventDefault()}
onPaste={(event) => event.preventDefault()}
@@ -160,6 +220,6 @@ const RiskAcknowledgementModal = ({
</div>
</Modal>
);
};
});
export default RiskAcknowledgementModal;

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import RiskAcknowledgementModal from '../../../common/modals/RiskAcknowledgementModal';
import {
@@ -6,13 +6,17 @@ import {
STATUS_CODE_RISK_CHECKLIST_KEYS,
} from './statusCodeRiskGuard';
const StatusCodeRiskGuardModal = ({
const StatusCodeRiskGuardModal = React.memo(function StatusCodeRiskGuardModal({
visible,
detailItems,
onCancel,
onConfirm,
}) => {
const { t } = useTranslation();
}) {
const { t, i18n } = useTranslation();
const checklist = useMemo(
() => STATUS_CODE_RISK_CHECKLIST_KEYS.map((item) => t(item)),
[t, i18n.language],
);
return (
<RiskAcknowledgementModal
@@ -21,7 +25,7 @@ const StatusCodeRiskGuardModal = ({
markdownContent={t(STATUS_CODE_RISK_I18N_KEYS.markdown)}
detailTitle={t(STATUS_CODE_RISK_I18N_KEYS.detailTitle)}
detailItems={detailItems}
checklist={STATUS_CODE_RISK_CHECKLIST_KEYS.map((item) => t(item))}
checklist={checklist}
inputPrompt={t(STATUS_CODE_RISK_I18N_KEYS.inputPrompt)}
requiredText={t(STATUS_CODE_RISK_I18N_KEYS.confirmText)}
inputPlaceholder={t(STATUS_CODE_RISK_I18N_KEYS.inputPlaceholder)}
@@ -32,6 +36,6 @@ const StatusCodeRiskGuardModal = ({
onConfirm={onConfirm}
/>
);
};
});
export default StatusCodeRiskGuardModal;