@alstar/studio 0.0.0-beta.10 → 0.0.0-beta.11

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 (43) hide show
  1. package/api/api-key.ts +40 -44
  2. package/api/auth.ts +66 -0
  3. package/api/backup.ts +30 -28
  4. package/api/block.ts +109 -110
  5. package/api/index.ts +17 -10
  6. package/api/mcp.ts +39 -42
  7. package/components/AdminPanel.ts +17 -4
  8. package/components/Backup.ts +5 -2
  9. package/components/BlockFieldRenderer.ts +6 -2
  10. package/components/Entries.ts +2 -2
  11. package/components/FieldRenderer.ts +1 -1
  12. package/components/Settings.ts +21 -18
  13. package/components/SiteLayout.ts +8 -5
  14. package/components/Users.ts +46 -0
  15. package/components/fields/Markdown.ts +1 -1
  16. package/components/fields/Slug.ts +113 -0
  17. package/components/fields/Text.ts +1 -1
  18. package/components/fields/index.ts +4 -1
  19. package/components/icons.ts +36 -0
  20. package/index.ts +40 -12
  21. package/package.json +6 -3
  22. package/pages/entry/[id].ts +1 -3
  23. package/pages/error.ts +14 -0
  24. package/pages/index.ts +1 -1
  25. package/pages/login.ts +21 -0
  26. package/pages/register.ts +33 -0
  27. package/pages/settings.ts +1 -3
  28. package/public/studio/css/settings.css +4 -0
  29. package/public/studio/js/markdown-editor.js +1 -1
  30. package/public/studio/js/sortable-list.js +1 -1
  31. package/public/studio/main.css +5 -0
  32. package/public/studio/main.js +12 -0
  33. package/queries/block-2.ts +339 -0
  34. package/queries/getBlockTrees-2.ts +71 -0
  35. package/queries/index.ts +1 -1
  36. package/readme.md +2 -2
  37. package/schema.sql +18 -0
  38. package/schemas.ts +11 -1
  39. package/types.ts +11 -0
  40. package/utils/auth.ts +54 -0
  41. package/utils/create-hash.ts +9 -0
  42. package/utils/define.ts +1 -3
  43. package/utils/startup-log.ts +15 -6
