fix(network): footer Local toggle dead in Tauri (window.confirm no-op) (#169)
The footer Local/Network pill called window.confirm() before enabling, but window.confirm is a no-op in the Tauri webview (returns false), so the enable action was silently swallowed — the button appeared to do nothing. Replace it with a reliable in-app confirm popover (Cancel/Enable). The backend endpoint was always working (verified: enable opens a real listener on the share port). Adds a regression test for the Local -> confirm -> Enable -> POST flow. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c993e14072
commit
3901a3cf4a
@@ -166,3 +166,32 @@
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.net-toggle__confirm-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.net-toggle__enable {
|
||||
background: rgba(184, 187, 38, 0.15);
|
||||
border: 1px solid rgba(184, 187, 38, 0.4);
|
||||
color: #b8bb26;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
font-family: inherit;
|
||||
padding: 5px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.net-toggle__enable:hover { background: rgba(184, 187, 38, 0.25); }
|
||||
.net-toggle__cancel {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border, #504945);
|
||||
color: inherit;
|
||||
font-size: 11px;
|
||||
font-family: inherit;
|
||||
padding: 5px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.net-toggle__enable:disabled,
|
||||
.net-toggle__cancel:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
@@ -11,6 +11,7 @@ export default function NetworkToggle() {
|
||||
const [st, setSt] = useState({ enabled: false });
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [confirming, setConfirming] = useState(false);
|
||||
const [qrs, setQrs] = useState({});
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
@@ -31,10 +32,11 @@ export default function NetworkToggle() {
|
||||
return () => { cancelled = true; };
|
||||
}, [st.enabled, st.pin, st.share_port, st.lan_addresses]);
|
||||
|
||||
// NOTE: do NOT use window.confirm here — it's a no-op in the Tauri webview
|
||||
// (returns false), which silently swallowed the enable action.
|
||||
const enable = async () => {
|
||||
if (!window.confirm('Share OmniVoice on your local network? Other devices will be able to reach it with the access PIN.')) return;
|
||||
setBusy(true);
|
||||
try { setSt(await apiPost('/system/network/enable')); setOpen(true); }
|
||||
try { setSt(await apiPost('/system/network/enable')); setConfirming(false); setOpen(true); }
|
||||
catch (e) { toast.error(`Could not enable sharing: ${e.message}`); }
|
||||
finally { setBusy(false); }
|
||||
};
|
||||
@@ -51,7 +53,7 @@ export default function NetworkToggle() {
|
||||
<div className="net-toggle">
|
||||
<button
|
||||
className={`net-toggle__pill ${st.enabled ? 'net-toggle__pill--on' : ''}`}
|
||||
onClick={st.enabled ? () => setOpen((o) => !o) : enable}
|
||||
onClick={st.enabled ? () => setOpen((o) => !o) : () => setConfirming((c) => !c)}
|
||||
disabled={busy}
|
||||
title={st.enabled ? 'Sharing on — click for details' : 'Share on your network'}
|
||||
>
|
||||
@@ -59,6 +61,20 @@ export default function NetworkToggle() {
|
||||
<span>{busy ? 'Switching…' : st.enabled ? 'Network' : 'Local'}</span>
|
||||
</button>
|
||||
|
||||
{!st.enabled && confirming && (
|
||||
<div className="net-toggle__panel net-toggle__panel--confirm">
|
||||
<div className="net-toggle__panel-title">Share on your network?</div>
|
||||
<p className="net-toggle__hint">
|
||||
Other devices on your Wi-Fi/Ethernet will be able to reach OmniVoice
|
||||
using the access PIN shown once it's on.
|
||||
</p>
|
||||
<div className="net-toggle__confirm-actions">
|
||||
<button type="button" className="net-toggle__cancel" onClick={() => setConfirming(false)} disabled={busy}>Cancel</button>
|
||||
<button type="button" className="net-toggle__enable" onClick={enable} disabled={busy}>{busy ? 'Enabling…' : 'Enable'}</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{st.enabled && open && (
|
||||
<div className="net-toggle__panel">
|
||||
<div className="net-toggle__panel-title">Shared on your network</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// frontend/src/components/NetworkToggle.test.jsx
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||
import NetworkToggle from './NetworkToggle';
|
||||
|
||||
describe('NetworkToggle', () => {
|
||||
@@ -13,4 +13,29 @@ describe('NetworkToggle', () => {
|
||||
render(<NetworkToggle />);
|
||||
await waitFor(() => expect(screen.getByText(/local/i)).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('Local → in-app confirm → Enable calls the enable endpoint (regression: no window.confirm)', async () => {
|
||||
const posts = [];
|
||||
global.fetch = vi.fn((url, opts) => {
|
||||
const u = String(url);
|
||||
if ((opts?.method || 'GET') === 'POST') posts.push(u);
|
||||
if (u.endsWith('/system/network/enable')) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: async () => ({ enabled: true, share_port: 5050, pin: '123456', lan_addresses: [] }),
|
||||
});
|
||||
}
|
||||
return Promise.resolve({ ok: true, json: async () => ({ enabled: false }) });
|
||||
});
|
||||
|
||||
render(<NetworkToggle />);
|
||||
const pill = await screen.findByRole('button', { name: /local/i });
|
||||
fireEvent.click(pill); // opens the in-app confirm — must NOT depend on window.confirm
|
||||
const enableBtn = await screen.findByRole('button', { name: /^enable$/i });
|
||||
fireEvent.click(enableBtn);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(posts.some((u) => u.endsWith('/system/network/enable'))).toBe(true),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user