@byline/host-tanstack-start 4.15.0 → 4.17.0
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/dist/admin-shell/chrome/admin-menu-drawer.d.ts +2 -1
- package/dist/admin-shell/chrome/admin-menu-drawer.js +17 -5
- package/dist/admin-shell/chrome/dashboard.js +41 -5
- package/dist/admin-shell/chrome/dashboard.test.d.ts +8 -0
- package/dist/admin-shell/collections/api.d.ts +2 -2
- package/dist/admin-shell/collections/create.d.ts +2 -2
- package/dist/admin-shell/collections/create.js +1 -0
- package/dist/admin-shell/collections/edit.d.ts +2 -2
- package/dist/admin-shell/collections/edit.js +1 -0
- package/dist/admin-shell/collections/history.d.ts +4 -12
- package/dist/admin-shell/collections/history.js +112 -303
- package/dist/admin-shell/collections/reindex-button.d.ts +1 -1
- package/dist/admin-shell/collections/reindex-button.js +5 -1
- package/dist/admin-shell/collections/restore-version-modal.d.ts +4 -4
- package/dist/admin-shell/collections/restore-version-modal.js +8 -24
- package/dist/admin-shell/collections/version-history.d.ts +72 -0
- package/dist/admin-shell/collections/version-history.js +259 -0
- package/dist/admin-shell/singletons/history.d.ts +19 -0
- package/dist/admin-shell/singletons/history.js +170 -0
- package/dist/admin-shell/singletons/history.test.d.ts +8 -0
- package/dist/admin-shell/singletons/preview-link.d.ts +12 -0
- package/dist/admin-shell/singletons/preview-link.js +54 -0
- package/dist/admin-shell/singletons/view-menu.d.ts +13 -0
- package/dist/admin-shell/singletons/view-menu.js +116 -0
- package/dist/admin-shell/singletons/view.d.ts +18 -0
- package/dist/admin-shell/singletons/view.js +259 -0
- package/dist/admin-shell/singletons/view.test.d.ts +8 -0
- package/dist/integrations/analytics-agent.d.ts +20 -0
- package/dist/integrations/analytics-agent.js +57 -0
- package/dist/integrations/analytics-events.d.ts +51 -0
- package/dist/integrations/analytics-events.js +129 -0
- package/dist/integrations/api-utils.d.ts +18 -4
- package/dist/integrations/api-utils.js +10 -3
- package/dist/integrations/serve-analytics-agent.d.ts +8 -0
- package/dist/integrations/serve-analytics-agent.js +11 -0
- package/dist/routes/analytics-range.d.ts +12 -0
- package/dist/routes/analytics-range.js +35 -0
- package/dist/routes/create-admin-analytics-route.d.ts +8 -0
- package/dist/routes/create-admin-analytics-route.js +108 -0
- package/dist/routes/create-admin-dashboard-route.js +2 -2
- package/dist/routes/create-admin-layout-route.js +10 -4
- package/dist/routes/create-collection-api-route.js +3 -3
- package/dist/routes/create-collection-create-route.js +4 -3
- package/dist/routes/create-collection-edit-route.js +4 -3
- package/dist/routes/create-collection-history-route.js +4 -3
- package/dist/routes/create-collection-list-route.js +4 -3
- package/dist/routes/create-singleton-history-route.d.ts +8 -0
- package/dist/routes/create-singleton-history-route.js +99 -0
- package/dist/routes/create-singleton-route.d.ts +35 -0
- package/dist/routes/create-singleton-route.js +130 -0
- package/dist/routes/get-multi-collection-definition.d.ts +10 -0
- package/dist/routes/get-multi-collection-definition.js +6 -0
- package/dist/routes/get-singleton-definition.d.ts +10 -0
- package/dist/routes/get-singleton-definition.js +6 -0
- package/dist/routes/index.d.ts +3 -0
- package/dist/routes/index.js +3 -0
- package/dist/server-fns/actors.d.ts +16 -0
- package/dist/server-fns/actors.js +25 -0
- package/dist/server-fns/admin-document-presentation.d.ts +30 -0
- package/dist/server-fns/admin-document-presentation.js +32 -0
- package/dist/server-fns/analytics/index.d.ts +9 -0
- package/dist/server-fns/analytics/index.js +2 -0
- package/dist/server-fns/analytics/maintenance.d.ts +22 -0
- package/dist/server-fns/analytics/maintenance.js +42 -0
- package/dist/server-fns/analytics/queries.d.ts +25 -0
- package/dist/server-fns/analytics/queries.js +54 -0
- package/dist/server-fns/collections/actors.d.ts +1 -10
- package/dist/server-fns/collections/actors.js +1 -25
- package/dist/server-fns/collections/get.js +9 -26
- package/dist/server-fns/collections/reorder.js +1 -1
- package/dist/server-fns/collections/scheduled-publication.js +2 -2
- package/dist/server-fns/collections/upload.js +3 -3
- package/dist/server-fns/collections/utils.d.ts +1 -7
- package/dist/server-fns/collections/utils.js +1 -4
- package/dist/server-fns/serialise.d.ts +14 -0
- package/dist/server-fns/serialise.js +4 -0
- package/dist/server-fns/singleton-document-read.d.ts +20 -0
- package/dist/server-fns/singleton-document-read.js +10 -0
- package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +12 -0
- package/dist/server-fns/singletons/cancel-scheduled-publish.js +7 -0
- package/dist/server-fns/singletons/change-status.d.ts +14 -0
- package/dist/server-fns/singletons/change-status.js +7 -0
- package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +14 -0
- package/dist/server-fns/singletons/confirm-scheduled-publish.js +9 -0
- package/dist/server-fns/singletons/copy-to-locale.d.ts +18 -0
- package/dist/server-fns/singletons/copy-to-locale.js +11 -0
- package/dist/server-fns/singletons/find-by-version.d.ts +16 -0
- package/dist/server-fns/singletons/find-by-version.js +9 -0
- package/dist/server-fns/singletons/get-scheduled-publish.d.ts +12 -0
- package/dist/server-fns/singletons/get-scheduled-publish.js +7 -0
- package/dist/server-fns/singletons/get.d.ts +24 -0
- package/dist/server-fns/singletons/get.js +6 -0
- package/dist/server-fns/singletons/history.d.ts +31 -0
- package/dist/server-fns/singletons/history.js +22 -0
- package/dist/server-fns/singletons/index.d.ts +19 -0
- package/dist/server-fns/singletons/index.js +12 -0
- package/dist/server-fns/singletons/restore-version.d.ts +14 -0
- package/dist/server-fns/singletons/restore-version.js +7 -0
- package/dist/server-fns/singletons/schedule-publish.d.ts +16 -0
- package/dist/server-fns/singletons/schedule-publish.js +10 -0
- package/dist/server-fns/singletons/unpublish.d.ts +12 -0
- package/dist/server-fns/singletons/unpublish.js +7 -0
- package/dist/server-fns/singletons/update.d.ts +14 -0
- package/dist/server-fns/singletons/update.js +14 -0
- package/dist/vite/analytics-dev.d.ts +22 -0
- package/dist/vite/analytics-dev.js +28 -0
- package/dist/vite/index.d.ts +1 -0
- package/dist/vite/index.js +1 -0
- package/dist/vitest-jsdom.test.d.ts +8 -0
- package/package.json +28 -16
- package/src/admin-shell/chrome/admin-menu-drawer.tsx +19 -5
- package/src/admin-shell/chrome/dashboard.test.tsx +199 -0
- package/src/admin-shell/chrome/dashboard.tsx +43 -9
- package/src/admin-shell/collections/api.tsx +2 -2
- package/src/admin-shell/collections/create.tsx +6 -2
- package/src/admin-shell/collections/edit.tsx +6 -2
- package/src/admin-shell/collections/history.module.css +1 -1
- package/src/admin-shell/collections/history.tsx +162 -442
- package/src/admin-shell/collections/reindex-button.tsx +4 -2
- package/src/admin-shell/collections/restore-version-modal.tsx +19 -25
- package/src/admin-shell/collections/version-history.tsx +349 -0
- package/src/admin-shell/singletons/history.test.tsx +407 -0
- package/src/admin-shell/singletons/history.tsx +185 -0
- package/src/admin-shell/singletons/preview-link.tsx +75 -0
- package/src/admin-shell/singletons/view-menu.tsx +136 -0
- package/src/admin-shell/singletons/view.test.tsx +400 -0
- package/src/admin-shell/singletons/view.tsx +351 -0
- package/src/integrations/analytics-agent.tsx +91 -0
- package/src/integrations/analytics-events.test.node.ts +245 -0
- package/src/integrations/analytics-events.ts +224 -0
- package/src/integrations/api-utils.ts +34 -9
- package/src/integrations/serve-analytics-agent.test.node.ts +20 -0
- package/src/integrations/serve-analytics-agent.ts +19 -0
- package/src/routes/analytics-range.ts +55 -0
- package/src/routes/create-admin-analytics-route.test.node.ts +46 -0
- package/src/routes/create-admin-analytics-route.tsx +109 -0
- package/src/routes/create-admin-dashboard-route.test.node.ts +70 -0
- package/src/routes/create-admin-dashboard-route.tsx +10 -2
- package/src/routes/create-admin-layout-route.tsx +15 -8
- package/src/routes/create-collection-api-route.tsx +4 -4
- package/src/routes/create-collection-create-route.tsx +5 -8
- package/src/routes/create-collection-edit-route.tsx +5 -4
- package/src/routes/create-collection-history-route.tsx +5 -8
- package/src/routes/create-collection-list-route.tsx +5 -8
- package/src/routes/create-singleton-history-route.test.node.ts +148 -0
- package/src/routes/create-singleton-history-route.tsx +116 -0
- package/src/routes/create-singleton-route.test.node.ts +172 -0
- package/src/routes/create-singleton-route.tsx +176 -0
- package/src/routes/get-multi-collection-definition.ts +16 -0
- package/src/routes/get-singleton-definition.ts +16 -0
- package/src/routes/index.ts +3 -0
- package/src/server-fns/actors.ts +43 -0
- package/src/server-fns/admin-document-presentation.ts +78 -0
- package/src/server-fns/analytics/index.ts +23 -0
- package/src/server-fns/analytics/maintenance.ts +54 -0
- package/src/server-fns/analytics/queries.ts +86 -0
- package/src/server-fns/collections/actors.ts +1 -52
- package/src/server-fns/collections/get.ts +10 -49
- package/src/server-fns/collections/reorder.ts +1 -1
- package/src/server-fns/collections/scheduled-publication.ts +7 -1
- package/src/server-fns/collections/upload-singleton.test.node.ts +164 -0
- package/src/server-fns/collections/upload.ts +7 -3
- package/src/server-fns/collections/utils.ts +1 -9
- package/src/server-fns/serialise.ts +17 -0
- package/src/server-fns/singleton-document-read.ts +32 -0
- package/src/server-fns/singletons/cancel-scheduled-publish.ts +19 -0
- package/src/server-fns/singletons/change-status.ts +19 -0
- package/src/server-fns/singletons/confirm-scheduled-publish.ts +23 -0
- package/src/server-fns/singletons/copy-to-locale.ts +32 -0
- package/src/server-fns/singletons/find-by-version.ts +23 -0
- package/src/server-fns/singletons/get-scheduled-publish.ts +19 -0
- package/src/server-fns/singletons/get.ts +32 -0
- package/src/server-fns/singletons/history.ts +41 -0
- package/src/server-fns/singletons/index.ts +20 -0
- package/src/server-fns/singletons/restore-version.ts +19 -0
- package/src/server-fns/singletons/schedule-publish.ts +24 -0
- package/src/server-fns/singletons/singletons.test.node.ts +298 -0
- package/src/server-fns/singletons/unpublish.ts +19 -0
- package/src/server-fns/singletons/update.ts +31 -0
- package/src/vite/analytics-dev.test.node.ts +126 -0
- package/src/vite/analytics-dev.ts +56 -0
- package/src/vite/index.ts +1 -0
- package/src/vitest-jsdom.test.tsx +27 -0
|
@@ -13,12 +13,14 @@ import { ADMIN_ACTIVITY_ABILITIES } from '@byline/admin/admin-activity'
|
|
|
13
13
|
import { ADMIN_PERMISSIONS_ABILITIES } from '@byline/admin/admin-permissions'
|
|
14
14
|
import { ADMIN_ROLES_ABILITIES } from '@byline/admin/admin-roles'
|
|
15
15
|
import { ADMIN_USERS_ABILITIES } from '@byline/admin/admin-users'
|
|
16
|
-
import {
|
|
16
|
+
import { ANALYTICS_ABILITIES } from '@byline/admin/analytics'
|
|
17
|
+
import { documentAbilityKey, getAdminConfig } from '@byline/core'
|
|
17
18
|
import { useTranslation } from '@byline/i18n/react'
|
|
18
19
|
import {
|
|
19
20
|
ActivityIcon,
|
|
21
|
+
AnalyticsIcon,
|
|
20
22
|
CalendarIcon,
|
|
21
|
-
|
|
23
|
+
DashboardIcon,
|
|
22
24
|
RolesIcon,
|
|
23
25
|
SettingsSlidersIcon,
|
|
24
26
|
Tooltip,
|
|
@@ -78,8 +80,10 @@ function MenuItem({ to, label, icon, pathname, compact }: MenuItemProps) {
|
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
export function AdminMenuDrawer({
|
|
83
|
+
analyticsEnabled = false,
|
|
81
84
|
scheduledPublicationEnabled = false,
|
|
82
85
|
}: {
|
|
86
|
+
analyticsEnabled?: boolean
|
|
83
87
|
scheduledPublicationEnabled?: boolean
|
|
84
88
|
}): React.JSX.Element | null {
|
|
85
89
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
|
@@ -96,12 +100,13 @@ export function AdminMenuDrawer({
|
|
|
96
100
|
const canReadRoles = has(ADMIN_ROLES_ABILITIES.read)
|
|
97
101
|
const canReadPermissions = has(ADMIN_PERMISSIONS_ABILITIES.read)
|
|
98
102
|
const canReadActivity = has(ADMIN_ACTIVITY_ABILITIES.read)
|
|
103
|
+
const canReadAnalytics = analyticsEnabled && has(ANALYTICS_ABILITIES.read)
|
|
99
104
|
const canSeeScheduledPublications =
|
|
100
105
|
scheduledPublicationEnabled &&
|
|
101
106
|
getAdminConfig().collections.some(
|
|
102
107
|
(collection) =>
|
|
103
|
-
has(
|
|
104
|
-
has(
|
|
108
|
+
has(documentAbilityKey(collection, 'changeStatus')) &&
|
|
109
|
+
has(documentAbilityKey(collection, 'publish'))
|
|
105
110
|
)
|
|
106
111
|
const showAdminSection = canReadUsers || canReadRoles || canReadPermissions || canReadActivity
|
|
107
112
|
|
|
@@ -139,10 +144,19 @@ export function AdminMenuDrawer({
|
|
|
139
144
|
<MenuItem
|
|
140
145
|
to={getAdminRoutePath()}
|
|
141
146
|
label={t('chrome.menu.dashboard')}
|
|
142
|
-
icon={<
|
|
147
|
+
icon={<DashboardIcon width="20px" height="20px" />}
|
|
143
148
|
pathname={pathname}
|
|
144
149
|
compact={compact}
|
|
145
150
|
/>
|
|
151
|
+
{canReadAnalytics && (
|
|
152
|
+
<MenuItem
|
|
153
|
+
to={getAdminRoutePath('analytics')}
|
|
154
|
+
label={t('chrome.menu.analytics')}
|
|
155
|
+
icon={<AnalyticsIcon width="20px" height="20px" />}
|
|
156
|
+
pathname={pathname}
|
|
157
|
+
compact={compact}
|
|
158
|
+
/>
|
|
159
|
+
)}
|
|
146
160
|
{canSeeScheduledPublications && (
|
|
147
161
|
<MenuItem
|
|
148
162
|
to={getAdminRoutePath('scheduled-publications')}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type React from 'react'
|
|
10
|
+
import { act } from 'react'
|
|
11
|
+
|
|
12
|
+
import { defineAdminConfig, defineSingleton, type MultiCollectionDefinition } from '@byline/core'
|
|
13
|
+
import { createRoot } from 'react-dom/client'
|
|
14
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
15
|
+
|
|
16
|
+
const abilityState = vi.hoisted(() => ({
|
|
17
|
+
isSuperAdmin: false,
|
|
18
|
+
abilities: [] as string[],
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
vi.mock('../../integrations/abilities.jsx', () => ({
|
|
22
|
+
useAbilities: () => ({
|
|
23
|
+
isSuperAdmin: abilityState.isSuperAdmin,
|
|
24
|
+
abilities: abilityState.abilities,
|
|
25
|
+
has: () => false,
|
|
26
|
+
hasAny: () => false,
|
|
27
|
+
}),
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
vi.mock('@byline/i18n/react', () => ({
|
|
31
|
+
useTranslation: () => ({
|
|
32
|
+
t: (key: string, values?: Record<string, string | number>) => {
|
|
33
|
+
if (key === 'dashboard.totalCount') return `${values?.count} total`
|
|
34
|
+
if (key === 'dashboard.collectionDescription') return `Manage ${values?.label}`
|
|
35
|
+
return key
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
}))
|
|
39
|
+
|
|
40
|
+
vi.mock('@byline/ui/react', () => {
|
|
41
|
+
type Props = { children?: React.ReactNode; className?: string }
|
|
42
|
+
const element = (tag: keyof React.JSX.IntrinsicElements) =>
|
|
43
|
+
function Element({ children, ...props }: Props) {
|
|
44
|
+
const Component = tag
|
|
45
|
+
return <Component {...props}>{children}</Component>
|
|
46
|
+
}
|
|
47
|
+
const Card = element('article') as ReturnType<typeof element> & {
|
|
48
|
+
Header: ReturnType<typeof element>
|
|
49
|
+
Title: ReturnType<typeof element>
|
|
50
|
+
Description: ReturnType<typeof element>
|
|
51
|
+
Content: ReturnType<typeof element>
|
|
52
|
+
}
|
|
53
|
+
Card.Header = element('header')
|
|
54
|
+
Card.Title = element('h3')
|
|
55
|
+
Card.Description = element('p')
|
|
56
|
+
Card.Content = element('div')
|
|
57
|
+
return { Card, Container: element('div'), Section: element('section') }
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
vi.mock('./loose-router.js', () => ({
|
|
61
|
+
Link: ({
|
|
62
|
+
to = '',
|
|
63
|
+
params = {},
|
|
64
|
+
search: _search,
|
|
65
|
+
children,
|
|
66
|
+
...props
|
|
67
|
+
}: React.AnchorHTMLAttributes<HTMLAnchorElement> & {
|
|
68
|
+
to?: string
|
|
69
|
+
params?: Record<string, string>
|
|
70
|
+
search?: unknown
|
|
71
|
+
}) => {
|
|
72
|
+
let href = to
|
|
73
|
+
for (const [name, value] of Object.entries(params)) {
|
|
74
|
+
href = href.replace(`$${name}`, value)
|
|
75
|
+
}
|
|
76
|
+
return (
|
|
77
|
+
<a href={href} {...props}>
|
|
78
|
+
{children}
|
|
79
|
+
</a>
|
|
80
|
+
)
|
|
81
|
+
},
|
|
82
|
+
}))
|
|
83
|
+
|
|
84
|
+
import { AdminDashboard } from './dashboard.js'
|
|
85
|
+
|
|
86
|
+
const articles: MultiCollectionDefinition = {
|
|
87
|
+
path: 'articles',
|
|
88
|
+
labels: { singular: 'Article', plural: 'Articles' },
|
|
89
|
+
fields: [{ name: 'title', label: 'Title', type: 'text' }],
|
|
90
|
+
showStats: true,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const settings = defineSingleton({
|
|
94
|
+
path: 'site-settings',
|
|
95
|
+
label: 'Site settings',
|
|
96
|
+
fields: [{ name: 'title', label: 'Title', type: 'text' }],
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
function renderDashboard(statsMap: Record<string, Array<{ status: string; count: number }>> = {}) {
|
|
100
|
+
const container = document.createElement('div')
|
|
101
|
+
document.body.append(container)
|
|
102
|
+
const root = createRoot(container)
|
|
103
|
+
act(() => root.render(<AdminDashboard statsMap={statsMap} />))
|
|
104
|
+
return {
|
|
105
|
+
container,
|
|
106
|
+
dispose() {
|
|
107
|
+
act(() => root.unmount())
|
|
108
|
+
container.remove()
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
describe('AdminDashboard document resources', () => {
|
|
114
|
+
beforeAll(() => {
|
|
115
|
+
;(
|
|
116
|
+
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
|
|
117
|
+
).IS_REACT_ACT_ENVIRONMENT = true
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
afterAll(() => {
|
|
121
|
+
delete (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean })
|
|
122
|
+
.IS_REACT_ACT_ENVIRONMENT
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
beforeEach(() => {
|
|
126
|
+
abilityState.isSuperAdmin = false
|
|
127
|
+
abilityState.abilities = []
|
|
128
|
+
defineAdminConfig({
|
|
129
|
+
routes: { admin: '/internal/cms' },
|
|
130
|
+
collections: [articles, settings],
|
|
131
|
+
admin: [
|
|
132
|
+
{ slug: articles.path, group: 'content' },
|
|
133
|
+
{ singleton: true, slug: settings.path, group: 'content' },
|
|
134
|
+
],
|
|
135
|
+
collectionGroups: [{ name: 'content', label: 'Content' }],
|
|
136
|
+
i18n: {
|
|
137
|
+
admin: { defaultLocale: 'en', locales: [] },
|
|
138
|
+
content: { defaultLocale: 'en', locales: [] },
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('shows a singleton only for its kind-aware read ability', () => {
|
|
144
|
+
abilityState.abilities = ['singletons.site-settings.read']
|
|
145
|
+
const allowed = renderDashboard()
|
|
146
|
+
expect(allowed.container.textContent).toContain('Site settings')
|
|
147
|
+
expect(allowed.container.textContent).not.toContain('Articles')
|
|
148
|
+
allowed.dispose()
|
|
149
|
+
|
|
150
|
+
abilityState.abilities = ['collections.site-settings.read']
|
|
151
|
+
const staleNamespace = renderDashboard()
|
|
152
|
+
expect(staleNamespace.container.textContent).not.toContain('Site settings')
|
|
153
|
+
staleNamespace.dispose()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('shows both resource kinds to a super admin in registry order', () => {
|
|
157
|
+
abilityState.isSuperAdmin = true
|
|
158
|
+
const view = renderDashboard({ articles: [{ status: 'draft', count: 3 }] })
|
|
159
|
+
|
|
160
|
+
const titles = [...view.container.querySelectorAll('.byline-dashboard-title-text')].map(
|
|
161
|
+
(element) => element.textContent
|
|
162
|
+
)
|
|
163
|
+
expect(titles).toEqual(['Articles', 'Site settings'])
|
|
164
|
+
expect(view.container.querySelector('.byline-dashboard-group-heading')?.textContent).toBe(
|
|
165
|
+
'Content'
|
|
166
|
+
)
|
|
167
|
+
view.dispose()
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('links a singleton under the custom admin base without collection stats', () => {
|
|
171
|
+
abilityState.isSuperAdmin = true
|
|
172
|
+
const view = renderDashboard({
|
|
173
|
+
articles: [{ status: 'draft', count: 3 }],
|
|
174
|
+
'site-settings': [{ status: 'published', count: 99 }],
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const link = view.container.querySelector<HTMLAnchorElement>(
|
|
178
|
+
'a[href="/internal/cms/singletons/site-settings"]'
|
|
179
|
+
)
|
|
180
|
+
const card = link?.closest('article')
|
|
181
|
+
expect(link).not.toBeNull()
|
|
182
|
+
expect(card?.querySelector('.byline-dashboard-stat-tile')).toBeNull()
|
|
183
|
+
expect(card?.querySelector('.byline-dashboard-title-meta')).toBeNull()
|
|
184
|
+
expect(card?.textContent).not.toContain('99')
|
|
185
|
+
expect(card?.textContent?.match(/Manage Site settings/g)).toHaveLength(1)
|
|
186
|
+
expect(
|
|
187
|
+
view.container.querySelector('a[href="/internal/cms/collections/articles"]')
|
|
188
|
+
).not.toBeNull()
|
|
189
|
+
expect(view.container.querySelector('.byline-dashboard-stat-tile')).not.toBeNull()
|
|
190
|
+
view.dispose()
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('hides an update-only singleton role from the dashboard', () => {
|
|
194
|
+
abilityState.abilities = ['singletons.site-settings.update']
|
|
195
|
+
const view = renderDashboard()
|
|
196
|
+
expect(view.container.textContent).not.toContain('Site settings')
|
|
197
|
+
view.dispose()
|
|
198
|
+
})
|
|
199
|
+
})
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type {
|
|
9
|
+
import type { MultiCollectionDefinition, SingletonDefinition, WorkflowStatus } from '@byline/core'
|
|
10
10
|
import {
|
|
11
11
|
filterReadableCollections,
|
|
12
12
|
getAdminConfig,
|
|
13
13
|
getWorkflowStatuses,
|
|
14
14
|
groupCollectionsForAdmin,
|
|
15
|
+
isSingleton,
|
|
15
16
|
} from '@byline/core'
|
|
16
17
|
import { useTranslation } from '@byline/i18n/react'
|
|
17
18
|
import { Card, Container, Section } from '@byline/ui/react'
|
|
@@ -68,7 +69,7 @@ function CollectionCard({
|
|
|
68
69
|
collection,
|
|
69
70
|
stats,
|
|
70
71
|
}: {
|
|
71
|
-
collection:
|
|
72
|
+
collection: MultiCollectionDefinition
|
|
72
73
|
stats: CollectionStatusCount[] | undefined
|
|
73
74
|
}) {
|
|
74
75
|
const { t } = useTranslation('byline-admin')
|
|
@@ -134,6 +135,35 @@ function CollectionCard({
|
|
|
134
135
|
)
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
function SingletonCard({ singleton }: { singleton: SingletonDefinition }) {
|
|
139
|
+
const { t } = useTranslation('byline-admin')
|
|
140
|
+
const destination = getAdminRoutePath('singletons', '$singleton')
|
|
141
|
+
const params = { singleton: singleton.path }
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<Card>
|
|
145
|
+
<Link
|
|
146
|
+
to={destination}
|
|
147
|
+
params={params}
|
|
148
|
+
className={cx('byline-dashboard-card-link', styles.cardLink)}
|
|
149
|
+
>
|
|
150
|
+
<Card.Header>
|
|
151
|
+
<div className={cx('byline-dashboard-card-header', styles.cardHeader)}>
|
|
152
|
+
<Card.Title className={cx('byline-dashboard-card-title', styles.cardTitle)}>
|
|
153
|
+
<span className={cx('byline-dashboard-title-text', styles.titleText)}>
|
|
154
|
+
{singleton.label}
|
|
155
|
+
</span>
|
|
156
|
+
</Card.Title>
|
|
157
|
+
<Card.Description className="muted">
|
|
158
|
+
{t('dashboard.collectionDescription', { label: singleton.label })}
|
|
159
|
+
</Card.Description>
|
|
160
|
+
</div>
|
|
161
|
+
</Card.Header>
|
|
162
|
+
</Link>
|
|
163
|
+
</Card>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
137
167
|
interface AdminDashboardProps {
|
|
138
168
|
statsMap: Record<string, CollectionStatusCount[]>
|
|
139
169
|
}
|
|
@@ -173,13 +203,17 @@ export function AdminDashboard({ statsMap }: AdminDashboardProps) {
|
|
|
173
203
|
</h2>
|
|
174
204
|
)}
|
|
175
205
|
<div className={cx('byline-dashboard-grid', styles.grid)}>
|
|
176
|
-
{bucket.collections.map((
|
|
177
|
-
|
|
178
|
-
key={
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
206
|
+
{bucket.collections.map((resource) =>
|
|
207
|
+
isSingleton(resource) ? (
|
|
208
|
+
<SingletonCard key={resource.path} singleton={resource} />
|
|
209
|
+
) : (
|
|
210
|
+
<CollectionCard
|
|
211
|
+
key={resource.path}
|
|
212
|
+
collection={resource}
|
|
213
|
+
stats={statsMap[resource.path]}
|
|
214
|
+
/>
|
|
215
|
+
)
|
|
216
|
+
)}
|
|
183
217
|
</div>
|
|
184
218
|
</section>
|
|
185
219
|
))}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type {
|
|
9
|
+
import type { MultiCollectionDefinition } from '@byline/core'
|
|
10
10
|
import type { AnyCollectionSchemaTypes } from '@byline/core/zod-schemas'
|
|
11
11
|
import { useTranslation } from '@byline/i18n/react'
|
|
12
12
|
import { Container, Section } from '@byline/ui/react'
|
|
@@ -32,7 +32,7 @@ export const ApiView = ({
|
|
|
32
32
|
contentLocales,
|
|
33
33
|
defaultContentLocale,
|
|
34
34
|
}: {
|
|
35
|
-
collectionDefinition:
|
|
35
|
+
collectionDefinition: MultiCollectionDefinition
|
|
36
36
|
initialData: AnyCollectionSchemaTypes['UpdateType']
|
|
37
37
|
/** Currently active locale from the route search param (undefined → all). */
|
|
38
38
|
locale?: string
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { useState } from 'react'
|
|
10
10
|
|
|
11
11
|
import { FormRenderer } from '@byline/admin/react'
|
|
12
|
-
import type { CollectionAdminConfig,
|
|
12
|
+
import type { CollectionAdminConfig, MultiCollectionDefinition } from '@byline/core'
|
|
13
13
|
import { useTranslation } from '@byline/i18n/react'
|
|
14
14
|
import { Container, Section, useToastManager } from '@byline/ui/react'
|
|
15
15
|
|
|
@@ -30,7 +30,7 @@ export const CreateView = ({
|
|
|
30
30
|
initialData,
|
|
31
31
|
from,
|
|
32
32
|
}: {
|
|
33
|
-
collectionDefinition:
|
|
33
|
+
collectionDefinition: MultiCollectionDefinition
|
|
34
34
|
adminConfig?: CollectionAdminConfig
|
|
35
35
|
// biome-ignore lint/suspicious/noExplicitAny: shape is collection-specific
|
|
36
36
|
initialData?: Record<string, any>
|
|
@@ -105,6 +105,10 @@ export const CreateView = ({
|
|
|
105
105
|
status: 'failed',
|
|
106
106
|
message: description,
|
|
107
107
|
})
|
|
108
|
+
|
|
109
|
+
// Rethrow so FormRenderer keeps the form dirty. The toast above has
|
|
110
|
+
// already told the editor what went wrong.
|
|
111
|
+
throw err
|
|
108
112
|
}
|
|
109
113
|
}
|
|
110
114
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { useState } from 'react'
|
|
10
10
|
|
|
11
11
|
import { FormRenderer } from '@byline/admin/react'
|
|
12
|
-
import type { CollectionAdminConfig,
|
|
12
|
+
import type { CollectionAdminConfig, MultiCollectionDefinition } from '@byline/core'
|
|
13
13
|
import {
|
|
14
14
|
getDefaultStatus,
|
|
15
15
|
getWorkflow,
|
|
@@ -56,7 +56,7 @@ export const EditView = ({
|
|
|
56
56
|
defaultContentLocale,
|
|
57
57
|
returnSearch,
|
|
58
58
|
}: {
|
|
59
|
-
collectionDefinition:
|
|
59
|
+
collectionDefinition: MultiCollectionDefinition
|
|
60
60
|
adminConfig?: CollectionAdminConfig
|
|
61
61
|
initialData: AnyCollectionSchemaTypes['UpdateType']
|
|
62
62
|
locale?: string
|
|
@@ -623,6 +623,10 @@ export const EditView = ({
|
|
|
623
623
|
})
|
|
624
624
|
|
|
625
625
|
setEditState({ status: 'failed', message: description })
|
|
626
|
+
|
|
627
|
+
// Rethrow so FormRenderer keeps the form dirty. The toast above has
|
|
628
|
+
// already told the editor what went wrong.
|
|
629
|
+
throw err
|
|
626
630
|
}
|
|
627
631
|
}
|
|
628
632
|
|