@antelopejs/dms 0.4.5 → 0.4.6
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/CHANGELOG.md +27 -0
- package/dist/hooks/invite-extensions.js +2 -2
- package/dist/hooks/invite-extensions.js.map +1 -1
- package/dist/implementations/dms/layout-banners.d.ts +18 -0
- package/dist/implementations/dms/layout-banners.js +60 -0
- package/dist/implementations/dms/layout-banners.js.map +1 -0
- package/dist/implementations/dms/page.d.ts +2 -0
- package/dist/implementations/dms/page.js +82 -27
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms/quick-actions.d.ts +5 -0
- package/dist/implementations/dms/quick-actions.js.map +1 -1
- package/dist/implementations/dms/resolver-timeout.d.ts +1 -0
- package/dist/implementations/dms/resolver-timeout.js +13 -0
- package/dist/implementations/dms/resolver-timeout.js.map +1 -0
- package/dist/implementations/dms/saas-mode.d.ts +8 -0
- package/dist/implementations/dms/saas-mode.js +20 -0
- package/dist/implementations/dms/saas-mode.js.map +1 -0
- package/dist/implementations/dms-auth/index.d.ts +2 -1
- package/dist/implementations/dms-auth/index.js +19 -8
- package/dist/implementations/dms-auth/index.js.map +1 -1
- package/dist/implementations/dms-html-render/service-jwt.js +2 -1
- package/dist/implementations/dms-html-render/service-jwt.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/pages/settings/users/index.d.ts +1 -0
- package/dist/pages/settings/users/index.js +1 -0
- package/dist/pages/settings/users/index.js.map +1 -1
- package/dist/pages/settings/users/invite-extension-routes.d.ts +4 -0
- package/dist/pages/settings/users/invite-extension-routes.js +57 -0
- package/dist/pages/settings/users/invite-extension-routes.js.map +1 -0
- package/dist/pages/settings/users/invites.d.ts +2 -1
- package/dist/pages/settings/users/invites.js +17 -5
- package/dist/pages/settings/users/invites.js.map +1 -1
- package/dist/pages/settings/users/members.d.ts +4 -1
- package/dist/pages/settings/users/members.js +10 -6
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.js +1 -1
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/pages/settings/users/quick-actions.d.ts +1 -0
- package/dist/pages/settings/users/quick-actions.js +22 -0
- package/dist/pages/settings/users/quick-actions.js.map +1 -0
- package/dist/routes/auth/signup.d.ts +2 -0
- package/dist/routes/auth/signup.js +7 -2
- package/dist/routes/auth/signup.js.map +1 -1
- package/dist/test/integration/invite-extension-edit.test.d.ts +1 -0
- package/dist/test/integration/invite-extension-edit.test.js +122 -0
- package/dist/test/integration/invite-extension-edit.test.js.map +1 -0
- package/dist/test/integration/saas-mode-ownership.test.d.ts +1 -0
- package/dist/test/integration/saas-mode-ownership.test.js +81 -0
- package/dist/test/integration/saas-mode-ownership.test.js.map +1 -0
- package/dist/test/integration/tenant-lifecycle.test.js +38 -0
- package/dist/test/integration/tenant-lifecycle.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/layout-banners.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms/layout-banners.test.js +217 -0
- package/dist/test/unit/implementations/dms/layout-banners.test.js.map +1 -0
- package/dist/test/unit/implementations/dms/quick-actions-button.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms/quick-actions-button.test.js +220 -0
- package/dist/test/unit/implementations/dms/quick-actions-button.test.js.map +1 -0
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js +1 -1
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js.map +1 -1
- package/dist/test/unit/implementations/dms-html-render/service-jwt.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms-html-render/service-jwt.test.js +37 -0
- package/dist/test/unit/implementations/dms-html-render/service-jwt.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/invite-extension.test.js +138 -12
- package/dist/test/unit/interfaces/dms/invite-extension.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/saas-mode.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/saas-mode.test.js +42 -0
- package/dist/test/unit/interfaces/dms/saas-mode.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/tenant-invite-email.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/tenant-invite-email.test.js +53 -0
- package/dist/test/unit/interfaces/dms/tenant-invite-email.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.js +80 -0
- package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.js.map +1 -0
- package/dist/test/unit/pages/settings/invite-member-quick-action.test.d.ts +1 -0
- package/dist/test/unit/pages/settings/invite-member-quick-action.test.js +104 -0
- package/dist/test/unit/pages/settings/invite-member-quick-action.test.js.map +1 -0
- package/dist/test/unit/pages/settings/notifications-access-gate.test.d.ts +1 -0
- package/dist/test/unit/pages/settings/notifications-access-gate.test.js +78 -0
- package/dist/test/unit/pages/settings/notifications-access-gate.test.js.map +1 -0
- package/dist/test/unit/signup-account-guard.test.js +20 -2
- package/dist/test/unit/signup-account-guard.test.js.map +1 -1
- package/dist/test/unit/utils/admin-invite-email.test.d.ts +1 -0
- package/dist/test/unit/utils/admin-invite-email.test.js +69 -0
- package/dist/test/unit/utils/admin-invite-email.test.js.map +1 -0
- package/dist/utils/admin-invite-email.d.ts +14 -0
- package/dist/utils/admin-invite-email.js +51 -0
- package/dist/utils/admin-invite-email.js.map +1 -0
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/validation/auth.schema.d.ts +3 -3
- package/dist/validation/auth.schema.js +1 -1
- package/dist/validation/auth.schema.js.map +1 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +37 -8
- package/frontend-vue/layers/dms-auth/i18n/locales/auth-en-GB.json +3 -1
- package/frontend-vue/layers/dms-auth/i18n/locales/auth-fr-FR.json +3 -1
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +50 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +16 -13
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +2 -2
- package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +11 -5
- package/frontend-vue/layers/dms-layout/app/build/components/notification/widgetRequest.ts +20 -0
- package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +59 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/useQuickActions.ts +15 -2
- package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +2 -0
- package/frontend-vue/layers/dms-layout/app/emails/EmailAdminInvite.vue +54 -9
- package/frontend-vue/layers/dms-layout/app/error.vue +17 -2
- package/frontend-vue/layers/dms-layout/app/types/page.ts +22 -0
- package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +48 -0
- package/frontend-vue/layers/dms-layout/app/utils/favorites.ts +94 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +21 -2
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +21 -2
- package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/index.ts +1 -1
- package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/{useOnboardingMiddleware.ts → useOnboarding.ts} +0 -12
- package/frontend-vue/layers/dms-onboarding/app/custom-pages/onboarding.vue +4 -1
- package/frontend-vue/layers/dms-onboarding/app/middleware/onboarding.global.ts +21 -4
- package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +13 -3
- package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +13 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewConfig.ts +2 -7
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/columnVisibility.ts +13 -0
- package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/activity/ActivityItem.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/banner/Banner.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/card/NavCard.vue +2 -2
- package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +6 -16
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +2 -2
- package/frontend-vue/layers/dms-ui/app/components/chart/internal/TrendBadge.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/copy/CopyButton.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +3 -3
- package/frontend-vue/layers/dms-ui/app/components/master-detail/MasterDetail.vue +5 -1
- package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/status/StatusSummary.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +33 -10
- package/frontend-vue/layers/dms-ui/app/composables/chart/donutRecords.ts +26 -0
- package/frontend-vue/layers/dms-ui/app/composables/tab/types/props.ts +4 -2
- package/frontend-vue/layers/dms-ui/app/composables/tab/useTab.ts +3 -3
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/column.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/custom-button.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/types/quick-actions.ts +38 -3
- package/frontend-vue/tests/admin-invite-email.test.ts +86 -0
- package/frontend-vue/tests/breadcrumb-target.test.ts +50 -0
- package/frontend-vue/tests/chart-nested-donut.test.ts +97 -0
- package/frontend-vue/tests/error-page-message.test.ts +83 -0
- package/frontend-vue/tests/favorite-page.test.ts +104 -0
- package/frontend-vue/tests/icon-aria-hidden.test.ts +29 -0
- package/frontend-vue/tests/layout-banners.test.ts +224 -0
- package/frontend-vue/tests/notification-popover-errors.test.ts +147 -0
- package/frontend-vue/tests/onboarding-middleware.test.ts +103 -0
- package/frontend-vue/tests/password-strength-meter.test.ts +33 -0
- package/frontend-vue/tests/quick-action-intent.test.ts +74 -0
- package/frontend-vue/tests/quick-actions.test.ts +23 -0
- package/frontend-vue/tests/signup-invitation-link.test.ts +126 -0
- package/frontend-vue/tests/skeleton-paragraph.test.ts +30 -0
- package/frontend-vue/tests/tab-without-component-id.test.ts +51 -0
- package/frontend-vue/tests/table-column-visibility.test.ts +20 -0
- package/frontend-vue/tests/vue-inventory.test.ts +1 -1
- package/package.json +2 -2
- package/skills/dms/REFERENCE.md +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { PageValidation } from "../types/page";
|
|
2
|
+
|
|
3
|
+
export interface BreadcrumbTargetSource {
|
|
4
|
+
layoutUrl?: string;
|
|
5
|
+
validation?: PageValidation;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The URL that opens the page at `path`. A page that declares
|
|
10
|
+
* `validation.requiredQueryParams` 404s without them, so the URL carries the
|
|
11
|
+
* values `currentQuery` holds for them — and only those — and there is no URL
|
|
12
|
+
* when any is missing, empty or repeated.
|
|
13
|
+
*/
|
|
14
|
+
export function buildPageTarget(
|
|
15
|
+
path: string,
|
|
16
|
+
validation: PageValidation | undefined,
|
|
17
|
+
currentQuery: Record<string, unknown>,
|
|
18
|
+
): string | undefined {
|
|
19
|
+
const requiredParams = validation?.requiredQueryParams ?? [];
|
|
20
|
+
const carriedQuery = new URLSearchParams();
|
|
21
|
+
|
|
22
|
+
for (const param of requiredParams) {
|
|
23
|
+
const value = currentQuery[param];
|
|
24
|
+
if (typeof value !== "string" || value === "") {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
carriedQuery.set(param, value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const search = carriedQuery.toString();
|
|
31
|
+
return search ? `${path}?${search}` : path;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The link of a breadcrumb crumb: the page's {@link buildPageTarget}.
|
|
36
|
+
* Categories without a page behind them get no link.
|
|
37
|
+
*/
|
|
38
|
+
export function buildBreadcrumbTarget(
|
|
39
|
+
path: string,
|
|
40
|
+
metadata: BreadcrumbTargetSource,
|
|
41
|
+
currentQuery: Record<string, unknown>,
|
|
42
|
+
): string | undefined {
|
|
43
|
+
if (!metadata.layoutUrl) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return buildPageTarget(path, metadata.validation, currentQuery);
|
|
48
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { getQuery, parseURL } from "ufo";
|
|
2
|
+
import type { FavoritePage } from "../composables/favorites/useFavoritePages";
|
|
3
|
+
import type { PageValidation } from "../types/page";
|
|
4
|
+
import { buildPageTarget } from "./breadcrumb";
|
|
5
|
+
|
|
6
|
+
const FAVORITE_TITLE_SEPARATOR = " · ";
|
|
7
|
+
|
|
8
|
+
export interface FavoritePageMetadata {
|
|
9
|
+
id?: string;
|
|
10
|
+
displayName?: string;
|
|
11
|
+
icon?: string;
|
|
12
|
+
validation?: PageValidation;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface FavoritePageSource {
|
|
16
|
+
path: string;
|
|
17
|
+
metadata: FavoritePageMetadata;
|
|
18
|
+
query: Record<string, unknown>;
|
|
19
|
+
translate: (text: string) => string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type FavoriteRouteResolver = (
|
|
23
|
+
path: string,
|
|
24
|
+
) => { metadata: FavoritePageMetadata } | null;
|
|
25
|
+
|
|
26
|
+
function requiredQueryValues(
|
|
27
|
+
validation: PageValidation | undefined,
|
|
28
|
+
query: Record<string, unknown>,
|
|
29
|
+
): string[] {
|
|
30
|
+
return (validation?.requiredQueryParams ?? []).map((param) =>
|
|
31
|
+
String(query[param]),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// A page narrowed by its query shows one entity out of many: its generic
|
|
36
|
+
// display name would label every favorite of it the same way.
|
|
37
|
+
function entityTitle(pageTitle: string, queryValues: string[]): string {
|
|
38
|
+
return [pageTitle, ...queryValues].join(FAVORITE_TITLE_SEPARATOR);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The favorite that reopens the page at `path` as it is showing now. A page
|
|
43
|
+
* requiring query parameters keeps them in its path and gets a title telling
|
|
44
|
+
* its entities apart; there is no favorite when a required value is missing.
|
|
45
|
+
*/
|
|
46
|
+
export function buildFavoritePage(
|
|
47
|
+
source: FavoritePageSource,
|
|
48
|
+
): FavoritePage | null {
|
|
49
|
+
const { path, metadata, query } = source;
|
|
50
|
+
const target = buildPageTarget(path, metadata.validation, query);
|
|
51
|
+
if (!target) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const displayName = metadata.displayName || path;
|
|
56
|
+
const queryValues = requiredQueryValues(metadata.validation, query);
|
|
57
|
+
if (queryValues.length === 0) {
|
|
58
|
+
return {
|
|
59
|
+
id: metadata.id || path,
|
|
60
|
+
path,
|
|
61
|
+
title: displayName,
|
|
62
|
+
icon: metadata.icon,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
id: target,
|
|
68
|
+
path: target,
|
|
69
|
+
title: entityTitle(source.translate(displayName), queryValues),
|
|
70
|
+
icon: metadata.icon,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Whether a stored favorite still opens a page: its path matches one, and its
|
|
76
|
+
* query holds every value that page requires.
|
|
77
|
+
*/
|
|
78
|
+
export function isFavoritePathValid(
|
|
79
|
+
favoritePath: string,
|
|
80
|
+
resolveRoute: FavoriteRouteResolver,
|
|
81
|
+
): boolean {
|
|
82
|
+
const { pathname } = parseURL(favoritePath);
|
|
83
|
+
const route = resolveRoute(pathname);
|
|
84
|
+
if (!route) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return (
|
|
88
|
+
buildPageTarget(
|
|
89
|
+
pathname,
|
|
90
|
+
route.metadata.validation,
|
|
91
|
+
getQuery(favoritePath),
|
|
92
|
+
) !== undefined
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -40,7 +40,9 @@
|
|
|
40
40
|
"soon": "Coming soon"
|
|
41
41
|
},
|
|
42
42
|
"quickActions": {
|
|
43
|
-
"title": "Quick actions"
|
|
43
|
+
"title": "Quick actions",
|
|
44
|
+
"category_members": "Members",
|
|
45
|
+
"invite_member": "Invite a member"
|
|
44
46
|
},
|
|
45
47
|
"commandPalette": {
|
|
46
48
|
"button": "Search…",
|
|
@@ -218,7 +220,8 @@
|
|
|
218
220
|
"cancel_confirm_description": "Are you sure you want to cancel this invitation? The invite link will no longer work."
|
|
219
221
|
},
|
|
220
222
|
"error": {
|
|
221
|
-
"not_found": "Invitation not found"
|
|
223
|
+
"not_found": "Invitation not found",
|
|
224
|
+
"resolved": "This invitation has already been accepted or cancelled"
|
|
222
225
|
}
|
|
223
226
|
},
|
|
224
227
|
"roles": {
|
|
@@ -269,5 +272,21 @@
|
|
|
269
272
|
"intro": "Browse the modules available on this platform.",
|
|
270
273
|
"empty": "No modules are accessible to you yet.",
|
|
271
274
|
"open": "Open module"
|
|
275
|
+
},
|
|
276
|
+
"emails": {
|
|
277
|
+
"admin_invite": {
|
|
278
|
+
"title": "You're Invited!",
|
|
279
|
+
"title_workspace": "You're invited to {workspace}",
|
|
280
|
+
"greeting": "Hello,",
|
|
281
|
+
"invitation": "You've been invited to join our platform.",
|
|
282
|
+
"invitation_to_target": "You've been invited to join {target}.",
|
|
283
|
+
"invitation_from_inviter": "{inviter} invited you to join {target}.",
|
|
284
|
+
"workspace_on_platform": "{workspace} on {platform}",
|
|
285
|
+
"call_to_action": "Click the button below to create your account:",
|
|
286
|
+
"button": "Create Account",
|
|
287
|
+
"expires_label": "This invitation expires in",
|
|
288
|
+
"expires_in": "{days} days",
|
|
289
|
+
"footer": "If you didn't expect this invitation, you can safely ignore this email."
|
|
290
|
+
}
|
|
272
291
|
}
|
|
273
292
|
}
|
|
@@ -40,7 +40,9 @@
|
|
|
40
40
|
"soon": "Bientôt disponible"
|
|
41
41
|
},
|
|
42
42
|
"quickActions": {
|
|
43
|
-
"title": "Actions rapides"
|
|
43
|
+
"title": "Actions rapides",
|
|
44
|
+
"category_members": "Membres",
|
|
45
|
+
"invite_member": "Inviter un membre"
|
|
44
46
|
},
|
|
45
47
|
"commandPalette": {
|
|
46
48
|
"button": "Rechercher…",
|
|
@@ -222,7 +224,8 @@
|
|
|
222
224
|
"cancel_confirm_description": "Êtes-vous sûr de vouloir annuler cette invitation ? Le lien d'invitation ne fonctionnera plus."
|
|
223
225
|
},
|
|
224
226
|
"error": {
|
|
225
|
-
"not_found": "Invitation introuvable"
|
|
227
|
+
"not_found": "Invitation introuvable",
|
|
228
|
+
"resolved": "Cette invitation a déjà été acceptée ou annulée"
|
|
226
229
|
}
|
|
227
230
|
},
|
|
228
231
|
"roles": {
|
|
@@ -273,5 +276,21 @@
|
|
|
273
276
|
"intro": "Parcourez les modules disponibles sur cette plateforme.",
|
|
274
277
|
"empty": "Aucun module ne vous est accessible pour le moment.",
|
|
275
278
|
"open": "Ouvrir le module"
|
|
279
|
+
},
|
|
280
|
+
"emails": {
|
|
281
|
+
"admin_invite": {
|
|
282
|
+
"title": "Vous êtes invité !",
|
|
283
|
+
"title_workspace": "Vous êtes invité à rejoindre {workspace}",
|
|
284
|
+
"greeting": "Bonjour,",
|
|
285
|
+
"invitation": "Vous avez été invité à rejoindre notre plateforme.",
|
|
286
|
+
"invitation_to_target": "Vous avez été invité à rejoindre {target}.",
|
|
287
|
+
"invitation_from_inviter": "{inviter} vous invite à rejoindre {target}.",
|
|
288
|
+
"workspace_on_platform": "{workspace} sur {platform}",
|
|
289
|
+
"call_to_action": "Cliquez sur le bouton ci-dessous pour créer votre compte :",
|
|
290
|
+
"button": "Créer mon compte",
|
|
291
|
+
"expires_label": "Cette invitation expire dans",
|
|
292
|
+
"expires_in": "{days} jours",
|
|
293
|
+
"footer": "Si vous n'attendiez pas cette invitation, vous pouvez ignorer cet e-mail."
|
|
294
|
+
}
|
|
276
295
|
}
|
|
277
296
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./
|
|
1
|
+
export * from "./useOnboarding";
|
|
@@ -37,15 +37,3 @@ export const setOnboardingComplete = () => {
|
|
|
37
37
|
);
|
|
38
38
|
cache.value = { hasOnboarded: true };
|
|
39
39
|
};
|
|
40
|
-
|
|
41
|
-
export const useOnboardingMiddleware = async () => {
|
|
42
|
-
const { hasOnboarded } = await useOnboarding();
|
|
43
|
-
|
|
44
|
-
if (!hasOnboarded) return;
|
|
45
|
-
|
|
46
|
-
throw createError({
|
|
47
|
-
statusCode: HTTP_PRECONDITION_FAILED,
|
|
48
|
-
statusMessage: "Invalid request",
|
|
49
|
-
message: "Onboarding already completed",
|
|
50
|
-
});
|
|
51
|
-
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import Register from "../components/onboarding/steps/register.vue";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// A custom page runs the global middleware only once it declares its meta:
|
|
5
|
+
// that is what lets the onboarding middleware send a visitor away from a
|
|
6
|
+
// wizard that is already finished, on a direct load as on a navigation.
|
|
7
|
+
defineDmsPageMeta({});
|
|
5
8
|
</script>
|
|
6
9
|
|
|
7
10
|
<template>
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
const ONBOARDING_PATH = "/onboarding";
|
|
2
|
+
const LOGIN_PATH = "/auth";
|
|
3
|
+
|
|
4
|
+
// Where a visit to the finished wizard lands: the app for a signed-in user, the
|
|
5
|
+
// login page for anyone else rather than a homepage whose 401 would bounce
|
|
6
|
+
// them there anyway.
|
|
7
|
+
function completedOnboardingDestination(): string {
|
|
8
|
+
const { loggedIn } = useUserSession();
|
|
9
|
+
return loggedIn.value ? useHomepage() : LOGIN_PATH;
|
|
10
|
+
}
|
|
11
|
+
|
|
1
12
|
export default defineDmsMiddleware(async (to) => {
|
|
2
|
-
|
|
3
|
-
if (to.path.startsWith("/onboarding")) return;
|
|
13
|
+
const isOnboardingRoute = to.path.startsWith(ONBOARDING_PATH);
|
|
4
14
|
|
|
5
15
|
// While the instance has no admin yet, funnel every route to the onboarding
|
|
6
16
|
// wizard. Running as a global middleware (instead of a plugin) lets this take
|
|
@@ -11,11 +21,18 @@ export default defineDmsMiddleware(async (to) => {
|
|
|
11
21
|
try {
|
|
12
22
|
info = await useOnboarding();
|
|
13
23
|
} catch {
|
|
14
|
-
// If the onboarding status can't be resolved, don't block navigation
|
|
24
|
+
// If the onboarding status can't be resolved, don't block navigation: the
|
|
25
|
+
// wizard stays reachable, and it is its own register call that refuses a
|
|
26
|
+
// second admin.
|
|
15
27
|
return;
|
|
16
28
|
}
|
|
17
29
|
|
|
30
|
+
if (isOnboardingRoute) {
|
|
31
|
+
if (!info.hasOnboarded) return;
|
|
32
|
+
return navigateDms(completedOnboardingDestination(), { replace: true });
|
|
33
|
+
}
|
|
34
|
+
|
|
18
35
|
if (info.hasOnboarded) return;
|
|
19
36
|
|
|
20
|
-
return navigateDms(
|
|
37
|
+
return navigateDms(ONBOARDING_PATH);
|
|
21
38
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { Component } from "vue";
|
|
3
|
+
import { DialogDescription, DialogTitle, VisuallyHidden } from "reka-ui";
|
|
3
4
|
|
|
4
5
|
type DrawerDirection = "top" | "bottom" | "left" | "right";
|
|
5
6
|
|
|
@@ -82,15 +83,24 @@ function onContentResolve() {
|
|
|
82
83
|
<!-- Same container as the body so the title aligns with the form -->
|
|
83
84
|
<UContainer class="flex w-full flex-1 items-center justify-between gap-4">
|
|
84
85
|
<div v-if="headerComponent" class="flex-1">
|
|
86
|
+
<VisuallyHidden>
|
|
87
|
+
<DialogTitle>{{ title }}</DialogTitle>
|
|
88
|
+
<DialogDescription>{{ description }}</DialogDescription>
|
|
89
|
+
</VisuallyHidden>
|
|
85
90
|
<Suspense>
|
|
86
91
|
<component :is="headerComponent" v-bind="headerComponentOptions" />
|
|
87
92
|
</Suspense>
|
|
88
93
|
</div>
|
|
89
94
|
<div v-else class="flex-1">
|
|
90
|
-
<h2 class="text-highlighted font-semibold">
|
|
91
|
-
|
|
95
|
+
<DialogTitle as="h2" class="text-highlighted font-semibold">
|
|
96
|
+
{{ title }}
|
|
97
|
+
</DialogTitle>
|
|
98
|
+
<DialogDescription v-if="description" class="text-muted text-sm">
|
|
92
99
|
{{ description }}
|
|
93
|
-
</
|
|
100
|
+
</DialogDescription>
|
|
101
|
+
<VisuallyHidden v-else>
|
|
102
|
+
<DialogDescription />
|
|
103
|
+
</VisuallyHidden>
|
|
94
104
|
</div>
|
|
95
105
|
<UButton
|
|
96
106
|
icon="i-lucide-x"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { Component } from "vue";
|
|
3
|
+
import { DialogDescription, DialogTitle, VisuallyHidden } from "reka-ui";
|
|
3
4
|
import type { ModalSize } from "../../../../types/modal";
|
|
4
5
|
|
|
5
6
|
interface DynamicModalProps {
|
|
@@ -98,15 +99,24 @@ function onContentResolve() {
|
|
|
98
99
|
<template #header>
|
|
99
100
|
<div class="flex w-full items-center justify-between gap-4">
|
|
100
101
|
<div v-if="headerComponent" class="flex-1">
|
|
102
|
+
<VisuallyHidden>
|
|
103
|
+
<DialogTitle>{{ title }}</DialogTitle>
|
|
104
|
+
<DialogDescription>{{ description }}</DialogDescription>
|
|
105
|
+
</VisuallyHidden>
|
|
101
106
|
<Suspense>
|
|
102
107
|
<component :is="headerComponent" v-bind="headerComponentOptions" />
|
|
103
108
|
</Suspense>
|
|
104
109
|
</div>
|
|
105
110
|
<div v-else class="flex-1">
|
|
106
|
-
<h2 class="text-highlighted font-semibold">
|
|
107
|
-
|
|
111
|
+
<DialogTitle as="h2" class="text-highlighted font-semibold">
|
|
112
|
+
{{ title }}
|
|
113
|
+
</DialogTitle>
|
|
114
|
+
<DialogDescription v-if="description" class="text-muted text-sm">
|
|
108
115
|
{{ description }}
|
|
109
|
-
</
|
|
116
|
+
</DialogDescription>
|
|
117
|
+
<VisuallyHidden v-else>
|
|
118
|
+
<DialogDescription />
|
|
119
|
+
</VisuallyHidden>
|
|
110
120
|
</div>
|
|
111
121
|
<UButton
|
|
112
122
|
icon="i-lucide-x"
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
KANBAN_DISPLAY_ID,
|
|
9
9
|
} from "../../../composables/table-view/types";
|
|
10
10
|
import type { FormContainerType } from "./types";
|
|
11
|
+
import { buildInitialColumnVisibility } from "./utils/columnVisibility";
|
|
11
12
|
|
|
12
13
|
const BUILTIN_DISPLAYS: Record<
|
|
13
14
|
string,
|
|
@@ -110,13 +111,7 @@ export const useTableViewConfig = <T extends Data>(
|
|
|
110
111
|
|
|
111
112
|
const defaultDisplay = config.defaultDisplay ?? TABLE_DISPLAY_ID;
|
|
112
113
|
|
|
113
|
-
const initialVisibility = config.columns
|
|
114
|
-
(acc, column) => {
|
|
115
|
-
acc[column.id] = column.visible ?? true;
|
|
116
|
-
return acc;
|
|
117
|
-
},
|
|
118
|
-
{} as Record<string, boolean>,
|
|
119
|
-
);
|
|
114
|
+
const initialVisibility = buildInitialColumnVisibility(config.columns ?? []);
|
|
120
115
|
|
|
121
116
|
const tableProps = computed<Partial<TableProps<T>>>(() => ({
|
|
122
117
|
caption: processI18n(config.caption ?? ""),
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { VisibilityState } from "@tanstack/vue-table";
|
|
2
|
+
import type { TableViewColumn } from "../../../../composables/table-view/types/column";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The visibility a table opens with before the user picks columns: a column
|
|
6
|
+
* declared `isVisible: false` starts hidden but stays in the column picker.
|
|
7
|
+
*/
|
|
8
|
+
export const buildInitialColumnVisibility = (
|
|
9
|
+
columns: Pick<TableViewColumn, "id" | "isVisible">[],
|
|
10
|
+
): VisibilityState =>
|
|
11
|
+
Object.fromEntries(
|
|
12
|
+
columns.map((column) => [column.id, column.isVisible ?? true]),
|
|
13
|
+
);
|
|
@@ -35,7 +35,7 @@ const bubbleClass = computed(() => SOFT_TINT[props.iconColor]);
|
|
|
35
35
|
:class="bubbleClass"
|
|
36
36
|
class="grid size-[26px] shrink-0 place-items-center rounded-full"
|
|
37
37
|
>
|
|
38
|
-
<UIcon :name="icon" class="size-3.5" aria-hidden="true" />
|
|
38
|
+
<UIcon :name="icon" class="size-3.5" :aria-hidden="true" />
|
|
39
39
|
</div>
|
|
40
40
|
|
|
41
41
|
<div class="min-w-0 flex-1">
|
|
@@ -24,7 +24,7 @@ defineProps<Props>();
|
|
|
24
24
|
<div
|
|
25
25
|
class="bg-primary/10 ring-primary/20 group-hover:bg-primary/15 flex size-10 shrink-0 items-center justify-center rounded-lg ring transition-colors"
|
|
26
26
|
>
|
|
27
|
-
<UIcon :name="icon" class="text-primary size-5" aria-hidden="true" />
|
|
27
|
+
<UIcon :name="icon" class="text-primary size-5" :aria-hidden="true" />
|
|
28
28
|
</div>
|
|
29
29
|
|
|
30
30
|
<h3 class="text-highlighted min-w-0 truncate text-base font-semibold">
|
|
@@ -34,7 +34,7 @@ defineProps<Props>();
|
|
|
34
34
|
<UIcon
|
|
35
35
|
name="i-ph-caret-right"
|
|
36
36
|
class="text-dimmed ml-auto size-4 shrink-0 opacity-0 transition-all group-hover:translate-x-0.5 group-hover:opacity-100"
|
|
37
|
-
aria-hidden="true"
|
|
37
|
+
:aria-hidden="true"
|
|
38
38
|
/>
|
|
39
39
|
</div>
|
|
40
40
|
|
|
@@ -5,6 +5,7 @@ import type { MixedSeriesDef } from "../../composables/chart/useApexChart.types"
|
|
|
5
5
|
import { useChartFetch } from "../../composables/chart/useChartFetch";
|
|
6
6
|
import { useThemeRevision } from "../../composables/chart/useThemeRevision";
|
|
7
7
|
import { formatValue } from "../../composables/chart/formatValue";
|
|
8
|
+
import { toDonutRecords } from "../../composables/chart/donutRecords";
|
|
8
9
|
import {
|
|
9
10
|
CHART_EVENT_NAMES,
|
|
10
11
|
isSegmentChart,
|
|
@@ -144,24 +145,13 @@ const finalSeries = computed<ChartSeries[]>(() => {
|
|
|
144
145
|
return staticSeries.value;
|
|
145
146
|
});
|
|
146
147
|
|
|
148
|
+
// A donut plots the first series only; a nested one plots its card's, like
|
|
149
|
+
// every other nested chart type.
|
|
147
150
|
const finalDonutData = computed<DonutRecord[]>(() => {
|
|
148
151
|
if (!isCircular.value) return [];
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return first.data.map((entry) => {
|
|
153
|
-
if (typeof entry === "number") {
|
|
154
|
-
return { label: String(entry), value: entry };
|
|
155
|
-
}
|
|
156
|
-
const point = entry as { label?: string; x?: unknown; y?: unknown };
|
|
157
|
-
return {
|
|
158
|
-
label: point.label ?? String(point.x ?? ""),
|
|
159
|
-
value: Number(point.y ?? 0),
|
|
160
|
-
};
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return staticDonut.value;
|
|
152
|
+
const source = nestedContext ? nestedContext.cardSeries : fetchSeries.value;
|
|
153
|
+
const records = toDonutRecords(source[0]);
|
|
154
|
+
return records.length > 0 ? records : staticDonut.value;
|
|
165
155
|
});
|
|
166
156
|
|
|
167
157
|
function buildCircularClickPayload(opts: {
|
|
@@ -155,10 +155,10 @@ const primaryDotColor = computed(() => resolveChartColor("primary"));
|
|
|
155
155
|
<div class="mb-4 flex flex-wrap items-start justify-between gap-3">
|
|
156
156
|
<div class="min-w-0">
|
|
157
157
|
<p class="text-muted text-sm">{{ processI18n(title) }}</p>
|
|
158
|
-
<
|
|
158
|
+
<div class="text-2xl font-semibold tracking-tight tabular-nums">
|
|
159
159
|
<USkeleton v-if="isLoading && data === null" class="h-8 w-32" />
|
|
160
160
|
<span v-else>{{ formatted }}</span>
|
|
161
|
-
</
|
|
161
|
+
</div>
|
|
162
162
|
<div
|
|
163
163
|
v-if="
|
|
164
164
|
(showDelta && delta !== null) || previousFormatted || description
|
|
@@ -51,7 +51,7 @@ const display = computed(() => {
|
|
|
51
51
|
class="inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs font-medium whitespace-nowrap tabular-nums transition-colors"
|
|
52
52
|
:class="colorClass"
|
|
53
53
|
>
|
|
54
|
-
<UIcon :name="icon" class="size-3" aria-hidden="true" />
|
|
54
|
+
<UIcon :name="icon" class="size-3" :aria-hidden="true" />
|
|
55
55
|
{{ display }}
|
|
56
56
|
</span>
|
|
57
57
|
</template>
|
|
@@ -118,7 +118,7 @@ const iconWrapClass = computed(() =>
|
|
|
118
118
|
<div class="flex items-start justify-between gap-3">
|
|
119
119
|
<div class="min-w-0 flex-1">
|
|
120
120
|
<p :class="labelClass">{{ processI18n(title) }}</p>
|
|
121
|
-
<
|
|
121
|
+
<div class="mt-1 text-2xl font-semibold tracking-tight tabular-nums">
|
|
122
122
|
<USkeleton v-if="isLoading && data === null" class="h-8 w-24" />
|
|
123
123
|
<span v-else class="inline-flex items-baseline gap-x-1">
|
|
124
124
|
<span
|
|
@@ -135,7 +135,7 @@ const iconWrapClass = computed(() =>
|
|
|
135
135
|
{{ formattedParts.unit }}
|
|
136
136
|
</span>
|
|
137
137
|
</span>
|
|
138
|
-
</
|
|
138
|
+
</div>
|
|
139
139
|
<div
|
|
140
140
|
v-if="showTrend || compareLabel || description"
|
|
141
141
|
class="mt-2 flex min-w-0 items-center gap-x-2"
|
|
@@ -153,7 +153,7 @@ const iconWrapClass = computed(() =>
|
|
|
153
153
|
<UIcon
|
|
154
154
|
:name="resolvedIcon"
|
|
155
155
|
:class="isStat ? 'size-4' : 'size-5'"
|
|
156
|
-
aria-hidden="true"
|
|
156
|
+
:aria-hidden="true"
|
|
157
157
|
/>
|
|
158
158
|
</div>
|
|
159
159
|
</div>
|
|
@@ -49,7 +49,11 @@ defineSlots<{
|
|
|
49
49
|
class="grid size-[26px] shrink-0 place-items-center"
|
|
50
50
|
:class="selected === item.value ? 'text-primary' : 'text-toned'"
|
|
51
51
|
>
|
|
52
|
-
<UIcon
|
|
52
|
+
<UIcon
|
|
53
|
+
:name="item.icon"
|
|
54
|
+
class="size-[18px]"
|
|
55
|
+
:aria-hidden="true"
|
|
56
|
+
/>
|
|
53
57
|
</span>
|
|
54
58
|
<span class="min-w-0">
|
|
55
59
|
<span class="text-highlighted block text-sm font-semibold">
|
|
@@ -68,7 +68,7 @@ const resolvedIcon = computed(() => props.icon || DEFAULT_ICON[props.status]);
|
|
|
68
68
|
:class="RING[status]"
|
|
69
69
|
class="grid size-16 shrink-0 place-items-center rounded-full border-2"
|
|
70
70
|
>
|
|
71
|
-
<UIcon :name="resolvedIcon" class="size-[30px]" aria-hidden="true" />
|
|
71
|
+
<UIcon :name="resolvedIcon" class="size-[30px]" :aria-hidden="true" />
|
|
72
72
|
</div>
|
|
73
73
|
<div>
|
|
74
74
|
<p
|