miolo 3.0.0-beta.21 → 3.0.0-beta.210

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/bin/build/build.mjs +53 -0
  2. package/bin/build/build_bin.mjs +17 -0
  3. package/bin/build/cli/client.mjs +52 -0
  4. package/bin/build/cli/css.mjs +22 -0
  5. package/bin/build/cli/index.mjs +40 -0
  6. package/bin/build/cli/ssr.mjs +21 -0
  7. package/bin/build/server/aliases.mjs +112 -0
  8. package/bin/build/server/babel.config.js +24 -0
  9. package/bin/build/server/banner.mjs +20 -0
  10. package/bin/build/server/bundle.mjs +111 -0
  11. package/bin/build/server/fix.mjs +15 -0
  12. package/bin/build/server/index.mjs +69 -0
  13. package/bin/build/server/options.mjs +83 -0
  14. package/bin/create/auth.mjs +23 -0
  15. package/bin/create/copy.mjs +175 -0
  16. package/bin/create/docker.mjs +25 -0
  17. package/bin/create/index.mjs +137 -0
  18. package/bin/create/pkgjson.mjs +72 -0
  19. package/bin/create/prepare-template.mjs +158 -0
  20. package/bin/create/validation.mjs +27 -0
  21. package/bin/dev/dev.mjs +32 -23
  22. package/bin/dev/dev_start.mjs +6 -5
  23. package/bin/index.mjs +94 -52
  24. package/bin/prod-bin/create-bin.mjs +42 -27
  25. package/bin/prod-bin/run.mjs +13 -9
  26. package/bin/{prod-run → run}/pid.mjs +4 -4
  27. package/bin/run/restart.mjs +13 -0
  28. package/bin/run/start.mjs +18 -0
  29. package/bin/run/stop.mjs +20 -0
  30. package/bin/util.mjs +35 -11
  31. package/package.json +59 -39
  32. package/src/config/.env +34 -12
  33. package/src/config/defaults.mjs +253 -185
  34. package/src/config/env.mjs +40 -22
  35. package/src/config/index.mjs +19 -24
  36. package/src/config/util.mjs +25 -10
  37. package/src/db-conn.mjs +34 -0
  38. package/src/engines/cron/emails.mjs +10 -5
  39. package/src/engines/cron/index.mjs +45 -51
  40. package/src/engines/cron/init.mjs +16 -17
  41. package/src/engines/cron/ipsum.mjs +65 -60
  42. package/src/engines/cron/syscheck.mjs +30 -30
  43. package/src/engines/emailer/index.mjs +1 -2
  44. package/src/engines/emailer/queue.mjs +14 -20
  45. package/src/engines/emailer/transporter.mjs +86 -74
  46. package/src/engines/geoip/index.mjs +23 -28
  47. package/src/engines/http/index.mjs +26 -15
  48. package/src/engines/logger/buildErrorEmailBody.mjs +72 -0
  49. package/src/engines/logger/index.mjs +114 -122
  50. package/src/engines/logger/injectStackTrace.mjs +59 -0
  51. package/src/engines/logger/logger_mail.mjs +47 -61
  52. package/src/engines/logger/reopenTransportOnHupSignal.mjs +12 -13
  53. package/src/engines/parser/Parser.mjs +77 -60
  54. package/src/engines/parser/index.mjs +1 -1
  55. package/src/engines/schema/diffObjs.mjs +41 -0
  56. package/src/engines/schema/index.mjs +4 -0
  57. package/src/engines/schema/input.mjs +54 -0
  58. package/src/engines/schema/output.mjs +66 -0
  59. package/src/engines/socket/index.mjs +44 -46
  60. package/src/index.mjs +15 -10
  61. package/src/middleware/auth/basic.mjs +41 -40
  62. package/src/middleware/auth/custom.mjs +10 -13
  63. package/src/middleware/auth/guest.mjs +27 -27
  64. package/src/middleware/auth/passport/index.mjs +374 -0
  65. package/src/middleware/auth/passport/session/index.mjs +43 -0
  66. package/src/middleware/auth/{credentials → passport}/session/store.mjs +35 -15
  67. package/src/middleware/auth/passport/session/store_koa_redis.mjs +3 -0
  68. package/src/middleware/context/cache/index.mjs +78 -33
  69. package/src/middleware/context/cache/options.mjs +19 -21
  70. package/src/middleware/context/db.mjs +45 -20
  71. package/src/middleware/context/index.mjs +12 -12
  72. package/src/middleware/extra.mjs +4 -5
  73. package/src/middleware/http/body.mjs +25 -25
  74. package/src/middleware/http/catcher.mjs +81 -8
  75. package/src/middleware/http/custom_blacklist.mjs +19 -16
  76. package/src/middleware/http/headers.mjs +37 -34
  77. package/src/middleware/http/ratelimit.mjs +16 -23
  78. package/src/middleware/http/request.mjs +60 -65
  79. package/src/middleware/routes/catch_js_error.mjs +30 -23
  80. package/src/middleware/routes/robots.mjs +4 -7
  81. package/src/middleware/routes/router/crud/attachCrudRoutes.mjs +108 -90
  82. package/src/middleware/routes/router/crud/getCrudConfig.mjs +31 -55
  83. package/src/middleware/routes/router/defaults.mjs +6 -19
  84. package/src/middleware/routes/router/index.mjs +17 -21
  85. package/src/middleware/routes/router/queries/attachQueriesRoutes.mjs +227 -50
  86. package/src/middleware/routes/router/queries/getQueriesConfig.mjs +45 -55
  87. package/src/middleware/routes/router/utils.mjs +41 -26
  88. package/src/middleware/ssr/context.mjs +5 -7
  89. package/src/middleware/ssr/html.mjs +66 -43
  90. package/src/middleware/ssr/loader.mjs +11 -14
  91. package/src/middleware/ssr/ssr_render.mjs +39 -22
  92. package/src/middleware/static/index.mjs +33 -14
  93. package/src/middleware/vite/devserver.mjs +38 -22
  94. package/src/middleware/vite/watcher.mjs +12 -14
  95. package/src/server-cron.mjs +13 -8
  96. package/src/server-dev.mjs +13 -16
  97. package/src/server.mjs +49 -51
  98. package/template/.agent/skills/miolo-app-arch/SKILL.md +218 -0
  99. package/template/.agent/skills/miolo-auth/SKILL.md +450 -0
  100. package/template/.agent/skills/miolo-cli-router/SKILL.md +394 -0
  101. package/template/.agent/skills/miolo-database/SKILL.md +358 -0
  102. package/template/.agent/skills/miolo-react-patterns/SKILL.md +426 -0
  103. package/template/.agent/skills/miolo-routing/SKILL.md +326 -0
  104. package/template/.agent/skills/miolo-schemas/SKILL.md +329 -0
  105. package/template/.agent/skills/miolo-session-context/SKILL.md +397 -0
  106. package/template/.agent/skills/miolo-ssr/SKILL.md +433 -0
  107. package/template/.editorconfig +18 -0
  108. package/template/.env +120 -0
  109. package/template/biome.json +63 -0
  110. package/template/components.json +21 -0
  111. package/template/db/init.sh +89 -0
  112. package/template/db/sql/00_drop.sql +2 -0
  113. package/template/db/sql/01_users.sql +31 -0
  114. package/template/db/sql/02_todos.sql +20 -0
  115. package/template/docker/Dockerfile +13 -0
  116. package/template/docker/docker-compose.yaml +79 -0
  117. package/template/gitignore +42 -0
  118. package/template/jsconfig.json +18 -0
  119. package/template/package.json +88 -0
  120. package/template/postcss.config.js +9 -0
  121. package/template/src/cli/App.jsx +25 -0
  122. package/template/src/cli/components/JsonTreeViewer.jsx +128 -0
  123. package/template/src/cli/components/shadcn-io/spinner/index.jsx +232 -0
  124. package/template/src/cli/components/stepper.jsx +408 -0
  125. package/template/src/cli/components/ui/avatar.jsx +36 -0
  126. package/template/src/cli/components/ui/badge.jsx +31 -0
  127. package/template/src/cli/components/ui/breadcrumb.jsx +97 -0
  128. package/template/src/cli/components/ui/card.jsx +73 -0
  129. package/template/src/cli/components/ui/collapsible.jsx +16 -0
  130. package/template/src/cli/components/ui/dropdown-menu.jsx +179 -0
  131. package/template/src/cli/components/ui/field.jsx +217 -0
  132. package/template/src/cli/components/ui/input.jsx +19 -0
  133. package/template/src/cli/components/ui/label.jsx +17 -0
  134. package/template/src/cli/components/ui/pagination.jsx +99 -0
  135. package/template/src/cli/components/ui/patched/alert.jsx +56 -0
  136. package/template/src/cli/components/ui/patched/button.jsx +45 -0
  137. package/template/src/cli/components/ui/patched/dialog.jsx +114 -0
  138. package/template/src/cli/components/ui/patched/sidebar.jsx +660 -0
  139. package/template/src/cli/components/ui/select.jsx +141 -0
  140. package/template/src/cli/components/ui/separator.jsx +21 -0
  141. package/template/src/cli/components/ui/sheet.jsx +115 -0
  142. package/template/src/cli/components/ui/skeleton.jsx +13 -0
  143. package/template/src/cli/components/ui/sonner.jsx +22 -0
  144. package/template/src/cli/components/ui/switch.jsx +25 -0
  145. package/template/src/cli/components/ui/table.jsx +88 -0
  146. package/template/src/cli/components/ui/textarea.jsx +16 -0
  147. package/template/src/cli/components/ui/tooltip.jsx +45 -0
  148. package/template/src/cli/config/store_keys.mjs +2 -0
  149. package/template/src/cli/context/data/DataContext.jsx +5 -0
  150. package/template/src/cli/context/data/DataProvider.jsx +44 -0
  151. package/template/src/cli/context/data/useBreads.mjs +15 -0
  152. package/template/src/cli/context/data/useDataContext.mjs +4 -0
  153. package/template/src/cli/context/session/SessionContext.mjs +4 -0
  154. package/template/src/cli/context/session/SessionProvider.jsx +31 -0
  155. package/template/src/cli/context/session/makePermissioner.mjs +34 -0
  156. package/template/src/cli/context/session/useSessionContext.mjs +6 -0
  157. package/template/src/cli/context/theme/ThemeContext.mjs +4 -0
  158. package/template/src/cli/context/theme/ThemeProvider.jsx +49 -0
  159. package/template/src/cli/context/theme/useThemeContext.mjs +6 -0
  160. package/template/src/cli/context/ui/UIContext.jsx +5 -0
  161. package/template/src/cli/context/ui/UIProvider.jsx +16 -0
  162. package/template/src/cli/context/ui/useUIContext.mjs +4 -0
  163. package/template/src/cli/context/util.mjs +17 -0
  164. package/template/src/cli/entry-cli.jsx +33 -0
  165. package/template/src/cli/hooks/useIsMobile.mjs +19 -0
  166. package/template/src/cli/hooks/useStoragedState.mjs +63 -0
  167. package/template/src/cli/index.html +29 -0
  168. package/template/src/cli/layout/app-sidebar.jsx +25 -0
  169. package/template/src/cli/layout/main-layout.jsx +63 -0
  170. package/template/src/cli/layout/nav-last-todos.jsx +72 -0
  171. package/template/src/cli/layout/nav-main.jsx +39 -0
  172. package/template/src/cli/layout/nav-user.jsx +105 -0
  173. package/template/src/cli/layout/prop-switcher.jsx +93 -0
  174. package/template/src/cli/lib/utils.mjs +10 -0
  175. package/template/src/cli/pages/Index.jsx +13 -0
  176. package/template/src/cli/pages/IndexOffline.jsx +13 -0
  177. package/template/src/cli/pages/IndexOnline.jsx +18 -0
  178. package/template/src/cli/pages/dash/Dashboard.jsx +29 -0
  179. package/template/src/cli/pages/offline/Login.jsx +43 -0
  180. package/template/src/cli/pages/offline/LoginForm.jsx +115 -0
  181. package/template/src/cli/pages/security/Security.jsx +39 -0
  182. package/template/src/cli/pages/security/SecurityForm.jsx +106 -0
  183. package/template/src/cli/pages/todos/TodoActions.jsx +99 -0
  184. package/template/src/cli/pages/todos/TodoAdd.jsx +43 -0
  185. package/template/src/cli/pages/todos/TodoList.jsx +60 -0
  186. package/template/src/cli/pages/todos/Todos.jsx +23 -0
  187. package/template/src/cli/pages/todos/context/TodosContext.jsx +5 -0
  188. package/template/src/cli/pages/todos/context/TodosProvider.jsx +191 -0
  189. package/template/src/cli/pages/todos/context/useTodosContext.mjs +4 -0
  190. package/template/src/ns/models/Todo.mjs +29 -0
  191. package/template/src/ns/models/TodoList.mjs +8 -0
  192. package/template/src/ns/models/User.mjs +40 -0
  193. package/template/src/server/bot/check_today.mjs +10 -0
  194. package/template/src/server/io/cache/base.mjs +21 -0
  195. package/template/src/server/io/db/filter.mjs +92 -0
  196. package/template/src/server/io/db/todos/delete.mjs +29 -0
  197. package/template/src/server/io/db/todos/find.mjs +13 -0
  198. package/template/src/server/io/db/todos/read.mjs +83 -0
  199. package/template/src/server/io/db/todos/toggle.mjs +37 -0
  200. package/template/src/server/io/db/todos/upsave.mjs +32 -0
  201. package/template/src/server/io/db/triggers/user.mjs +13 -0
  202. package/template/src/server/io/db/users/auth.mjs +132 -0
  203. package/template/src/server/io/db/users/pwd.mjs +38 -0
  204. package/template/src/server/io/db/users/save.mjs +17 -0
  205. package/template/src/server/miolo/auth/basic.mjs +15 -0
  206. package/template/src/server/miolo/auth/guest.mjs +3 -0
  207. package/template/src/server/miolo/auth/passport.mjs +73 -0
  208. package/template/src/server/miolo/cache.mjs +11 -0
  209. package/template/src/server/miolo/cron/foo.mjs +7 -0
  210. package/template/src/server/miolo/cron/index.mjs +28 -0
  211. package/template/src/server/miolo/cron/invalidate.mjs +21 -0
  212. package/template/src/server/miolo/db.mjs +36 -0
  213. package/template/src/server/miolo/http.mjs +14 -0
  214. package/template/src/server/miolo/index.mjs +43 -0
  215. package/template/src/server/miolo/routes/crud.mjs +16 -0
  216. package/template/src/server/miolo/routes/index.mjs +8 -0
  217. package/template/src/server/miolo/ssr/entry-server.jsx +13 -0
  218. package/template/src/server/miolo/ssr/loader.mjs +18 -0
  219. package/template/src/server/routes/index.mjs +66 -0
  220. package/template/src/server/routes/todos/mod.mjs +52 -0
  221. package/template/src/server/routes/todos/read.mjs +45 -0
  222. package/template/src/server/routes/todos/special.mjs +47 -0
  223. package/template/src/server/routes/users/user.mjs +54 -0
  224. package/template/src/server/server.mjs +10 -0
  225. package/template/src/server/utils/crypt.mjs +38 -0
  226. package/template/src/server/utils/io.mjs +15 -0
  227. package/template/src/server/utils/pwdfor.mjs +25 -0
  228. package/template/src/server/utils/schema.mjs +22 -0
  229. package/template/src/static/img/default/profile.png +0 -0
  230. package/template/src/static/img/favicon.ico +0 -0
  231. package/template/src/static/img/miolo_logo.png +0 -0
  232. package/template/src/static/img/miolo_name.png +0 -0
  233. package/template/src/static/public/manifest.json +21 -0
  234. package/template/src/static/public/sw.js +79 -0
  235. package/template/src/static/style/globals.css +156 -0
  236. package/template/src/static/style/json-tree.css +54 -0
  237. package/template/src/static/style/skeleton.css +49 -0
  238. package/bin/prod-build/build-client.mjs +0 -67
  239. package/bin/prod-build/build-server.mjs +0 -58
  240. package/bin/prod-run/restart.mjs +0 -9
  241. package/bin/prod-run/start.mjs +0 -15
  242. package/bin/prod-run/stop.mjs +0 -20
  243. package/src/engines/logger/verify.mjs +0 -22
  244. package/src/middleware/auth/credentials/index.mjs +0 -151
  245. package/src/middleware/auth/credentials/session/index.mjs +0 -24
  246. package/src/middleware/auth/credentials/session/store_koa_redis.mjs +0 -3
