Wire up automatic first-run detection for setup wizard

Wizard now shows automatically on fresh installs. Detection uses a
server-side flag (setup.completed) plus download_source.mode as a
fallback for existing users who configured settings before the wizard
existed. Config.json template defaults no longer fool the check.

Script load order fixed — setup-wizard.js loads before script.js so
openSetupWizard exists when DOMContentLoaded fires. Both finish and
skip paths set the server flag and localStorage, then continue app
initialization via callback.
This commit is contained in:
Broque Thomas 2026-04-11 23:03:51 -07:00
parent 08de91685d
commit eff22f55d7
4 changed files with 71 additions and 23 deletions

View file

@ -6125,18 +6125,22 @@ def get_mirrored_playlists_list():
@app.route('/api/setup/status', methods=['GET'])
def setup_status_endpoint():
"""Check if first-run setup has been completed."""
# Consider setup incomplete if no slskd URL and no download source configured
slskd_url = config_manager.get('soulseek.slskd_url', '')
# The setup wizard sets this flag when completed. download_source.mode is only
# set by user action (wizard or settings page), never by config.json defaults.
setup_done = config_manager.get('setup.completed', False)
download_mode = config_manager.get('download_source.mode', '')
transfer_path = config_manager.get('soulseek.transfer_path', '')
has_any_config = bool(slskd_url) or bool(download_mode) or bool(transfer_path)
# Either the explicit flag or a user-configured download source means setup is done
has_user_config = bool(setup_done) or bool(download_mode)
return jsonify({
"setup_complete": has_any_config,
"has_download_source": bool(download_mode),
"has_slskd": bool(slskd_url),
"has_transfer_path": bool(transfer_path),
"setup_complete": has_user_config,
})
@app.route('/api/setup/complete', methods=['POST'])
def setup_complete_endpoint():
"""Mark first-run setup as completed."""
config_manager.set('setup.completed', True)
return jsonify({"success": True})
@app.route('/api/test-connection', methods=['POST'])
def test_connection_endpoint():
data = request.get_json()

View file

@ -7351,6 +7351,7 @@
<script src="{{ url_for('static', filename='vendor/socket.io.min.js') }}"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
<script src="{{ url_for('static', filename='setup-wizard.js') }}"></script>
<script src="{{ url_for('static', filename='script.js') }}"></script>
<!-- Notification bell + floating helper toggle — always accessible above modals -->
<!-- Global Search Bar — Spotlight-style search from anywhere -->
@ -7376,7 +7377,6 @@
<script src="{{ url_for('static', filename='helper.js') }}"></script>
<script src="{{ url_for('static', filename='particles.js') }}"></script>
<script src="{{ url_for('static', filename='worker-orbs.js') }}"></script>
<script src="{{ url_for('static', filename='setup-wizard.js') }}"></script>
</body>
</html>

View file

@ -2616,6 +2616,36 @@ async function checkAdminPinRequired() {
document.addEventListener('DOMContentLoaded', async function () {
console.log('SoulSync WebUI initializing...');
// Check if first-run setup wizard should be shown
const params = new URLSearchParams(window.location.search);
const forceSetup = params.get('setup') === '1';
let showWizard = forceSetup;
if (!forceSetup) {
try {
const setupResp = await fetch('/api/setup/status');
const setupData = await setupResp.json();
if (!setupData.setup_complete) {
showWizard = true;
localStorage.removeItem('soulsync_setup_complete');
}
} catch (e) {
console.warn('Setup status check failed, continuing normal init:', e);
}
}
if (showWizard && typeof openSetupWizard === 'function') {
window._onSetupWizardComplete = function () {
_continueAppInit();
};
openSetupWizard();
return; // Defer init until wizard closes
}
_continueAppInit();
});
async function _continueAppInit() {
// Initialize profile management UI handlers
initProfileManagement();
@ -2627,7 +2657,7 @@ document.addEventListener('DOMContentLoaded', async function () {
}
initApp();
});
}
function initApp() {
// Initialize components

View file

@ -62,6 +62,16 @@ function openSetupWizard() {
function closeSetupWizard() {
const overlay = document.getElementById('setup-wizard-overlay');
if (overlay) overlay.style.display = 'none';
// Mark as complete so it doesn't show again (server + client)
localStorage.setItem('soulsync_setup_complete', 'true');
fetch('/api/setup/complete', { method: 'POST' }).catch(() => {});
// Continue app initialization if wizard was shown on first run
if (typeof window._onSetupWizardComplete === 'function') {
window._onSetupWizardComplete();
window._onSetupWizardComplete = null;
}
}
// ---- Navigation ----
@ -957,12 +967,26 @@ async function _wizardFinish() {
console.error('Wizard final save error:', e);
}
// Mark setup complete on both server and client
try {
await fetch('/api/setup/complete', { method: 'POST' });
} catch (e) {
console.error('Failed to mark setup complete on server:', e);
}
localStorage.setItem('soulsync_setup_complete', 'true');
closeSetupWizard();
const overlay = document.getElementById('setup-wizard-overlay');
if (overlay) overlay.style.display = 'none';
// Reload settings into the main UI
if (typeof loadSettings === 'function') loadSettings();
if (typeof showToast === 'function') showToast('Setup complete — welcome to SoulSync!', 'success');
// Continue app initialization if wizard was shown on first run
if (typeof window._onSetupWizardComplete === 'function') {
window._onSetupWizardComplete();
window._onSetupWizardComplete = null;
}
}
// ---- Utility ----
@ -973,18 +997,8 @@ function _escHtml(str) {
}
// ---- Dev Trigger ----
// Open wizard with: openSetupWizard() from console, or ?setup=1 URL param
(function _checkWizardAutoOpen() {
const params = new URLSearchParams(window.location.search);
if (params.get('setup') === '1') {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => setTimeout(openSetupWizard, 300));
} else {
setTimeout(openSetupWizard, 300);
}
}
})();
// Open wizard manually: openSetupWizard() from console, or ?setup=1 URL param
// First-run auto-detection is handled in script.js DOMContentLoaded
// Expose globally
window.openSetupWizard = openSetupWizard;