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.
- package/bin/build/build.mjs +53 -0
- package/bin/build/build_bin.mjs +17 -0
- package/bin/build/cli/client.mjs +52 -0
- package/bin/build/cli/css.mjs +22 -0
- package/bin/build/cli/index.mjs +40 -0
- package/bin/build/cli/ssr.mjs +21 -0
- package/bin/build/server/aliases.mjs +112 -0
- package/bin/build/server/babel.config.js +24 -0
- package/bin/build/server/banner.mjs +20 -0
- package/bin/build/server/bundle.mjs +111 -0
- package/bin/build/server/fix.mjs +15 -0
- package/bin/build/server/index.mjs +69 -0
- package/bin/build/server/options.mjs +83 -0
- package/bin/create/auth.mjs +23 -0
- package/bin/create/copy.mjs +175 -0
- package/bin/create/docker.mjs +25 -0
- package/bin/create/index.mjs +137 -0
- package/bin/create/pkgjson.mjs +72 -0
- package/bin/create/prepare-template.mjs +158 -0
- package/bin/create/validation.mjs +27 -0
- package/bin/dev/dev.mjs +32 -23
- package/bin/dev/dev_start.mjs +6 -5
- package/bin/index.mjs +94 -52
- package/bin/prod-bin/create-bin.mjs +42 -27
- package/bin/prod-bin/run.mjs +13 -9
- package/bin/{prod-run → run}/pid.mjs +4 -4
- package/bin/run/restart.mjs +13 -0
- package/bin/run/start.mjs +18 -0
- package/bin/run/stop.mjs +20 -0
- package/bin/util.mjs +35 -11
- package/package.json +59 -39
- package/src/config/.env +34 -12
- package/src/config/defaults.mjs +253 -185
- package/src/config/env.mjs +40 -22
- package/src/config/index.mjs +19 -24
- package/src/config/util.mjs +25 -10
- package/src/db-conn.mjs +34 -0
- package/src/engines/cron/emails.mjs +10 -5
- package/src/engines/cron/index.mjs +45 -51
- package/src/engines/cron/init.mjs +16 -17
- package/src/engines/cron/ipsum.mjs +65 -60
- package/src/engines/cron/syscheck.mjs +30 -30
- package/src/engines/emailer/index.mjs +1 -2
- package/src/engines/emailer/queue.mjs +14 -20
- package/src/engines/emailer/transporter.mjs +86 -74
- package/src/engines/geoip/index.mjs +23 -28
- package/src/engines/http/index.mjs +26 -15
- package/src/engines/logger/buildErrorEmailBody.mjs +72 -0
- package/src/engines/logger/index.mjs +114 -122
- package/src/engines/logger/injectStackTrace.mjs +59 -0
- package/src/engines/logger/logger_mail.mjs +47 -61
- package/src/engines/logger/reopenTransportOnHupSignal.mjs +12 -13
- package/src/engines/parser/Parser.mjs +77 -60
- package/src/engines/parser/index.mjs +1 -1
- package/src/engines/schema/diffObjs.mjs +41 -0
- package/src/engines/schema/index.mjs +4 -0
- package/src/engines/schema/input.mjs +54 -0
- package/src/engines/schema/output.mjs +66 -0
- package/src/engines/socket/index.mjs +44 -46
- package/src/index.mjs +15 -10
- package/src/middleware/auth/basic.mjs +41 -40
- package/src/middleware/auth/custom.mjs +10 -13
- package/src/middleware/auth/guest.mjs +27 -27
- package/src/middleware/auth/passport/index.mjs +374 -0
- package/src/middleware/auth/passport/session/index.mjs +43 -0
- package/src/middleware/auth/{credentials → passport}/session/store.mjs +35 -15
- package/src/middleware/auth/passport/session/store_koa_redis.mjs +3 -0
- package/src/middleware/context/cache/index.mjs +78 -33
- package/src/middleware/context/cache/options.mjs +19 -21
- package/src/middleware/context/db.mjs +45 -20
- package/src/middleware/context/index.mjs +12 -12
- package/src/middleware/extra.mjs +4 -5
- package/src/middleware/http/body.mjs +25 -25
- package/src/middleware/http/catcher.mjs +81 -8
- package/src/middleware/http/custom_blacklist.mjs +19 -16
- package/src/middleware/http/headers.mjs +37 -34
- package/src/middleware/http/ratelimit.mjs +16 -23
- package/src/middleware/http/request.mjs +60 -65
- package/src/middleware/routes/catch_js_error.mjs +30 -23
- package/src/middleware/routes/robots.mjs +4 -7
- package/src/middleware/routes/router/crud/attachCrudRoutes.mjs +108 -90
- package/src/middleware/routes/router/crud/getCrudConfig.mjs +31 -55
- package/src/middleware/routes/router/defaults.mjs +6 -19
- package/src/middleware/routes/router/index.mjs +17 -21
- package/src/middleware/routes/router/queries/attachQueriesRoutes.mjs +227 -50
- package/src/middleware/routes/router/queries/getQueriesConfig.mjs +45 -55
- package/src/middleware/routes/router/utils.mjs +41 -26
- package/src/middleware/ssr/context.mjs +5 -7
- package/src/middleware/ssr/html.mjs +66 -43
- package/src/middleware/ssr/loader.mjs +11 -14
- package/src/middleware/ssr/ssr_render.mjs +39 -22
- package/src/middleware/static/index.mjs +33 -14
- package/src/middleware/vite/devserver.mjs +38 -22
- package/src/middleware/vite/watcher.mjs +12 -14
- package/src/server-cron.mjs +13 -8
- package/src/server-dev.mjs +13 -16
- package/src/server.mjs +49 -51
- package/template/.agent/skills/miolo-app-arch/SKILL.md +218 -0
- package/template/.agent/skills/miolo-auth/SKILL.md +450 -0
- package/template/.agent/skills/miolo-cli-router/SKILL.md +394 -0
- package/template/.agent/skills/miolo-database/SKILL.md +358 -0
- package/template/.agent/skills/miolo-react-patterns/SKILL.md +426 -0
- package/template/.agent/skills/miolo-routing/SKILL.md +326 -0
- package/template/.agent/skills/miolo-schemas/SKILL.md +329 -0
- package/template/.agent/skills/miolo-session-context/SKILL.md +397 -0
- package/template/.agent/skills/miolo-ssr/SKILL.md +433 -0
- package/template/.editorconfig +18 -0
- package/template/.env +120 -0
- package/template/biome.json +63 -0
- package/template/components.json +21 -0
- package/template/db/init.sh +89 -0
- package/template/db/sql/00_drop.sql +2 -0
- package/template/db/sql/01_users.sql +31 -0
- package/template/db/sql/02_todos.sql +20 -0
- package/template/docker/Dockerfile +13 -0
- package/template/docker/docker-compose.yaml +79 -0
- package/template/gitignore +42 -0
- package/template/jsconfig.json +18 -0
- package/template/package.json +88 -0
- package/template/postcss.config.js +9 -0
- package/template/src/cli/App.jsx +25 -0
- package/template/src/cli/components/JsonTreeViewer.jsx +128 -0
- package/template/src/cli/components/shadcn-io/spinner/index.jsx +232 -0
- package/template/src/cli/components/stepper.jsx +408 -0
- package/template/src/cli/components/ui/avatar.jsx +36 -0
- package/template/src/cli/components/ui/badge.jsx +31 -0
- package/template/src/cli/components/ui/breadcrumb.jsx +97 -0
- package/template/src/cli/components/ui/card.jsx +73 -0
- package/template/src/cli/components/ui/collapsible.jsx +16 -0
- package/template/src/cli/components/ui/dropdown-menu.jsx +179 -0
- package/template/src/cli/components/ui/field.jsx +217 -0
- package/template/src/cli/components/ui/input.jsx +19 -0
- package/template/src/cli/components/ui/label.jsx +17 -0
- package/template/src/cli/components/ui/pagination.jsx +99 -0
- package/template/src/cli/components/ui/patched/alert.jsx +56 -0
- package/template/src/cli/components/ui/patched/button.jsx +45 -0
- package/template/src/cli/components/ui/patched/dialog.jsx +114 -0
- package/template/src/cli/components/ui/patched/sidebar.jsx +660 -0
- package/template/src/cli/components/ui/select.jsx +141 -0
- package/template/src/cli/components/ui/separator.jsx +21 -0
- package/template/src/cli/components/ui/sheet.jsx +115 -0
- package/template/src/cli/components/ui/skeleton.jsx +13 -0
- package/template/src/cli/components/ui/sonner.jsx +22 -0
- package/template/src/cli/components/ui/switch.jsx +25 -0
- package/template/src/cli/components/ui/table.jsx +88 -0
- package/template/src/cli/components/ui/textarea.jsx +16 -0
- package/template/src/cli/components/ui/tooltip.jsx +45 -0
- package/template/src/cli/config/store_keys.mjs +2 -0
- package/template/src/cli/context/data/DataContext.jsx +5 -0
- package/template/src/cli/context/data/DataProvider.jsx +44 -0
- package/template/src/cli/context/data/useBreads.mjs +15 -0
- package/template/src/cli/context/data/useDataContext.mjs +4 -0
- package/template/src/cli/context/session/SessionContext.mjs +4 -0
- package/template/src/cli/context/session/SessionProvider.jsx +31 -0
- package/template/src/cli/context/session/makePermissioner.mjs +34 -0
- package/template/src/cli/context/session/useSessionContext.mjs +6 -0
- package/template/src/cli/context/theme/ThemeContext.mjs +4 -0
- package/template/src/cli/context/theme/ThemeProvider.jsx +49 -0
- package/template/src/cli/context/theme/useThemeContext.mjs +6 -0
- package/template/src/cli/context/ui/UIContext.jsx +5 -0
- package/template/src/cli/context/ui/UIProvider.jsx +16 -0
- package/template/src/cli/context/ui/useUIContext.mjs +4 -0
- package/template/src/cli/context/util.mjs +17 -0
- package/template/src/cli/entry-cli.jsx +33 -0
- package/template/src/cli/hooks/useIsMobile.mjs +19 -0
- package/template/src/cli/hooks/useStoragedState.mjs +63 -0
- package/template/src/cli/index.html +29 -0
- package/template/src/cli/layout/app-sidebar.jsx +25 -0
- package/template/src/cli/layout/main-layout.jsx +63 -0
- package/template/src/cli/layout/nav-last-todos.jsx +72 -0
- package/template/src/cli/layout/nav-main.jsx +39 -0
- package/template/src/cli/layout/nav-user.jsx +105 -0
- package/template/src/cli/layout/prop-switcher.jsx +93 -0
- package/template/src/cli/lib/utils.mjs +10 -0
- package/template/src/cli/pages/Index.jsx +13 -0
- package/template/src/cli/pages/IndexOffline.jsx +13 -0
- package/template/src/cli/pages/IndexOnline.jsx +18 -0
- package/template/src/cli/pages/dash/Dashboard.jsx +29 -0
- package/template/src/cli/pages/offline/Login.jsx +43 -0
- package/template/src/cli/pages/offline/LoginForm.jsx +115 -0
- package/template/src/cli/pages/security/Security.jsx +39 -0
- package/template/src/cli/pages/security/SecurityForm.jsx +106 -0
- package/template/src/cli/pages/todos/TodoActions.jsx +99 -0
- package/template/src/cli/pages/todos/TodoAdd.jsx +43 -0
- package/template/src/cli/pages/todos/TodoList.jsx +60 -0
- package/template/src/cli/pages/todos/Todos.jsx +23 -0
- package/template/src/cli/pages/todos/context/TodosContext.jsx +5 -0
- package/template/src/cli/pages/todos/context/TodosProvider.jsx +191 -0
- package/template/src/cli/pages/todos/context/useTodosContext.mjs +4 -0
- package/template/src/ns/models/Todo.mjs +29 -0
- package/template/src/ns/models/TodoList.mjs +8 -0
- package/template/src/ns/models/User.mjs +40 -0
- package/template/src/server/bot/check_today.mjs +10 -0
- package/template/src/server/io/cache/base.mjs +21 -0
- package/template/src/server/io/db/filter.mjs +92 -0
- package/template/src/server/io/db/todos/delete.mjs +29 -0
- package/template/src/server/io/db/todos/find.mjs +13 -0
- package/template/src/server/io/db/todos/read.mjs +83 -0
- package/template/src/server/io/db/todos/toggle.mjs +37 -0
- package/template/src/server/io/db/todos/upsave.mjs +32 -0
- package/template/src/server/io/db/triggers/user.mjs +13 -0
- package/template/src/server/io/db/users/auth.mjs +132 -0
- package/template/src/server/io/db/users/pwd.mjs +38 -0
- package/template/src/server/io/db/users/save.mjs +17 -0
- package/template/src/server/miolo/auth/basic.mjs +15 -0
- package/template/src/server/miolo/auth/guest.mjs +3 -0
- package/template/src/server/miolo/auth/passport.mjs +73 -0
- package/template/src/server/miolo/cache.mjs +11 -0
- package/template/src/server/miolo/cron/foo.mjs +7 -0
- package/template/src/server/miolo/cron/index.mjs +28 -0
- package/template/src/server/miolo/cron/invalidate.mjs +21 -0
- package/template/src/server/miolo/db.mjs +36 -0
- package/template/src/server/miolo/http.mjs +14 -0
- package/template/src/server/miolo/index.mjs +43 -0
- package/template/src/server/miolo/routes/crud.mjs +16 -0
- package/template/src/server/miolo/routes/index.mjs +8 -0
- package/template/src/server/miolo/ssr/entry-server.jsx +13 -0
- package/template/src/server/miolo/ssr/loader.mjs +18 -0
- package/template/src/server/routes/index.mjs +66 -0
- package/template/src/server/routes/todos/mod.mjs +52 -0
- package/template/src/server/routes/todos/read.mjs +45 -0
- package/template/src/server/routes/todos/special.mjs +47 -0
- package/template/src/server/routes/users/user.mjs +54 -0
- package/template/src/server/server.mjs +10 -0
- package/template/src/server/utils/crypt.mjs +38 -0
- package/template/src/server/utils/io.mjs +15 -0
- package/template/src/server/utils/pwdfor.mjs +25 -0
- package/template/src/server/utils/schema.mjs +22 -0
- package/template/src/static/img/default/profile.png +0 -0
- package/template/src/static/img/favicon.ico +0 -0
- package/template/src/static/img/miolo_logo.png +0 -0
- package/template/src/static/img/miolo_name.png +0 -0
- package/template/src/static/public/manifest.json +21 -0
- package/template/src/static/public/sw.js +79 -0
- package/template/src/static/style/globals.css +156 -0
- package/template/src/static/style/json-tree.css +54 -0
- package/template/src/static/style/skeleton.css +49 -0
- package/bin/prod-build/build-client.mjs +0 -67
- package/bin/prod-build/build-server.mjs +0 -58
- package/bin/prod-run/restart.mjs +0 -9
- package/bin/prod-run/start.mjs +0 -15
- package/bin/prod-run/stop.mjs +0 -20
- package/src/engines/logger/verify.mjs +0 -22
- package/src/middleware/auth/credentials/index.mjs +0 -151
- package/src/middleware/auth/credentials/session/index.mjs +0 -24
- 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'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
|
+
}
|