fix(advanced settings): hide gateway fields in pure p2p mode and align inputs

When pure browser P2P is enabled, the IPFS gateway and pubsub provider
fields no longer apply, so hide them and toggle their visibility reactively
from the now-controlled pure P2P checkbox. Rename "Node RPC" to "Full node
WebSocket RPC", add a placeholder, and drop the old info toggle.

Give the WebSocket RPC text input the same content-box width, border, and
padding as the textareas above it so all field right edges align.
This commit is contained in:
Tommaso Casaburi
2026-05-23 17:23:05 +07:00
parent f4965ee678
commit b849f7c376
3 changed files with 86 additions and 58 deletions
@@ -210,6 +210,28 @@ describe('AdvancedSettings', () => {
expect(textInputs[2]?.value).toBe('/tmp/connected-node');
});
it('hides gateway mode settings while browser pure p2p is enabled', async () => {
await renderSettings(false);
expect(container.textContent).not.toContain('IPFS gateways:');
expect(container.textContent).not.toContain('pubsub providers:');
expect(container.textContent).toContain('http routers:');
expect(container.textContent).toContain('Full node WebSocket RPC:');
const nodeRpcInput = Array.from(container.querySelectorAll<HTMLInputElement>('input[type="text"]')).find(
(input) => input.placeholder === 'ws://<IP>:<port>/<secretAuthKey>',
);
expect(nodeRpcInput).toBeTruthy();
const checkbox = container.querySelector<HTMLInputElement>('input[type="checkbox"]');
await act(async () => {
checkbox?.click();
});
expect(container.textContent).toContain('IPFS gateways:');
expect(container.textContent).toContain('pubsub providers:');
});
it('saves the browser pure p2p toggle through advanced settings', async () => {
localStorage.setItem('5chan:pure-p2p-browser-enabled', 'false');
@@ -298,14 +320,16 @@ describe('AdvancedSettings', () => {
expect(localStorage.getItem('5chan:pure-p2p-browser-enabled')).toBe('false');
});
it('toggles the node rpc instructions panel', async () => {
it('renders the full node WebSocket RPC field without redundant instructions', async () => {
await renderSettings(false);
expect(container.textContent).not.toContain('secret auth key');
await clickButton('?');
expect(container.textContent).toContain('secret auth key');
await clickButton('X');
expect(container.textContent).not.toContain('secret auth key');
const nodeRpcInput = Array.from(container.querySelectorAll<HTMLInputElement>('input[type="text"]')).find(
(input) => input.placeholder === 'ws://<IP>:<port>/<secretAuthKey>',
);
expect(container.textContent).toContain('Full node WebSocket RPC:');
expect(nodeRpcInput).toBeTruthy();
expect(container.querySelector('button')?.textContent).not.toBe('?');
});
it('alerts with the error message when saving fails with an Error instance', async () => {
@@ -37,16 +37,6 @@
cursor: pointer;
}
.p2pRpcSettingsInfo {
padding: 5px 20px 0 0;
word-break: break-word;
}
.p2pRpcSettingsInfo ol {
padding-bottom: 5px;
padding-left: 20px;
}
.saveOptions {
display: block;
margin: 15px auto 15px;
@@ -64,7 +54,7 @@
font-family: monospace;
}
.content input[type="text"] {
.content input[type='text'] {
outline: none;
width: 55%;
font-size: 13px;
@@ -91,6 +81,11 @@
margin-right: 5px;
}
.p2pRPCSettings button {
margin-left: 5px;
.p2pRPCSettings input[type='text'] {
display: block;
box-sizing: content-box;
border: 1px solid #aaa;
width: 95%;
margin: 0 2px 0 0;
padding: 2px 4px 3px;
}
@@ -13,6 +13,8 @@ interface SettingsProps {
ethRpcRef?: RefObject<HTMLTextAreaElement>;
p2pRpcRef?: RefObject<HTMLInputElement>;
p2pDataPathRef?: RefObject<HTMLInputElement>;
onPureP2PBrowserChange?: (enabled: boolean) => void;
pureP2PBrowserEnabled?: boolean;
pureP2PBrowserRef?: RefObject<HTMLInputElement>;
}
@@ -158,31 +160,21 @@ const BlockchainProvidersSettings = ({ ethRpcRef }: SettingsProps) => {
};
const P2pRPCSettings = ({ p2pRpcRef }: SettingsProps) => {
const [showInfo, setShowInfo] = useState(false);
const account = useAccount() as AccountShape | undefined;
const protocolOptions = getProtocolOptions(account);
const pkcRpcClientsOptions = getNodeRpcClientsOptions(protocolOptions);
return (
<div className={styles.p2pRPCSettings}>
<div>
<input type='text' defaultValue={pkcRpcClientsOptions} ref={p2pRpcRef} autoCorrect='off' autoCapitalize='off' spellCheck='false' />
<button onClick={() => setShowInfo(!showInfo)}>{showInfo ? 'X' : '?'}</button>
</div>
{showInfo && (
<div className={styles.p2pRpcSettingsInfo}>
use a P2P full node locally, or remotely with SSL
<br />
<ol>
<li>get secret auth key from the node</li>
<li>get IP address and port used by the node</li>
<li>
enter: <code>{`ws://<IP>:<port>/<secretAuthKey>`}</code>
</li>
<li>click save to connect</li>
</ol>
</div>
)}
<input
type='text'
defaultValue={pkcRpcClientsOptions}
placeholder='ws://<IP>:<port>/<secretAuthKey>'
ref={p2pRpcRef}
autoCorrect='off'
autoCapitalize='off'
spellCheck='false'
/>
</div>
);
};
@@ -202,14 +194,21 @@ const P2pDataPathSettings = ({ p2pDataPathRef }: SettingsProps) => {
);
};
const PureP2PBrowserSettings = ({ pureP2PBrowserRef }: SettingsProps) => {
const PureP2PBrowserSettings = ({ onPureP2PBrowserChange, pureP2PBrowserEnabled, pureP2PBrowserRef }: SettingsProps) => {
const { t } = useTranslation();
const account = useAccount() as AccountShape | undefined;
const isChecked = pureP2PBrowserEnabled ?? isBrowserPureP2PEnabled(account);
return (
<div className={styles.pureP2PSettings}>
<label>
<input className={styles.pureP2PCheckbox} type='checkbox' defaultChecked={isBrowserPureP2PEnabled(account)} ref={pureP2PBrowserRef} />
<input
className={styles.pureP2PCheckbox}
type='checkbox'
checked={isChecked}
ref={pureP2PBrowserRef}
onChange={(event) => onPureP2PBrowserChange?.(event.currentTarget.checked)}
/>
{t('enable_pure_p2p')}
</label>
<div className={styles.settingTip}>{t('enable_pure_p2p_tip')}</div>
@@ -231,6 +230,10 @@ const AdvancedSettings = () => {
const { t } = useTranslation();
const account = useAccount() as AccountShape | undefined;
const protocolOptions = getProtocolOptions(account);
const canConfigurePureP2PBrowser = canConfigureBrowserPureP2P();
const [browserPureP2PSelection, setBrowserPureP2PSelection] = useState<boolean | undefined>(undefined);
const browserPureP2PEnabled = browserPureP2PSelection ?? (canConfigurePureP2PBrowser && isBrowserPureP2PEnabled(account));
const shouldShowGatewayModeSettings = !canConfigurePureP2PBrowser || !browserPureP2PEnabled;
const ipfsGatewayUrlsRef = useRef<HTMLTextAreaElement>(null);
const mediaIpfsGatewayUrlRef = useRef<HTMLInputElement>(null);
@@ -242,11 +245,11 @@ const AdvancedSettings = () => {
const pureP2PBrowserRef = useRef<HTMLInputElement>(null);
const handleSave = async () => {
const ipfsGatewayUrls = getTrimmedLines(ipfsGatewayUrlsRef.current?.value);
const ipfsGatewayUrls = ipfsGatewayUrlsRef.current ? getTrimmedLines(ipfsGatewayUrlsRef.current.value) : protocolOptions?.ipfsGatewayUrls;
const mediaIpfsGatewayUrl = mediaIpfsGatewayUrlRef.current?.value.trim();
const mediaIpfsGatewayUrl = mediaIpfsGatewayUrlRef.current ? mediaIpfsGatewayUrlRef.current.value.trim() : account?.mediaIpfsGatewayUrl;
const pubsubKuboRpcClientsOptions = getTrimmedLines(pubsubProvidersRef.current?.value);
const pubsubKuboRpcClientsOptions = pubsubProvidersRef.current ? getTrimmedLines(pubsubProvidersRef.current.value) : getPubsubRpcClientsOptions(protocolOptions);
const ethRpcUrls = getTrimmedLines(ethRpcRef.current?.value);
@@ -254,7 +257,7 @@ const AdvancedSettings = () => {
const pkcRpcClientsOptions = p2pRpcRef.current?.value.trim() ? [p2pRpcRef.current.value.trim()] : undefined;
const dataPath = p2pDataPathRef.current?.value.trim() || undefined;
const pureP2PBrowserPreference = canConfigureBrowserPureP2P() ? pureP2PBrowserRef.current?.checked : undefined;
const pureP2PBrowserPreference = canConfigurePureP2PBrowser ? browserPureP2PEnabled : undefined;
const chainProviders: Record<string, { urls: string[] | undefined; chainId: number }> = {};
if (ethRpcUrls && ethRpcUrls.length > 0) {
@@ -313,18 +316,22 @@ const AdvancedSettings = () => {
return (
<div className={styles.content}>
<div className={styles.category}>
<span className={styles.categoryTitle}>IPFS gateways:</span>
<span className={styles.categorySettings}>
<IPFSGatewaysSettings ipfsGatewayUrlsRef={ipfsGatewayUrlsRef} mediaIpfsGatewayUrlRef={mediaIpfsGatewayUrlRef} />
</span>
</div>
<div className={styles.category}>
<span className={styles.categoryTitle}>pubsub providers:</span>
<span className={styles.categorySettings}>
<PubsubProvidersSettings pubsubProvidersRef={pubsubProvidersRef} />
</span>
</div>
{shouldShowGatewayModeSettings ? (
<>
<div className={styles.category}>
<span className={styles.categoryTitle}>IPFS gateways:</span>
<span className={styles.categorySettings}>
<IPFSGatewaysSettings ipfsGatewayUrlsRef={ipfsGatewayUrlsRef} mediaIpfsGatewayUrlRef={mediaIpfsGatewayUrlRef} />
</span>
</div>
<div className={styles.category}>
<span className={styles.categoryTitle}>pubsub providers:</span>
<span className={styles.categorySettings}>
<PubsubProvidersSettings pubsubProvidersRef={pubsubProvidersRef} />
</span>
</div>
</>
) : null}
<div className={styles.category}>
<span className={styles.categoryTitle}>http routers:</span>
<span className={styles.categorySettings}>
@@ -340,7 +347,7 @@ const AdvancedSettings = () => {
</span>
</div>
<div className={styles.category}>
<span className={styles.categoryTitle}>Node RPC:</span>
<span className={styles.categoryTitle}>Full node WebSocket RPC:</span>
<span className={styles.categorySettings}>
<P2pRPCSettings p2pRpcRef={p2pRpcRef} />
</span>
@@ -353,7 +360,9 @@ const AdvancedSettings = () => {
</span>
</div>
)}
{canConfigureBrowserPureP2P() && <PureP2PBrowserSettings pureP2PBrowserRef={pureP2PBrowserRef} />}
{canConfigurePureP2PBrowser ? (
<PureP2PBrowserSettings pureP2PBrowserEnabled={browserPureP2PEnabled} pureP2PBrowserRef={pureP2PBrowserRef} onPureP2PBrowserChange={setBrowserPureP2PSelection} />
) : null}
<button className={styles.saveOptions} onClick={handleSave}>
{t('save_advanced_settings')}
</button>