From c0c3c92b559c5bc53d38591d44b3b7c214562195 Mon Sep 17 00:00:00 2001 From: dvirlabs Date: Fri, 4 Jul 2025 07:26:24 +0300 Subject: [PATCH] Add toast alerts and fix bugs --- backend/__pycache__/main.cpython-313.pyc | Bin 7087 -> 7258 bytes backend/apps.yaml | 4 + backend/main.py | 19 +++-- frontend/package-lock.json | 23 +++++- frontend/package.json | 3 +- frontend/src/App.jsx | 92 +++++++++++++++++------ frontend/src/components/AppModal.jsx | 61 ++++++++++++--- frontend/src/services/api.js | 4 +- 8 files changed, 165 insertions(+), 41 deletions(-) diff --git a/backend/__pycache__/main.cpython-313.pyc b/backend/__pycache__/main.cpython-313.pyc index 6ae05f8249b6f6c5a64b90f6bee804efa4fdc503..1be8463ec9cbd14bdebd59cb12cd61bb56982f87 100644 GIT binary patch delta 1265 zcmb7^UuYvm9LHy}`Lo&VCTWspFWYJolWUu%jgd3$*=uYI>0PUB(XQ9Oks~zQSnMTX zvPD|_Q@jTY;#v9iLElbMxC6mT;P8AosCn{f18UFUL%Fv@;Q|&B^ud|NKg9>Zf&I*P ze*9*Bv)`TB&juUkrJp59K(wvQ6<+%Bb7>DRi;tmfM+&nbLbK;v{b{P6eg{J2k*sGn zeLCLKs`szCs!u_wKNCohWyA0IH=M5iYWfg6w(Plbxsuq-U0sI{%%664)69yKVjCTa z?S?#TIMRkWO%xicu3H2XeiB}>eN5TOk3f$t2wS`~l{Ks8ej7qri)vvjs8+%k%rRSo zA1jx~i#pkSaDj#!wu-i*ua$}oj#iy%NF}mXSS#k%uM~4vH4>mHF+1$@QG5XmJ{)0l zDy;Hb=$_=hHg(U=5Q=V3@1A?#_ukNV=4jPw|JsB{pzfH%!_XrpESG5hSan&vgz-l( z?K!~L!0nxA^P-7Ggf>hFmFF&7RjUO?g+N;x_t13K@_vLu=rX+PZHI1$4}Nmm&4Z{F zFwM3@T~MIc)gKTMT9#W==zH2#tO`l2I-WAYkt7Gi6fMA-2^I0OwWaCzI|HM>CpbxU z!i$a|z7MZC!f((~5=T**3!+sVbIp)PX*vlqgczQtxJcqmj!?YtV2_@Lt#;~Ys94fN ztEG)%KBSjIT7FHxWokGodQRV{Tv;jQwT5k_RMa(U{3x3~M$xcsl=C@V%f}=VW>LsdlL(~1HoH~pQ)FT6jk%=$P z-i_Uf-A&v{eCwTr*V|IwA6y+bx&4AMGH0ab>#hZZUx00Y93HlHdHcAUUCyMrTAz>^ zvenYUxD$FjQM^-~_T0uNMtf>pP;uI- zytb}^=8nd>b**^HpZ~CC77Fcy!W>;lr%$DeTJth^{n>vkSTItHb=Q)?FHsBTg9>*b zhB9&MLDEY3)u5b7ay3QB^w?@iVI0FR{_7Sp#di3PEaQ3jMDDdL(c$&#H}dlsFG4b) zq#9g7(@7@{6L#-NjDa3o)G(Jz6`iolK-l*pY-i*g!xC%+MsWph2UfYGHZO#gY~Xv* zy?f^EnL{yL7sEH6t&5Lu+nZKo;$d5nElr-OeI*cZnz6LAc8q9u?Bwv-oy}uJyQT>D ll?YCNwIf1*4ZR%`egR?gA@bCbXLsO6m%&u5$-_=Z_aExdBz6D* delta 1078 zcmb7?O-vI(6vt<}bh~YLyZzX*v{c(7mMq0usii0)s0B3;REl!2(Nw!23AW92jlq~` zVuB<_qj{c;2Z>)76EVH}eUVd%F_NB)C%IwdV4??SRE&vwaFhMLH}Ac9yYD|!>wK7! zK1q@d(MR7meq!e%={n}z!zq}PO+H3^T+ZLr00442@hV*vNV2S)^26fZn+ z2)G$!#fLp`&E|(`d4t4LDR9Ob#~IsZ6NEh~wnNOJ!h5Bi6EhOTok9Ex_B+Elx{Bjw z)XfFa7}gv^WG($F2{MEjx+(fel)2j}M$UNYq@hC52#pm_74jjY7}E0-#?p0q0;GqP z^-|O_6 zb@QrjaL=>yPhFC-2OQ4eFKy-_<&7TwZ*9~+w8`s}x}lRjY_{Lj`5p$pxDJ-Qvif%^ zhCr!L*@x-Q0+XV7Rf++4TA%$feS1|-%w?)_XI1E=(~kxf?ztFBujik~dCC`?)O0KN zQn96*EH7Ja=`OgdUJ2JM$BP9+FBlp}WZJpL?>x9`pl^iGOM^5M$-w@GR`V!b%v8=b z9KiNNtcV-e4HAF_pFc`kXn<%8DVC}$70U);x0SGiBkY$+f?*Uw{$6@vbN(^&GLPH} z%=@$4J8XfTM%DL`N0RGm!=<6OVt7Fe-x^pDH=eh$?BPbAPprez%)|IH=B-Xiw`a$oJZ6{#{$Tjn7VX!!;JQUW>v diff --git a/backend/apps.yaml b/backend/apps.yaml index ea06927..8c186c3 100644 --- a/backend/apps.yaml +++ b/backend/apps.yaml @@ -35,3 +35,7 @@ sections: name: Vault url: https://vault.dvirlabs.com name: Dev-tools +- apps: [] + name: fgbhn +- apps: [] + name: dfgb diff --git a/backend/main.py b/backend/main.py index 9be818c..b60c0e4 100644 --- a/backend/main.py +++ b/backend/main.py @@ -20,7 +20,6 @@ app.add_middleware( load_dotenv() -# ENV MINIO_ENDPOINT = os.getenv("MINIO_ENDPOINT") MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY") MINIO_SECRET_KEY = os.getenv("MINIO_SECRET_KEY") @@ -34,12 +33,13 @@ minio_client = Minio( ) BUCKET = MINIO_BUCKET or "navix-icons" +APPS_FILE = Path(__file__).parent / "apps.yaml" + @router.get("/") def root(): return {"message": "Welcome to the FastAPI application!"} -APPS_FILE = Path(__file__).parent / "apps.yaml" @router.get("/apps") def get_apps(): @@ -48,15 +48,19 @@ def get_apps(): with open(APPS_FILE, "r") as f: return yaml.safe_load(f) + class AppData(BaseModel): name: str icon: str description: str url: str + class AppEntry(BaseModel): section: str app: AppData + original_name: str | None = None + @router.post("/add_app") def add_app(entry: AppEntry): @@ -81,6 +85,7 @@ def add_app(entry: AppEntry): return {"status": "added"} + @router.post("/edit_app") def edit_app(entry: AppEntry): if not APPS_FILE.exists(): @@ -93,20 +98,21 @@ def edit_app(entry: AppEntry): for section in current["sections"]: if section["name"] == entry.section: for i, app in enumerate(section["apps"]): - if app["name"] == entry.app.name: + if app["name"] == (entry.original_name or entry.app.name): section["apps"][i] = entry.app.dict() updated = True break break if not updated: - return {"error": "App not found to edit"}, 404 + return JSONResponse(status_code=404, content={"error": "App not found to edit"}) with open(APPS_FILE, "w") as f: yaml.safe_dump(current, f) return {"status": "updated"} + @router.post("/delete_app") def delete_app(entry: AppEntry): if not APPS_FILE.exists(): @@ -125,7 +131,7 @@ def delete_app(entry: AppEntry): break if not deleted: - return {"error": "App not found to delete"}, 404 + return JSONResponse(status_code=404, content={"error": "App not found to delete"}) with open(APPS_FILE, "w") as f: yaml.safe_dump(current, f) @@ -138,10 +144,11 @@ def get_public_icon_url(filename: str): url = f"https://{MINIO_ENDPOINT}/{BUCKET}/{filename}" return JSONResponse(content={"url": url}) + app.include_router(router, prefix="/api") +# ✅ This is the missing part: if __name__ == "__main__": import uvicorn uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) - diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0d90a6d..885f3d7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,7 +11,8 @@ "react": "^19.1.0", "react-beautiful-dnd": "^13.1.1", "react-dom": "^19.1.0", - "react-icons": "^5.5.0" + "react-icons": "^5.5.0", + "react-toastify": "^11.0.5" }, "devDependencies": { "@eslint/js": "^9.25.0", @@ -1488,6 +1489,14 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2440,6 +2449,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-toastify": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz", + "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, "node_modules/redux": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index e2a3347..45ce11c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,7 +13,8 @@ "react": "^19.1.0", "react-beautiful-dnd": "^13.1.1", "react-dom": "^19.1.0", - "react-icons": "^5.5.0" + "react-icons": "^5.5.0", + "react-toastify": "^11.0.5" }, "devDependencies": { "@eslint/js": "^9.25.0", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 4077ff1..193523c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -4,8 +4,15 @@ import SectionGrid from './components/SectionGrid'; import AppModal from './components/AppModal'; import ConfirmDialog from './components/ConfirmDialog'; import Clock from './components/Clock'; +import { ToastContainer, toast } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; import { IoIosAdd } from 'react-icons/io'; -import { fetchSections, addAppToSection, editAppInSection, deleteAppFromSection } from './services/api'; +import { + fetchSections, + addAppToSection, + editAppInSection, + deleteAppFromSection, +} from './services/api'; function App() { const [sections, setSections] = useState([]); @@ -14,10 +21,13 @@ function App() { const [confirmData, setConfirmData] = useState(null); const loadSections = () => { - fetchSections() - .then(data => setSections(data.sections || [])) - .catch(err => console.error('Failed to fetch sections:', err)); - }; + fetchSections() + .then(data => { + const filtered = (data.sections || []).filter(section => (section.apps?.length ?? 0) > 0); + setSections(filtered); + }) + .catch(err => console.error('Failed to fetch sections:', err)); +}; useEffect(() => { loadSections(); @@ -26,7 +36,7 @@ function App() { const handleDelete = (app) => { setConfirmData({ message: `Are you sure you want to delete "${app.name}"?`, - app + app, }); }; @@ -34,16 +44,48 @@ function App() { if (!confirmData?.app) return; try { - await deleteAppFromSection({ section: confirmData.app.section, app: confirmData.app }); + await deleteAppFromSection({ + section: confirmData.app.section, + app: confirmData.app, + }); + toast.error(`App "${confirmData.app.name}" deleted successfully!`); loadSections(); } catch (err) { console.error('Failed to delete app:', err); - alert('Failed to delete app'); + toast.error('Failed to delete app'); } finally { setConfirmData(null); } }; + const handleAddSubmit = async (data) => { + try { + await addAppToSection(data); + toast.success(`App "${data.app.name}" added successfully!`); + setShowAdd(false); + loadSections(); + } catch (err) { + console.error('Failed to add app:', err); + toast.error('Failed to add app'); + } + }; + + const handleEditSubmit = async (data) => { + try { + await editAppInSection({ + section: data.section, + app: data.app, + original_name: data.original_name || data.app.name, + }); + toast.warning(`App "${data.app.name}" updated successfully!`); + setEditData(null); + loadSections(); + } catch (err) { + console.error('Failed to update app:', err); + toast.error('Failed to update app'); + } + }; + return (
@@ -55,11 +97,8 @@ function App() { {showAdd && ( { - setShowAdd(false); - loadSections(); - }} + onSubmit={handleAddSubmit} + onClose={() => setShowAdd(false)} sections={sections} /> )} @@ -68,11 +107,8 @@ function App() { { - setEditData(null); - loadSections(); - }} + onSubmit={handleEditSubmit} + onClose={() => setEditData(null)} sections={sections} /> )} @@ -85,18 +121,32 @@ function App() { /> )} - {sections.map(section => ( + {sections.map((section) => ( + setEditData({ ...app, section: section.name, original_name: app.name }) + } onDelete={handleDelete} /> ))} setShowAdd(true)} /> + +
); } -export default App; \ No newline at end of file +export default App; diff --git a/frontend/src/components/AppModal.jsx b/frontend/src/components/AppModal.jsx index 6823f3c..07efc06 100644 --- a/frontend/src/components/AppModal.jsx +++ b/frontend/src/components/AppModal.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import '../style/AddAppModal.css'; function AppModal({ mode = 'add', initialData = {}, onSubmit, onClose, sections = [] }) { @@ -12,7 +12,24 @@ function AppModal({ mode = 'add', initialData = {}, onSubmit, onClose, sections const handleSubmit = async (e) => { e.preventDefault(); - await onSubmit({ section, app: { name, icon, description, url } }); + + const appData = { + name, + icon, + description, + url, + }; + + const payload = { + section, + app: appData, + }; + + if (mode === 'edit') { + payload.original_name = initialData.original_name || initialData.name; + } + + await onSubmit(payload); setOpen(false); if (onClose) onClose(); }; @@ -40,8 +57,10 @@ function AppModal({ mode = 'add', initialData = {}, onSubmit, onClose, sections disabled={mode === 'edit'} > - {sections.map(s => ( - + {sections.map((s) => ( + ))} @@ -51,15 +70,37 @@ function AppModal({ mode = 'add', initialData = {}, onSubmit, onClose, sections type="text" placeholder="New section name" value={section} - onChange={e => setSection(e.target.value)} + onChange={(e) => setSection(e.target.value)} required /> )} - setName(e.target.value)} required /> - setIcon(e.target.value)} /> - setDescription(e.target.value)} /> - setUrl(e.target.value)} required /> + setName(e.target.value)} + required + /> + setIcon(e.target.value)} + /> + setDescription(e.target.value)} + /> + setUrl(e.target.value)} + required + /> @@ -67,4 +108,4 @@ function AppModal({ mode = 'add', initialData = {}, onSubmit, onClose, sections ); } -export default AppModal; \ No newline at end of file +export default AppModal; diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index 6794a9e..5e7f656 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -23,11 +23,11 @@ export async function getIconUrl(filename) { return data.url; } -export async function editAppInSection({ section, app }) { +export async function editAppInSection({ section, app, original_name }) { const res = await fetch(`${API_BASE}/edit_app`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ section, app }) + body: JSON.stringify({ section, app, original_name }) }); if (!res.ok) throw new Error(await res.text());