@@ -0,0 +1,13 @@
1
+ import { Route, Routes } from "react-router"
2
+
3
+ import Login from "#cli/pages/offline/Login.jsx"
4
+
5
+ export default function IndexOffline() {
6
+ return (
7
+ <Routes>
8
+ <Route index element={<Login />} />
9
+
10
+ <Route path="*" element={<Login />} />
11
+ </Routes>
12
+ )
13
+ }
@@ -0,0 +1,18 @@
1
+ import { Route, Routes } from "react-router"
2
+ import MainLayout from "#cli/layout/main-layout.jsx"
3
+ import Dashboard from "#cli/pages/dash/Dashboard.jsx"
4
+ import Security from "#cli/pages/security/Security.jsx"
5
+
6
+ export default function IndexOnline() {
7
+ return (
8
+ <Routes>
9
+ <Route path={"/"} element={<MainLayout />}>
10
+ <Route index element={<Dashboard />} />
11
+
12
+ <Route path={"security"} element={<Security />} />
13
+
14
+ <Route path={"*"} element={<Dashboard />} />
15
+ </Route>
16
+ </Routes>
17
+ )
18
+ }
@@ -0,0 +1,29 @@
1
+ import useBreads from "#cli/context/data/useBreads.mjs"
2
+
3
+ import TodosProvider from "../todos/context/TodosProvider.jsx"
4
+ import Todos from "../todos/Todos.jsx"
5
+
6
+ export default function Dashboard() {
7
+ useBreads(() => {
8
+ return [["Inicio"]]
9
+ }, [])
10
+
11
+ return (
12
+ <div className="flex flex-1 flex-col gap-4 p-4">
13
+ <TodosProvider>
14
+ <Todos />
15
+ </TodosProvider>
16
+ {/*
17
+ <div className="grid auto-rows-min gap-4 md:grid-cols-3">
18
+ <div className="bg-muted/50 aspect-video rounded-xl">
19
+
20
+ </div>
21
+ <div className="bg-muted/50 aspect-video rounded-xl" />
22
+ <div className="bg-muted/50 aspect-video rounded-xl" />
23
+ </div>
24
+ <div className="bg-muted/50 min-h-[100vh] flex-1 rounded-xl md:min-h-min">
25
+ </div>
26
+ */}
27
+ </div>
28
+ )
29
+ }
@@ -0,0 +1,43 @@
1
+ import { LoginForm } from "./LoginForm.jsx"
2
+
3
+ const Login = () => {
4
+ return (
5
+ <div className="grid min-h-svh lg:grid-cols-2">
6
+ <div className="flex flex-col gap-4 p-6 md:p-10">
7
+ <div className="flex justify-center gap-2 md:justify-start">
8
+ <img
9
+ src="/static/img/miolo_name.png"
10
+ alt="miolo logo"
11
+ className="absolute inset-0 h-40 w-40 object-contain dark:brightness-[0.8] "
12
+ />
13
+
14
+ {/*
15
+ <a href="#" className="flex items-center gap-2 font-medium">
16
+ <div className="bg-primary text-primary-foreground flex size-6 items-center justify-center rounded-md">
17
+ <GalleryVerticalEnd className="size-4" />
18
+ </div>
19
+ Acme Inc.
20
+
21
+ </a>
22
+ */}
23
+ </div>
24
+ <div className="flex flex-1 items-center justify-center">
25
+ <div className="w-full max-w-xs">
26
+ <LoginForm
27
+ /* TODO orgotLink*/
28
+ />
29
+ </div>
30
+ </div>
31
+ </div>
32
+ <div className="bg-muted relative hidden lg:block">
33
+ <img
34
+ src="/static/img/miolo_logo.png"
35
+ alt="miolo logo"
36
+ className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.8] dark:grayscale"
37
+ />
38
+ </div>
39
+ </div>
40
+ )
41
+ }
42
+
43
+ export default Login
@@ -0,0 +1,115 @@
1
+ import { useCallback, useState } from "react"
2
+ import { Input } from "#cli/components/ui/input.jsx"
3
+ import { Label } from "#cli/components/ui/label.jsx"
4
+ import { Button } from "#cli/components/ui/patched/button.jsx"
5
+ import useSessionContext from "#cli/context/session/useSessionContext.mjs"
6
+ import { cn } from "#cli/lib/utils.mjs"
7
+
8
+ export function LoginForm({ className, forgotLink }) {
9
+ const { localLogin, googleLogin } = useSessionContext()
10
+ const [email, setEmail] = useState("")
11
+ const [pwd, setPwd] = useState("")
12
+ const [valid, setValid] = useState(true)
13
+
14
+ const handleLocalLogin = useCallback(
15
+ async (ev) => {
16
+ ev.preventDefault()
17
+ const data = await localLogin({ username: email, password: pwd })
18
+ setValid(data?.authenticated === true)
19
+ },
20
+ [email, pwd, localLogin]
21
+ )
22
+
23
+ return (
24
+ <>
25
+ <form className={cn("flex flex-col gap-6", className)} onSubmit={handleLocalLogin}>
26
+ <div className="flex flex-col items-center gap-2 text-center">
27
+ <h1 className="text-2xl font-bold">miolo-sample</h1>
28
+ </div>
29
+ <br />
30
+ <div className="grid gap-6">
31
+ <div className="grid gap-3">
32
+ <Label htmlFor="email">Email</Label>
33
+ <Input
34
+ id="email"
35
+ type="email"
36
+ placeholder="m@example.com"
37
+ required
38
+ value={email}
39
+ onChange={(e) => setEmail(e.target.value)}
40
+ className={cn(valid ? "valid" : "invalid")}
41
+ />
42
+ </div>
43
+ <div className="grid gap-3">
44
+ <div className="flex items-center">
45
+ <Label htmlFor="password">Password</Label>
46
+ </div>
47
+ <Input
48
+ id="password"
49
+ type="password"
50
+ required
51
+ value={pwd}
52
+ onChange={(e) => setPwd(e.target.value)}
53
+ className={cn(valid ? "valid" : "invalid")}
54
+ />
55
+ </div>
56
+ {!valid && (
57
+ <div className="relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center">
58
+ <p className="text-red-500 text-sm">Email o contraseña incorrectos</p>
59
+ </div>
60
+ )}
61
+ <Button type="submit" className="w-full">
62
+ Login
63
+ </Button>
64
+ {forgotLink !== undefined ? (
65
+ <div className="relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center">
66
+ <a
67
+ href={forgotLink}
68
+ className="bg-background text-muted-foreground relative z-10 px-2 ml-auto text-sm underline-offset-4 hover:underline"
69
+ >
70
+ Has olvidado la contraseña?
71
+ </a>
72
+ </div>
73
+ ) : null}
74
+ </div>
75
+ {/*
76
+ <div className="text-center text-sm">
77
+ Don&apos;t have an account?{" "}
78
+ <a href="#" className="underline underline-offset-4">
79
+ Sign up
80
+ </a>
81
+ </div>
82
+ */}
83
+ </form>
84
+
85
+ <div className="mt-8 after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t">
86
+ <span className="bg-background text-muted-foreground relative z-10 px-2">Or</span>
87
+ </div>
88
+ <Button variant="outline" className="w-full mt-8" onClick={googleLogin}>
89
+ <svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
90
+ <title>Google</title>
91
+ <path
92
+ d="M23.75,16A7.7446,7.7446,0,0,1,8.7177,18.6259L4.2849,22.1721A13.244,13.244,0,0,0,29.25,16"
93
+ fill="#00ac47"
94
+ />
95
+ <path
96
+ d="M23.75,16a7.7387,7.7387,0,0,1-3.2516,6.2987l4.3824,3.5059A13.2042,13.2042,0,0,0,29.25,16"
97
+ fill="#4285f4"
98
+ />
99
+ <path
100
+ d="M8.25,16a7.698,7.698,0,0,1,.4677-2.6259L4.2849,9.8279a13.177,13.177,0,0,0,0,12.3442l4.4328-3.5462A7.698,7.698,0,0,1,8.25,16Z"
101
+ fill="#ffba00"
102
+ />
103
+ <polygon fill="#2ab2db" points="8.718 13.374 8.718 13.374 8.718 13.374 8.718 13.374" />
104
+ <path
105
+ d="M16,8.25a7.699,7.699,0,0,1,4.558,1.4958l4.06-3.7893A13.2152,13.2152,0,0,0,4.2849,9.8279l4.4328,3.5462A7.756,7.756,0,0,1,16,8.25Z"
106
+ fill="#ea4435"
107
+ />
108
+ <polygon fill="#2ab2db" points="8.718 18.626 8.718 18.626 8.718 18.626 8.718 18.626" />
109
+ <path d="M29.25,15v1L27,19.5H16.5V14H28.25A1,1,0,0,1,29.25,15Z" fill="#4285f4" />
110
+ </svg>
111
+ Login with Google
112
+ </Button>
113
+ </>
114
+ )
115
+ }
@@ -0,0 +1,39 @@
1
+ import { useCallback } from "react"
2
+ import useBreads from "#cli/context/data/useBreads.mjs"
3
+ import useSessionContext from "#cli/context/session/useSessionContext.mjs"
4
+ import useUIContext from "#cli/context/ui/useUIContext.mjs"
5
+ import SecurityForm from "./SecurityForm.jsx"
6
+
7
+ export default function Security() {
8
+ const { session, fetcher } = useSessionContext()
9
+ const { toast } = useUIContext()
10
+
11
+ const passwordChange = useCallback(
12
+ async (pwds) => {
13
+ try {
14
+ const params = { username: session.username, passwords: pwds }
15
+ const resp = await fetcher.post("/user/chpwd", params)
16
+ if (resp.ok === true) {
17
+ toast.success(resp.data.msg)
18
+ } else {
19
+ toast.error(resp.error)
20
+ }
21
+
22
+ return { ok: resp.ok, error: resp?.error }
23
+ } catch (e) {
24
+ return { ok: false, error: `Error al modificar la contraseña: ${e}` }
25
+ }
26
+ },
27
+ [fetcher, session.username, toast]
28
+ )
29
+
30
+ useBreads(() => [["/", "Inicio"], ["Seguridad"]], [])
31
+
32
+ return (
33
+ <div className="flex flex-1 items-center justify-center">
34
+ <div className="w-full max-w-xs">
35
+ <SecurityForm onChangePassword={passwordChange} />
36
+ </div>
37
+ </div>
38
+ )
39
+ }
@@ -0,0 +1,106 @@
1
+ import { useCallback, useEffect, useState } from "react"
2
+ import { Input } from "#cli/components/ui/input.jsx"
3
+ import { Label } from "#cli/components/ui/label.jsx"
4
+ import { Button } from "#cli/components/ui/patched/button.jsx"
5
+ import { cn } from "#cli/lib/utils.mjs"
6
+
7
+ export default function SecurityForm({ className, onChangePassword }) {
8
+ const [current, setCurrent] = useState("")
9
+ const [pwdNew, setPwdNew] = useState("")
10
+ const [pwdRepeat, setPwdRepeat] = useState("")
11
+ const [error, setError] = useState(true)
12
+ const [ready, setReady] = useState(false)
13
+
14
+ const validate = useCallback(() => {
15
+ if (!current || !pwdNew || !pwdRepeat) {
16
+ //setError('Todos los campos son obligatorios')
17
+ return false
18
+ }
19
+ if (pwdNew === current) {
20
+ setError("La contraseña nueva no puede ser igual a la actual")
21
+ return false
22
+ }
23
+ if (pwdNew !== pwdRepeat) {
24
+ setError("Las contraseñas no coinciden")
25
+ return false
26
+ }
27
+ setError(undefined)
28
+ return true
29
+ }, [pwdNew, pwdRepeat, current])
30
+
31
+ const handleCallback = useCallback(
32
+ async (ev) => {
33
+ ev.preventDefault()
34
+ if (!ready) return
35
+
36
+ const { ok, error } = await onChangePassword({ current: current, new: pwdNew })
37
+
38
+ if (ok === true) {
39
+ setError(undefined)
40
+ } else {
41
+ setError(error)
42
+ }
43
+ },
44
+ [onChangePassword, current, pwdNew, ready]
45
+ )
46
+
47
+ useEffect(() => {
48
+ const ok = validate()
49
+ setReady(ok)
50
+ }, [validate])
51
+
52
+ return (
53
+ <form className={cn("flex flex-col gap-6", className)} onSubmit={handleCallback}>
54
+ <div className="flex flex-col items-center gap-2 text-center">
55
+ <h2 className="font-bcurrent">Cambiar contraseña</h2>
56
+ </div>
57
+ <div className="grid gap-6">
58
+ <div className="grid gap-3">
59
+ <Label htmlFor="current">Contraseña actual</Label>
60
+ <Input
61
+ id="current"
62
+ type="password"
63
+ required
64
+ value={current}
65
+ onKeyDown={validate}
66
+ onChange={(e) => setCurrent(e.target.value)}
67
+ />
68
+ </div>
69
+ <div className="grid gap-3">
70
+ <div className="flex items-center">
71
+ <Label htmlFor="new">Contraseña nueva</Label>
72
+ </div>
73
+ <Input
74
+ id="new"
75
+ type="password"
76
+ required
77
+ value={pwdNew}
78
+ onKeyDown={validate}
79
+ onChange={(e) => setPwdNew(e.target.value)}
80
+ />
81
+ </div>
82
+ <div className="grid gap-3">
83
+ <div className="flex items-center">
84
+ <Label htmlFor="repeat">Repita la contraseña nueva</Label>
85
+ </div>
86
+ <Input
87
+ id="repeat"
88
+ type="password"
89
+ required
90
+ value={pwdRepeat}
91
+ onKeyDown={validate}
92
+ onChange={(e) => setPwdRepeat(e.target.value)}
93
+ />
94
+ </div>
95
+ {error && (
96
+ <div className="relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center">
97
+ <p className="text-red-500 text-sm">{error}</p>
98
+ </div>
99
+ )}
100
+ <Button type="submit" className="w-full" disabled={!ready}>
101
+ Cambiar contraseña
102
+ </Button>
103
+ </div>
104
+ </form>
105
+ )
106
+ }
@@ -0,0 +1,99 @@
1
+ import { Bomb, Bone, CircleQuestionMark, Phone, RefreshCw } from "lucide-react"
2
+ import { useState } from "react"
3
+ import { Field, FieldContent, FieldLabel } from "#cli/components/ui/field.jsx"
4
+ import { Button } from "#cli/components/ui/patched/button.jsx"
5
+ import { Switch } from "#cli/components/ui/switch.jsx"
6
+
7
+ import useTodosContext from "./context/useTodosContext.mjs"
8
+
9
+ const throwAnError = () => {
10
+ const obj = {}
11
+ const _foo = obj.foo.bar
12
+ }
13
+
14
+ export default function TodoActions() {
15
+ const {
16
+ refreshTodoList,
17
+ checkLastHours,
18
+ insertFakeTodo,
19
+ canEdit,
20
+ useCrud,
21
+ setUseCrud,
22
+ pingSocket
23
+ } = useTodosContext()
24
+ const [hours, _setHours] = useState(1)
25
+
26
+ return (
27
+ <div className="flex flex-col gap-4 p-4">
28
+ <h1 className="text-3xl font-bold text-center mb-8 text-gray-800 dark:text-white">Actions</h1>
29
+
30
+ <div className="flex flex-col gap-2 mt-4">
31
+ <Button
32
+ onClick={refreshTodoList}
33
+ className={`px-4 py-6 text-white rounded-lg transition-colors flex items-center gap-2 cursor-pointer ${useCrud ? "bg-pink-500 hover:bg-pink-400" : "bg-blue-500 hover:bg-blue-400"}`}
34
+ >
35
+ <RefreshCw size={18} />
36
+ Refresh
37
+ </Button>
38
+ <Button
39
+ onClick={() => insertFakeTodo()}
40
+ disabled={!canEdit}
41
+ className={`px-4 py-6 text-white rounded-lg transition-colors flex items-center gap-2 cursor-pointer ${useCrud ? "bg-pink-500 hover:bg-pink-400" : "bg-blue-500 hover:bg-blue-400"}`}
42
+ >
43
+ <Bone size={18} />
44
+ Insert fake
45
+ </Button>
46
+
47
+ <Button
48
+ onClick={() => checkLastHours({ hours })}
49
+ className="px-4 py-6 bg-pink-500 text-white rounded-lg hover:bg-pink-400 transition-colors flex items-center gap-2 cursor-pointer"
50
+ >
51
+ <CircleQuestionMark size={18} />
52
+ {`How many todos I have added in the last ${hours} hours?`}
53
+ </Button>
54
+ <Button
55
+ onClick={() => throwAnError()}
56
+ className="px-4 py-6 bg-pink-500 text-white rounded-lg hover:bg-pink-400 transition-colors flex items-center gap-2 cursor-pointer"
57
+ >
58
+ <Bomb size={18} />
59
+ {`Throw an JS error`}
60
+ </Button>
61
+
62
+ <Button
63
+ onClick={() => pingSocket()}
64
+ disabled={!canEdit}
65
+ className={`px-4 py-6 text-white rounded-lg transition-colors flex items-center gap-2 cursor-pointer ${useCrud ? "bg-pink-500 hover:bg-pink-400" : "bg-blue-500 hover:bg-blue-400"}`}
66
+ >
67
+ <Phone size={18} />
68
+ Ping through socket
69
+ </Button>
70
+ </div>
71
+
72
+ <h1 className="text-3xl font-bold text-center mt-8 mb-8 text-gray-800 dark:text-white">
73
+ Options
74
+ </h1>
75
+
76
+ <div className="flex flex-col gap-2 mt-4">
77
+ <Field orientation="horizontal" className="max-w-sm">
78
+ <FieldContent>
79
+ <FieldLabel htmlFor="switch-focus-mode">
80
+ Use <span className="font-bold text-pink-500">CRUD</span>
81
+ </FieldLabel>
82
+ <div className="text-sm text-muted-foreground">
83
+ <div>
84
+ <span className="font-bold text-pink-500">CRUD</span> are the routes exposed by{" "}
85
+ <b>miolo</b> directly from database: <i>one table, one route</i>.
86
+ </div>
87
+ <div>
88
+ <span className="font-bold text-blue-500">API</span> are the routes exposed by the
89
+ developer.
90
+ </div>
91
+ <div>Some actions have the two ways: just pick one of them.</div>
92
+ </div>
93
+ </FieldContent>
94
+ <Switch id="switch-focus-mode" checked={useCrud} onCheckedChange={setUseCrud} />
95
+ </Field>
96
+ </div>
97
+ </div>
98
+ )
99
+ }
@@ -0,0 +1,43 @@
1
+ import { Plus } from "lucide-react"
2
+ import { useState } from "react"
3
+ import { Button } from "#cli/components/ui/patched/button.jsx"
4
+ import useTodosContext from "./context/useTodosContext.mjs"
5
+
6
+ export default function TodoAdd() {
7
+ const { addTodo, canEdit, useCrud } = useTodosContext()
8
+
9
+ const [inputText, setInputText] = useState("")
10
+
11
+ const handleSubmit = (e) => {
12
+ e.preventDefault()
13
+ if (inputText.trim()) {
14
+ addTodo(inputText)
15
+ setInputText("")
16
+ }
17
+ }
18
+
19
+ return (
20
+ <form onSubmit={handleSubmit} className="mb-8">
21
+ <div className="flex gap-2">
22
+ <input
23
+ type="text"
24
+ value={inputText}
25
+ onChange={(e) => setInputText(e.target.value)}
26
+ placeholder={
27
+ canEdit ? "Things to be done..." : "LogIn in order to be able to add todos :/"
28
+ }
29
+ disabled={!canEdit}
30
+ className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-pink-500 focus:border-transparent dark:bg-gray-700 dark:text-white"
31
+ />
32
+ <Button
33
+ type="submit"
34
+ disabled={!canEdit}
35
+ className={`px-4 py-6 text-white rounded-lg transition-colors flex items-center gap-2 cursor-pointer ${useCrud ? "bg-pink-500 hover:bg-pink-400" : "bg-blue-500 hover:bg-blue-400"}`}
36
+ >
37
+ <Plus size={18} />
38
+ Add
39
+ </Button>
40
+ </div>
41
+ </form>
42
+ )
43
+ }
@@ -0,0 +1,60 @@
1
+ import { Check, CircleQuestionMark, Trash2 } from "lucide-react"
2
+ import { Button } from "#cli/components/ui/patched/button.jsx"
3
+ import useTodosContext from "./context/useTodosContext.mjs"
4
+
5
+ export default function TodoList() {
6
+ const { todoList, toggleTodo, removeTodo, canEdit, useCrud } = useTodosContext()
7
+
8
+ return (
9
+ <div className="space-y-3">
10
+ {todoList.length === 0 ? (
11
+ <p className="text-center text-gray-500 dark:text-gray-400">No todos yet. Add one above!</p>
12
+ ) : (
13
+ <ul className="space-y-2">
14
+ {todoList.map((todo) => (
15
+ <li
16
+ key={todo.id}
17
+ className="flex items-center justify-between p-4 bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-md transition-shadow"
18
+ >
19
+ <div className="flex items-center space-x-3">
20
+ <Button
21
+ onClick={() => toggleTodo(todo.id)}
22
+ disabled={!canEdit}
23
+ className={`w-6 h-6 rounded-full border-2 flex items-center justify-center transition-colors ${
24
+ todo.done
25
+ ? useCrud
26
+ ? "bg-green-500 hover:border-pink-500 text-white "
27
+ : "bg-green-500 hover:border-blue-500 text-white "
28
+ : useCrud
29
+ ? "hover:border-pink-500"
30
+ : "hover:border-blue-500"
31
+ }`}
32
+ aria-label={todo.done ? "Mark as not done" : "Mark as done"}
33
+ >
34
+ {todo.done ? <Check size={16} /> : <CircleQuestionMark size={16} />}
35
+ </Button>
36
+ <span
37
+ className={`text-lg ${
38
+ todo.done
39
+ ? "line-through text-gray-500 dark:text-gray-400"
40
+ : "text-gray-800 dark:text-gray-200"
41
+ }`}
42
+ >
43
+ {todo.description}
44
+ </span>
45
+ </div>
46
+ <Button
47
+ onClick={() => removeTodo(todo.id)}
48
+ disabled={!canEdit}
49
+ className={`p-1.5 rounded-full transition-colors ${useCrud ? "text-pink-500 hover:text-pink-400 dark:text-gray-400 dark:hover:text-pink-400 hover:bg-pink-50 dark:hover:bg-gray-700" : "text-gray-500 hover:text-blue-500 dark:text-gray-400 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-gray-700"}`}
50
+ aria-label="Delete todo"
51
+ >
52
+ <Trash2 size={18} />
53
+ </Button>
54
+ </li>
55
+ ))}
56
+ </ul>
57
+ )}
58
+ </div>
59
+ )
60
+ }
@@ -0,0 +1,23 @@
1
+ import TodoActions from "./TodoActions.jsx"
2
+ import TodoAdd from "./TodoAdd.jsx"
3
+ import TodoList from "./TodoList.jsx"
4
+
5
+ export default function TodosList() {
6
+ return (
7
+ <div className="flex flex-row gap-4 p-4">
8
+ <div className="p-4 flex-1">
9
+ <h1 className="text-3xl font-bold text-center mb-8 text-gray-800 dark:text-white">
10
+ My Todo List
11
+ </h1>
12
+
13
+ {/* Add Todo Form */}
14
+ <TodoAdd />
15
+
16
+ {/* Todo List */}
17
+ <TodoList />
18
+ </div>
19
+
20
+ <TodoActions />
21
+ </div>
22
+ )
23
+ }
@@ -0,0 +1,5 @@
1
+ import { createContext } from "react"
2
+
3
+ const TodosContext = createContext()
4
+
5
+ export default TodosContext