Add public read-only mode toggle
This commit is contained in:
28
TODO.md
28
TODO.md
@@ -43,20 +43,20 @@ Concrete follow-up work for Jellomator, prioritized by implementation risk and u
|
||||
|
||||
## P2 - UX and Product Improvements
|
||||
|
||||
- Replace browser `alert()` with inline form errors/toasts.
|
||||
- Show server errors near submit controls.
|
||||
- Add success toasts for create/update/delete.
|
||||
- Remove forced reload in auth forms.
|
||||
- Replace `location.reload()` with state refresh only.
|
||||
- Keep SPA navigation predictable on setup/login/logout.
|
||||
- Add drag-and-drop ordering in admin.
|
||||
- Persist `sort_order` updates.
|
||||
- Provide keyboard-accessible move controls as fallback.
|
||||
- Add duplicate/cloning for links.
|
||||
- Pre-fill form from an existing link.
|
||||
- Save as new record with unique name validation.
|
||||
- Add public read-only mode toggle.
|
||||
- Hide admin entry points and editing affordances for non-admin view.
|
||||
- [x] Replace browser `alert()` with inline form errors/toasts.
|
||||
- [x] Show server errors near submit controls.
|
||||
- [x] Add success toasts for create/update/delete.
|
||||
- [x] Remove forced reload in auth forms.
|
||||
- [x] Replace `location.reload()` with state refresh only.
|
||||
- [x] Keep SPA navigation predictable on setup/login/logout.
|
||||
- [x] Add drag-and-drop ordering in admin.
|
||||
- [x] Persist `sort_order` updates.
|
||||
- [x] Provide keyboard-accessible move controls as fallback.
|
||||
- [x] Add duplicate/cloning for links.
|
||||
- [x] Pre-fill form from an existing link.
|
||||
- [x] Save as new record with unique name validation.
|
||||
- [x] Add public read-only mode toggle.
|
||||
- [x] Hide admin entry points and editing affordances for non-admin view.
|
||||
|
||||
## P3 - Nice-to-Have
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export default function App() {
|
||||
const [page, setPage] = useState<Page>('loading');
|
||||
const [query, setQuery] = useState('');
|
||||
const [toast, setToast] = useState<{ message: string; tone: 'success' | 'error' } | null>(null);
|
||||
const [publicMode, setPublicMode] = useState<boolean>(() => window.localStorage.getItem('public_mode') === '1');
|
||||
|
||||
async function refresh() {
|
||||
const current = await api.request<SetupState>('/api/me');
|
||||
@@ -29,7 +30,7 @@ export default function App() {
|
||||
if (!current.current_user) {
|
||||
setPage(path.startsWith('/admin') ? 'login' : 'dashboard');
|
||||
} else {
|
||||
setPage(path.startsWith('/admin') ? 'admin' : 'dashboard');
|
||||
setPage(path.startsWith('/admin') && !publicMode ? 'admin' : 'dashboard');
|
||||
}
|
||||
setLinks(await api.request<LinkItem[]>('/api/links'));
|
||||
}
|
||||
@@ -43,7 +44,7 @@ export default function App() {
|
||||
|
||||
useEffect(() => {
|
||||
refresh().catch(() => setPage('setup'));
|
||||
}, []);
|
||||
}, [publicMode]);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePopState = () => {
|
||||
@@ -107,6 +108,14 @@ export default function App() {
|
||||
query={query}
|
||||
setQuery={setQuery}
|
||||
canLogout={Boolean(state?.current_user)}
|
||||
canAdmin={Boolean(state?.current_user) && !publicMode}
|
||||
publicMode={publicMode}
|
||||
onTogglePublicMode={() => {
|
||||
const next = !publicMode;
|
||||
setPublicMode(next);
|
||||
window.localStorage.setItem('public_mode', next ? '1' : '0');
|
||||
if (next && window.location.pathname.startsWith('/admin')) nav('/');
|
||||
}}
|
||||
onAdmin={() => nav('/admin')}
|
||||
onLogout={async () => {
|
||||
await api.request('/api/logout', { method: 'POST' });
|
||||
@@ -155,6 +164,9 @@ function Dashboard({
|
||||
query,
|
||||
setQuery,
|
||||
canLogout,
|
||||
canAdmin,
|
||||
publicMode,
|
||||
onTogglePublicMode,
|
||||
onAdmin,
|
||||
onLogout,
|
||||
}: {
|
||||
@@ -162,6 +174,9 @@ function Dashboard({
|
||||
query: string;
|
||||
setQuery: (value: string) => void;
|
||||
canLogout: boolean;
|
||||
canAdmin: boolean;
|
||||
publicMode: boolean;
|
||||
onTogglePublicMode: () => void;
|
||||
onAdmin: () => void;
|
||||
onLogout: () => Promise<void>;
|
||||
}) {
|
||||
@@ -191,8 +206,11 @@ function Dashboard({
|
||||
<header className="row-between">
|
||||
<h1 className="title-sm">Jellomator</h1>
|
||||
<div className="flex items-center gap-2">
|
||||
<button type="button" className="btn-subtle" onClick={onTogglePublicMode}>
|
||||
{publicMode ? 'Exit public mode' : 'Public mode'}
|
||||
</button>
|
||||
{canLogout ? <button type="button" className="btn-subtle" onClick={onLogout}>Logout</button> : null}
|
||||
<button type="button" className="btn-subtle" onClick={onAdmin}>Admin</button>
|
||||
{canAdmin ? <button type="button" className="btn-subtle" onClick={onAdmin}>Admin</button> : null}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user