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:
@@ -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
|
||||||
|
|||||||
@@ -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] = {}
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user