diff --git a/src/components/SettingsPanel.jsx b/src/components/SettingsPanel.jsx
index d3ebefb0..3295c080 100644
--- a/src/components/SettingsPanel.jsx
+++ b/src/components/SettingsPanel.jsx
@@ -2812,6 +2812,213 @@ export const SettingsPanel = ({
+
+ {/* QRZ.com XML API Credentials */}
+
+
+ 📡 QRZ.com Callsign Lookup
+ {qrzStatus?.configured && (
+
+ {qrzStatus.hasSession ? '● Connected' : '○ Configured'}
+ {qrzStatus.source === 'env' ? ' (env)' : ''}
+
+ )}
+
+
+ Enables precise station locations from{' '}
+
+ QRZ.com
+ {' '}
+ user profiles (user-supplied coordinates, geocoded addresses, grid squares). Without this, locations
+ fall back to HamQTH (country-level only). Requires a QRZ Logbook Data subscription.
+
+
Note this is a server setting and is not related to clicking a callsign to go to
+ qrz.com. If you are not running a server, you will likely not have the permissions to change this.
+
+ {qrzStatus?.source === 'env' ? (
+
+ ✓ Credentials configured via{' '}
+
+ QRZ_USERNAME
+ {' '}
+ /{' '}
+
+ QRZ_PASSWORD
+ {' '}
+ in .env file
+ {qrzStatus.lookupCount > 0 && (
+
+ {' '}
+ — {qrzStatus.lookupCount} lookups this session
+
+ )}
+
+ ) : (
+ <>
+
+ setQrzUsername(e.target.value)}
+ style={{
+ flex: 1,
+ padding: '8px 12px',
+ background: 'var(--bg-primary)',
+ border: '1px solid var(--border-color)',
+ borderRadius: '4px',
+ color: 'var(--text-primary)',
+ fontSize: '12px',
+ fontFamily: 'var(--font-mono)',
+ boxSizing: 'border-box',
+ }}
+ />
+ setQrzPassword(e.target.value)}
+ style={{
+ flex: 1,
+ padding: '8px 12px',
+ background: 'var(--bg-primary)',
+ border: '1px solid var(--border-color)',
+ borderRadius: '4px',
+ color: 'var(--text-primary)',
+ fontSize: '12px',
+ fontFamily: 'var(--font-mono)',
+ boxSizing: 'border-box',
+ }}
+ />
+
+
+ {
+ setQrzTesting(true);
+ setQrzMessage(null);
+ try {
+ const res = await fetch('/api/qrz/configure', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ username: qrzUsername.trim(), password: qrzPassword.trim() }),
+ });
+ const data = await res.json();
+ if (data.success) {
+ setQrzMessage({ type: 'success', text: 'Connected to QRZ.com successfully!' });
+ setQrzPassword('');
+ // Refresh status
+ const st = await fetch('/api/qrz/status').then((r) => r.json());
+ setQrzStatus(st);
+ } else {
+ setQrzMessage({ type: 'error', text: data.error || 'Login failed' });
+ }
+ } catch (e) {
+ setQrzMessage({ type: 'error', text: 'Connection error' });
+ }
+ setQrzTesting(false);
+ }}
+ style={{
+ padding: '6px 14px',
+ fontSize: '11px',
+ fontWeight: '600',
+ borderRadius: '4px',
+ border: 'none',
+ cursor: qrzTesting || !qrzUsername.trim() || !qrzPassword.trim() ? 'not-allowed' : 'pointer',
+ background: 'var(--accent-amber)',
+ color: '#000',
+ opacity: qrzTesting || !qrzUsername.trim() || !qrzPassword.trim() ? 0.5 : 1,
+ }}
+ >
+ {qrzTesting ? 'Testing...' : 'Save & Test'}
+
+ {qrzStatus?.configured && qrzStatus.source !== 'env' && (
+ {
+ await fetch('/api/qrz/remove', { method: 'POST' });
+ setQrzUsername('');
+ setQrzPassword('');
+ setQrzMessage(null);
+ const st = await fetch('/api/qrz/status').then((r) => r.json());
+ setQrzStatus(st);
+ }}
+ style={{
+ padding: '6px 12px',
+ fontSize: '11px',
+ borderRadius: '4px',
+ border: '1px solid var(--border-color)',
+ cursor: 'pointer',
+ background: 'transparent',
+ color: 'var(--text-muted)',
+ }}
+ >
+ Remove
+
+ )}
+ {qrzStatus?.configured && qrzStatus.lookupCount > 0 && (
+
+ {qrzStatus.lookupCount} lookups this session
+
+ )}
+
+ {qrzMessage && (
+
+ {qrzMessage.type === 'success' ? '✓' : '✗'} {qrzMessage.text}
+
+ )}
+ >
+ )}
+
)}
@@ -4215,213 +4422,6 @@ export const SettingsPanel = ({
)}
- {/* QRZ.com XML API Credentials */}
-
-
- 📡 QRZ.com Callsign Lookup
- {qrzStatus?.configured && (
-
- {qrzStatus.hasSession ? '● Connected' : '○ Configured'}
- {qrzStatus.source === 'env' ? ' (env)' : ''}
-
- )}
-
-
- Enables precise station locations from{' '}
-
- QRZ.com
- {' '}
- user profiles (user-supplied coordinates, geocoded addresses, grid squares). Without this, locations
- fall back to HamQTH (country-level only). Requires a QRZ Logbook Data subscription.
-
-
Note this is a server setting and is not related to clicking a callsign to go to
- qrz.com. If you are not running a server, you will likely not have the permissions to change this.
-
- {qrzStatus?.source === 'env' ? (
-
- ✓ Credentials configured via{' '}
-
- QRZ_USERNAME
- {' '}
- /{' '}
-
- QRZ_PASSWORD
- {' '}
- in .env file
- {qrzStatus.lookupCount > 0 && (
-
- {' '}
- — {qrzStatus.lookupCount} lookups this session
-
- )}
-
- ) : (
- <>
-
- setQrzUsername(e.target.value)}
- style={{
- flex: 1,
- padding: '8px 12px',
- background: 'var(--bg-primary)',
- border: '1px solid var(--border-color)',
- borderRadius: '4px',
- color: 'var(--text-primary)',
- fontSize: '12px',
- fontFamily: 'var(--font-mono)',
- boxSizing: 'border-box',
- }}
- />
- setQrzPassword(e.target.value)}
- style={{
- flex: 1,
- padding: '8px 12px',
- background: 'var(--bg-primary)',
- border: '1px solid var(--border-color)',
- borderRadius: '4px',
- color: 'var(--text-primary)',
- fontSize: '12px',
- fontFamily: 'var(--font-mono)',
- boxSizing: 'border-box',
- }}
- />
-
-
- {
- setQrzTesting(true);
- setQrzMessage(null);
- try {
- const res = await fetch('/api/qrz/configure', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ username: qrzUsername.trim(), password: qrzPassword.trim() }),
- });
- const data = await res.json();
- if (data.success) {
- setQrzMessage({ type: 'success', text: 'Connected to QRZ.com successfully!' });
- setQrzPassword('');
- // Refresh status
- const st = await fetch('/api/qrz/status').then((r) => r.json());
- setQrzStatus(st);
- } else {
- setQrzMessage({ type: 'error', text: data.error || 'Login failed' });
- }
- } catch (e) {
- setQrzMessage({ type: 'error', text: 'Connection error' });
- }
- setQrzTesting(false);
- }}
- style={{
- padding: '6px 14px',
- fontSize: '11px',
- fontWeight: '600',
- borderRadius: '4px',
- border: 'none',
- cursor: qrzTesting || !qrzUsername.trim() || !qrzPassword.trim() ? 'not-allowed' : 'pointer',
- background: 'var(--accent-amber)',
- color: '#000',
- opacity: qrzTesting || !qrzUsername.trim() || !qrzPassword.trim() ? 0.5 : 1,
- }}
- >
- {qrzTesting ? 'Testing...' : 'Save & Test'}
-
- {qrzStatus?.configured && qrzStatus.source !== 'env' && (
- {
- await fetch('/api/qrz/remove', { method: 'POST' });
- setQrzUsername('');
- setQrzPassword('');
- setQrzMessage(null);
- const st = await fetch('/api/qrz/status').then((r) => r.json());
- setQrzStatus(st);
- }}
- style={{
- padding: '6px 12px',
- fontSize: '11px',
- borderRadius: '4px',
- border: '1px solid var(--border-color)',
- cursor: 'pointer',
- background: 'transparent',
- color: 'var(--text-muted)',
- }}
- >
- Remove
-
- )}
- {qrzStatus?.configured && qrzStatus.lookupCount > 0 && (
-
- {qrzStatus.lookupCount} lookups this session
-
- )}
-
- {qrzMessage && (
-
- {qrzMessage.type === 'success' ? '✓' : '✗'} {qrzMessage.text}
-
- )}
- >
- )}
-
-
{/* Open-Meteo API Key (optional) */}