diff --git a/bincio/serve/routers/ideas.py b/bincio/serve/routers/ideas.py index e864e88..9c17e8e 100644 --- a/bincio/serve/routers/ideas.py +++ b/bincio/serve/routers/ideas.py @@ -126,6 +126,28 @@ async def toggle_idea_status( return JSONResponse({"status": idea["status"]}) +@router.post("/api/ideas/{idea_id}/reopen") +async def reopen_idea( + idea_id: str, + bincio_session: Optional[str] = Cookie(default=None), +) -> JSONResponse: + user = deps._require_user(bincio_session) + if not user.is_admin: + raise HTTPException(403, "Forbidden") + dd = deps._get_data_dir() + path = _ideas_dir(dd) / f"{idea_id}.json" + if not path.exists(): + raise HTTPException(404, "Not found") + with open(path, "r+", encoding="utf-8") as f: + _fcntl.flock(f, _fcntl.LOCK_EX) + idea = json.load(f) + idea["status"] = "open" + f.seek(0) + f.truncate() + json.dump(idea, f, ensure_ascii=False, indent=2) + return JSONResponse({"status": "open"}) + + @router.post("/api/ideas/{idea_id}/decline") async def decline_idea( idea_id: str, diff --git a/site/src/components/IdeasPage.svelte b/site/src/components/IdeasPage.svelte index 066118e..975eac5 100644 --- a/site/src/components/IdeasPage.svelte +++ b/site/src/components/IdeasPage.svelte @@ -184,6 +184,21 @@ return s === 'awaiting' ? 0 : s === 'done' ? 2 : s === 'declined' ? 3 : 1; } + async function reopenIdea(idea: Idea) { + const r = await fetch(`/api/ideas/${idea.id}/reopen`, { + method: 'POST', + credentials: 'include', + }); + if (r.ok) { + idea.status = 'open'; + ideas = [...ideas].sort((a, b) => + statusOrder(a.status) - statusOrder(b.status) || + b.vote_count - a.vote_count || + b.created_at - a.created_at + ); + } + } + async function declineIdea(idea: Idea) { const r = await fetch(`/api/ideas/${idea.id}/decline`, { method: 'POST', @@ -408,6 +423,14 @@ style="color: {btnColor}; border: 1px solid {borderColor}" title="Cycle status" >{nextLabel} + {#if awaiting} + + {/if}