diff --git a/.playwright-mcp/console-2026-04-14T09-51-59-546Z.log b/.playwright-mcp/console-2026-04-14T09-51-59-546Z.log new file mode 100644 index 0000000..f771fd2 --- /dev/null +++ b/.playwright-mcp/console-2026-04-14T09-51-59-546Z.log @@ -0,0 +1,17 @@ +[ 665ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:3000/node_modules/.vite/deps/react-dom_client.js?v=bf7d8134:14336 +[ 708ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 709ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 715ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 715ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 715ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 715ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 715ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 715ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 715ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 715ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 715ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 716ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 716ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 716ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 716ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 716ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 diff --git a/.playwright-mcp/console-2026-04-14T09-52-33-736Z.log b/.playwright-mcp/console-2026-04-14T09-52-33-736Z.log new file mode 100644 index 0000000..842b82c --- /dev/null +++ b/.playwright-mcp/console-2026-04-14T09-52-33-736Z.log @@ -0,0 +1,17 @@ +[ 358ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:3000/node_modules/.vite/deps/react-dom_client.js?v=bf7d8134:14336 +[ 375ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 375ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 379ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 379ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 379ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 379ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 380ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 380ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 380ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 380ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 380ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 380ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 380ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 380ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 380ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 380ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 diff --git a/.playwright-mcp/console-2026-04-14T09-53-25-466Z.log b/.playwright-mcp/console-2026-04-14T09-53-25-466Z.log new file mode 100644 index 0000000..669e57e --- /dev/null +++ b/.playwright-mcp/console-2026-04-14T09-53-25-466Z.log @@ -0,0 +1,20 @@ +[ 137ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:3000/node_modules/.vite/deps/react-dom_client.js?v=bf7d8134:14336 +[ 143ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 143ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 145ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 145ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 145ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 145ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 145ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 145ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 145ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 145ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 145ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 145ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 145ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 145ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 146ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 146ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 175ms] [ERROR] Failed to load resource: the server responded with a status of 401 (Unauthorized) @ http://localhost:3000/api/auth/session:0 +[ 43606ms] [ERROR] Failed to load resource: the server responded with a status of 401 (Unauthorized) @ http://localhost:3000/api/auth/login:0 +[ 77901ms] [ERROR] Unsupported style property %s. Did you mean %s? &[data-active] &[dataActive] @ http://localhost:3000/node_modules/.vite/deps/react-dom_client.js?v=bf7d8134:1804 diff --git a/.playwright-mcp/console-2026-04-15T00-10-32-890Z.log b/.playwright-mcp/console-2026-04-15T00-10-32-890Z.log new file mode 100644 index 0000000..f1bb14a --- /dev/null +++ b/.playwright-mcp/console-2026-04-15T00-10-32-890Z.log @@ -0,0 +1,18 @@ +[ 240ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:3000/node_modules/.vite/deps/react-dom_client.js?v=bf7d8134:14336 +[ 265ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 265ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 272ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 272ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 272ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 272ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 272ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 272ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 272ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 272ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 272ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 272ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 272ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 272ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 273ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 273ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 428ms] [ERROR] Unsupported style property %s. Did you mean %s? &[data-active] &[dataActive] @ http://localhost:3000/node_modules/.vite/deps/react-dom_client.js?v=bf7d8134:1804 diff --git a/.playwright-mcp/console-2026-04-15T00-12-16-354Z.log b/.playwright-mcp/console-2026-04-15T00-12-16-354Z.log new file mode 100644 index 0000000..b48232e --- /dev/null +++ b/.playwright-mcp/console-2026-04-15T00-12-16-354Z.log @@ -0,0 +1,18 @@ +[ 193ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:3000/node_modules/.vite/deps/react-dom_client.js?v=bf7d8134:14336 +[ 216ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 216ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 222ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 222ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 223ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 223ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 223ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 223ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 223ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 223ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 223ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 223ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 223ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 223ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 223ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 223ms] [ERROR] forwardRef render functions accept exactly two parameters: props and ref. %s Did you forget to use the ref parameter? @ http://localhost:3000/node_modules/.vite/deps/react-B6J-hxuQ.js?v=bf7d8134:644 +[ 279ms] [ERROR] Unsupported style property %s. Did you mean %s? &[data-active] &[dataActive] @ http://localhost:3000/node_modules/.vite/deps/react-dom_client.js?v=bf7d8134:1804 diff --git a/.playwright-mcp/page-2026-04-14T09-52-00-449Z.yml b/.playwright-mcp/page-2026-04-14T09-52-00-449Z.yml new file mode 100644 index 0000000..134745c --- /dev/null +++ b/.playwright-mcp/page-2026-04-14T09-52-00-449Z.yml @@ -0,0 +1,21 @@ +- generic [active] [ref=e1]: + - generic: + - generic: + - generic: Loading... + - generic [ref=e4]: + - generic [ref=e5]: + - img [ref=e6] + - img [ref=e8] + - heading "Bun + Elysia + Vite + React" [level=1] [ref=e16] + - paragraph [ref=e17]: Full-stack starter template with Mantine UI, TanStack Router, and session-based auth. + - generic [ref=e18]: + - link "Login" [ref=e19] [cursor=pointer]: + - /url: /login + - generic [ref=e20]: + - img [ref=e22] + - generic [ref=e26]: Login + - link "Dashboard" [ref=e27] [cursor=pointer]: + - /url: /dashboard + - generic [ref=e28]: + - img [ref=e30] + - generic [ref=e34]: Dashboard \ No newline at end of file diff --git a/.playwright-mcp/page-2026-04-14T09-52-34-199Z.yml b/.playwright-mcp/page-2026-04-14T09-52-34-199Z.yml new file mode 100644 index 0000000..3636b06 --- /dev/null +++ b/.playwright-mcp/page-2026-04-14T09-52-34-199Z.yml @@ -0,0 +1,4 @@ +- generic [active]: + - generic: + - generic: + - generic: Loading... \ No newline at end of file diff --git a/.playwright-mcp/page-2026-04-14T09-53-25-737Z.yml b/.playwright-mcp/page-2026-04-14T09-53-25-737Z.yml new file mode 100644 index 0000000..4df40d9 --- /dev/null +++ b/.playwright-mcp/page-2026-04-14T09-53-25-737Z.yml @@ -0,0 +1,39 @@ +- generic [active] [ref=e1]: + - generic: + - generic: + - generic: Loading... + - generic [ref=e6]: + - heading "Login" [level=2] [ref=e7] + - paragraph [ref=e8]: + - text: "Demo:" + - strong [ref=e9]: superadmin@example.com + - text: / + - strong [ref=e10]: superadmin123 + - text: "or:" + - strong [ref=e11]: user@example.com + - text: / + - strong [ref=e12]: user123 + - generic [ref=e13]: + - generic [ref=e14]: Email * + - generic [ref=e15]: + - img [ref=e17] + - textbox "Email" [ref=e20]: + - /placeholder: email@example.com + - generic [ref=e21]: + - generic [ref=e22]: Password * + - generic [ref=e23]: + - img [ref=e25] + - textbox "Password" [ref=e30] + - button [ref=e32] [cursor=pointer]: + - img [ref=e34] + - button "Sign in" [ref=e36] [cursor=pointer]: + - generic [ref=e37]: + - img [ref=e39] + - generic [ref=e43]: Sign in + - separator [ref=e44]: + - generic [ref=e45]: or + - link "Login with Google" [ref=e46] [cursor=pointer]: + - /url: /api/auth/google + - generic [ref=e47]: + - img [ref=e49] + - generic [ref=e54]: Login with Google \ No newline at end of file diff --git a/.playwright-mcp/page-2026-04-14T09-54-11-031Z.yml b/.playwright-mcp/page-2026-04-14T09-54-11-031Z.yml new file mode 100644 index 0000000..ced0efe --- /dev/null +++ b/.playwright-mcp/page-2026-04-14T09-54-11-031Z.yml @@ -0,0 +1,40 @@ +- generic [ref=e6]: + - heading "Login" [level=2] [ref=e7] + - paragraph [ref=e8]: + - text: "Demo:" + - strong [ref=e9]: superadmin@example.com + - text: / + - strong [ref=e10]: superadmin123 + - text: "or:" + - strong [ref=e11]: user@example.com + - text: / + - strong [ref=e12]: user123 + - alert [ref=e55]: + - generic [ref=e56]: + - img [ref=e58] + - generic [ref=e61]: Email atau password salah + - generic [ref=e13]: + - generic [ref=e14]: Email * + - generic [ref=e15]: + - img [ref=e17] + - textbox "Email" [ref=e20]: + - /placeholder: email@example.com + - text: superadmin@example.com + - generic [ref=e21]: + - generic [ref=e22]: Password * + - generic [ref=e23]: + - img [ref=e25] + - textbox "Password" [ref=e30]: superadmin123 + - button [ref=e32] [cursor=pointer]: + - img [ref=e34] + - button "Sign in" [ref=e36] [cursor=pointer]: + - generic [ref=e37]: + - img [ref=e39] + - generic [ref=e43]: Sign in + - separator [ref=e44]: + - generic [ref=e45]: or + - link "Login with Google" [ref=e46] [cursor=pointer]: + - /url: /api/auth/google + - generic [ref=e47]: + - img [ref=e49] + - generic [ref=e54]: Login with Google \ No newline at end of file diff --git a/.playwright-mcp/page-2026-04-15T00-10-33-255Z.yml b/.playwright-mcp/page-2026-04-15T00-10-33-255Z.yml new file mode 100644 index 0000000..3636b06 --- /dev/null +++ b/.playwright-mcp/page-2026-04-15T00-10-33-255Z.yml @@ -0,0 +1,4 @@ +- generic [active]: + - generic: + - generic: + - generic: Loading... \ No newline at end of file diff --git a/.playwright-mcp/page-2026-04-15T00-12-16-729Z.yml b/.playwright-mcp/page-2026-04-15T00-12-16-729Z.yml new file mode 100644 index 0000000..1e0331d --- /dev/null +++ b/.playwright-mcp/page-2026-04-15T00-12-16-729Z.yml @@ -0,0 +1,131 @@ +- generic [active] [ref=e1]: + - generic: + - generic: + - generic: Loading... + - generic [ref=e3]: + - banner [ref=e4]: + - generic [ref=e5]: + - generic [ref=e6]: + - button [ref=e7] [cursor=pointer] + - generic [ref=e9]: + - img [ref=e11] + - paragraph [ref=e13]: Monitoring System + - generic [ref=e14]: + - button "Toggle color scheme" [ref=e15] [cursor=pointer]: + - img [ref=e17] + - generic "User" [ref=e20] [cursor=pointer]: + - img [ref=e21] + - navigation [ref=e23]: + - generic [ref=e24]: + - link "Dashboard" [ref=e25] [cursor=pointer]: + - /url: /dashboard + - img [ref=e27] + - generic [ref=e31]: Dashboard + - img [ref=e33] + - link "Applications" [ref=e35] [cursor=pointer]: + - /url: /apps + - img [ref=e37] + - generic [ref=e41]: Applications + - img [ref=e43] + - link "Log Activity" [ref=e45] [cursor=pointer]: + - /url: /logs + - img [ref=e47] + - generic [ref=e50]: Log Activity + - img [ref=e52] + - link "Error Reports" [ref=e54] [cursor=pointer]: + - /url: /bug-reports + - img [ref=e56] + - generic [ref=e58]: Error Reports + - img [ref=e60] + - link "Users" [ref=e62] [cursor=pointer]: + - /url: /users + - img [ref=e64] + - generic [ref=e67]: Users + - img [ref=e69] + - generic [ref=e72]: + - generic [ref=e73]: + - paragraph [ref=e74]: SYSTEM STATUS + - paragraph [ref=e77]: All Systems Operational + - button "Log out" [ref=e78] [cursor=pointer]: + - generic [ref=e79]: + - img [ref=e81] + - generic [ref=e85]: Log out + - main [ref=e86]: + - generic [ref=e88]: + - generic [ref=e90]: + - heading "Overview Dashboard" [level=2] [ref=e91] + - paragraph [ref=e92]: Welcome back, Super Admin. Here is what's happening today. + - generic [ref=e93]: + - generic [ref=e94]: + - img [ref=e97] + - generic [ref=e101]: + - paragraph [ref=e102]: Total Applications + - paragraph [ref=e103]: "1" + - generic [ref=e104]: + - img [ref=e107] + - generic [ref=e109]: + - paragraph [ref=e110]: New Errors + - paragraph [ref=e111]: "1" + - generic [ref=e112]: + - img [ref=e115] + - generic [ref=e120]: + - paragraph [ref=e121]: Users + - paragraph [ref=e122]: "4" + - generic [ref=e123]: + - heading "Registered Applications" [level=3] [ref=e124] + - link "View All Apps" [ref=e125] [cursor=pointer]: + - /url: /apps + - generic [ref=e126]: + - generic [ref=e127]: View All Apps + - img [ref=e129] + - generic [ref=e132]: + - generic [ref=e133]: + - generic [ref=e134]: + - img [ref=e137] + - generic [ref=e139]: + - paragraph [ref=e140]: Desa+ + - paragraph [ref=e141]: VERSION 2.4.1 + - generic [ref=e143]: ACTIVE + - link "View" [ref=e144] [cursor=pointer]: + - /url: /apps/desa-plus + - generic [ref=e145]: + - generic [ref=e146]: View + - img [ref=e148] + - generic [ref=e150]: + - heading "Recent Error Reports" [level=3] [ref=e151] + - link "View All Errors" [ref=e152] [cursor=pointer]: + - /url: /bug-reports + - generic [ref=e153]: + - generic [ref=e154]: View All Errors + - img [ref=e156] + - table [ref=e159]: + - rowgroup [ref=e160]: + - row "Application Error Message Version Time Severity" [ref=e161]: + - columnheader "Application" [ref=e162] + - columnheader "Error Message" [ref=e163] + - columnheader "Version" [ref=e164] + - columnheader "Time" [ref=e165] + - columnheader "Severity" [ref=e166] + - rowgroup [ref=e167]: + - row "desa-plus error saat menambah data project v2.1 1 days ago ON_HOLD" [ref=e168]: + - cell "desa-plus" [ref=e169]: + - paragraph [ref=e170]: desa-plus + - cell "error saat menambah data project" [ref=e171]: + - paragraph [ref=e172]: error saat menambah data project + - cell "v2.1" [ref=e173]: + - generic [ref=e175]: v2.1 + - cell "1 days ago" [ref=e176]: + - paragraph [ref=e177]: 1 days ago + - cell "ON_HOLD" [ref=e178]: + - generic [ref=e180]: ON_HOLD + - row "desa-plus error pada saat login v2.1.0 1 days ago OPEN" [ref=e181]: + - cell "desa-plus" [ref=e182]: + - paragraph [ref=e183]: desa-plus + - cell "error pada saat login" [ref=e184]: + - paragraph [ref=e185]: error pada saat login + - cell "v2.1.0" [ref=e186]: + - generic [ref=e188]: v2.1.0 + - cell "1 days ago" [ref=e189]: + - paragraph [ref=e190]: 1 days ago + - cell "OPEN" [ref=e191]: + - generic [ref=e193]: OPEN \ No newline at end of file diff --git a/.qwen/settings.json b/.qwen/settings.json new file mode 100644 index 0000000..4e3ca7d --- /dev/null +++ b/.qwen/settings.json @@ -0,0 +1,13 @@ +{ + "mcpServers": { + "playwright": { + "command": "npx", + "args": [ + "@playwright/mcp@latest", + "--headless" + ], + "timeout": 30000 + } + }, + "$version": 3 +} \ No newline at end of file diff --git a/.qwen/settings.json.orig b/.qwen/settings.json.orig new file mode 100644 index 0000000..5747629 --- /dev/null +++ b/.qwen/settings.json.orig @@ -0,0 +1,9 @@ +{ + "mcpServers": { + "playwright": { + "command": "npx", + "args": ["@playwright/mcp@latest", "--headless"], + "timeout": 30000 + } + } +} diff --git a/CLAUDE.md b/CLAUDE.md index 4063a63..af4f5fe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,6 +50,15 @@ React 19 + Vite 8 (middleware mode in dev). File-based routing with TanStack Rou - HMR: Vite 8 with `@vitejs/plugin-react` v6. `dedupeRefreshPlugin` fixes double React Refresh injection. - Editor: `REACT_EDITOR` env var. `zed` and `subl` use `file:line:col`, others use `--goto file:line:col`. +## Playwright MCP + +Playwright MCP server enables AI-assisted browser automation for testing and debugging. + +- MCP config: `.qwen/settings.json` — Qwen Code auto-loads on session start +- Playwright config: `playwright.config.ts` — E2E test configuration +- Run manually: `bun run mcp:playwright` — starts headless browser MCP server +- Install browsers: `bunx playwright install` — downloads Chromium and other browsers + ## Testing Tests use `bun:test`. Three levels: diff --git a/PLAYWRIGHT_MCP.md b/PLAYWRIGHT_MCP.md new file mode 100644 index 0000000..71f2df4 --- /dev/null +++ b/PLAYWRIGHT_MCP.md @@ -0,0 +1,100 @@ +# Playwright MCP Setup + +This project includes Playwright MCP (Model Context Protocol) for AI-assisted browser automation. + +## What is Playwright MCP? + +Playwright MCP allows AI assistants (like Claude) to interact with a real browser through the Model Context Protocol. This enables: +- Automated browser testing +- Web scraping and data extraction +- Visual testing and screenshots +- Navigation and interaction with web pages + +## Setup + +All dependencies are already installed: +- `@playwright/mcp` - MCP server for Playwright +- `@playwright/test` - Playwright test framework +- `playwright` - Browser automation library +- Chromium browser (downloaded via `bunx playwright install`) + +## Configuration + +### Qwen Code MCP Config (`.qwen/settings.json`) + +Qwen Code automatically loads this file on new session start: +```json +{ + "mcpServers": { + "playwright": { + "command": "npx", + "args": ["@playwright/mcp@latest", "--headless"], + "timeout": 30000 + } + } +} +``` + +### Playwright Config (`playwright.config.ts`) +Standard E2E test configuration with: +- Chromium browser +- Base URL: http://localhost:3000 +- Auto-starts dev server for testing + +## Usage + +### Start MCP Server +```bash +bun run mcp:playwright +``` + +This starts the MCP server on port 3000 in headless mode. AI assistants can connect to this server to control the browser. + +### Run E2E Tests +```bash +# Using Playwright's test runner +bunx playwright test + +# Using the existing test suite +bun run test:e2e +``` + +### Install/Update Browsers +```bash +# Install all browsers +bunx playwright install + +# Install specific browser +bunx playwright install chromium +``` + +## Integration with AI Assistants + +When using an AI assistant that supports MCP: +1. Start your app: `bun run dev` +2. Start the MCP server: `bun run mcp:playwright` +3. The AI assistant can now: + - Navigate to your app + - Take screenshots + - Click elements and fill forms + - Test user flows + - Debug UI issues + +## Available MCP Tools + +The Playwright MCP server provides tools for: +- `browser_navigate` - Navigate to a URL +- `browser_screenshot` - Take a screenshot +- `browser_click` - Click an element +- `browser_type` - Type text into an element +- `browser_select_option` - Select dropdown options +- `browser_hover` - Hover over elements +- `browser_evaluate` - Execute JavaScript +- `browser_snapshot` - Get page accessibility snapshot +- And more... + +## Files + +- `mcp.json` - MCP server configuration +- `playwright.config.ts` - Playwright test configuration +- `tests/e2e/` - E2E test files diff --git a/bun.lockb b/bun.lockb index a4112fe..c126c49 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/login-snapshot.yml b/login-snapshot.yml new file mode 100644 index 0000000..7b848e1 --- /dev/null +++ b/login-snapshot.yml @@ -0,0 +1,35 @@ +- generic [ref=e6]: + - heading "Login" [level=2] [ref=e7] + - paragraph [ref=e8]: + - text: "Demo:" + - strong [ref=e9]: superadmin@example.com + - text: / + - strong [ref=e10]: superadmin123 + - text: "or:" + - strong [ref=e11]: user@example.com + - text: / + - strong [ref=e12]: user123 + - generic [ref=e13]: + - generic [ref=e14]: Email * + - generic [ref=e15]: + - img [ref=e17] + - textbox "Email" [ref=e20]: + - /placeholder: email@example.com + - generic [ref=e21]: + - generic [ref=e22]: Password * + - generic [ref=e23]: + - img [ref=e25] + - textbox "Password" [ref=e30] + - button [ref=e32] [cursor=pointer]: + - img [ref=e34] + - button "Sign in" [ref=e36] [cursor=pointer]: + - generic [ref=e37]: + - img [ref=e39] + - generic [ref=e43]: Sign in + - separator [ref=e44]: + - generic [ref=e45]: or + - link "Login with Google" [ref=e46] [cursor=pointer]: + - /url: /api/auth/google + - generic [ref=e47]: + - img [ref=e49] + - generic [ref=e54]: Login with Google \ No newline at end of file diff --git a/package.json b/package.json index d0c4921..8dc37ea 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "db:seed": "bun run prisma/seed.ts", "db:studio": "bunx prisma studio", "db:generate": "bunx prisma generate", - "db:push": "bunx prisma db push" + "db:push": "bunx prisma db push", + "mcp:playwright": "playwright-mcp --headless --port 3000" }, "dependencies": { "@elysiajs/cors": "^1.4.1", @@ -43,11 +44,14 @@ }, "devDependencies": { "@biomejs/biome": "^2.4.10", + "@playwright/mcp": "^0.0.70", + "@playwright/test": "^1.59.1", "@tanstack/router-vite-plugin": "^1.166.27", "@types/bun": "latest", "@types/react": "^19", "@types/react-dom": "^19", "@vitejs/plugin-react": "^6.0.1", + "playwright": "^1.59.1", "prisma": "6", "puppeteer-core": "^24.40.0", "typescript": "^6.0.2", diff --git a/playwright-report/index.html b/playwright-report/index.html new file mode 100644 index 0000000..d8b3907 --- /dev/null +++ b/playwright-report/index.html @@ -0,0 +1,90 @@ + + + + + + + + + Playwright Test Report + + + + +
+ + + \ No newline at end of file diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..d24867d --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,27 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './tests/e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + baseURL: 'http://localhost:3000', + trace: 'on-first-retry', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'bun run dev', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/prisma/migrations/20260415014750_tambah_table_app_and_enum_role/migration.sql b/prisma/migrations/20260415014750_tambah_table_app_and_enum_role/migration.sql new file mode 100644 index 0000000..6a14dba --- /dev/null +++ b/prisma/migrations/20260415014750_tambah_table_app_and_enum_role/migration.sql @@ -0,0 +1,40 @@ +/* + Warnings: + + - The values [USER,SUPER_ADMIN] on the enum `Role` will be removed. If these variants are still used in the database, this will fail. + - You are about to drop the column `app` on the `bug` table. All the data in the column will be lost. + +*/ +-- AlterEnum +BEGIN; +CREATE TYPE "Role_new" AS ENUM ('ADMIN', 'DEVELOPER'); +ALTER TABLE "public"."user" ALTER COLUMN "role" DROP DEFAULT; +ALTER TABLE "user" ALTER COLUMN "role" TYPE "Role_new" USING ("role"::text::"Role_new"); +ALTER TYPE "Role" RENAME TO "Role_old"; +ALTER TYPE "Role_new" RENAME TO "Role"; +DROP TYPE "public"."Role_old"; +ALTER TABLE "user" ALTER COLUMN "role" SET DEFAULT 'ADMIN'; +COMMIT; + +-- AlterTable +ALTER TABLE "bug" DROP COLUMN "app", +ADD COLUMN "appId" TEXT; + +-- AlterTable +ALTER TABLE "user" ALTER COLUMN "role" SET DEFAULT 'ADMIN'; + +-- CreateTable +CREATE TABLE "App" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "version" TEXT NOT NULL, + "minVersion" TEXT NOT NULL, + "maintenance" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "App_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "bug" ADD CONSTRAINT "bug_appId_fkey" FOREIGN KEY ("appId") REFERENCES "App"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20260415024317_update_enum_app/migration.sql b/prisma/migrations/20260415024317_update_enum_app/migration.sql new file mode 100644 index 0000000..9ce2368 --- /dev/null +++ b/prisma/migrations/20260415024317_update_enum_app/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "App" ALTER COLUMN "version" DROP NOT NULL, +ALTER COLUMN "minVersion" DROP NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 64b5f82..5667144 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -9,9 +9,7 @@ datasource db { } enum Role { - USER ADMIN - SUPER_ADMIN DEVELOPER } @@ -44,7 +42,7 @@ model User { name String email String @unique password String - role Role @default(USER) + role Role @default(ADMIN) active Boolean @default(true) image String? createdAt DateTime @default(now()) @@ -71,6 +69,19 @@ model Session { @@map("session") } +model App { + id String @id @default(uuid()) + name String + version String? + minVersion String? + maintenance Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + bugs Bug[] + +} + model Log { id String @id @default(uuid()) userId String @@ -86,12 +97,12 @@ model Log { model Bug { id String @id @default(uuid()) userId String? - app String? + appId String? affectedVersion String device String os String status BugStatus - source BugSource + source BugSource description String stackTrace String? fixedVersion String? @@ -100,6 +111,7 @@ model Bug { updatedAt DateTime @updatedAt user User? @relation(fields: [userId], references: [id]) + app App? @relation(fields: [appId], references: [id]) images BugImage[] logs BugLog[] diff --git a/prisma/seed.ts b/prisma/seed.ts index f90de89..31e64df 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -6,9 +6,7 @@ const SUPER_ADMIN_EMAILS = (process.env.SUPER_ADMIN_EMAIL ?? '').split(',').map( async function main() { const users = [ - { name: 'Super Admin', email: 'superadmin@example.com', password: 'superadmin123', role: 'SUPER_ADMIN' as const }, { name: 'Admin', email: 'admin@example.com', password: 'admin123', role: 'ADMIN' as const }, - { name: 'User', email: 'user@example.com', password: 'user123', role: 'USER' as const }, ] for (const u of users) { @@ -21,13 +19,28 @@ async function main() { console.log(`Seeded: ${u.email} (${u.role})`) } - // Promote super admin emails from env + // Promote DEVELOPER emails from env for (const email of SUPER_ADMIN_EMAILS) { - const user = await prisma.user.findUnique({ where: { email } }) - if (user && user.role !== 'SUPER_ADMIN') { - await prisma.user.update({ where: { email }, data: { role: 'SUPER_ADMIN' } }) - console.log(`Promoted to SUPER_ADMIN: ${email}`) - } + const password = await Bun.password.hash('developer123', { algorithm: 'bcrypt' }) + await prisma.user.upsert({ + where: { email }, + update: { role: 'DEVELOPER', password }, + create: { name: email.split('@')[0].toUpperCase(), email, password, role: 'DEVELOPER' }, + }) + console.log(`Promoted to DEVELOPER: ${email}`) + } + + const apps = [ + { id: 'desa-plus', name: 'Desa+' }, + ] + + for (const a of apps) { + await prisma.app.upsert({ + where: { id: a.id }, + update: { name: a.name }, + create: { id: a.id, name: a.name }, + }) + console.log(`Seeded: ${a.name}`) } } diff --git a/src/app.ts b/src/app.ts index b1a38ef..f860ae5 100644 --- a/src/app.ts +++ b/src/app.ts @@ -37,8 +37,8 @@ export function createApp() { return { error: 'Email atau password salah' } } // Auto-promote super admin from env - if (env.SUPER_ADMIN_EMAILS.includes(user.email) && user.role !== 'SUPER_ADMIN') { - user = await prisma.user.update({ where: { id: user.id }, data: { role: 'SUPER_ADMIN' } }) + if (env.SUPER_ADMIN_EMAILS.includes(user.email) && user.role !== 'DEVELOPER') { + user = await prisma.user.update({ where: { id: user.id }, data: { role: 'DEVELOPER' } }) } const token = crypto.randomUUID() const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours @@ -78,80 +78,7 @@ export function createApp() { return { user: session.user } }) - // ─── Google OAuth ────────────────────────────────── - .get('/api/auth/google', ({ request, set }) => { - const origin = new URL(request.url).origin - const params = new URLSearchParams({ - client_id: env.GOOGLE_CLIENT_ID, - redirect_uri: `${origin}/api/auth/callback/google`, - response_type: 'code', - scope: 'openid email profile', - access_type: 'offline', - prompt: 'consent', - }) - set.status = 302; set.headers['location'] = `https://accounts.google.com/o/oauth2/v2/auth?${params}` - }) - .get('/api/auth/callback/google', async ({ request, set }) => { - const url = new URL(request.url) - const code = url.searchParams.get('code') - const origin = url.origin - - if (!code) { - set.status = 302; set.headers['location'] = '/login?error=google_failed' - return - } - - // Exchange code for tokens - const tokenRes = await fetch('https://oauth2.googleapis.com/token', { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ - code, - client_id: env.GOOGLE_CLIENT_ID, - client_secret: env.GOOGLE_CLIENT_SECRET, - redirect_uri: `${origin}/api/auth/callback/google`, - grant_type: 'authorization_code', - }), - }) - - if (!tokenRes.ok) { - set.status = 302; set.headers['location'] = '/login?error=google_failed' - return - } - - const tokens = (await tokenRes.json()) as { access_token: string } - - // Get user info - const userInfoRes = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', { - headers: { Authorization: `Bearer ${tokens.access_token}` }, - }) - - if (!userInfoRes.ok) { - set.status = 302; set.headers['location'] = '/login?error=google_failed' - return - } - - const googleUser = (await userInfoRes.json()) as { email: string; name: string } - - // Upsert user (no password for Google users) - const isSuperAdmin = env.SUPER_ADMIN_EMAILS.includes(googleUser.email) - const user = await prisma.user.upsert({ - where: { email: googleUser.email }, - update: { name: googleUser.name, ...(isSuperAdmin ? { role: 'SUPER_ADMIN' } : {}) }, - create: { email: googleUser.email, name: googleUser.name, password: '', role: isSuperAdmin ? 'SUPER_ADMIN' : 'USER' }, - }) - - // Create session - const token = crypto.randomUUID() - const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000) - await prisma.session.create({ data: { token, userId: user.id, expiresAt } }) - - await createSystemLog(user.id, 'LOGIN', 'Logged in via Google') - - set.headers['set-cookie'] = `session=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400` - set.status = 302; set.headers['location'] = user.role === 'SUPER_ADMIN' ? '/dashboard' : '/profile' - }) // ─── Monitoring API ──────────────────────────────── .get('/api/dashboard/stats', async () => { @@ -172,7 +99,7 @@ export function createApp() { }) return bugs.map(b => ({ id: b.id, - app: b.app, + app: b.appId, message: b.description, version: b.affectedVersion, time: b.createdAt.toISOString(), @@ -180,18 +107,56 @@ export function createApp() { })) }) - .get('/api/apps', async () => { - const desaPlusErrors = await prisma.bug.count({ where: { app: { in: ['desa-plus', 'desa_plus'] }, status: 'OPEN' } }) - return [ - { id: 'desa-plus', name: 'Desa+', status: 'active', users: 12450, errors: desaPlusErrors, version: '2.4.1' }, - ] + .get('/api/apps', async ({ query }) => { + const search = (query.search as string) || '' + const where: any = {} + if (search) { + where.name = { contains: search, mode: 'insensitive' } + } + + const apps = await prisma.app.findMany({ + where, + include: { + _count: { select: { bugs: true } }, + bugs: { where: { status: 'OPEN' }, select: { id: true } }, + }, + orderBy: { name: 'asc' }, + }) + + return apps.map((app) => ({ + id: app.id, + name: app.name, + status: app.maintenance ? 'warning' : app.bugs.length > 0 ? 'error' : 'active', + errors: app.bugs.length, + version: app.version ?? '-', + maintenance: app.maintenance, + })) }) - .get('/api/apps/:appId', ({ params: { appId } }) => { - const apps = { - 'desa-plus': { id: 'desa-plus', name: 'Desa+', status: 'active', users: 12450, errors: 12, version: '2.4.1' }, + .get('/api/apps/:appId', async ({ params: { appId }, set }) => { + const app = await prisma.app.findUnique({ + where: { id: appId }, + include: { + _count: { select: { bugs: true } }, + bugs: { where: { status: 'OPEN' }, select: { id: true } }, + }, + }) + + if (!app) { + set.status = 404 + return { error: 'App not found' } + } + + return { + id: app.id, + name: app.name, + status: app.maintenance ? 'warning' : app.bugs.length > 0 ? 'error' : 'active', + errors: app.bugs.length, + version: app.version ?? '-', + minVersion: app.minVersion, + maintenance: app.maintenance, + totalBugs: app._count.bugs, } - return apps[appId as keyof typeof apps] || { id: appId, name: appId, status: 'active', users: 0, errors: 0, version: '1.0.0' } }) .get('/api/logs', async ({ query }) => { @@ -246,7 +211,7 @@ export function createApp() { } const body = (await request.json()) as { type: string, message: string } - const actingUserId = userId || (await prisma.user.findFirst({ where: { role: 'SUPER_ADMIN' } }))?.id || '' + const actingUserId = userId || (await prisma.user.findFirst({ where: { role: 'DEVELOPER' } }))?.id || '' await createSystemLog(actingUserId, body.type as any, body.message) return { ok: true } @@ -419,7 +384,7 @@ export function createApp() { ] } if (app && app !== 'all') { - where.app = app + where.appId = app } if (status && status !== 'all') { where.status = status @@ -463,12 +428,12 @@ export function createApp() { } const body = (await request.json()) as any - const defaultAdmin = await prisma.user.findFirst({ where: { role: 'SUPER_ADMIN' } }) + const defaultAdmin = await prisma.user.findFirst({ where: { role: 'DEVELOPER' } }) const actingUserId = userId || defaultAdmin?.id || '' const bug = await prisma.bug.create({ data: { - app: body.app, + appId: body.app, affectedVersion: body.affectedVersion, device: body.device, os: body.os, @@ -508,7 +473,7 @@ export function createApp() { } const body = (await request.json()) as { feedBack: string } - const defaultAdmin = await prisma.user.findFirst({ where: { role: 'SUPER_ADMIN' } }) + const defaultAdmin = await prisma.user.findFirst({ where: { role: 'DEVELOPER' } }) const actingUserId = userId || defaultAdmin?.id || undefined const bug = await prisma.bug.update({ @@ -538,7 +503,7 @@ export function createApp() { } const body = (await request.json()) as { status: string; description?: string } - const defaultAdmin = await prisma.user.findFirst({ where: { role: 'SUPER_ADMIN' } }) + const defaultAdmin = await prisma.user.findFirst({ where: { role: 'DEVELOPER' } }) const actingUserId = userId || defaultAdmin?.id || undefined const bug = await prisma.bug.update({ @@ -562,6 +527,30 @@ export function createApp() { return bug }) + // ─── System Status API ───────────────────────────── + .get('/api/system/status', async () => { + try { + // Check database connectivity + await prisma.$queryRaw`SELECT 1` + const activeSessions = await prisma.session.count({ + where: { expiresAt: { gte: new Date() } }, + }) + return { + status: 'operational', + database: 'connected', + activeSessions, + uptime: process.uptime(), + } + } catch { + return { + status: 'degraded', + database: 'disconnected', + activeSessions: 0, + uptime: process.uptime(), + } + } + }) + // ─── Example API ─────────────────────────────────── .get('/api/hello', () => ({ message: 'Hello, world!', diff --git a/src/frontend/components/AppCard.tsx b/src/frontend/components/AppCard.tsx index 3a8d485..7c5fcff 100644 --- a/src/frontend/components/AppCard.tsx +++ b/src/frontend/components/AppCard.tsx @@ -1,17 +1,18 @@ -import { Card, Group, Text, ThemeIcon, Badge, Avatar, Stack, Button, Progress, Box, useComputedColorScheme } from '@mantine/core' +import { Avatar, Button, Card, Group, Stack, Text, useComputedColorScheme } from '@mantine/core' import { Link } from '@tanstack/react-router' -import { TbDeviceMobile, TbActivity, TbAlertTriangle, TbChevronRight } from 'react-icons/tb' +import { TbChevronRight, TbDeviceMobile } from 'react-icons/tb' interface AppCardProps { id: string name: string status: 'active' | 'warning' | 'error' - users: number + users?: number errors: number version: string + maintenance?: boolean } -export function AppCard({ id, name, status, users, errors, version }: AppCardProps) { +export function AppCard({ id, name, status, errors, version }: AppCardProps) { const statusColor = status === 'active' ? 'teal' : status === 'warning' ? 'orange' : 'red' const scheme = useComputedColorScheme('light', { getInitialValueInEffect: true }) @@ -46,12 +47,12 @@ export function AppCard({ id, name, status, users, errors, version }: AppCardPro {name} - VERSION {version} + {/* VERSION {version} */} - + {/* {status.toUpperCase()} - + */} {/* diff --git a/src/frontend/components/DashboardLayout.tsx b/src/frontend/components/DashboardLayout.tsx index 5ec63f3..7829efb 100644 --- a/src/frontend/components/DashboardLayout.tsx +++ b/src/frontend/components/DashboardLayout.tsx @@ -1,4 +1,5 @@ import { APP_CONFIGS } from '@/frontend/config/appMenus' +import { useLogout, useSession } from '@/frontend/hooks/useAuth' import { ActionIcon, AppShell, @@ -7,6 +8,7 @@ import { Burger, Button, Group, + Loader, Menu, NavLink, Select, @@ -17,6 +19,7 @@ import { useMantineColorScheme } from '@mantine/core' import { useDisclosure } from '@mantine/hooks' +import { useQuery } from '@tanstack/react-query' import { Link, useLocation, useMatches, useNavigate, useParams } from '@tanstack/react-router' import { TbAlertTriangle, @@ -50,6 +53,26 @@ export function DashboardLayout({ children }: DashboardLayoutProps) { const matches = useMatches() const currentPath = matches[matches.length - 1]?.pathname + // ─── Connect to auth system ────────────────────────── + const { data: sessionData } = useSession() + const user = sessionData?.user + const logout = useLogout() + + // ─── Fetch registered apps from database ───────────── + const { data: appsData } = useQuery({ + queryKey: ['apps'], + queryFn: () => fetch('/api/apps', { credentials: 'include' }).then((r) => r.json()), + staleTime: 60_000, + }) + + // ─── Fetch system status from database ─────────────── + const { data: systemStatus } = useQuery({ + queryKey: ['system', 'status'], + queryFn: () => fetch('/api/system/status', { credentials: 'include' }).then((r) => r.json()), + refetchInterval: 30_000, // refresh every 30 seconds + staleTime: 15_000, + }) + const globalNav = [ { label: 'Dashboard', icon: TbDashboard, to: '/dashboard' }, { label: 'Applications', icon: TbApps, to: '/apps' }, @@ -61,6 +84,21 @@ export function DashboardLayout({ children }: DashboardLayoutProps) { const activeApp = appId ? APP_CONFIGS[appId] : null const navLinks = activeApp ? activeApp.menus : globalNav + // Build app selector data from API + const appSelectData = (appsData || []).map((app: any) => ({ + value: app.id, + label: app.name, + })) + + // System status indicator + const isOperational = systemStatus?.status === 'operational' + const statusColor = isOperational ? '#10b981' : '#f59e0b' + const statusText = isOperational ? 'All Systems Operational' : 'System Degraded' + + const handleLogout = () => { + logout.mutate() + } + return ( + > + {user?.name?.charAt(0).toUpperCase()} + + {user && ( + <> + + {user.name} + {user.email} + + + + )} Application - }>Profile - }>Settings + } + onClick={() => navigate({ to: '/profile' })} + > + Profile + + } + onClick={() => navigate({ to: '/dashboard' })} + > + Settings + Danger Zone - }> - Logout + } + onClick={handleLogout} + disabled={logout.isPending} + > + {logout.isPending ? 'Logging out...' : 'Logout'} @@ -160,10 +224,8 @@ export function DashboardLayout({ children }: DashboardLayoutProps) { ({ value: a.id, label: a.name })) || []} value={createForm.app} onChange={(val) => setCreateForm({ ...createForm, app: val as any })} + placeholder="Select application" + disabled={!appsList} /> ({ value: a.id, label: a.name })) || []} value={createForm.app} onChange={(val) => setCreateForm({ ...createForm, app: val as any })} + placeholder="Select application" + disabled={!appsList} /> fetch('/api/auth/session', { credentials: 'include' }).then((r) => r.json()), }) if (!data?.user) throw redirect({ to: '/login' }) - if (data.user.role !== 'SUPER_ADMIN') throw redirect({ to: '/profile' }) } catch (e) { if (e instanceof Error) throw redirect({ to: '/login' }) throw e diff --git a/src/frontend/routes/login.tsx b/src/frontend/routes/login.tsx index 81079e9..5432337 100644 --- a/src/frontend/routes/login.tsx +++ b/src/frontend/routes/login.tsx @@ -1,3 +1,4 @@ +import { useLogin } from '@/frontend/hooks/useAuth' import { Alert, Button, @@ -13,8 +14,7 @@ import { import { createFileRoute, redirect } from '@tanstack/react-router' import { useState } from 'react' import { FcGoogle } from 'react-icons/fc' -import { TbAlertCircle, TbLogin, TbLock, TbMail } from 'react-icons/tb' -import { useLogin } from '@/frontend/hooks/useAuth' +import { TbAlertCircle, TbLock, TbLogin, TbMail } from 'react-icons/tb' export const Route = createFileRoute('/login')({ validateSearch: (search: Record): { error?: string } => ({ @@ -27,7 +27,7 @@ export const Route = createFileRoute('/login')({ queryFn: () => fetch('/api/auth/session', { credentials: 'include' }).then((r) => r.json()), }) if (data?.user) { - throw redirect({ to: data.user.role === 'SUPER_ADMIN' ? '/dashboard' : '/profile' }) + throw redirect({ to: '/dashboard' }) } } catch (e) { if (e instanceof Error) return @@ -57,12 +57,6 @@ function LoginPage() { Login - - Demo: superadmin@example.com / superadmin123 -
- or: user@example.com / user123 -
- {(login.isError || searchError) && ( } color="red" variant="light"> {login.isError ? login.error.message : 'Google login failed, please try again.'} @@ -95,18 +89,6 @@ function LoginPage() { > Sign in - - - -
diff --git a/src/frontend/routes/profile.tsx b/src/frontend/routes/profile.tsx index e41b1d1..be019ed 100644 --- a/src/frontend/routes/profile.tsx +++ b/src/frontend/routes/profile.tsx @@ -30,9 +30,8 @@ export const Route = createFileRoute('/profile')({ }) const roleBadgeColor: Record = { - USER: 'blue', ADMIN: 'violet', - SUPER_ADMIN: 'red', + DEVELOPER: 'red', } function ProfilePage() { diff --git a/src/frontend/routes/users.tsx b/src/frontend/routes/users.tsx index 52c21ef..2ebb47b 100644 --- a/src/frontend/routes/users.tsx +++ b/src/frontend/routes/users.tsx @@ -59,20 +59,15 @@ const getRoleColor = (role: string) => { } const roles = [ - { - name: 'SUPER_ADMIN', - color: 'red', - permissions: ['Full Access', 'User Mgmt', 'Role Mgmt', 'App Config', 'Logs & Errors'] - }, { name: 'DEVELOPER', - color: 'brand-blue', - permissions: ['View All Apps', 'Manage Assigned App', 'View Logs', 'Resolve Errors', 'Village Setup'] + color: 'red', + permissions: ['Full Access', 'Error Feedback', 'Error Management', 'App Version Management', 'User Management'] }, { - name: 'QA', + name: 'ADMIN', color: 'orange', - permissions: ['View All Apps', 'View Logs', 'Report Errors', 'Test App Features'] + permissions: ['View All Apps', 'View Logs', 'Report Errors'] }, ] @@ -414,10 +409,8 @@ function UsersPage() { setEditForm({ ...editForm, role: val || 'USER' })} diff --git a/test-results/.last-run.json b/test-results/.last-run.json new file mode 100644 index 0000000..5fca3f8 --- /dev/null +++ b/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "failed", + "failedTests": [] +} \ No newline at end of file diff --git a/tests/helpers.ts b/tests/helpers.ts index ab03451..6f75fb0 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -9,7 +9,7 @@ export function createTestApp() { } /** Create a test user with hashed password, returns the user record */ -export async function seedTestUser(email = 'test@example.com', password = 'test123', name = 'Test User', role: 'USER' | 'ADMIN' | 'SUPER_ADMIN' = 'USER') { +export async function seedTestUser(email = 'test@example.com', password = 'test123', name = 'Test User', role: 'ADMIN' | 'DEVELOPER' = 'DEVELOPER') { const hashed = await Bun.password.hash(password, { algorithm: 'bcrypt' }) return prisma.user.upsert({ where: { email },