Implement forced first-login setup and dropzone UX fixes

#4 Forced First-Login Setup:
- Add before_request hook to redirect to /setup if no users exist
- Skip redirect for static files and setup routes

#5 Dropzone UX Fixes:
- Make preview images clickable to replace file
- Make entire drop zone clickable
- QR zone resets after 2s on error, allowing retry
- Clear file input on error so same file can be re-selected

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee
2026-01-05 18:28:28 -05:00
parent b1ddfaa75b
commit 559dcd3dcf
3 changed files with 63 additions and 9 deletions

View File

@@ -167,21 +167,24 @@ Polish and UX improvements after the 4.1.1 stability release.
## 4. Forced First-Login Setup ## 4. Forced First-Login Setup
**Status:** Planned **Status:** Done
**Problem:** Users can navigate the app without creating an admin account first. Should force password setup before anything else. **Problem:** Users can navigate the app without creating an admin account first. Should force password setup before anything else.
**Solution:** Middleware/decorator that redirects to setup page if no users exist. **Solution:** Middleware/decorator that redirects to setup page if no users exist.
### Files to Modify ### Implementation
- `frontends/web/app.py` (add before_request check) - Added `@app.before_request` hook that redirects to /setup if no users exist
- `frontends/web/templates/setup.html` (ensure it blocks other nav) - Skips redirect for static files and setup-related routes
### Files Modified
- `frontends/web/app.py` (added require_setup before_request hook)
--- ---
## 5. Dropzone UX Fixes ## 5. Dropzone UX Fixes
**Status:** Planned **Status:** Done
**Problem:** Dropzone has some interaction bugs: **Problem:** Dropzone has some interaction bugs:
- Dropzone doesn't clear properly if first QR image fails - Dropzone doesn't clear properly if first QR image fails
@@ -189,9 +192,14 @@ Polish and UX improvements after the 4.1.1 stability release.
**Solution:** Fix JS event handling and state management **Solution:** Fix JS event handling and state management
### Files to Modify ### Implementation
- Added click handler on preview images to trigger file input
- Made entire drop zone clickable (not just label)
- QR zone now resets after 2 seconds on error, allowing retry
- Clear file input on QR error so same file can be re-selected
### Files Modified
- `frontends/web/static/js/stegasoo.js` - `frontends/web/static/js/stegasoo.js`
- `frontends/web/static/css/style.css` (clickable preview)
--- ---
@@ -272,6 +280,6 @@ Polish and UX improvements after the 4.1.1 stability release.
## Notes ## Notes
- Keep 4.1.2 focused - 9 features (2 done) - Keep 4.1.2 focused - 9 features (4 done)
- Don't break DCT compatibility (4.1.1 RS format is stable) - Don't break DCT compatibility (4.1.1 RS format is stable)
- Test on Pi before release - Test on Pi before release

View File

@@ -192,6 +192,24 @@ app.config["HTTPS_ENABLED"] = os.environ.get("STEGASOO_HTTPS_ENABLED", "false").
# Initialize auth module # Initialize auth module
init_auth(app) init_auth(app)
@app.before_request
def require_setup():
"""Force redirect to setup if no users exist (first-run)."""
if not app.config.get("AUTH_ENABLED", True):
return None
# Skip for static files and setup-related routes
if request.endpoint in ("static", "setup", "setup_recovery", None):
return None
# If no users exist, redirect to setup
if not user_exists():
return redirect(url_for("setup"))
return None
# Temporary file storage for sharing (file_id -> {data, timestamp, filename}) # Temporary file storage for sharing (file_id -> {data, timestamp, filename})
TEMP_FILES: dict[str, dict] = {} TEMP_FILES: dict[str, dict] = {}
THUMBNAIL_FILES: dict[str, bytes] = {} THUMBNAIL_FILES: dict[str, bytes] = {}

View File

@@ -99,6 +99,23 @@ const Stegasoo = {
} }
}); });
} }
// Make preview clickable to replace file
if (preview) {
preview.style.cursor = 'pointer';
preview.addEventListener('click', (e) => {
e.stopPropagation();
input.click();
});
}
// Make entire zone clickable (in case label/preview don't cover it)
zone.addEventListener('click', (e) => {
// Only trigger if not clicking directly on the input
if (e.target !== input) {
input.click();
}
});
}); });
}, },
@@ -584,6 +601,17 @@ const Stegasoo = {
<span>No QR code detected</span> <span>No QR code detected</span>
`; `;
} }
// Reset after delay so user can try again
setTimeout(() => {
container.classList.remove('error');
container.classList.add('d-none');
label?.classList.remove('d-none');
// Clear the file input so same file can be re-selected
input.value = '';
// Remove loader
if (loader) loader.remove();
}, 2000);
}); });
}); });
}, },