@stacksjs/defaults 0.72.98 → 0.72.100
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/ai/skills/stacks-auth/SKILL.md +35 -5
- package/ai/skills/stacks-config/SKILL.md +11 -1
- package/ai/skills/stacks-desktop/SKILL.md +70 -0
- package/ai/skills/stacks-env/SKILL.md +26 -0
- package/ai/skills/stacks-events/SKILL.md +76 -60
- package/ai/skills/stacks-jobs/SKILL.md +3 -0
- package/ai/skills/stacks-listeners/SKILL.md +39 -9
- package/ai/skills/stacks-middleware/SKILL.md +50 -12
- package/ai/skills/stacks-queue/SKILL.md +5 -0
- package/ai/skills/stacks-router/SKILL.md +17 -7
- package/ai/skills/stacks-scheduler/SKILL.md +1 -1
- package/app/Actions/Dashboard/Commerce/CommerceCustomersAction.ts +1 -1
- package/app/Actions/Dashboard/Commerce/CommerceDeliveryAction.ts +1 -1
- package/app/Actions/Dashboard/Commerce/CommerceOrdersAction.ts +1 -1
- package/app/Actions/Dashboard/Commerce/CommercePosAction.ts +1 -1
- package/app/Actions/Dashboard/Commerce/CommercePosCheckoutAction.ts +1 -1
- package/app/Actions/Dashboard/Commerce/CommerceProductDetailAction.ts +1 -1
- package/app/Actions/Dashboard/Commerce/CommerceProductsAction.ts +1 -1
- package/app/Actions/Dashboard/Content/content-input.ts +2 -2
- package/app/Actions/Dashboard/Content/post-input.ts +4 -4
- package/app/Actions/Dashboard/Email/inbox-request.ts +1 -1
- package/app/Actions/Dashboard/Email/mail-preference.ts +1 -1
- package/app/Actions/Dashboard/Marketing/CampaignIndexAction.ts +1 -1
- package/app/Actions/Dashboard/Marketing/CampaignStoreAction.ts +1 -1
- package/app/Actions/Dashboard/Marketing/CampaignUpdateAction.ts +1 -1
- package/app/Actions/Dashboard/Teams/TeamInvitationDestroyAction.ts +2 -2
- package/app/Actions/Dashboard/Teams/TeamInvitationResendAction.ts +2 -2
- package/app/Actions/Dashboard/Teams/TeamInviteAction.ts +5 -5
- package/app/Actions/Dashboard/Teams/TeamMemberDestroyAction.ts +2 -2
- package/app/Actions/Dashboard/Teams/TeamMemberUpdateAction.ts +2 -2
- package/app/Actions/Dashboard/Teams/TeamPeopleIndexAction.ts +2 -2
- package/app/Actions/Dashboard/Teams/team-invitation-delivery.ts +2 -2
- package/app/Events.ts +9 -4
- package/app/Gates.ts +83 -85
- package/app/Listener.ts +34 -81
- package/app/Middleware.ts +22 -7
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
- package/project/storage/framework/server/tsconfig.docker.json +1 -2
- package/resources/components/Buttons/Counter.stx +30 -21
- package/resources/components/Dashboard/Analytics/WebAnalyticsDashboard.stx +1 -3
- package/resources/components/Dashboard/Auth/ForgotPasswordDashboard.stx +1 -1
- package/resources/components/Dashboard/Auth/LoginDashboard.stx +2 -1
- package/resources/components/Dashboard/Auth/RegisterDashboard.stx +2 -1
- package/resources/components/Dashboard/Auth/ResetPasswordDashboard.stx +1 -1
- package/resources/components/Dashboard/Ci/CiDashboard.stx +9 -9
- package/resources/components/Dashboard/Ci/CiRunHistoryDrawer.stx +7 -7
- package/resources/components/Dashboard/Commerce/CommerceOverviewCharts.stx +2 -1
- package/resources/components/Dashboard/Commerce/CommercePaymentsDashboard.stx +1 -1
- package/resources/components/Dashboard/Content/BlogDashboard.stx +37 -15
- package/resources/components/Dashboard/Content/ContentDashboard.stx +3 -26
- package/resources/components/Dashboard/Email/EmailActivityDashboard.stx +2 -15
- package/resources/components/Dashboard/Environment/EnvironmentDashboard.stx +9 -1
- package/resources/components/Dashboard/Kanban/KanbanBoardDashboard.stx +15 -13
- package/resources/components/Dashboard/Kanban/KanbanBoardsDashboard.stx +10 -8
- package/resources/components/Dashboard/Kanban/KanbanCardDialog.stx +10 -10
- package/resources/components/Dashboard/Management/PermissionsDashboard.stx +3 -3
- package/resources/components/Dashboard/Modals/Popups/Alert.stx +4 -1
- package/resources/components/Dashboard/Navbar.stx +1 -1
- package/resources/components/Dashboard/Settings/AppearanceSettingsDashboard.stx +11 -6
- package/resources/components/Dashboard/Settings/SettingsDashboard.stx +2 -2
- package/resources/components/Dashboard/Transaction/index.stx +6 -6
- package/resources/components/Dashboard/UI/Avatar.stx +2 -2
- package/resources/components/Dashboard/UI/DropdownItem.stx +4 -4
- package/resources/components/Dashboard/UI/WindowControls.stx +17 -2
- package/resources/components/Docs/Demo/ComboboxDemo.stx +8 -3
- package/resources/components/Docs/Demo/DropdownDemo.stx +3 -3
- package/resources/components/Docs/Demo/StepperDemo.stx +1 -1
- package/resources/components/Forum/ForumLayout.stx +1 -1
- package/resources/components/Forum/ForumReplyForm.stx +1 -1
- package/resources/components/Marketing/ComingSoon.stx +5 -5
- package/resources/components/Storefront/CartDrawer.stx +44 -22
- package/resources/components/Storefront/ProductGallery.stx +3 -2
- package/resources/components/Storefront/QuantitySelector.stx +12 -7
- package/resources/functions/storefront/cart-cookie.ts +58 -0
- package/resources/layouts/storefront.stx +3 -2
- package/resources/views/cart.stx +19 -3
- package/resources/views/checkout/contact.stx +14 -3
- package/resources/views/checkout/payment.stx +14 -3
- package/resources/views/checkout/shipping.stx +14 -3
- package/resources/views/cms/blocks/form.stx +16 -11
- package/resources/views/cms/page.stx +28 -0
- package/resources/views/coming-soon.stx +0 -6
- package/resources/views/orders/[id].stx +19 -3
- package/views/dashboard/composables/index.ts +1 -1
- package/views/dashboard/composables/useChart.ts +26 -14
- package/views/dashboard/layouts/default.stx +42 -25
package/app/Listener.ts
CHANGED
|
@@ -1,84 +1,37 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { handleError } from '@stacksjs/error-handling'
|
|
3
|
-
import { emitter } from '@stacksjs/events'
|
|
1
|
+
import { registerAppListeners } from '@stacksjs/events'
|
|
4
2
|
import { path as p } from '@stacksjs/path'
|
|
5
|
-
import events from './Events'
|
|
6
3
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
if (pending) return pending
|
|
41
|
-
|
|
42
|
-
// Start the import and cache the promise to deduplicate concurrent calls
|
|
43
|
-
pending = (async () => {
|
|
44
|
-
try {
|
|
45
|
-
const imported = await import(p.appPath(`Actions/${listener}.ts`))
|
|
46
|
-
const actionModule = imported.default
|
|
47
|
-
if (actionModule && typeof actionModule.handle === 'function') {
|
|
48
|
-
actionCache.set(listener, actionModule)
|
|
49
|
-
return actionModule
|
|
50
|
-
}
|
|
51
|
-
handleError(`Action "${listener}" does not export a handle method`)
|
|
52
|
-
return null
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
handleError(`Failed to import action "${listener}"`, error)
|
|
56
|
-
return null
|
|
57
|
-
}
|
|
58
|
-
finally {
|
|
59
|
-
pendingImports.delete(listener)
|
|
60
|
-
}
|
|
61
|
-
})()
|
|
62
|
-
|
|
63
|
-
pendingImports.set(listener, pending)
|
|
64
|
-
return pending
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function processListeners(type: string, listeners: string[], event: any) {
|
|
68
|
-
for (const listener of listeners) {
|
|
69
|
-
try {
|
|
70
|
-
if (typeof listener === 'function') {
|
|
71
|
-
await (listener as any)(event)
|
|
72
|
-
continue
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const action = await resolveAction(listener)
|
|
76
|
-
if (action) {
|
|
77
|
-
await action.handle(event)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
catch (error) {
|
|
81
|
-
handleError(`Failed to execute listener "${listener}" for event "${type}"`, error)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
4
|
+
/**
|
|
5
|
+
* **Event Listener Bootstrap**
|
|
6
|
+
*
|
|
7
|
+
* Registers every listener the application declares: the `app/Events.ts` map of
|
|
8
|
+
* event name to listener names, and any module under `app/Listeners/` that
|
|
9
|
+
* declares its own `listensTo`.
|
|
10
|
+
*
|
|
11
|
+
* This file used to carry its own implementation of the first half - a wildcard
|
|
12
|
+
* handler that read the map on every dispatch and imported the named action out
|
|
13
|
+
* of `app/Actions/`. It was a second implementation of what
|
|
14
|
+
* `registerAppListeners` already does, and having two was worse than having the
|
|
15
|
+
* wrong one:
|
|
16
|
+
*
|
|
17
|
+
* - In dev they both ran. `injectGlobalAutoImports()` calls
|
|
18
|
+
* `registerAppListeners()` and the dev API entrypoint then called
|
|
19
|
+
* `handleEvents()`, so every listener named in the map fired TWICE per
|
|
20
|
+
* event - two welcome emails, two rows, and no error anywhere to say so.
|
|
21
|
+
* - The copy resolved names against `app/Actions/` alone, so a listener under
|
|
22
|
+
* `app/Listeners/` and any of the framework's default actions were
|
|
23
|
+
* unreachable from the map.
|
|
24
|
+
* - It matched events by exact name only, dropping the glob subscriptions
|
|
25
|
+
* (`user:*`) the emitter supports.
|
|
26
|
+
*
|
|
27
|
+
* Delegating keeps this file as the app's hook into boot - override it to
|
|
28
|
+
* register listeners of your own - with one implementation behind it, whose
|
|
29
|
+
* registry also makes the call idempotent across a dev-server reload.
|
|
30
|
+
*/
|
|
31
|
+
export async function handleEvents(): Promise<number> {
|
|
32
|
+
// Anchored to the project root rather than left to `process.cwd()`. A buddy
|
|
33
|
+
// command, a test runner or a worker started from a subdirectory would
|
|
34
|
+
// otherwise look for `app/Events.ts` beside wherever it happened to be
|
|
35
|
+
// launched, find nothing, and register no listeners without saying so.
|
|
36
|
+
return registerAppListeners({ base: p.projectPath() })
|
|
84
37
|
}
|
package/app/Middleware.ts
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
[key: string]: string
|
|
3
|
-
}
|
|
1
|
+
import { defineMiddleware } from '@stacksjs/router'
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
4
|
* The application's middleware aliases.
|
|
7
5
|
*
|
|
8
|
-
* Aliases may be used instead of class names to conveniently assign middleware
|
|
6
|
+
* Aliases may be used instead of class names to conveniently assign middleware
|
|
7
|
+
* to routes and groups. Each one names a class under `app/Middleware/`, or one
|
|
8
|
+
* of the framework defaults behind it, and the name is checked.
|
|
9
|
+
*
|
|
10
|
+
* This map is MERGED over the framework defaults rather than replacing them,
|
|
11
|
+
* so an alias the framework adds later is available here without an edit.
|
|
12
|
+
*
|
|
13
|
+
* Two forms are read off a reference before it is looked up:
|
|
14
|
+
*
|
|
15
|
+
* - `!alias` inverts it: the route passes only when that middleware refuses.
|
|
16
|
+
* `'!auth'` is a guests-only route; `'!env:production'` is everywhere but
|
|
17
|
+
* production.
|
|
18
|
+
* - `alias:params` passes `params` to the middleware, but only when the
|
|
19
|
+
* whole string is not itself an alias - so `'env:production'` is the alias
|
|
20
|
+
* below and `'throttle:60,1'` passes `60,1` to `throttle`.
|
|
21
|
+
*
|
|
22
|
+
* A middleware with no alias is still reachable by its class name, which is how
|
|
23
|
+
* `'signed'` worked before it was listed here.
|
|
9
24
|
*/
|
|
10
|
-
export default {
|
|
25
|
+
export default defineMiddleware({
|
|
11
26
|
'maintenance': 'Maintenance',
|
|
12
27
|
'cors': 'Cors',
|
|
13
28
|
'auth': 'Auth',
|
|
@@ -19,6 +34,7 @@ export default {
|
|
|
19
34
|
'abilities': 'Abilities',
|
|
20
35
|
'can': 'Can',
|
|
21
36
|
'throttle': 'Throttle',
|
|
37
|
+
'signed': 'Signed',
|
|
22
38
|
'env': 'Env',
|
|
23
39
|
'env:local': 'EnvLocal',
|
|
24
40
|
'env:development': 'EnvDevelopment',
|
|
@@ -32,5 +48,4 @@ export default {
|
|
|
32
48
|
'csrf': 'Csrf',
|
|
33
49
|
'compress': 'Compress',
|
|
34
50
|
// Add more middleware aliases here
|
|
35
|
-
|
|
36
|
-
} satisfies Middleware
|
|
51
|
+
})
|
package/ide/vscode/package.json
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.72.
|
|
5
|
+
"version": "0.72.100",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/stacksjs/stacks.git",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@iconify-json/f7": "^1.2.2",
|
|
53
53
|
"@iconify-json/hugeicons": "^1.2.27",
|
|
54
|
-
"@stacksjs/mobile": "^0.72.
|
|
54
|
+
"@stacksjs/mobile": "^0.72.100",
|
|
55
55
|
"@stacksjs/sanitizer": "^0.2.113"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
"Actions/*": ["../app/Actions/*"],
|
|
12
12
|
"Jobs/*": ["../app/Jobs/*"],
|
|
13
13
|
"./app/*": ["../app/*"], // it's outside the dist folder
|
|
14
|
-
"~/*": ["../*"]
|
|
15
|
-
"../../../env": ["../storage/framework/env.ts"]
|
|
14
|
+
"~/*": ["../*"]
|
|
16
15
|
},
|
|
17
16
|
"resolveJsonModule": true,
|
|
18
17
|
"allowImportingTsExtensions": true,
|
|
@@ -1,28 +1,37 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script client>
|
|
2
|
+
/*
|
|
3
|
+
* A counter, in signals.
|
|
4
|
+
*
|
|
5
|
+
* This used to be a `<script client>` that ran `document.querySelector` on
|
|
6
|
+
* mount, read the count back out of `textContent`, `parseInt`ed it and wrote a
|
|
7
|
+
* number back into that string property. It worked, but as the component every
|
|
8
|
+
* new app has in `Buttons/`, it taught the one pattern the stx guidance rules
|
|
9
|
+
* out: reaching for the DOM instead of state. It also only ever bound the
|
|
10
|
+
* first `[data-counter]` on the page, so a second `<Counter />` was inert.
|
|
11
|
+
*/
|
|
2
12
|
interface CounterProps {
|
|
3
13
|
count?: number
|
|
4
14
|
}
|
|
5
15
|
|
|
6
16
|
const { count = 0 } = defineProps<CounterProps>()
|
|
17
|
+
|
|
18
|
+
const total = state(count)
|
|
19
|
+
|
|
20
|
+
function increase(): void {
|
|
21
|
+
total.set(total() + 1)
|
|
22
|
+
}
|
|
7
23
|
</script>
|
|
24
|
+
|
|
8
25
|
<div>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<script client>
|
|
22
|
-
document.querySelector('[data-counter]')?.addEventListener('click', function() {
|
|
23
|
-
const countEl = document.querySelector('[data-count]')
|
|
24
|
-
if (countEl) {
|
|
25
|
-
countEl.textContent = parseInt(countEl.textContent || '0') + 1
|
|
26
|
-
}
|
|
27
|
-
})
|
|
28
|
-
</script>
|
|
26
|
+
<button
|
|
27
|
+
type="button"
|
|
28
|
+
class="text-white text-xs bg-blue-500 hover:bg-blue-600 btn"
|
|
29
|
+
@click="increase"
|
|
30
|
+
>
|
|
31
|
+
<i
|
|
32
|
+
class="-ml-1 mr-3 h-4 w-4 i-hugeicons-outline-plus"
|
|
33
|
+
aria-hidden="true"
|
|
34
|
+
></i>
|
|
35
|
+
Counter: <span>{{ total() }}</span>
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
@@ -211,11 +211,9 @@ import type {
|
|
|
211
211
|
} from '~/storage/framework/defaults/functions/analytics'
|
|
212
212
|
import { fetchWebAnalytics } from '~/storage/framework/defaults/functions/analytics'
|
|
213
213
|
import { pushToast } from '~/storage/framework/defaults/functions/toasts'
|
|
214
|
+
import type { ChartCtor, ChartLike } from '~/storage/framework/defaults/views/dashboard/composables/useChart'
|
|
214
215
|
import { useChart } from '~/storage/framework/defaults/views/dashboard/composables/useChart'
|
|
215
216
|
|
|
216
|
-
interface ChartLike { destroy: () => void }
|
|
217
|
-
type ChartCtor = new (canvas: HTMLElement, config: { type: string, data: unknown, options?: unknown }) => ChartLike
|
|
218
|
-
|
|
219
217
|
const analyticsOverview = state<AnalyticsOverview>({
|
|
220
218
|
realtimeVisitors: 0,
|
|
221
219
|
uniqueVisitors: 0,
|
|
@@ -11,7 +11,7 @@ const isLoading = state(false)
|
|
|
11
11
|
const error = state('')
|
|
12
12
|
const success = state(false)
|
|
13
13
|
|
|
14
|
-
async function handleForgotPassword(data) {
|
|
14
|
+
async function handleForgotPassword(data: { email: string }): Promise<void> {
|
|
15
15
|
isLoading.set(true)
|
|
16
16
|
error.set('')
|
|
17
17
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script client>
|
|
2
|
+
import type { AuthUser } from '../../../../types/dashboard'
|
|
2
3
|
import { describeAuthError, useAuth } from '../../../../functions/auth'
|
|
3
4
|
|
|
4
5
|
const { login } = useAuth()
|
|
@@ -12,7 +13,7 @@ function safeRedirect(): string {
|
|
|
12
13
|
return redirect.startsWith('/') && !redirect.startsWith('//') ? redirect : '/'
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
async function handleLogin(credentials) {
|
|
16
|
+
async function handleLogin(credentials: AuthUser): Promise<void> {
|
|
16
17
|
isLoading.set(true)
|
|
17
18
|
error.set('')
|
|
18
19
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script client>
|
|
2
|
+
import type { RegisterCredentials } from '../../../../types/dashboard'
|
|
2
3
|
import { describeAuthError, useAuth } from '../../../../functions/auth'
|
|
3
4
|
|
|
4
5
|
const { register } = useAuth()
|
|
@@ -12,7 +13,7 @@ function safeRedirect(): string {
|
|
|
12
13
|
return redirect.startsWith('/') && !redirect.startsWith('//') ? redirect : '/'
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
async function handleRegister(credentials) {
|
|
16
|
+
async function handleRegister(credentials: RegisterCredentials): Promise<void> {
|
|
16
17
|
isLoading.set(true)
|
|
17
18
|
error.set('')
|
|
18
19
|
|
|
@@ -41,33 +41,33 @@
|
|
|
41
41
|
return 'Runners: ' + r.running + ' running, ' + r.queued + ' queued / ' + r.cap + ' cap'
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
-
function tabClass(id) {
|
|
44
|
+
function tabClass(id: string) {
|
|
45
45
|
const base = 'px-4 py-2 font-medium text-sm whitespace-nowrap bg-transparent border-b-2 transition-colors cursor-pointer '
|
|
46
46
|
return activeTab() === id
|
|
47
47
|
? base + 'border-violet-400 text-violet-600 dark:text-violet-400'
|
|
48
48
|
: base + 'border-transparent text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300'
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
function filterClass(id) {
|
|
51
|
+
function filterClass(id: string) {
|
|
52
52
|
const base = 'px-3 py-1 text-xs rounded-full border transition-colors cursor-pointer '
|
|
53
53
|
return statusFilter() === id
|
|
54
54
|
? base + 'bg-violet-500 border-violet-500 text-white'
|
|
55
55
|
: base + 'bg-transparent border-zinc-300 dark:border-zinc-700 text-zinc-600 dark:text-zinc-400 hover:border-zinc-400'
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function borderColor(status) {
|
|
58
|
+
function borderColor(status: string) {
|
|
59
59
|
if (status === 'success') return 'border-color: rgba(16,185,129,0.3)'
|
|
60
60
|
if (status === 'failure' || status === 'error') return 'border-color: rgba(239,68,68,0.3)'
|
|
61
61
|
if (status === 'pending') return 'border-color: rgba(234,179,8,0.3)'
|
|
62
62
|
return 'border-color: rgba(39,39,42,1)'
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
function isSuccess(status) { return status === 'success' }
|
|
66
|
-
function isFailing(status) { return status === 'failure' || status === 'error' }
|
|
67
|
-
function isPending(status) { return status === 'pending' }
|
|
68
|
-
function isNoRuns(status) { return status === 'no_runs' }
|
|
65
|
+
function isSuccess(status: string) { return status === 'success' }
|
|
66
|
+
function isFailing(status: string) { return status === 'failure' || status === 'error' }
|
|
67
|
+
function isPending(status: string) { return status === 'pending' }
|
|
68
|
+
function isNoRuns(status: string) { return status === 'no_runs' }
|
|
69
69
|
|
|
70
|
-
function timeAgo(dateStr) {
|
|
70
|
+
function timeAgo(dateStr: string | null | undefined) {
|
|
71
71
|
if (!dateStr) return ''
|
|
72
72
|
const diff = Date.now() - new Date(dateStr).getTime()
|
|
73
73
|
const mins = Math.floor(diff / 60000)
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
return days + 'd ago'
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
function truncate(str, len) {
|
|
81
|
+
function truncate(str: string, len: number) {
|
|
82
82
|
if (!str) return ''
|
|
83
83
|
return str.length > len ? str.slice(0, len) + '...' : str
|
|
84
84
|
}
|
|
@@ -11,7 +11,7 @@ const jobsByRunId = derived(() => ci.drilldownJobsByRunId())
|
|
|
11
11
|
const loadingJobsRunId = derived(() => ci.loadingJobsForRunId())
|
|
12
12
|
const title = derived(() => target()?.fullName || 'Run history')
|
|
13
13
|
|
|
14
|
-
function runRowClass(conclusion, status) {
|
|
14
|
+
function runRowClass(conclusion: string, status: string) {
|
|
15
15
|
const base = 'px-3 py-2 -mx-3 border-l-2 cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-900 transition-colors '
|
|
16
16
|
if (status !== 'completed') return base + 'border-yellow-400'
|
|
17
17
|
if (conclusion === 'success') return base + 'border-emerald-400'
|
|
@@ -20,14 +20,14 @@ function runRowClass(conclusion, status) {
|
|
|
20
20
|
return base + 'border-zinc-200 dark:border-zinc-800'
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function runStatusBadge(conclusion, status) {
|
|
23
|
+
function runStatusBadge(conclusion: string, status: string) {
|
|
24
24
|
if (status !== 'completed') return { label: status, cls: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300' }
|
|
25
25
|
if (conclusion === 'success') return { label: 'success', cls: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300' }
|
|
26
26
|
if (conclusion === 'failure') return { label: 'failure', cls: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300' }
|
|
27
27
|
return { label: conclusion || 'unknown', cls: 'bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300' }
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
function formatDuration(ms) {
|
|
30
|
+
function formatDuration(ms: number) {
|
|
31
31
|
if (ms == null) return ''
|
|
32
32
|
const seconds = Math.floor(ms / 1000)
|
|
33
33
|
if (seconds < 60) return seconds + 's'
|
|
@@ -38,7 +38,7 @@ function formatDuration(ms) {
|
|
|
38
38
|
return hours + 'h ' + (minutes % 60) + 'm'
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function timeAgo(dateStr) {
|
|
41
|
+
function timeAgo(dateStr: string | null | undefined) {
|
|
42
42
|
if (!dateStr) return ''
|
|
43
43
|
const diff = Date.now() - new Date(dateStr).getTime()
|
|
44
44
|
const minutes = Math.floor(diff / 60000)
|
|
@@ -48,16 +48,16 @@ function timeAgo(dateStr) {
|
|
|
48
48
|
return Math.floor(hours / 24) + 'd ago'
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
function truncate(value, length) {
|
|
51
|
+
function truncate(value: string, length: number) {
|
|
52
52
|
if (!value) return ''
|
|
53
53
|
return value.length > length ? value.slice(0, length) + '...' : value
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function jobsForRun(runId) {
|
|
56
|
+
function jobsForRun(runId: number) {
|
|
57
57
|
return jobsByRunId()[runId] || []
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
function failedJobs(runId) {
|
|
60
|
+
function failedJobs(runId: number) {
|
|
61
61
|
return jobsForRun(runId).filter(job => job.conclusion && job.conclusion !== 'success' && job.conclusion !== 'skipped' && job.conclusion !== 'neutral')
|
|
62
62
|
}
|
|
63
63
|
</script>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script client>
|
|
2
2
|
import type { CommerceDashboardChartSeries } from '../../../../app/Actions/Dashboard/Commerce/commerce-dashboard'
|
|
3
|
+
import type { ChartOptions } from '../../../../views/dashboard/composables'
|
|
3
4
|
import { useLazyCharts } from '../../../../views/dashboard/composables'
|
|
4
5
|
|
|
5
6
|
const series = useReactiveProp('series', {
|
|
@@ -41,7 +42,7 @@ const ordersData = {
|
|
|
41
42
|
data: [] as number[],
|
|
42
43
|
}],
|
|
43
44
|
}
|
|
44
|
-
const chartOptions = {
|
|
45
|
+
const chartOptions: ChartOptions = {
|
|
45
46
|
responsive: true,
|
|
46
47
|
maintainAspectRatio: false,
|
|
47
48
|
plugins: { legend: { display: true, position: 'top' } },
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script client>
|
|
2
|
-
import type { PaymentRecord } from '../../../../app/Actions/Dashboard/Commerce/payment-records'
|
|
2
|
+
import type { PaymentRecord, PaymentSummary } from '../../../../app/Actions/Dashboard/Commerce/payment-records'
|
|
3
3
|
import {
|
|
4
4
|
paymentsToCsv,
|
|
5
5
|
summarizePayments,
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
import { publicApplicationUrl } from '../../../../functions/public-application-url'
|
|
3
3
|
|
|
4
4
|
export const blogUrl = publicApplicationUrl('/blog')
|
|
5
|
+
|
|
6
|
+
// Declared so the client block below gets `blogUrl` typed rather than inferred
|
|
7
|
+
// off the implicit bridge.
|
|
8
|
+
defineClientPayload({ blogUrl })
|
|
5
9
|
</script>
|
|
6
10
|
|
|
7
11
|
<template>
|
|
@@ -198,6 +202,7 @@ export const blogUrl = publicApplicationUrl('/blog')
|
|
|
198
202
|
<script client>
|
|
199
203
|
// Plain async transport for /api/dashboard/blog, which reads and writes the
|
|
200
204
|
// markdown files in content/blog. It owns no state; the signals below do.
|
|
205
|
+
import type { BlogPost, BlogPostDraft } from '~/storage/framework/defaults/functions/blog'
|
|
201
206
|
import { deleteBlogPost, fetchBlogPost, fetchBlogPosts, saveBlogPost } from '~/storage/framework/defaults/functions/blog'
|
|
202
207
|
|
|
203
208
|
const publicBlogBaseUrl = {{ blogUrl }}
|
|
@@ -205,7 +210,7 @@ const publicBlogBaseUrl = {{ blogUrl }}
|
|
|
205
210
|
// Fixed-length list purely to render the loading skeleton rows.
|
|
206
211
|
const skeletonRows = [1, 2, 3, 4]
|
|
207
212
|
|
|
208
|
-
const posts = state([])
|
|
213
|
+
const posts = state<BlogPost[]>([])
|
|
209
214
|
const isLoading = state(true)
|
|
210
215
|
const isSaving = state(false)
|
|
211
216
|
const loadError = state('')
|
|
@@ -213,18 +218,29 @@ const searchQuery = state('')
|
|
|
213
218
|
const statusFilter = state('all')
|
|
214
219
|
|
|
215
220
|
const isEditorOpen = state(false)
|
|
216
|
-
const editingPost = state(null)
|
|
221
|
+
const editingPost = state<BlogPost | null>(null)
|
|
217
222
|
const loadedBody = state('')
|
|
218
223
|
const bodyLoading = state(false)
|
|
219
224
|
const bodyLoadFailed = state(false)
|
|
220
225
|
const formError = state('')
|
|
221
226
|
|
|
222
|
-
const postToDelete = state(null)
|
|
227
|
+
const postToDelete = state<BlogPost | null>(null)
|
|
223
228
|
const isDeleting = state(false)
|
|
224
229
|
const deleteError = state('')
|
|
225
230
|
let editorRequestId = 0
|
|
226
231
|
|
|
227
|
-
|
|
232
|
+
/**
|
|
233
|
+
* A caught value's message.
|
|
234
|
+
*
|
|
235
|
+
* `catch (error)` binds `unknown`, and reading `.message` off it assumes every
|
|
236
|
+
* throw is an `Error` - a rejected fetch or a thrown string would render
|
|
237
|
+
* `undefined` into the banner the user reads.
|
|
238
|
+
*/
|
|
239
|
+
function messageOf(error: unknown): string {
|
|
240
|
+
return error instanceof Error ? error.message : String(error)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function publicPostUrl(slug: string) {
|
|
228
244
|
return `${publicBlogBaseUrl}/${encodeURIComponent(String(slug))}`
|
|
229
245
|
}
|
|
230
246
|
|
|
@@ -256,13 +272,13 @@ const filteredPosts = derived(() => {
|
|
|
256
272
|
})
|
|
257
273
|
})
|
|
258
274
|
|
|
259
|
-
function statusClass(draft) {
|
|
275
|
+
function statusClass(draft: boolean) {
|
|
260
276
|
return draft
|
|
261
277
|
? 'bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-200'
|
|
262
278
|
: 'bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-200'
|
|
263
279
|
}
|
|
264
280
|
|
|
265
|
-
function formatDate(value) {
|
|
281
|
+
function formatDate(value: string | number | Date | null | undefined) {
|
|
266
282
|
if (!value)
|
|
267
283
|
return 'No date'
|
|
268
284
|
|
|
@@ -278,7 +294,7 @@ function clearFilters() {
|
|
|
278
294
|
statusFilter.set('all')
|
|
279
295
|
}
|
|
280
296
|
|
|
281
|
-
async function openEditor(post) {
|
|
297
|
+
async function openEditor(post: BlogPost) {
|
|
282
298
|
const requestId = ++editorRequestId
|
|
283
299
|
formError.set('')
|
|
284
300
|
editingPost.set(post)
|
|
@@ -293,7 +309,7 @@ async function openEditor(post) {
|
|
|
293
309
|
await loadEditorBody(post, requestId)
|
|
294
310
|
}
|
|
295
311
|
|
|
296
|
-
async function loadEditorBody(post, requestId = ++editorRequestId) {
|
|
312
|
+
async function loadEditorBody(post: BlogPost, requestId = ++editorRequestId) {
|
|
297
313
|
bodyLoading.set(true)
|
|
298
314
|
bodyLoadFailed.set(false)
|
|
299
315
|
formError.set('')
|
|
@@ -307,7 +323,7 @@ async function loadEditorBody(post, requestId = ++editorRequestId) {
|
|
|
307
323
|
catch (error) {
|
|
308
324
|
if (requestId === editorRequestId) {
|
|
309
325
|
bodyLoadFailed.set(true)
|
|
310
|
-
formError.set(`Couldn't load this post's content: ${error
|
|
326
|
+
formError.set(`Couldn't load this post's content: ${messageOf(error)}`)
|
|
311
327
|
}
|
|
312
328
|
}
|
|
313
329
|
finally {
|
|
@@ -335,7 +351,7 @@ function closeEditor() {
|
|
|
335
351
|
formError.set('')
|
|
336
352
|
}
|
|
337
353
|
|
|
338
|
-
async function submit(draft) {
|
|
354
|
+
async function submit(draft: BlogPostDraft) {
|
|
339
355
|
isSaving.set(true)
|
|
340
356
|
formError.set('')
|
|
341
357
|
|
|
@@ -351,14 +367,14 @@ async function submit(draft) {
|
|
|
351
367
|
}
|
|
352
368
|
catch (error) {
|
|
353
369
|
// Keep the editor open on failure so the author's work is not thrown away.
|
|
354
|
-
formError.set(error
|
|
370
|
+
formError.set(messageOf(error))
|
|
355
371
|
}
|
|
356
372
|
finally {
|
|
357
373
|
isSaving.set(false)
|
|
358
374
|
}
|
|
359
375
|
}
|
|
360
376
|
|
|
361
|
-
function askDelete(post) {
|
|
377
|
+
function askDelete(post: BlogPost) {
|
|
362
378
|
deleteError.set('')
|
|
363
379
|
postToDelete.set(post)
|
|
364
380
|
}
|
|
@@ -384,7 +400,7 @@ async function confirmDelete() {
|
|
|
384
400
|
postToDelete.set(null)
|
|
385
401
|
}
|
|
386
402
|
catch (error) {
|
|
387
|
-
deleteError.set(`Couldn't delete "${post.title}": ${error
|
|
403
|
+
deleteError.set(`Couldn't delete "${post.title}": ${messageOf(error)}`)
|
|
388
404
|
}
|
|
389
405
|
finally {
|
|
390
406
|
isDeleting.set(false)
|
|
@@ -399,7 +415,7 @@ async function load() {
|
|
|
399
415
|
posts.set(list)
|
|
400
416
|
}
|
|
401
417
|
catch (error) {
|
|
402
|
-
loadError.set(error
|
|
418
|
+
loadError.set(messageOf(error))
|
|
403
419
|
}
|
|
404
420
|
finally {
|
|
405
421
|
isLoading.set(false)
|
|
@@ -408,5 +424,11 @@ async function load() {
|
|
|
408
424
|
|
|
409
425
|
// onMount, not effect: load() writes the same `posts` signal the derived signals
|
|
410
426
|
// read, so an effect would re-trigger itself on every load.
|
|
411
|
-
|
|
427
|
+
//
|
|
428
|
+
// Wrapped rather than passed directly. `onMount` takes `() => void | StxCleanup`,
|
|
429
|
+
// and an async function returns a promise - which stx would treat as the
|
|
430
|
+
// cleanup value if it took one, and which hides a rejection either way. `load`
|
|
431
|
+
// handles its own errors into `loadError`, so the promise is deliberately
|
|
432
|
+
// dropped here.
|
|
433
|
+
onMount(() => { void load() })
|
|
412
434
|
</script>
|