mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix(codebase audit): preserve cleanup without regressions
Fix codebase audit regressions while preserving UI/UX behavior and adding review-driven hardening.
This commit is contained in:
@@ -245,7 +245,7 @@ describe('AccountSettings', () => {
|
||||
expect(hookMocks.exportAccount).toHaveBeenCalledOnce();
|
||||
expect(createObjectUrlSpy).toHaveBeenCalledOnce();
|
||||
expect(anchorClickSpy).toHaveBeenCalledOnce();
|
||||
expect(createdAnchor?.download).toBe('Account 1.json');
|
||||
expect(createdAnchor?.download).toBe('Account_1.json');
|
||||
expect(revokeObjectUrlSpy).toHaveBeenCalledWith('blob:test-account');
|
||||
});
|
||||
|
||||
|
||||
@@ -52,6 +52,11 @@ const rememberImportedAccountAddress = (address: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getSafeAccountBackupFileName = (accountName: string | undefined): string => {
|
||||
const safeName = (accountName || 'account').replace(/[^\w.-]/g, '_') || 'account';
|
||||
return `${safeName}.json`;
|
||||
};
|
||||
|
||||
// Inner component keyed by account id so state resets when user switches account
|
||||
const AccountSettingsEditor = ({
|
||||
account,
|
||||
@@ -98,7 +103,7 @@ const AccountSettingsEditor = ({
|
||||
const fileUrl = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = fileUrl;
|
||||
link.download = `${account?.name ?? 'account'}.json`;
|
||||
link.download = getSafeAccountBackupFileName(account?.name);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
@@ -120,7 +125,7 @@ const AccountSettingsEditor = ({
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
const fileContent = e.target!.result;
|
||||
const fileContent = e.target?.result ?? reader.result;
|
||||
if (typeof fileContent !== 'string') {
|
||||
alert('File content is not a string.');
|
||||
return;
|
||||
|
||||
@@ -23,6 +23,7 @@ const SettingsModal = () => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const hash = location.hash.slice(1);
|
||||
const hashSection = hashToSection(hash);
|
||||
|
||||
const closeModal = useCallback(() => {
|
||||
const newPath = location.pathname.replace(/\/settings$/, '');
|
||||
@@ -43,30 +44,29 @@ const SettingsModal = () => {
|
||||
}, [closeModal]);
|
||||
|
||||
const [expandedSections, setExpandedSections] = useState<Set<string>>(() => {
|
||||
const section = hashToSection(hash);
|
||||
return section ? new Set([section]) : new Set();
|
||||
return hashSection ? new Set([hashSection]) : new Set();
|
||||
});
|
||||
|
||||
const showInterfaceSettings = expandedSections.has('interface-settings');
|
||||
const showMediaHostingSettings = expandedSections.has('media-hosting-settings');
|
||||
const showAccountSettings = expandedSections.has('account-settings');
|
||||
const showSubscriptionsSettings = expandedSections.has('subscriptions-settings');
|
||||
const showAdvancedSettings = expandedSections.has('advanced-settings');
|
||||
const visibleExpandedSections = useMemo(() => {
|
||||
if (!hashSection || expandedSections.has(hashSection)) return expandedSections;
|
||||
const nextSections = new Set(expandedSections);
|
||||
nextSections.add(hashSection);
|
||||
return nextSections;
|
||||
}, [expandedSections, hashSection]);
|
||||
|
||||
const allExpanded = useMemo(() => allSectionIds.every((id) => expandedSections.has(id)), [expandedSections]);
|
||||
const showInterfaceSettings = visibleExpandedSections.has('interface-settings');
|
||||
const showMediaHostingSettings = visibleExpandedSections.has('media-hosting-settings');
|
||||
const showAccountSettings = visibleExpandedSections.has('account-settings');
|
||||
const showSubscriptionsSettings = visibleExpandedSections.has('subscriptions-settings');
|
||||
const showAdvancedSettings = visibleExpandedSections.has('advanced-settings');
|
||||
|
||||
const allExpanded = useMemo(() => allSectionIds.every((id) => visibleExpandedSections.has(id)), [visibleExpandedSections]);
|
||||
|
||||
const basePath = location.pathname;
|
||||
|
||||
useEffect(() => {
|
||||
const section = hashToSection(hash);
|
||||
if (section && !expandedSections.has(section)) {
|
||||
setExpandedSections((prev) => new Set(prev).add(section));
|
||||
}
|
||||
}, [hash]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const handleCategoryClick = (categoryId: string) => {
|
||||
const isOpening = !expandedSections.has(categoryId);
|
||||
const next = new Set(expandedSections);
|
||||
const isOpening = !visibleExpandedSections.has(categoryId);
|
||||
const next = new Set(visibleExpandedSections);
|
||||
if (isOpening) {
|
||||
next.add(categoryId);
|
||||
} else {
|
||||
@@ -104,10 +104,20 @@ const SettingsModal = () => {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.overlay} role='button' tabIndex={0} onClick={closeModal} onKeyDown={handleKeyDown(closeModal)} />
|
||||
<div className={styles.settingsModal}>
|
||||
<div className={styles.settingsModal} role='dialog' aria-modal='true' aria-labelledby='settings-modal-title'>
|
||||
<div className={styles.header}>
|
||||
<span className={styles.title}>{t('settings')}</span>
|
||||
<span className={styles.closeButton} role='button' tabIndex={0} title='close' onClick={closeModal} onKeyDown={handleKeyDown(closeModal)} />
|
||||
<span id='settings-modal-title' className={styles.title}>
|
||||
{t('settings')}
|
||||
</span>
|
||||
<span
|
||||
className={styles.closeButton}
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
title='close'
|
||||
aria-label={t('close')}
|
||||
onClick={closeModal}
|
||||
onKeyDown={handleKeyDown(closeModal)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.expandAllSettings}>
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user