includio-cms 0.1.4 → 0.5.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/CHANGELOG.md +68 -0
- package/ROADMAP.md +18 -13
- package/dist/admin/api/accept-invite.js +1 -5
- package/dist/admin/api/invite.js +7 -16
- package/dist/admin/client/account/account-page.svelte +20 -50
- package/dist/admin/client/account/lang.d.ts +15 -23
- package/dist/admin/client/account/lang.js +51 -67
- package/dist/admin/client/account/preferences-section.svelte +26 -84
- package/dist/admin/client/account/profile-section.svelte +60 -40
- package/dist/admin/client/account/schema.d.ts +11 -3
- package/dist/admin/client/account/schema.js +25 -16
- package/dist/admin/client/account/security-section.svelte +139 -105
- package/dist/admin/client/account/sessions-section.svelte +35 -34
- package/dist/admin/client/admin/admin-after-login-layout-content.svelte +3 -5
- package/dist/admin/client/admin/admin-layout.svelte +3 -2
- package/dist/admin/client/admin/admin-preloader.svelte +36 -0
- package/dist/admin/client/admin/admin-preloader.svelte.d.ts +18 -0
- package/dist/admin/client/admin/dashboard-page.svelte +55 -41
- package/dist/admin/client/collection/a11y-score-cell.svelte +45 -0
- package/dist/admin/client/collection/a11y-score-cell.svelte.d.ts +6 -0
- package/dist/admin/client/collection/bulk-actions-bar.svelte +83 -0
- package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +9 -0
- package/dist/admin/client/collection/collection-entries.svelte +255 -260
- package/dist/admin/client/collection/collection-view.svelte.d.ts +4 -3
- package/dist/admin/client/collection/collection-view.svelte.js +9 -5
- package/dist/admin/client/collection/collection.svelte +22 -12
- package/dist/admin/client/collection/data-table.svelte +50 -39
- package/dist/admin/client/collection/data-table.svelte.d.ts +1 -0
- package/dist/admin/client/collection/date-cell.svelte +7 -5
- package/dist/admin/client/collection/date-cell.svelte.d.ts +1 -1
- package/dist/admin/client/collection/empty-state.svelte +28 -0
- package/dist/admin/client/collection/empty-state.svelte.d.ts +9 -0
- package/dist/admin/client/collection/entry-link.svelte +10 -4
- package/dist/admin/client/collection/entry-link.svelte.d.ts +1 -0
- package/dist/admin/client/collection/grid-view.svelte +21 -23
- package/dist/admin/client/collection/grid-view.svelte.d.ts +1 -2
- package/dist/admin/client/collection/row-actions.svelte +60 -0
- package/dist/admin/client/collection/row-actions.svelte.d.ts +9 -0
- package/dist/admin/client/collection/status-badge.svelte +7 -8
- package/dist/admin/client/collection/table-pagination.svelte +122 -79
- package/dist/admin/client/collection/table-pagination.svelte.d.ts +1 -0
- package/dist/admin/client/collection/table-toolbar.svelte +108 -88
- package/dist/admin/client/collection/table-toolbar.svelte.d.ts +8 -9
- package/dist/admin/client/entry/entry-form.svelte +109 -1
- package/dist/admin/client/entry/entry-header.svelte +96 -37
- package/dist/admin/client/entry/entry-header.svelte.d.ts +5 -0
- package/dist/admin/client/entry/entry.svelte +171 -60
- package/dist/admin/client/entry/header/a11y-validator.d.ts +46 -0
- package/dist/admin/client/entry/header/a11y-validator.js +311 -0
- package/dist/admin/client/entry/header/publish-panel.svelte +373 -131
- package/dist/admin/client/entry/header/publish-panel.svelte.d.ts +4 -0
- package/dist/admin/client/entry/header/save-indicator.svelte +33 -23
- package/dist/admin/client/entry/header/schedule-popover.svelte +1 -1
- package/dist/admin/client/entry/header/status-badge.svelte +25 -118
- package/dist/admin/client/entry/header/version-history-sheet.svelte +314 -98
- package/dist/admin/client/form/form-submission/form-submission.svelte +271 -83
- package/dist/admin/client/form/form-submission/submission-field.svelte +12 -12
- package/dist/admin/client/form/form-submissions.svelte +421 -139
- package/dist/admin/client/form/submission-link.svelte +8 -2
- package/dist/admin/client/form/submission-link.svelte.d.ts +1 -0
- package/dist/admin/client/form/submission-status-badge.svelte +18 -4
- package/dist/admin/client/form/submission-status-badge.svelte.d.ts +1 -0
- package/dist/admin/client/login/lang.d.ts +32 -0
- package/dist/admin/client/login/lang.js +66 -2
- package/dist/admin/client/login/login-form.svelte +237 -95
- package/dist/admin/client/login/login-form.svelte.d.ts +2 -17
- package/dist/admin/client/login/login-page.svelte +34 -98
- package/dist/admin/client/login/reset-password-page.svelte +235 -0
- package/dist/admin/client/login/reset-password-page.svelte.d.ts +4 -0
- package/dist/admin/client/login/schema.d.ts +15 -0
- package/dist/admin/client/login/schema.js +21 -0
- package/dist/admin/client/users/accept-invite-page.svelte +166 -37
- package/dist/admin/client/users/create-user-dialog.svelte +15 -7
- package/dist/admin/client/users/delete-user-dialog.svelte +81 -16
- package/dist/admin/client/users/delete-user-dialog.svelte.d.ts +4 -1
- package/dist/admin/client/users/edit-user-dialog.svelte +3 -0
- package/dist/admin/client/users/invite-user-dialog.svelte +16 -3
- package/dist/admin/client/users/lang.d.ts +27 -0
- package/dist/admin/client/users/lang.js +64 -10
- package/dist/admin/client/users/pending-invitations.svelte +59 -23
- package/dist/admin/client/users/users-page.svelte +471 -72
- package/dist/admin/components/accessibility/accessibility-overview.svelte +2 -7
- package/dist/admin/components/dashboard/a11y-gauge.svelte +90 -0
- package/dist/admin/components/dashboard/a11y-gauge.svelte.d.ts +18 -0
- package/dist/admin/components/dashboard/accessibility-hub.svelte +13 -12
- package/dist/admin/components/dashboard/form-submissions-widget.svelte +71 -113
- package/dist/admin/components/dashboard/index.d.ts +4 -2
- package/dist/admin/components/dashboard/index.js +4 -2
- package/dist/admin/components/dashboard/recent-activity.svelte +53 -75
- package/dist/admin/components/dashboard/recent-entries.svelte +94 -0
- package/dist/admin/components/dashboard/recent-entries.svelte.d.ts +18 -0
- package/dist/admin/components/dashboard/stat-card.svelte +2 -2
- package/dist/admin/components/dashboard/tip-of-the-day.svelte +109 -0
- package/dist/admin/components/dashboard/tip-of-the-day.svelte.d.ts +3 -0
- package/dist/admin/components/dashboard/welcome-header.svelte +45 -0
- package/dist/admin/components/dashboard/welcome-header.svelte.d.ts +3 -0
- package/dist/admin/components/fields/{array-field.svelte → blocks-field.svelte} +4 -4
- package/dist/admin/components/fields/{array-field.svelte.d.ts → blocks-field.svelte.d.ts} +5 -5
- package/dist/admin/components/fields/content-field.svelte +27 -0
- package/dist/admin/components/fields/content-field.svelte.d.ts +31 -0
- package/dist/admin/components/fields/field-renderer.svelte +9 -7
- package/dist/admin/components/fields/image-field.svelte +2 -2
- package/dist/admin/components/fields/media-field.svelte +2 -2
- package/dist/admin/components/fields/seo-field.svelte +205 -25
- package/dist/admin/components/fields/simple-array-field.svelte +289 -0
- package/dist/admin/components/fields/simple-array-field.svelte.d.ts +30 -0
- package/dist/admin/components/fields/slug-field.svelte +3 -2
- package/dist/admin/components/fields/standalone-field-renderer.svelte +148 -0
- package/dist/admin/components/fields/standalone-field-renderer.svelte.d.ts +9 -0
- package/dist/admin/components/fields/text-field-wrapper.svelte +13 -1
- package/dist/admin/components/fields/text-field-wrapper.svelte.d.ts +2 -2
- package/dist/admin/components/fields/url-field.svelte +5 -4
- package/dist/admin/components/layout/app-sidebar.svelte +27 -24
- package/dist/admin/components/layout/lang.d.ts +6 -0
- package/dist/admin/components/layout/lang.js +13 -1
- package/dist/admin/components/layout/layout-renderer.svelte +352 -0
- package/dist/admin/components/layout/layout-renderer.svelte.d.ts +14 -0
- package/dist/admin/components/layout/nav-breadcrumbs.svelte +4 -4
- package/dist/admin/components/layout/nav-collections.svelte +65 -36
- package/dist/admin/components/layout/nav-footer.svelte +31 -0
- package/dist/admin/components/layout/nav-footer.svelte.d.ts +18 -0
- package/dist/admin/components/layout/nav-forms.svelte +55 -30
- package/dist/admin/components/layout/nav-main.svelte +14 -52
- package/dist/admin/components/layout/nav-search.svelte +4 -3
- package/dist/admin/components/layout/nav-singletons.svelte +59 -17
- package/dist/admin/components/layout/nav-singletons.svelte.d.ts +17 -8
- package/dist/admin/components/layout/site-header.svelte +74 -13
- package/dist/admin/components/media/alt-input.svelte +32 -22
- package/dist/admin/components/media/bulk-action-bar.svelte +139 -150
- package/dist/admin/components/media/file/file-details.svelte +299 -217
- package/dist/admin/components/media/file/file-miniature.svelte +54 -41
- package/dist/admin/components/media/file/file-miniature.svelte.d.ts +1 -0
- package/dist/admin/components/media/file/file-preview.svelte +1 -1
- package/dist/admin/components/media/file-upload.svelte +24 -26
- package/dist/admin/components/media/files-list.svelte +112 -40
- package/dist/admin/components/media/files-list.svelte.d.ts +2 -0
- package/dist/admin/components/media/focal-point-input.svelte +122 -26
- package/dist/admin/components/media/media-library.svelte +127 -70
- package/dist/admin/components/media/media-search.svelte +6 -6
- package/dist/admin/components/media/media-sort.svelte +3 -1
- package/dist/admin/components/media/multi-file-summary.svelte +88 -68
- package/dist/admin/components/media/tag-combobox.svelte +141 -66
- package/dist/admin/components/media/tag-combobox.svelte.d.ts +1 -0
- package/dist/admin/components/media/tag-sidebar.svelte +139 -121
- package/dist/admin/components/tiptap/FigureNodeView.svelte +144 -15
- package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +254 -0
- package/dist/admin/components/tiptap/InlineBlockNodeView.svelte.d.ts +4 -0
- package/dist/admin/components/tiptap/SlashCommandPopup.svelte +212 -0
- package/dist/admin/components/tiptap/SlashCommandPopup.svelte.d.ts +8 -0
- package/dist/admin/components/tiptap/content-editor.svelte +280 -0
- package/dist/admin/components/tiptap/content-editor.svelte.d.ts +9 -0
- package/dist/admin/components/tiptap/editor-toolbar.svelte +230 -0
- package/dist/admin/components/tiptap/editor-toolbar.svelte.d.ts +16 -0
- package/dist/admin/components/tiptap/heading-a11y-plugin.d.ts +2 -0
- package/dist/admin/components/tiptap/heading-a11y-plugin.js +67 -0
- package/dist/admin/components/tiptap/image-dialog.svelte +172 -11
- package/dist/admin/components/tiptap/inline-block-node.d.ts +19 -0
- package/dist/admin/components/tiptap/inline-block-node.js +98 -0
- package/dist/admin/components/tiptap/link-dialog.svelte +9 -4
- package/dist/admin/components/tiptap/slash-command.d.ts +17 -0
- package/dist/admin/components/tiptap/slash-command.js +181 -0
- package/dist/admin/components/tiptap/structured-content-utils.d.ts +21 -0
- package/dist/admin/components/tiptap/structured-content-utils.js +150 -0
- package/dist/admin/components/tiptap/tiptap-editor.svelte +18 -190
- package/dist/admin/email/invite-template.d.ts +8 -0
- package/dist/admin/email/invite-template.js +99 -0
- package/dist/admin/email/reset-password-template.d.ts +7 -0
- package/dist/admin/email/reset-password-template.js +96 -0
- package/dist/admin/remote/ai.remote.d.ts +1 -0
- package/dist/admin/remote/ai.remote.js +4 -1
- package/dist/admin/remote/entry.remote.d.ts +8 -0
- package/dist/admin/remote/entry.remote.js +53 -4
- package/dist/admin/remote/preview.remote.js +2 -1
- package/dist/admin/shared/password-schema.d.ts +5 -0
- package/dist/admin/shared/password-schema.js +10 -0
- package/dist/admin/styles/admin.css +1530 -151
- package/dist/admin/utils/formatDate.d.ts +1 -0
- package/dist/admin/utils/formatDate.js +8 -0
- package/dist/admin/utils/roleLabel.d.ts +2 -0
- package/dist/admin/utils/roleLabel.js +13 -0
- package/dist/ai-claude/index.d.ts +2 -0
- package/dist/ai-claude/index.js +56 -0
- package/dist/cms/runtime/api.d.ts +6 -1
- package/dist/cms/runtime/api.js +3 -0
- package/dist/cms/runtime/schemas.d.ts +9 -1
- package/dist/cms/runtime/schemas.js +8 -0
- package/dist/cms/runtime/types.d.ts +82 -10
- package/dist/cms/runtime/types.js +4 -0
- package/dist/components/ui/accordion/accordion.stories.svelte +39 -0
- package/dist/components/ui/accordion/accordion.stories.svelte.d.ts +27 -0
- package/dist/components/ui/alert/alert.stories.svelte +53 -0
- package/dist/components/ui/alert/alert.stories.svelte.d.ts +27 -0
- package/dist/components/ui/alert/alert.svelte +5 -0
- package/dist/components/ui/alert/alert.svelte.d.ts +9 -0
- package/dist/components/ui/avatar/avatar.stories.svelte +16 -0
- package/dist/components/ui/avatar/avatar.stories.svelte.d.ts +27 -0
- package/dist/components/ui/badge/badge.stories.svelte +33 -0
- package/dist/components/ui/badge/badge.stories.svelte.d.ts +27 -0
- package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte +33 -0
- package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte.d.ts +27 -0
- package/dist/components/ui/button/button.stories.svelte +43 -0
- package/dist/components/ui/button/button.stories.svelte.d.ts +27 -0
- package/dist/components/ui/button/button.svelte +1 -2
- package/dist/components/ui/button/button.svelte.d.ts +0 -3
- package/dist/components/ui/button-group/button-group-separator.svelte.d.ts +1 -1
- package/dist/components/ui/card/card.stories.svelte +42 -0
- package/dist/components/ui/card/card.stories.svelte.d.ts +27 -0
- package/dist/components/ui/command/command.stories.svelte +51 -0
- package/dist/components/ui/command/command.stories.svelte.d.ts +27 -0
- package/dist/components/ui/dialog/dialog.stories.svelte +29 -0
- package/dist/components/ui/dialog/dialog.stories.svelte.d.ts +27 -0
- package/dist/components/ui/field/field-label.svelte.d.ts +1 -1
- package/dist/components/ui/field/field.stories.svelte +21 -0
- package/dist/components/ui/field/field.stories.svelte.d.ts +27 -0
- package/dist/components/ui/input/input.stories.svelte +40 -0
- package/dist/components/ui/input/input.stories.svelte.d.ts +27 -0
- package/dist/components/ui/input/input.svelte +2 -4
- package/dist/components/ui/item/item-separator.svelte.d.ts +1 -1
- package/dist/components/ui/label/label.stories.svelte +20 -0
- package/dist/components/ui/label/label.stories.svelte.d.ts +27 -0
- package/dist/components/ui/popover/popover.stories.svelte +29 -0
- package/dist/components/ui/popover/popover.stories.svelte.d.ts +27 -0
- package/dist/components/ui/select/select-group-heading.svelte.d.ts +1 -1
- package/dist/components/ui/select/select.stories.svelte +23 -0
- package/dist/components/ui/select/select.stories.svelte.d.ts +27 -0
- package/dist/components/ui/separator/separator.stories.svelte +24 -0
- package/dist/components/ui/separator/separator.stories.svelte.d.ts +27 -0
- package/dist/components/ui/sheet/sheet.stories.svelte +29 -0
- package/dist/components/ui/sheet/sheet.stories.svelte.d.ts +27 -0
- package/dist/components/ui/sidebar/sidebar-group.svelte +3 -3
- package/dist/components/ui/sidebar/sidebar-group.svelte.d.ts +2 -2
- package/dist/components/ui/sidebar/sidebar-menu-button.svelte +28 -30
- package/dist/components/ui/sidebar/sidebar-menu-button.svelte.d.ts +7 -7
- package/dist/components/ui/sidebar/sidebar-separator.svelte.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-trigger.svelte +4 -4
- package/dist/components/ui/sonner/sonner.stories.svelte +22 -0
- package/dist/components/ui/sonner/sonner.stories.svelte.d.ts +26 -0
- package/dist/components/ui/sonner/sonner.svelte +8 -2
- package/dist/components/ui/sonner/toast-demo.svelte +29 -0
- package/dist/components/ui/sonner/toast-demo.svelte.d.ts +6 -0
- package/dist/components/ui/textarea/textarea.stories.svelte +22 -0
- package/dist/components/ui/textarea/textarea.stories.svelte.d.ts +27 -0
- package/dist/components/ui/textarea/textarea.svelte +0 -2
- package/dist/components/ui/toggle/toggle.stories.svelte +22 -0
- package/dist/components/ui/toggle/toggle.stories.svelte.d.ts +27 -0
- package/dist/components/ui/toggle-group/toggle-group.stories.svelte +17 -0
- package/dist/components/ui/toggle-group/toggle-group.stories.svelte.d.ts +27 -0
- package/dist/components/ui/tooltip/tooltip.stories.svelte +26 -0
- package/dist/components/ui/tooltip/tooltip.stories.svelte.d.ts +27 -0
- package/dist/core/fields/fieldSchemaToTs.d.ts +1 -0
- package/dist/core/fields/fieldSchemaToTs.js +133 -1
- package/dist/core/fields/layoutUtils.d.ts +17 -0
- package/dist/core/fields/layoutUtils.js +149 -0
- package/dist/core/fields/structuredToHtml.d.ts +9 -0
- package/dist/core/fields/structuredToHtml.js +161 -0
- package/dist/core/server/entries/operations/create.js +2 -1
- package/dist/core/server/entries/operations/get.js +8 -6
- package/dist/core/server/entries/operations/update.d.ts +3 -0
- package/dist/core/server/entries/operations/update.js +30 -2
- package/dist/core/server/fields/queryStructuredContent.d.ts +15 -0
- package/dist/core/server/fields/queryStructuredContent.js +65 -0
- package/dist/core/server/fields/resolveImageFields.js +51 -2
- package/dist/core/server/fields/resolveRelationFields.js +2 -2
- package/dist/core/server/fields/resolveRichtextLinks.js +80 -13
- package/dist/core/server/fields/resolveUrlFields.js +57 -6
- package/dist/core/server/fields/slugResolver.d.ts +10 -0
- package/dist/core/server/fields/slugResolver.js +34 -0
- package/dist/core/server/generator/fields.js +15 -4
- package/dist/core/server/generator/generator.js +3 -2
- package/dist/files-local/index.js +126 -64
- package/dist/server/auth.d.ts +5 -0
- package/dist/server/auth.js +12 -1
- package/dist/sveltekit/components/structured-content.svelte +204 -0
- package/dist/sveltekit/components/structured-content.svelte.d.ts +21 -0
- package/dist/sveltekit/config.d.ts +13 -3
- package/dist/sveltekit/index.d.ts +3 -0
- package/dist/sveltekit/index.js +3 -0
- package/dist/sveltekit/server/handle.js +1 -0
- package/dist/types/config.d.ts +3 -0
- package/dist/types/fields.d.ts +19 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/layout.d.ts +54 -0
- package/dist/types/layout.js +6 -0
- package/dist/types/structured-content.d.ts +63 -0
- package/dist/types/structured-content.js +1 -0
- package/dist/updates/0.1.5/index.d.ts +2 -0
- package/dist/updates/0.1.5/index.js +18 -0
- package/dist/updates/0.2.0/index.d.ts +2 -0
- package/dist/updates/0.2.0/index.js +11 -0
- package/dist/updates/0.2.2/index.d.ts +2 -0
- package/dist/updates/0.2.2/index.js +13 -0
- package/dist/updates/0.5.0/index.d.ts +2 -0
- package/dist/updates/0.5.0/index.js +14 -0
- package/dist/updates/index.js +5 -1
- package/package.json +16 -9
|
@@ -6,14 +6,26 @@
|
|
|
6
6
|
import Loader2 from '@tabler/icons-svelte/icons/loader-2';
|
|
7
7
|
import DeviceDesktop from '@tabler/icons-svelte/icons/device-desktop';
|
|
8
8
|
import Mail from '@tabler/icons-svelte/icons/mail';
|
|
9
|
+
import Users from '@tabler/icons-svelte/icons/users';
|
|
10
|
+
import Filter from '@tabler/icons-svelte/icons/filter';
|
|
11
|
+
import ArrowsSort from '@tabler/icons-svelte/icons/arrows-sort';
|
|
12
|
+
import SortAscending from '@tabler/icons-svelte/icons/sort-ascending';
|
|
13
|
+
import SortDescending from '@tabler/icons-svelte/icons/sort-descending';
|
|
14
|
+
import ChevronLeft from '@tabler/icons-svelte/icons/chevron-left';
|
|
15
|
+
import ChevronRight from '@tabler/icons-svelte/icons/chevron-right';
|
|
16
|
+
import X from '@tabler/icons-svelte/icons/x';
|
|
9
17
|
import Input from '../../../components/ui/input/input.svelte';
|
|
10
18
|
import Button from '../../../components/ui/button/button.svelte';
|
|
11
|
-
import {
|
|
19
|
+
import { Checkbox } from '../../../components/ui/checkbox/index.js';
|
|
20
|
+
import * as Popover from '../../../components/ui/popover/index.js';
|
|
12
21
|
import * as Table from '../../../components/ui/table/index.js';
|
|
13
22
|
import { authClient } from '../../auth-client.js';
|
|
14
23
|
import { usersLang } from './lang.js';
|
|
15
24
|
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
16
25
|
import { toLocaleCode } from '../../utils/formatDate.js';
|
|
26
|
+
import { getRoleLabel } from '../../utils/roleLabel.js';
|
|
27
|
+
import { toast } from 'svelte-sonner';
|
|
28
|
+
import { fly } from 'svelte/transition';
|
|
17
29
|
import CreateUserDialog from './create-user-dialog.svelte';
|
|
18
30
|
import EditUserDialog from './edit-user-dialog.svelte';
|
|
19
31
|
import DeleteUserDialog from './delete-user-dialog.svelte';
|
|
@@ -44,13 +56,24 @@
|
|
|
44
56
|
let loading = $state(true);
|
|
45
57
|
let searchQuery = $state('');
|
|
46
58
|
|
|
59
|
+
// Sorting
|
|
60
|
+
let sortColumn = $state<'name' | 'role' | 'createdAt'>('createdAt');
|
|
61
|
+
let sortDirection = $state<'desc' | 'asc'>('desc');
|
|
62
|
+
|
|
63
|
+
// Filtering
|
|
64
|
+
let roleFilter = $state<string>('all');
|
|
65
|
+
let filterOpen = $state(false);
|
|
66
|
+
|
|
67
|
+
// Selection
|
|
68
|
+
let selectedIds = $state<Set<string>>(new Set());
|
|
69
|
+
|
|
47
70
|
// Dialogs
|
|
48
71
|
let createOpen = $state(false);
|
|
49
72
|
let editOpen = $state(false);
|
|
50
73
|
let deleteOpen = $state(false);
|
|
51
74
|
let inviteOpen = $state(false);
|
|
52
75
|
let editingUser = $state<User | null>(null);
|
|
53
|
-
let
|
|
76
|
+
let deletingUser = $state<{ id: string; name: string } | null>(null);
|
|
54
77
|
|
|
55
78
|
// Sessions sheet
|
|
56
79
|
let sessionsOpen = $state(false);
|
|
@@ -64,7 +87,8 @@
|
|
|
64
87
|
let pageIndex = $state(0);
|
|
65
88
|
const pageSize = 20;
|
|
66
89
|
|
|
67
|
-
|
|
90
|
+
// Derived chain: users → searchFiltered → roleFiltered → sorted → paged
|
|
91
|
+
const searchFiltered = $derived(
|
|
68
92
|
searchQuery
|
|
69
93
|
? users.filter(
|
|
70
94
|
(u) =>
|
|
@@ -74,9 +98,114 @@
|
|
|
74
98
|
: users
|
|
75
99
|
);
|
|
76
100
|
|
|
77
|
-
const
|
|
101
|
+
const roleFiltered = $derived(
|
|
102
|
+
roleFilter === 'all' ? searchFiltered : searchFiltered.filter((u) => u.role === roleFilter)
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const sorted = $derived(
|
|
106
|
+
[...roleFiltered].sort((a, b) => {
|
|
107
|
+
const dir = sortDirection === 'asc' ? 1 : -1;
|
|
108
|
+
if (sortColumn === 'name') return a.name.localeCompare(b.name) * dir;
|
|
109
|
+
if (sortColumn === 'role') return a.role.localeCompare(b.role) * dir;
|
|
110
|
+
return (a.createdAt.getTime() - b.createdAt.getTime()) * dir;
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const totalItems = $derived(sorted.length);
|
|
78
115
|
const pageCount = $derived(Math.ceil(totalItems / pageSize));
|
|
79
|
-
const paged = $derived(
|
|
116
|
+
const paged = $derived(sorted.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize));
|
|
117
|
+
const showingStart = $derived(totalItems === 0 ? 0 : pageIndex * pageSize + 1);
|
|
118
|
+
const showingEnd = $derived(Math.min((pageIndex + 1) * pageSize, totalItems));
|
|
119
|
+
|
|
120
|
+
const allOnPageSelected = $derived(
|
|
121
|
+
paged.length > 0 && paged.every((u) => selectedIds.has(u.id))
|
|
122
|
+
);
|
|
123
|
+
const someSelected = $derived(selectedIds.size > 0);
|
|
124
|
+
|
|
125
|
+
// Avatar helpers
|
|
126
|
+
const avatarGradients = [
|
|
127
|
+
'linear-gradient(135deg, #5B4A9E, #B8A9E8)',
|
|
128
|
+
'linear-gradient(135deg, #3A8A5C, #7EC4A0)',
|
|
129
|
+
'linear-gradient(135deg, #C4893A, #E8C88A)',
|
|
130
|
+
'linear-gradient(135deg, #463879, #8B7BC7)',
|
|
131
|
+
'linear-gradient(135deg, #C44B4B, #E89B9B)',
|
|
132
|
+
'linear-gradient(135deg, #2E7D9B, #7BC4D9)'
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
function getInitials(name: string): string {
|
|
136
|
+
return name
|
|
137
|
+
.split(/\s+/)
|
|
138
|
+
.filter(Boolean)
|
|
139
|
+
.map((w) => w[0])
|
|
140
|
+
.slice(0, 2)
|
|
141
|
+
.join('')
|
|
142
|
+
.toUpperCase();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getAvatarGradient(name: string): string {
|
|
146
|
+
let hash = 0;
|
|
147
|
+
for (let i = 0; i < name.length; i++) {
|
|
148
|
+
hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0;
|
|
149
|
+
}
|
|
150
|
+
return avatarGradients[Math.abs(hash) % avatarGradients.length];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function toggleSort(column: 'name' | 'role' | 'createdAt') {
|
|
154
|
+
if (sortColumn === column) {
|
|
155
|
+
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
|
|
156
|
+
} else {
|
|
157
|
+
sortColumn = column;
|
|
158
|
+
sortDirection = column === 'createdAt' ? 'desc' : 'asc';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function toggleSelectAll() {
|
|
163
|
+
if (allOnPageSelected) {
|
|
164
|
+
for (const u of paged) selectedIds.delete(u.id);
|
|
165
|
+
} else {
|
|
166
|
+
for (const u of paged) selectedIds.add(u.id);
|
|
167
|
+
}
|
|
168
|
+
selectedIds = new Set(selectedIds);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function toggleSelect(id: string) {
|
|
172
|
+
if (selectedIds.has(id)) {
|
|
173
|
+
selectedIds.delete(id);
|
|
174
|
+
} else {
|
|
175
|
+
selectedIds.add(id);
|
|
176
|
+
}
|
|
177
|
+
selectedIds = new Set(selectedIds);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function clearSelection() {
|
|
181
|
+
selectedIds = new Set();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function handleBulkDelete() {
|
|
185
|
+
const ids = [...selectedIds].filter((id) => id !== currentUserId);
|
|
186
|
+
if (ids.length === 0) return;
|
|
187
|
+
|
|
188
|
+
for (const userId of ids) {
|
|
189
|
+
await authClient.admin.removeUser({ userId });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
toast.success(lang.userDeleted);
|
|
193
|
+
selectedIds = new Set();
|
|
194
|
+
await loadUsers();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function handleBulkChangeRole(newRole: 'admin' | 'user') {
|
|
198
|
+
const ids = [...selectedIds].filter((id) => id !== currentUserId);
|
|
199
|
+
if (ids.length === 0) return;
|
|
200
|
+
|
|
201
|
+
for (const userId of ids) {
|
|
202
|
+
await authClient.admin.setRole({ userId, role: newRole });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
toast.success(lang.userUpdated);
|
|
206
|
+
selectedIds = new Set();
|
|
207
|
+
await loadUsers();
|
|
208
|
+
}
|
|
80
209
|
|
|
81
210
|
$effect(() => {
|
|
82
211
|
loadUsers();
|
|
@@ -84,7 +213,7 @@
|
|
|
84
213
|
|
|
85
214
|
async function loadUsers() {
|
|
86
215
|
loading = true;
|
|
87
|
-
const { data
|
|
216
|
+
const { data } = await authClient.admin.listUsers({
|
|
88
217
|
query: { limit: 500 }
|
|
89
218
|
});
|
|
90
219
|
if (data) {
|
|
@@ -112,8 +241,8 @@
|
|
|
112
241
|
editOpen = true;
|
|
113
242
|
}
|
|
114
243
|
|
|
115
|
-
function openDelete(
|
|
116
|
-
|
|
244
|
+
function openDelete(user: User) {
|
|
245
|
+
deletingUser = { id: user.id, name: user.name };
|
|
117
246
|
deleteOpen = true;
|
|
118
247
|
}
|
|
119
248
|
|
|
@@ -122,74 +251,255 @@
|
|
|
122
251
|
sessionsUserName = user.name;
|
|
123
252
|
sessionsOpen = true;
|
|
124
253
|
}
|
|
254
|
+
|
|
255
|
+
function getSortIcon(column: string) {
|
|
256
|
+
if (sortColumn !== column) return ArrowsSort;
|
|
257
|
+
return sortDirection === 'asc' ? SortAscending : SortDescending;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function getAriaSort(column: string): 'ascending' | 'descending' | 'none' {
|
|
261
|
+
if (sortColumn !== column) return 'none';
|
|
262
|
+
return sortDirection === 'asc' ? 'ascending' : 'descending';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function generatePageNumbers(current: number, total: number): (number | '...')[] {
|
|
266
|
+
if (total <= 7) return Array.from({ length: total }, (_, i) => i);
|
|
267
|
+
const pages: (number | '...')[] = [0];
|
|
268
|
+
if (current > 2) pages.push('...');
|
|
269
|
+
for (let i = Math.max(1, current - 1); i <= Math.min(total - 2, current + 1); i++) {
|
|
270
|
+
pages.push(i);
|
|
271
|
+
}
|
|
272
|
+
if (current < total - 3) pages.push('...');
|
|
273
|
+
pages.push(total - 1);
|
|
274
|
+
return pages;
|
|
275
|
+
}
|
|
125
276
|
</script>
|
|
126
277
|
|
|
127
|
-
<div class="
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
<div class="flex items-center gap-
|
|
131
|
-
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
278
|
+
<div class="p-5 pb-24 md:p-7">
|
|
279
|
+
<!-- Header -->
|
|
280
|
+
<div class="mb-6 users-fade-up">
|
|
281
|
+
<div class="mb-1 flex items-center gap-3">
|
|
282
|
+
<h1 class="text-2xl font-bold">{lang.title}</h1>
|
|
283
|
+
{#if !loading}
|
|
284
|
+
<span
|
|
285
|
+
class="inline-flex items-center justify-center rounded-full px-2.5 py-0.5 text-xs font-bold"
|
|
286
|
+
style="background: var(--lavender-lighter); color: var(--primary);"
|
|
287
|
+
>
|
|
288
|
+
{users.length}
|
|
289
|
+
</span>
|
|
136
290
|
{/if}
|
|
137
|
-
<Button variant="gradient" size="sm" onclick={() => (createOpen = true)}>
|
|
138
|
-
<Plus class="size-4" />
|
|
139
|
-
{lang.createUser}
|
|
140
|
-
</Button>
|
|
141
291
|
</div>
|
|
292
|
+
<p class="text-sm" style="color: var(--muted-foreground);">{lang.description}</p>
|
|
142
293
|
</div>
|
|
143
294
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
searchQuery
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
295
|
+
<!-- Toolbar -->
|
|
296
|
+
<div class="mb-4 flex flex-wrap items-center gap-2.5 users-fade-up">
|
|
297
|
+
<div class="relative min-w-[240px] flex-1 sm:max-w-xs">
|
|
298
|
+
<Search class="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2" style="color: var(--text-light);" />
|
|
299
|
+
<Input
|
|
300
|
+
type="text"
|
|
301
|
+
placeholder={lang.search}
|
|
302
|
+
class="pl-9"
|
|
303
|
+
value={searchQuery}
|
|
304
|
+
oninput={(e) => {
|
|
305
|
+
searchQuery = e.currentTarget.value;
|
|
306
|
+
pageIndex = 0;
|
|
307
|
+
}}
|
|
308
|
+
/>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<Popover.Root bind:open={filterOpen}>
|
|
312
|
+
<Popover.Trigger>
|
|
313
|
+
<Button variant="outline" size="sm" class="gap-1.5">
|
|
314
|
+
<Filter class="size-4" />
|
|
315
|
+
{lang.filterByRole}
|
|
316
|
+
{#if roleFilter !== 'all'}
|
|
317
|
+
<span
|
|
318
|
+
class="ml-1 inline-flex size-5 items-center justify-center rounded-full text-[10px] font-bold"
|
|
319
|
+
style="background: var(--primary); color: white;"
|
|
320
|
+
>1</span>
|
|
321
|
+
{/if}
|
|
322
|
+
</Button>
|
|
323
|
+
</Popover.Trigger>
|
|
324
|
+
<Popover.Content class="w-48 p-2" align="start">
|
|
325
|
+
{#each [
|
|
326
|
+
{ value: 'all', label: lang.allRoles },
|
|
327
|
+
{ value: 'admin', label: lang.roleAdmin },
|
|
328
|
+
{ value: 'user', label: lang.roleUser }
|
|
329
|
+
] as option}
|
|
330
|
+
<button
|
|
331
|
+
class="flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm transition-colors hover:bg-accent"
|
|
332
|
+
class:font-semibold={roleFilter === option.value}
|
|
333
|
+
style={roleFilter === option.value ? 'color: var(--primary);' : ''}
|
|
334
|
+
onclick={() => {
|
|
335
|
+
roleFilter = option.value;
|
|
336
|
+
pageIndex = 0;
|
|
337
|
+
filterOpen = false;
|
|
338
|
+
}}
|
|
339
|
+
>
|
|
340
|
+
{option.label}
|
|
341
|
+
</button>
|
|
342
|
+
{/each}
|
|
343
|
+
</Popover.Content>
|
|
344
|
+
</Popover.Root>
|
|
345
|
+
|
|
346
|
+
<div class="flex-1"></div>
|
|
347
|
+
|
|
348
|
+
{#if emailConfigured}
|
|
349
|
+
<Button variant="secondary" size="sm" onclick={() => (inviteOpen = true)}>
|
|
350
|
+
<Mail class="size-4" />
|
|
351
|
+
{lang.invite.inviteUser}
|
|
352
|
+
</Button>
|
|
353
|
+
{/if}
|
|
354
|
+
<Button variant="default" size="sm" onclick={() => (createOpen = true)}>
|
|
355
|
+
<Plus class="size-4" />
|
|
356
|
+
{lang.createUser}
|
|
357
|
+
</Button>
|
|
156
358
|
</div>
|
|
157
359
|
|
|
360
|
+
<!-- Content -->
|
|
158
361
|
{#if loading}
|
|
159
|
-
<div class="flex items-center justify-center py-
|
|
160
|
-
<Loader2 class="
|
|
362
|
+
<div class="flex items-center justify-center py-20">
|
|
363
|
+
<Loader2 class="size-6 animate-spin" style="color: var(--muted-foreground);" />
|
|
364
|
+
</div>
|
|
365
|
+
{:else if users.length === 0}
|
|
366
|
+
<!-- Empty state -->
|
|
367
|
+
<div class="flex flex-col items-center justify-center py-20 text-center users-fade-up">
|
|
368
|
+
<div
|
|
369
|
+
class="mb-5 flex size-[72px] items-center justify-center rounded-xl"
|
|
370
|
+
style="background: var(--muted); color: var(--text-light);"
|
|
371
|
+
>
|
|
372
|
+
<Users class="size-8" />
|
|
373
|
+
</div>
|
|
374
|
+
<h2 class="mb-1.5 text-lg font-bold">{lang.emptyTitle}</h2>
|
|
375
|
+
<p class="mb-5 max-w-sm text-sm" style="color: var(--muted-foreground);">
|
|
376
|
+
{lang.emptyDescription}
|
|
377
|
+
</p>
|
|
378
|
+
<Button onclick={() => (createOpen = true)}>
|
|
379
|
+
<Plus class="size-4" />
|
|
380
|
+
{lang.addUser}
|
|
381
|
+
</Button>
|
|
161
382
|
</div>
|
|
162
383
|
{:else if paged.length === 0}
|
|
163
|
-
<p class="
|
|
384
|
+
<p class="py-16 text-center text-sm" style="color: var(--muted-foreground);">
|
|
385
|
+
{lang.noResults}
|
|
386
|
+
</p>
|
|
164
387
|
{:else}
|
|
165
|
-
|
|
388
|
+
<!-- Table -->
|
|
389
|
+
<div class="overflow-hidden rounded-xl border bg-card shadow-sm users-fade-up">
|
|
166
390
|
<Table.Root>
|
|
167
391
|
<Table.Header>
|
|
168
|
-
<Table.Row>
|
|
169
|
-
<Table.Head>
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
392
|
+
<Table.Row class="bg-muted/50">
|
|
393
|
+
<Table.Head class="w-11 text-center">
|
|
394
|
+
<Checkbox
|
|
395
|
+
checked={allOnPageSelected}
|
|
396
|
+
onCheckedChange={toggleSelectAll}
|
|
397
|
+
aria-label={lang.selectAll}
|
|
398
|
+
/>
|
|
399
|
+
</Table.Head>
|
|
400
|
+
<Table.Head>
|
|
401
|
+
<button
|
|
402
|
+
class="inline-flex items-center gap-1 text-xs font-bold uppercase tracking-wide transition-colors hover:text-primary"
|
|
403
|
+
onclick={() => toggleSort('name')}
|
|
404
|
+
aria-sort={getAriaSort('name')}
|
|
405
|
+
>
|
|
406
|
+
{lang.name}
|
|
407
|
+
<svelte:component this={getSortIcon('name')} class="users-sort-icon size-3.5" />
|
|
408
|
+
</button>
|
|
409
|
+
</Table.Head>
|
|
410
|
+
<Table.Head>
|
|
411
|
+
<button
|
|
412
|
+
class="inline-flex items-center gap-1 text-xs font-bold uppercase tracking-wide transition-colors hover:text-primary"
|
|
413
|
+
onclick={() => toggleSort('role')}
|
|
414
|
+
aria-sort={getAriaSort('role')}
|
|
415
|
+
>
|
|
416
|
+
{lang.role}
|
|
417
|
+
<svelte:component this={getSortIcon('role')} class="users-sort-icon size-3.5" />
|
|
418
|
+
</button>
|
|
419
|
+
</Table.Head>
|
|
420
|
+
<Table.Head>
|
|
421
|
+
<button
|
|
422
|
+
class="inline-flex items-center gap-1 text-xs font-bold uppercase tracking-wide transition-colors hover:text-primary"
|
|
423
|
+
onclick={() => toggleSort('createdAt')}
|
|
424
|
+
aria-sort={getAriaSort('createdAt')}
|
|
425
|
+
>
|
|
426
|
+
{lang.createdAt}
|
|
427
|
+
<svelte:component this={getSortIcon('createdAt')} class="users-sort-icon size-3.5" />
|
|
428
|
+
</button>
|
|
429
|
+
</Table.Head>
|
|
430
|
+
<Table.Head class="w-[120px]">
|
|
431
|
+
<span class="sr-only">{lang.actions}</span>
|
|
432
|
+
</Table.Head>
|
|
174
433
|
</Table.Row>
|
|
175
434
|
</Table.Header>
|
|
176
435
|
<Table.Body>
|
|
177
436
|
{#each paged as user (user.id)}
|
|
178
|
-
<Table.Row
|
|
179
|
-
|
|
180
|
-
|
|
437
|
+
<Table.Row
|
|
438
|
+
class="transition-colors"
|
|
439
|
+
style={selectedIds.has(user.id) ? 'background: rgba(184,169,232,0.15);' : ''}
|
|
440
|
+
onmouseenter={(e) => {
|
|
441
|
+
if (!selectedIds.has(user.id)) e.currentTarget.style.background = 'var(--lavender-lighter)';
|
|
442
|
+
}}
|
|
443
|
+
onmouseleave={(e) => {
|
|
444
|
+
if (!selectedIds.has(user.id)) e.currentTarget.style.background = '';
|
|
445
|
+
}}
|
|
446
|
+
>
|
|
447
|
+
<Table.Cell class="text-center">
|
|
448
|
+
<Checkbox
|
|
449
|
+
checked={selectedIds.has(user.id)}
|
|
450
|
+
onCheckedChange={() => toggleSelect(user.id)}
|
|
451
|
+
aria-label="{lang.selectUser}: {user.name}"
|
|
452
|
+
/>
|
|
453
|
+
</Table.Cell>
|
|
454
|
+
<Table.Cell>
|
|
455
|
+
<div class="flex items-center gap-3">
|
|
456
|
+
<div
|
|
457
|
+
class="users-avatar"
|
|
458
|
+
style="background: {getAvatarGradient(user.name)};"
|
|
459
|
+
aria-hidden="true"
|
|
460
|
+
>
|
|
461
|
+
{getInitials(user.name) || '?'}
|
|
462
|
+
</div>
|
|
463
|
+
<div class="min-w-0">
|
|
464
|
+
<div class="truncate text-sm font-semibold">{user.name}</div>
|
|
465
|
+
<div class="truncate text-xs" style="color: var(--text-light);">
|
|
466
|
+
{user.email}
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
</div>
|
|
470
|
+
</Table.Cell>
|
|
471
|
+
<Table.Cell>
|
|
472
|
+
<span
|
|
473
|
+
class="users-role-badge"
|
|
474
|
+
class:users-role-admin={user.role === 'admin'}
|
|
475
|
+
class:users-role-user={user.role !== 'admin'}
|
|
476
|
+
>
|
|
477
|
+
{getRoleLabel(user.role, interfaceLanguage.current)}
|
|
478
|
+
</span>
|
|
479
|
+
</Table.Cell>
|
|
181
480
|
<Table.Cell>
|
|
182
|
-
<
|
|
183
|
-
{user.
|
|
184
|
-
</
|
|
481
|
+
<span class="text-xs" style="color: var(--muted-foreground);">
|
|
482
|
+
{formatDate(user.createdAt)}
|
|
483
|
+
</span>
|
|
185
484
|
</Table.Cell>
|
|
186
|
-
<Table.Cell>
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
485
|
+
<Table.Cell>
|
|
486
|
+
<div class="flex items-center justify-end gap-0.5">
|
|
487
|
+
<Button
|
|
488
|
+
variant="ghost"
|
|
489
|
+
size="icon"
|
|
490
|
+
class="h-8 w-8"
|
|
491
|
+
onclick={() => openSessions(user)}
|
|
492
|
+
title={lang.sessions.title}
|
|
493
|
+
>
|
|
190
494
|
<DeviceDesktop class="size-4" />
|
|
191
495
|
</Button>
|
|
192
|
-
<Button
|
|
496
|
+
<Button
|
|
497
|
+
variant="ghost"
|
|
498
|
+
size="icon"
|
|
499
|
+
class="h-8 w-8"
|
|
500
|
+
onclick={() => openEdit(user)}
|
|
501
|
+
title={lang.editUser}
|
|
502
|
+
>
|
|
193
503
|
<Pencil class="size-4" />
|
|
194
504
|
</Button>
|
|
195
505
|
{#if user.id !== currentUserId}
|
|
@@ -197,7 +507,8 @@
|
|
|
197
507
|
variant="ghost"
|
|
198
508
|
size="icon"
|
|
199
509
|
class="text-destructive h-8 w-8"
|
|
200
|
-
onclick={() => openDelete(user
|
|
510
|
+
onclick={() => openDelete(user)}
|
|
511
|
+
title={lang.deleteUser}
|
|
201
512
|
>
|
|
202
513
|
<Trash class="size-4" />
|
|
203
514
|
</Button>
|
|
@@ -210,31 +521,119 @@
|
|
|
210
521
|
</Table.Root>
|
|
211
522
|
</div>
|
|
212
523
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
524
|
+
<!-- Pagination -->
|
|
525
|
+
{#if pageCount > 1 || totalItems > 0}
|
|
526
|
+
<div class="mt-4 flex items-center justify-between users-fade-up">
|
|
527
|
+
<p class="text-sm" style="color: var(--muted-foreground);">
|
|
528
|
+
{lang.showing(showingStart, showingEnd, totalItems)}
|
|
529
|
+
</p>
|
|
530
|
+
{#if pageCount > 1}
|
|
531
|
+
<div class="flex items-center gap-1">
|
|
532
|
+
<Button
|
|
533
|
+
variant="outline"
|
|
534
|
+
size="icon"
|
|
535
|
+
class="h-8 w-8"
|
|
536
|
+
disabled={pageIndex === 0}
|
|
537
|
+
onclick={() => (pageIndex -= 1)}
|
|
538
|
+
aria-label="Previous page"
|
|
539
|
+
>
|
|
540
|
+
<ChevronLeft class="size-4" />
|
|
541
|
+
</Button>
|
|
542
|
+
{#each generatePageNumbers(pageIndex, pageCount) as p}
|
|
543
|
+
{#if p === '...'}
|
|
544
|
+
<span class="px-1 text-sm" style="color: var(--muted-foreground);">...</span>
|
|
545
|
+
{:else}
|
|
546
|
+
<Button
|
|
547
|
+
variant={pageIndex === p ? 'default' : 'outline'}
|
|
548
|
+
size="sm"
|
|
549
|
+
class="h-8 min-w-8 px-2"
|
|
550
|
+
onclick={() => (pageIndex = p)}
|
|
551
|
+
aria-current={pageIndex === p ? 'page' : undefined}
|
|
552
|
+
>
|
|
553
|
+
{p + 1}
|
|
554
|
+
</Button>
|
|
555
|
+
{/if}
|
|
556
|
+
{/each}
|
|
557
|
+
<Button
|
|
558
|
+
variant="outline"
|
|
559
|
+
size="icon"
|
|
560
|
+
class="h-8 w-8"
|
|
561
|
+
disabled={pageIndex >= pageCount - 1}
|
|
562
|
+
onclick={() => (pageIndex += 1)}
|
|
563
|
+
aria-label="Next page"
|
|
564
|
+
>
|
|
565
|
+
<ChevronRight class="size-4" />
|
|
566
|
+
</Button>
|
|
567
|
+
</div>
|
|
568
|
+
{/if}
|
|
229
569
|
</div>
|
|
230
570
|
{/if}
|
|
231
571
|
{/if}
|
|
232
572
|
|
|
573
|
+
<!-- Pending invitations -->
|
|
233
574
|
{#if emailConfigured}
|
|
234
575
|
<PendingInvitations refreshTrigger={inviteRefresh} />
|
|
235
576
|
{/if}
|
|
236
577
|
</div>
|
|
237
578
|
|
|
579
|
+
<!-- Bulk actions bar -->
|
|
580
|
+
{#if someSelected}
|
|
581
|
+
<div
|
|
582
|
+
class="users-bulk-bar"
|
|
583
|
+
transition:fly={{ y: 60, duration: 250 }}
|
|
584
|
+
>
|
|
585
|
+
<div
|
|
586
|
+
class="flex items-center gap-3 rounded-xl px-5 py-2.5 shadow-xl"
|
|
587
|
+
style="background: var(--plum-darker); color: white;"
|
|
588
|
+
>
|
|
589
|
+
<span class="text-sm font-bold whitespace-nowrap">{lang.selected(selectedIds.size)}</span>
|
|
590
|
+
<div class="h-5 w-px" style="background: rgba(255,255,255,0.2);"></div>
|
|
591
|
+
|
|
592
|
+
<Popover.Root>
|
|
593
|
+
<Popover.Trigger>
|
|
594
|
+
<button
|
|
595
|
+
class="inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-xs font-semibold text-white transition-colors hover:bg-white/10"
|
|
596
|
+
style="border-color: rgba(255,255,255,0.2);"
|
|
597
|
+
>
|
|
598
|
+
{lang.changeRole}
|
|
599
|
+
</button>
|
|
600
|
+
</Popover.Trigger>
|
|
601
|
+
<Popover.Content class="w-40 p-1" align="center" side="top">
|
|
602
|
+
<button
|
|
603
|
+
class="flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm transition-colors hover:bg-accent"
|
|
604
|
+
onclick={() => handleBulkChangeRole('admin')}
|
|
605
|
+
>
|
|
606
|
+
{lang.roleAdmin}
|
|
607
|
+
</button>
|
|
608
|
+
<button
|
|
609
|
+
class="flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm transition-colors hover:bg-accent"
|
|
610
|
+
onclick={() => handleBulkChangeRole('user')}
|
|
611
|
+
>
|
|
612
|
+
{lang.roleUser}
|
|
613
|
+
</button>
|
|
614
|
+
</Popover.Content>
|
|
615
|
+
</Popover.Root>
|
|
616
|
+
|
|
617
|
+
<button
|
|
618
|
+
class="inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-xs font-semibold text-white transition-colors hover:bg-red-500/20"
|
|
619
|
+
style="border-color: rgba(196,75,75,0.5);"
|
|
620
|
+
onclick={handleBulkDelete}
|
|
621
|
+
>
|
|
622
|
+
<Trash class="size-3.5" />
|
|
623
|
+
{lang.deleteUser}
|
|
624
|
+
</button>
|
|
625
|
+
|
|
626
|
+
<button
|
|
627
|
+
class="ml-1 inline-flex items-center justify-center rounded-md p-1 text-white/70 transition-colors hover:bg-white/10 hover:text-white"
|
|
628
|
+
onclick={clearSelection}
|
|
629
|
+
aria-label={lang.cancel}
|
|
630
|
+
>
|
|
631
|
+
<X class="size-4" />
|
|
632
|
+
</button>
|
|
633
|
+
</div>
|
|
634
|
+
</div>
|
|
635
|
+
{/if}
|
|
636
|
+
|
|
238
637
|
<CreateUserDialog bind:open={createOpen} onOpenChange={(v) => (createOpen = v)} onCreated={loadUsers} />
|
|
239
638
|
|
|
240
639
|
<EditUserDialog
|
|
@@ -249,7 +648,7 @@
|
|
|
249
648
|
bind:open={deleteOpen}
|
|
250
649
|
onOpenChange={(v) => (deleteOpen = v)}
|
|
251
650
|
onDeleted={loadUsers}
|
|
252
|
-
|
|
651
|
+
user={deletingUser}
|
|
253
652
|
{currentUserId}
|
|
254
653
|
/>
|
|
255
654
|
|
|
@@ -22,9 +22,7 @@
|
|
|
22
22
|
};
|
|
23
23
|
</script>
|
|
24
24
|
|
|
25
|
-
<Card.Root
|
|
26
|
-
class="glass-card rounded-2xl border-slate-200/50 bg-white/80 backdrop-blur-xl transition-all duration-300 hover:-translate-y-1 hover:shadow-xl dark:border-white/10 dark:bg-slate-900/60"
|
|
27
|
-
>
|
|
25
|
+
<Card.Root>
|
|
28
26
|
<Card.Header>
|
|
29
27
|
<Card.Title>{lang[interfaceLanguage.current].title}</Card.Title>
|
|
30
28
|
<Card.Description>{lang[interfaceLanguage.current].description}</Card.Description>
|
|
@@ -39,10 +37,7 @@
|
|
|
39
37
|
<div class="space-y-4">
|
|
40
38
|
<p>
|
|
41
39
|
{lang[interfaceLanguage.current].altInfo}
|
|
42
|
-
<span
|
|
43
|
-
class="bg-gradient-to-r from-[#2D4A77] to-[#4975AE] bg-clip-text text-2xl font-bold text-transparent"
|
|
44
|
-
>{overview.filesWithoutAlt}</span
|
|
45
|
-
>
|
|
40
|
+
<span class="text-2xl font-bold text-primary">{overview.filesWithoutAlt}</span>
|
|
46
41
|
</p>
|
|
47
42
|
</div>
|
|
48
43
|
{/await}
|