@@ -11,7 +11,7 @@ export default () => {
11
11
  return html`
12
12
  <div class="admin-panel" id="admin_panel">
13
13
  <h1>
14
- <a href="/admin" aria-label="Go to dashboard"> ${logo} </a>
14
+ <a href="/studio" aria-label="Go to dashboard"> ${logo} </a>
15
15
  </h1>
16
16
 
17
17
  <aside style="width: 100%;">
@@ -24,7 +24,7 @@ export default () => {
24
24
  <ul>
25
25
  <li>
26
26
  <a
27
- href="/admin/entry/${data.id}"
27
+ href="/studio/entry/${data.id}"
28
28
  id="block_link_${data.id}"
29
29
  >
30
30
  ${block.label}
@@ -37,7 +37,7 @@ export default () => {
37
37
 
38
38
  return html`
39
39
  <form
40
- data-on-submit="@post('/admin/api/block', { contentType: 'form' })"
40
+ data-on-submit="@post('/studio/api/block', { contentType: 'form' })"
41
41
  style="display: flex; align-items: center; gap: 1rem;"
42
42
  >
43
43
  <input type="hidden" name="name" value="${name}" />
@@ -60,14 +60,27 @@ export default () => {
60
60
  <footer>
61
61
  <a
62
62
  role="button"
63
- href="/admin/settings"
63
+ href="/studio/settings"
64
64
  class="ghost"
65
65
  style="padding: 10px; margin: 0 -13px; display: flex;"
66
66
  data-tooltip="Settings"
67
67
  data-placement="right"
68
+ aria-label="Settings"
68
69
  >
69
70
  ${icons.cog}
70
71
  </a>
72
+
73
+ <a
74
+ role="button"
75
+ href="/"
76
+ class="ghost"
77
+ style="padding: 10px; margin: 0 -13px; display: flex;"
78
+ data-tooltip="Leave Studio"
79
+ data-placement="right"
80
+ aria-label="Leave"
81
+ >
82
+ ${icons.leave}
83
+ </a>
71
84
  </footer>
72
85
  </div>
73
86
  `
@@ -1,10 +1,13 @@
1
1
  import { html } from 'hono/html'
2
2
 
3
3
  export default () => {
4
- return html`<article>
4
+ return html`<article data-signals="{ status: null, message: '' }">
5
5
  <header>Backup</header>
6
- <form data-on-submit="@post('/admin/api/backup', { contentType: 'form' })">
6
+ <form
7
+ data-on-submit="@post('/studio/api/backup', { contentType: 'form' })"
8
+ >
7
9
  <button type="submit">Backup database</button>
10
+ <p data-style-color="$status === 200 ? 'green' : 'red'" data-text="$message || '&nbsp;'"></p>
8
11
  </form>
9
12
  </article>`
10
13
  }
@@ -42,7 +42,7 @@ export default (props: {
42
42
  return html`
43
43
  <li>
44
44
  <form
45
- data-on-submit="@post('/admin/api/new-block', { contentType: 'form' })"
45
+ data-on-submit="@post('/studio/api/new-block', { contentType: 'form' })"
46
46
  >
47
47
  <button type="submit" class="ghost">${block.label}</button>
48
48
  <input type="hidden" name="type" value="${block.type}" />
@@ -78,6 +78,10 @@ export default (props: {
78
78
  ${struct.label}
79
79
 
80
80
  <aside>
81
+ <!-- <label style="margin: 0; border-bottom: none" data-tooltip="Disable" data-placement="top">
82
+ <input name="enable" type="checkbox" role="switch" checked />
83
+ </label> -->
84
+
81
85
  <button
82
86
  data-handle-for="${data.id}"
83
87
  class="ghost handle text-secondary"
@@ -87,7 +91,7 @@ export default (props: {
87
91
  </button>
88
92
 
89
93
  <form
90
- data-on-submit="@delete('/admin/api/block', { contentType: 'form' })"
94
+ data-on-submit="@delete('/studio/api/block', { contentType: 'form' })"
91
95
  >
92
96
  <button
93
97
  type="submit"
@@ -17,12 +17,12 @@ export default ({ name }: { name: string }) => {
17
17
 
18
18
  return html`
19
19
  <li>
20
- <a href="/admin/entry/${block.id}" id="block_link_${block.id}">
20
+ <a href="/studio/entry/${block.id}" id="block_link_${block.id}">
21
21
  ${title?.value || 'Untitled'}
22
22
  </a>
23
23
 
24
24
  <form
25
- data-on-submit="@delete('/admin/api/block', { contentType: 'form' })"
25
+ data-on-submit="@delete('/studio/api/block', { contentType: 'form' })"
26
26
  >
27
27
  <input type="hidden" name="id" value="${block.id}" />
28
28
  <button
@@ -17,7 +17,7 @@ export default (props: {
17
17
  }
18
18
 
19
19
  case 'slug': {
20
- return Field.Text({ entryId, parentId, name, id, structure })
20
+ return Field.Slug({ entryId, parentId, name, id, structure })
21
21
  }
22
22
 
23
23
  case 'markdown': {
@@ -3,6 +3,7 @@ import { html } from 'hono/html'
3
3
  import { sql } from '../utils/sql.ts'
4
4
  import * as icons from './icons.ts'
5
5
  import Backup from './Backup.ts'
6
+ import Users from './Users.ts'
6
7
 
7
8
  export default () => {
8
9
  const apiKeys = db.database
@@ -23,30 +24,30 @@ export default () => {
23
24
  <ul>
24
25
  ${apiKeys.map((apiKey) => {
25
26
  return html`<li>
26
- <p>${apiKey.name}</p>
27
- <input type="text" disabled value="${apiKey.hint}" />
28
- <form
29
- data-on-submit="@delete('/admin/api/api-key', { contentType: 'form' })"
27
+ <p>${apiKey.name}</p>
28
+ <input type="text" disabled value="${apiKey.hint}" />
29
+ <form
30
+ data-on-submit="@delete('/studio/api/api-key', { contentType: 'form' })"
31
+ >
32
+ <button
33
+ data-tooltip="Delete API key"
34
+ data-placement="left"
35
+ type="submit"
36
+ class="ghost"
30
37
  >
31
- <button
32
- data-tooltip="Delete API key"
33
- data-placement="left"
34
- type="submit"
35
- class="ghost"
36
- >
37
- ${icons.trash}
38
- </button>
38
+ ${icons.trash}
39
+ </button>
39
40
 
40
- <input type="hidden" name="value" value="${apiKey.value}" />
41
- </form>
42
- </li>`
41
+ <input type="hidden" name="value" value="${apiKey.value}" />
42
+ </form>
43
+ </li>`
43
44
  })}
44
45
  </ul>
45
46
 
46
47
  <form
47
- data-on-submit="@post('/admin/api/api-key', { contentType: 'form' })"
48
+ data-on-submit="@post('/studio/api/api-key', { contentType: 'form' })"
48
49
  >
49
- <label for="api_key_name">Generate API Key</label>
50
+ <label for="api_key_name"><small>Generate API Key</small></label>
50
51
 
51
52
  <input
52
53
  data-bind="name"
@@ -95,7 +96,9 @@ export default () => {
95
96
  </dialog>
96
97
  </article>
97
98
 
98
- <!-- {Backup()} -->
99
+ ${Backup()}
100
+
101
+ ${Users()}
99
102
  </div>
100
103
  `
101
104
  }
@@ -3,13 +3,14 @@ import { html } from 'hono/html'
3
3
  import { type HtmlEscapedString } from 'hono/utils/html'
4
4
  import { studioConfig } from '../index.ts'
5
5
 
6
- export default (props: {
6
+ export default (
7
7
  content:
8
8
  | string
9
9
  | Promise<string>
10
10
  | HtmlEscapedString
11
- | Promise<HtmlEscapedString>
12
- }) => {
11
+ | Promise<HtmlEscapedString>,
12
+ includeAdminPanel = true,
13
+ ) => {
13
14
  return html`
14
15
  <!DOCTYPE html>
15
16
  <html lang="en">
@@ -45,11 +46,13 @@ export default (props: {
45
46
  </head>
46
47
 
47
48
  <body data-barba="wrapper">
48
- <section style="margin-bottom: 0;">${adminPanel()}</section>
49
+ ${includeAdminPanel
50
+ ? html`<section style="margin-bottom: 0;">${adminPanel()}</section>`
51
+ : html`<div></div>`}
49
52
 
50
53
  <main>
51
54
  <section data-barba="container" data-barba-namespace="default">
52
- ${props.content}
55
+ ${content}
53
56
  </section>
54
57
  </main>
55
58
  </body>
@@ -0,0 +1,46 @@
1
+ import { db } from '@alstar/db'
2
+ import { html } from 'hono/html'
3
+ import { sql } from '../utils/sql.ts'
4
+
5
+ export default () => {
6
+ const users = db.database
7
+ .prepare(sql`
8
+ select
9
+ email
10
+ from
11
+ users
12
+ `)
13
+ .all()
14
+
15
+ return html`
16
+ <article>
17
+ <header>Users</header>
18
+ <ul>
19
+ ${users.map(user => html`<li>${user.email}</li>`)}
20
+ </ul>
21
+ <article>
22
+ <header>Register user</header>
23
+ <form
24
+ data-on-submit="@post('/studio/api/auth/register', { contentType: 'form' })"
25
+ >
26
+ <label for="register_email"><small>Email</small></label>
27
+ <input
28
+ id="register_email"
29
+ name="email"
30
+ type="email"
31
+ placeholder="Email"
32
+ />
33
+ <label for="register_password"><small>Password</small></label>
34
+ <input
35
+ id="register_password"
36
+ name="password"
37
+ type="password"
38
+ placeholder="Password"
39
+ />
40
+ <br />
41
+ <button type="submit" class="ghost">Create</button>
42
+ </form>
43
+ </article>
44
+ </article>
45
+ `
46
+ }
@@ -17,7 +17,7 @@ export default (props: {
17
17
 
18
18
  return html`
19
19
  <form
20
- data-on-input="@patch('/admin/api/block', { contentType: 'form' })"
20
+ data-on-input="@patch('/studio/api/block', { contentType: 'form' })"
21
21
  >
22
22
  <hgroup>
23
23
  <label for="block-${data.id}">${structure.label}</label>
@@ -0,0 +1,113 @@
1
+ import { getOrCreateRow } from '../../utils/get-or-create-row.ts'
2
+ import { html } from '../../utils/html.ts'
3
+ import type { FieldDefStructure } from '../../types.ts'
4
+ import * as icons from '../icons.ts'
5
+ import { Hono } from 'hono'
6
+ import { type HttpBindings } from '@hono/node-server'
7
+ import { streamSSE } from 'hono/streaming'
8
+ import { query } from '../../queries/index.ts'
9
+ import { slugify } from '../../utils/slugify.ts'
10
+
11
+ const app = new Hono<{ Bindings: HttpBindings }>()
12
+
13
+ app.get('/slug', async (c) => {
14
+ try {
15
+ const params = await c.req.query()
16
+
17
+ if (!params.entryId) {
18
+ return c.json({
19
+ status: 404,
20
+ message: 'Needs an entryId to generate slug',
21
+ })
22
+ }
23
+
24
+ const entry = query.root({ id: params.entryId })
25
+ const title = entry?.fields?.title?.value
26
+
27
+ if (!title) {
28
+ return c.json({ status: 404, message: 'No title to generate slug from' })
29
+ }
30
+
31
+ console.log('slug', slugify(title))
32
+
33
+ return streamSSE(c, async (stream) => {
34
+ await stream.writeSSE({
35
+ event: 'datastar-patch-signals',
36
+ data: `signals { slug: '${slugify(title)}' }`,
37
+ })
38
+ })
39
+ } catch (error) {
40
+ console.log(error)
41
+ return c.text('Error generating slug')
42
+ }
43
+ })
44
+
45
+ export const routes = app
46
+
47
+ export default (props: {
48
+ entryId: number
49
+ parentId: number
50
+ name: string
51
+ id?: number
52
+ structure: FieldDefStructure
53
+ sortOrder?: number
54
+ }) => {
55
+ const { entryId, parentId, name, structure, sortOrder = 0, id } = props
56
+
57
+ const data = getOrCreateRow({
58
+ parentId,
59
+ name,
60
+ field: structure,
61
+ sortOrder,
62
+ id,
63
+ })
64
+
65
+ if (!data) return html`<p>No block</p>`
66
+
67
+ return html`
68
+ <div
69
+ style="display: flex; align-items: center"
70
+ data-signals="{ slug: '${data.value}' }"
71
+ >
72
+ <form
73
+ data-ref="form"
74
+ data-on-input="@patch('/studio/api/block', { contentType: 'form' })"
75
+ data-on-submit="@patch('/studio/api/block', { contentType: 'form' })"
76
+ data-on-signal-patch="$form.requestSubmit()"
77
+ >
78
+ <hgroup>
79
+ <label for="block-${data.id}">${structure.label}</label>
80
+ <p><small>${structure.description}</small></p>
81
+ </hgroup>
82
+
83
+ <input
84
+ id="block-${data.id}"
85
+ name="value"
86
+ type="text"
87
+ data-bind="slug"
88
+ />
89
+
90
+ <input type="hidden" name="type" value="${structure.type}" />
91
+ <input type="hidden" name="id" value="${data.id}" />
92
+ <input type="hidden" name="entryId" value="${entryId}" />
93
+ <input type="hidden" name="parentId" value="${parentId}" />
94
+ <input type="hidden" name="name" value="${name}" />
95
+ </form>
96
+
97
+ <form
98
+ style="margin-top: 21px"
99
+ data-on-submit="@get('/studio/api/field/slug', { contentType: 'form' })"
100
+ >
101
+ <input type="hidden" name="entryId" value="${entryId}" />
102
+ <button
103
+ class="ghost"
104
+ aria-label="Generate slug"
105
+ data-tooltip="Generate slug"
106
+ data-placement="top"
107
+ >
108
+ ${icons.arrowsRound}
109
+ </button>
110
+ </form>
111
+ </div>
112
+ `
113
+ }
@@ -18,7 +18,7 @@ export default (props: {
18
18
 
19
19
  return html`
20
20
  <form
21
- data-on-input="@patch('/admin/api/block', { contentType: 'form' })"
21
+ data-on-input="@patch('/studio/api/block', { contentType: 'form' })"
22
22
  >
23
23
  <hgroup>
24
24
  <label for="block-${data.id}">${structure.label}</label>
@@ -1,4 +1,7 @@
1
1
  import Text from './Text.ts'
2
2
  import Markdown from './Markdown.ts'
3
+ import Slug, { routes as slugRoutes } from './slug.ts'
3
4
 
4
- export const Field = { Text, Markdown }
5
+ export const Field = { Text, Markdown, Slug }
6
+
7
+ export const fieldRoutes = [slugRoutes]
@@ -194,3 +194,39 @@ export const clipboard = html`
194
194
  />
195
195
  </svg>
196
196
  `
197
+
198
+ export const leave = html`
199
+ <svg
200
+ xmlns="http://www.w3.org/2000/svg"
201
+ width="16"
202
+ height="16"
203
+ viewBox="0 0 20 20"
204
+ >
205
+ <!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
206
+ <g fill="currentColor" fill-rule="evenodd" clip-rule="evenodd">
207
+ <path
208
+ d="M3 4.25A2.25 2.25 0 0 1 5.25 2h5.5A2.25 2.25 0 0 1 13 4.25v2a.75.75 0 0 1-1.5 0v-2a.75.75 0 0 0-.75-.75h-5.5a.75.75 0 0 0-.75.75v11.5c0 .414.336.75.75.75h5.5a.75.75 0 0 0 .75-.75v-2a.75.75 0 0 1 1.5 0v2A2.25 2.25 0 0 1 10.75 18h-5.5A2.25 2.25 0 0 1 3 15.75z"
209
+ />
210
+ <path
211
+ d="M19 10a.75.75 0 0 0-.75-.75H8.704l1.048-.943a.75.75 0 1 0-1.004-1.114l-2.5 2.25a.75.75 0 0 0 0 1.114l2.5 2.25a.75.75 0 1 0 1.004-1.114l-1.048-.943h9.546A.75.75 0 0 0 19 10"
212
+ />
213
+ </g>
214
+ </svg>
215
+ `
216
+
217
+ export const arrowsRound = html`
218
+ <svg
219
+ xmlns="http://www.w3.org/2000/svg"
220
+ width="16"
221
+ height="16"
222
+ viewBox="0 0 20 20"
223
+ >
224
+ <!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
225
+ <path
226
+ fill="currentColor"
227
+ fill-rule="evenodd"
228
+ d="M15.312 11.424a5.5 5.5 0 0 1-9.201 2.466l-.312-.311h2.433a.75.75 0 0 0 0-1.5H3.989a.75.75 0 0 0-.75.75v4.242a.75.75 0 0 0 1.5 0v-2.43l.31.31a7 7 0 0 0 11.712-3.138a.75.75 0 0 0-1.449-.39m1.23-3.723a.75.75 0 0 0 .219-.53V2.929a.75.75 0 0 0-1.5 0V5.36l-.31-.31A7 7 0 0 0 3.239 8.188a.75.75 0 1 0 1.448.389A5.5 5.5 0 0 1 13.89 6.11l.311.31h-2.432a.75.75 0 0 0 0 1.5h4.243a.75.75 0 0 0 .53-.219"
229
+ clip-rule="evenodd"
230
+ />
231
+ </svg>
232
+ `
package/index.ts CHANGED
@@ -1,17 +1,24 @@
1
+ import path from 'node:path'
2
+
1
3
  import { Hono } from 'hono'
2
- import { loadDb } from '@alstar/db'
3
4
  import { serve } from '@hono/node-server'
4
5
  import { serveStatic } from '@hono/node-server/serve-static'
6
+ import { HTTPException } from 'hono/http-exception'
7
+
8
+ import { loadDb } from '@alstar/db'
5
9
  import { createRefresher } from '@alstar/refresher'
6
10
 
7
- import * as types from './types.ts'
8
11
  import { createStudioTables } from './utils/create-studio-tables.ts'
9
12
  import { fileBasedRouter } from './utils/file-based-router.ts'
10
13
  import { getConfig } from './utils/get-config.ts'
11
14
  import startupLog from './utils/startup-log.ts'
12
- import { api } from './api/index.ts'
13
- import mcp from './api/mcp.ts'
14
- import path from 'path'
15
+ import { apiRoutes } from './api/index.ts'
16
+ import { mcpRoutes } from './api/mcp.ts'
17
+
18
+ import auth from './utils/auth.ts'
19
+ import ErrorPage from './pages/error.ts'
20
+
21
+ import * as types from './types.ts'
15
22
 
16
23
  export let rootdir = './node_modules/@alstar/studio'
17
24
 
@@ -23,7 +30,8 @@ export let studioConfig: types.StudioConfig = {
23
30
  }
24
31
 
25
32
  const createStudio = async (config: types.StudioConfig) => {
26
- const refresher = await createRefresher({ rootdir: '.' })
33
+ // const refresher = await createRefresher({ rootdir: ['.', import.meta.dirname] })
34
+ const refresher = await createRefresher({ rootdir: ['.'] })
27
35
 
28
36
  loadDb('./studio.db')
29
37
  createStudioTables()
@@ -44,26 +52,46 @@ const createStudio = async (config: types.StudioConfig) => {
44
52
  app.use('*', serveStatic({ root: path.join(rootdir, 'public') }))
45
53
  app.use('*', serveStatic({ root: './public' }))
46
54
 
55
+ /**
56
+ * Require authentication to access Studio
57
+ */
58
+ app.use('/studio/*', auth)
59
+
47
60
  /**
48
61
  * Studio API routes
49
62
  */
50
- app.route('/admin/api', api(studioStructure))
51
- app.route('/admin/mcp', mcp())
63
+ app.route('/studio/api', apiRoutes)
64
+ app.route('/studio/mcp', mcpRoutes)
52
65
 
53
66
  /**
54
67
  * Studio pages
55
68
  */
56
- const adminPages = await fileBasedRouter(path.join(rootdir, 'pages'))
57
-
58
- if (adminPages) app.route('/admin', adminPages)
69
+ const studioPages = await fileBasedRouter(path.join(rootdir, 'pages'))
70
+ if (studioPages) app.route('/studio', studioPages)
59
71
 
60
72
  /**
61
73
  * User pages
62
74
  */
63
75
  const pages = await fileBasedRouter('./pages')
64
-
65
76
  if (pages) app.route('/', pages)
66
77
 
78
+ /**
79
+ * Error pages
80
+ */
81
+ app.notFound((c) => c.html(ErrorPage()))
82
+ app.onError((err, c) => {
83
+ if (err instanceof HTTPException) {
84
+ // Get the custom response
85
+ const error = err.getResponse()
86
+ return c.html(ErrorPage(err))
87
+ }
88
+
89
+ return c.notFound()
90
+ })
91
+
92
+ /**
93
+ * Run server
94
+ */
67
95
  const server = serve({
68
96
  fetch: app.fetch,
69
97
  port: studioConfig.port,
package/package.json CHANGED
@@ -1,15 +1,18 @@
1
1
  {
2
2
  "name": "@alstar/studio",
3
- "version": "0.0.0-beta.10",
3
+ "version": "0.0.0-beta.11",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
+ "engines": {
7
+ "node": ">=23.8"
8
+ },
6
9
  "dependencies": {
7
10
  "@hono/node-server": "^1.18.1",
8
11
  "@starfederation/datastar-sdk": "1.0.0-RC.1",
9
12
  "hono": "^4.8.12",
10
- "@alstar/refresher": "0.0.0-beta.3",
13
+ "@alstar/db": "0.0.0-beta.1",
11
14
  "@alstar/ui": "0.0.0-beta.1",
12
- "@alstar/db": "0.0.0-beta.1"
15
+ "@alstar/refresher": "0.0.0-beta.3"
13
16
  },
14
17
  "devDependencies": {
15
18
  "@types/node": "^24.1.0",
@@ -11,7 +11,5 @@ export default defineEntry((c) => {
11
11
  return html`<p>Entry page url needs an ID param: "${id}"</p>`
12
12
  }
13
13
 
14
- return SiteLayout({
15
- content: Entry({ entryId: parseInt(id) }),
16
- })
14
+ return SiteLayout(Entry({ entryId: parseInt(id) }))
17
15
  })
package/pages/error.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { html } from "hono/html";
2
+ import SiteLayout from "../components/SiteLayout.ts";
3
+ import type { HTTPResponseError } from "hono/types";
4
+
5
+ export default ((err?: Error | HTTPResponseError) => {
6
+
7
+ return SiteLayout(html`
8
+ <article>
9
+ <header>Something went wrong</header>
10
+ <p>Try again</p>
11
+ <p>${err?.message}</p>
12
+ </article>
13
+ `, false)
14
+ })
package/pages/index.ts CHANGED
@@ -29,5 +29,5 @@ export default defineEntry(() => {
29
29
  </article>
30
30
  </div>`
31
31
 
32
- return SiteLayout({ content: !Object.values(studioStructure).length ? Discamer : '' })
32
+ return SiteLayout(!Object.values(studioStructure).length ? Discamer : '')
33
33
  })
package/pages/login.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { html } from "hono/html";
2
+ import { defineEntry } from "../utils/define.ts";
3
+ import SiteLayout from "../components/SiteLayout.ts";
4
+
5
+ export default defineEntry(c => {
6
+ return SiteLayout(html`
7
+ <div class="login-form">
8
+ <article>
9
+ <header>Login</header>
10
+ <form data-on-submit="@post('/studio/api/auth/login', { contentType: 'form' })">
11
+ <label for="email">Email</label>
12
+ <input id="email" name="email" type="text" placeholder="Email">
13
+ <label for="password">Password</label>
14
+ <input id="password" name="password" type="password" placeholder="Password">
15
+ <br>
16
+ <button style="width: 100%;">Login</button>
17
+ </form>
18
+ </article>
19
+ </div>
20
+ `, false)
21
+ })
@@ -0,0 +1,33 @@
1
+ import { html } from 'hono/html'
2
+ import { defineEntry } from '../utils/define.ts'
3
+ import SiteLayout from '../components/SiteLayout.ts'
4
+
5
+ export default defineEntry((c) => {
6
+ return SiteLayout(
7
+ html`
8
+ <div class="register-form" style="width: 300px">
9
+ <article>
10
+ <header>Register user</header>
11
+ <form
12
+ data-signals="{ status: 0 }"
13
+ data-on-submit="@post('/studio/api/auth/register', { contentType: 'form' })"
14
+ data-on-signal-patch="patch.status === 200 && window.location.reload()"
15
+ >
16
+ <label for="email">Email</label>
17
+ <input id="email" name="email" type="text" placeholder="Email" />
18
+ <label for="password">Password</label>
19
+ <input
20
+ id="password"
21
+ name="password"
22
+ type="password"
23
+ placeholder="Password"
24
+ />
25
+ <br />
26
+ <button style="width: 100%;">Register</button>
27
+ </form>
28
+ </article>
29
+ </div>
30
+ `,
31
+ false,
32
+ )
33
+ })