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

View File

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