betterstart-cli 0.0.13 → 0.0.14
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/assets/adapters/next/plugins/blog/schemas/menus.json +51 -11
- package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar.tsx +7 -1
- package/dist/assets/adapters/next/templates/init/components/layouts/sidebar-branding.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-grid.tsx +107 -0
- package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table.tsx +58 -45
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/lifecycle-hooks-tab.tsx +3 -3
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/snippets-tab.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/entity-filters-bar.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/media/edit-media-dialog-content.tsx +2 -2
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/sort-indicator.tsx +7 -3
- package/dist/assets/adapters/next/templates/init/data/navigation.ts +2 -0
- package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-table-button.ts +85 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/audit/get-audit-logs.ts +41 -16
- package/dist/assets/adapters/next/templates/init/lib/actions/audit/index.ts +2 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/audit/types.ts +5 -0
- package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/runner.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/types.ts +2 -1
- package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-detail-drawer.tsx +51 -43
- package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-detail-row.tsx +2 -4
- package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-page-content.tsx +465 -179
- package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-page-skeleton.tsx +3 -15
- package/dist/assets/adapters/next/templates/init/pages/dashboard-page.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/forgot-password-page-skeleton.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/login-page-skeleton.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/reset-password-page-skeleton.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/users/users-page-content.tsx +83 -2
- package/dist/assets/adapters/next/templates/init/pages/users/users-table.tsx +126 -78
- package/dist/assets/adapters/next/templates/init/utils/editor/tiptap.ts +7 -8
- package/dist/assets/adapters/next/templates/init/utils/form/list-field.ts +17 -0
- package/dist/assets/adapters/next/templates/init/utils/user/user.ts +70 -0
- package/dist/assets/shared-assets/react-admin/custom/content-editor/math-bubble-menu.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/selection-bubble-menu.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/source-mode.tsx +0 -2
- package/dist/assets/shared-assets/react-admin/custom/content-editor/table-button.tsx +17 -65
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-control-popover.tsx +3 -8
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-popover.tsx +1 -2
- package/dist/assets/shared-assets/react-admin/custom/date-range-picker.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/dynamic-list-field.tsx +20 -8
- package/dist/assets/shared-assets/react-admin/custom/gallery-field.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/custom/media-gallery-field.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/custom/nested-object-list-field.tsx +211 -0
- package/dist/assets/shared-assets/react-admin/custom/placeholder-card.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/placeholder.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/sortable-gallery-item.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/schema.json +136 -0
- package/dist/assets/shared-assets/react-admin/ui/accordion.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/alert.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/card.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/chart.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/checkbox.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/combobox.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/command.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/context-menu.tsx +6 -6
- package/dist/assets/shared-assets/react-admin/ui/drawer.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/dropdown-menu.tsx +6 -6
- package/dist/assets/shared-assets/react-admin/ui/empty.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/field.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/form.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/hover-card.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/input-group.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/item.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/menubar.tsx +7 -7
- package/dist/assets/shared-assets/react-admin/ui/navigation-menu.tsx +4 -4
- package/dist/assets/shared-assets/react-admin/ui/popover.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/resizable.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/select.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/sidebar.tsx +3 -3
- package/dist/assets/shared-assets/react-admin/ui/skeleton.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/tabs.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/textarea.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/tooltip.tsx +1 -1
- package/dist/cli.js +2688 -1644
- package/dist/cli.js.map +1 -1
- package/package.json +10 -2
- package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-filters.tsx +0 -152
package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-page-skeleton.tsx
CHANGED
|
@@ -1,19 +1,7 @@
|
|
|
1
|
-
import { PageHeader } from '@admin/components/shared/page-header'
|
|
2
|
-
import { Skeleton } from '@admin/components/ui/skeleton'
|
|
3
|
-
|
|
4
1
|
export function AuditLogPageSkeleton() {
|
|
5
2
|
return (
|
|
6
|
-
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
<div className="grid gap-2 md:grid-cols-4">
|
|
10
|
-
<Skeleton className="h-9" />
|
|
11
|
-
<Skeleton className="h-9" />
|
|
12
|
-
<Skeleton className="h-9" />
|
|
13
|
-
<Skeleton className="h-9" />
|
|
14
|
-
</div>
|
|
15
|
-
<Skeleton className="h-[520px]" />
|
|
16
|
-
</main>
|
|
17
|
-
</>
|
|
3
|
+
<div className="flex items-center justify-center h-48">
|
|
4
|
+
<div className="text-muted-foreground">Loading Audit Logs...</div>
|
|
5
|
+
</div>
|
|
18
6
|
)
|
|
19
7
|
}
|
|
@@ -63,7 +63,7 @@ export default function DashboardPage() {
|
|
|
63
63
|
<div className="flex flex-col w-full gap-1">
|
|
64
64
|
{envVariables.map((env) => (
|
|
65
65
|
<div
|
|
66
|
-
className="flex flex-col w-full bg-card border shadow-card border-border rounded-
|
|
66
|
+
className="flex flex-col w-full bg-card border shadow-card border-border rounded-lg pl-4 pr-2 py-2"
|
|
67
67
|
key={env.name}
|
|
68
68
|
>
|
|
69
69
|
<div className="flex items-center justify-between">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export function ForgotPasswordPageSkeleton() {
|
|
2
2
|
return (
|
|
3
|
-
<div className="overflow-hidden rounded-
|
|
3
|
+
<div className="overflow-hidden rounded-lg border bg-card">
|
|
4
4
|
<div className="grid md:grid-cols-[5fr_7fr]">
|
|
5
5
|
<div className="hidden min-h-[460px] bg-muted md:block" />
|
|
6
6
|
<div className="flex min-h-[460px] flex-col gap-10 p-6 md:p-20">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export function LoginPageSkeleton() {
|
|
2
2
|
return (
|
|
3
|
-
<div className="overflow-hidden rounded-
|
|
3
|
+
<div className="overflow-hidden rounded-lg border bg-card">
|
|
4
4
|
<div className="grid md:grid-cols-[5fr_7fr]">
|
|
5
5
|
<div className="hidden min-h-[540px] bg-muted md:block" />
|
|
6
6
|
<div className="flex min-h-[540px] flex-col gap-10 p-6 md:p-20">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export function ResetPasswordPageSkeleton() {
|
|
2
2
|
return (
|
|
3
|
-
<div className="overflow-hidden rounded-
|
|
3
|
+
<div className="overflow-hidden rounded-lg border bg-card">
|
|
4
4
|
<div className="grid md:grid-cols-[5fr_7fr]">
|
|
5
5
|
<div className="hidden min-h-[520px] bg-muted md:block" />
|
|
6
6
|
<div className="flex min-h-[520px] flex-col gap-10 p-6 md:p-20">
|
|
@@ -1,12 +1,83 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { EntityFiltersBar, type FilterDef } from '@admin/components/shared/entity-filters-bar'
|
|
3
4
|
import { PageHeader } from '@admin/components/shared/page-header'
|
|
5
|
+
import { UserRole } from '@admin/types/auth'
|
|
6
|
+
import { useIsFetching } from '@tanstack/react-query'
|
|
7
|
+
import { parseAsInteger, parseAsString, useQueryState, useQueryStates } from 'nuqs'
|
|
4
8
|
import React from 'react'
|
|
5
9
|
import { columns } from './columns'
|
|
6
10
|
import { CreateUserDialog } from './create-user-dialog'
|
|
7
11
|
import { UsersTable } from './users-table'
|
|
8
12
|
|
|
9
13
|
export function UsersPageContent() {
|
|
14
|
+
const isSearchFetching = useIsFetching({ queryKey: ['users'] }) > 0
|
|
15
|
+
const [search, setSearch] = useQueryState('q', parseAsString.withDefault(''))
|
|
16
|
+
const [, setPageIndex] = useQueryState('page', parseAsInteger.withDefault(0))
|
|
17
|
+
const [{ role, status }, setFilters] = useQueryStates({
|
|
18
|
+
role: parseAsString.withDefault(''),
|
|
19
|
+
status: parseAsString.withDefault('')
|
|
20
|
+
})
|
|
21
|
+
const activeFilterCount = [search, role, status].filter(Boolean).length
|
|
22
|
+
const filters = [
|
|
23
|
+
{
|
|
24
|
+
key: 'status',
|
|
25
|
+
label: 'Status',
|
|
26
|
+
type: 'select',
|
|
27
|
+
value: status,
|
|
28
|
+
options: [
|
|
29
|
+
{ label: 'Verified', value: 'verified' },
|
|
30
|
+
{ label: 'Unverified', value: 'unverified' }
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: 'role',
|
|
35
|
+
label: 'Role',
|
|
36
|
+
type: 'select',
|
|
37
|
+
value: role,
|
|
38
|
+
options: [
|
|
39
|
+
{ label: 'Admin', value: UserRole.ADMIN },
|
|
40
|
+
{ label: 'Editor', value: UserRole.EDITOR },
|
|
41
|
+
{ label: 'Member', value: UserRole.MEMBER }
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
] satisfies FilterDef[]
|
|
45
|
+
|
|
46
|
+
const handleSearch = React.useCallback(
|
|
47
|
+
(value: string | null) => {
|
|
48
|
+
React.startTransition(() => {
|
|
49
|
+
void setPageIndex(0)
|
|
50
|
+
void setSearch(value)
|
|
51
|
+
})
|
|
52
|
+
},
|
|
53
|
+
[setSearch, setPageIndex]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const handleFilterChange = React.useCallback(
|
|
57
|
+
(updates: Record<string, string | null>) => {
|
|
58
|
+
const nextFilters: { role?: string | null; status?: string | null } = {}
|
|
59
|
+
if ('role' in updates) nextFilters.role = updates.role
|
|
60
|
+
if ('status' in updates) nextFilters.status = updates.status
|
|
61
|
+
|
|
62
|
+
React.startTransition(() => {
|
|
63
|
+
void setPageIndex(0)
|
|
64
|
+
void setFilters(nextFilters)
|
|
65
|
+
})
|
|
66
|
+
},
|
|
67
|
+
[setFilters, setPageIndex]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const handleClearAll = React.useCallback(() => {
|
|
71
|
+
React.startTransition(() => {
|
|
72
|
+
void setPageIndex(0)
|
|
73
|
+
void setSearch(null)
|
|
74
|
+
void setFilters({
|
|
75
|
+
role: null,
|
|
76
|
+
status: null
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
}, [setFilters, setSearch, setPageIndex])
|
|
80
|
+
|
|
10
81
|
return (
|
|
11
82
|
<React.Fragment>
|
|
12
83
|
<PageHeader
|
|
@@ -17,8 +88,18 @@ export function UsersPageContent() {
|
|
|
17
88
|
</div>
|
|
18
89
|
}
|
|
19
90
|
/>
|
|
20
|
-
<
|
|
21
|
-
|
|
91
|
+
<EntityFiltersBar
|
|
92
|
+
filters={filters}
|
|
93
|
+
activeFilterCount={activeFilterCount}
|
|
94
|
+
onFilterChange={handleFilterChange}
|
|
95
|
+
onClearAll={handleClearAll}
|
|
96
|
+
search={search}
|
|
97
|
+
searchPlaceholder="Search users..."
|
|
98
|
+
isPending={isSearchFetching}
|
|
99
|
+
onSearch={handleSearch}
|
|
100
|
+
/>
|
|
101
|
+
<main className="space-y-4 px-4 pb-4 flex-1">
|
|
102
|
+
<UsersTable columns={columns} search={search} status={status} role={role} />
|
|
22
103
|
</main>
|
|
23
104
|
</React.Fragment>
|
|
24
105
|
)
|
|
@@ -1,64 +1,102 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { authClient } from '@admin/auth/client'
|
|
4
|
+
import {
|
|
5
|
+
DataGrid,
|
|
6
|
+
DataGridBody,
|
|
7
|
+
DataGridCell,
|
|
8
|
+
DataGridHead,
|
|
9
|
+
DataGridHeader,
|
|
10
|
+
DataGridRow
|
|
11
|
+
} from '@admin/components/shared/data-table/data-grid'
|
|
4
12
|
import { DataTablePagination } from '@admin/components/shared/data-table/data-table-pagination'
|
|
5
13
|
import { Card, CardContent } from '@admin/components/ui/card'
|
|
6
|
-
import {
|
|
7
|
-
Table,
|
|
8
|
-
TableBody,
|
|
9
|
-
TableCell,
|
|
10
|
-
TableHead,
|
|
11
|
-
TableHeader,
|
|
12
|
-
TableRow
|
|
13
|
-
} from '@admin/components/ui/table'
|
|
14
14
|
import { useTableUtils } from '@admin/hooks/use-table-utils'
|
|
15
15
|
import { useUsers } from '@admin/hooks/use-users'
|
|
16
16
|
import type { User } from '@admin/types/auth'
|
|
17
17
|
import { cn } from '@admin/utils/shared/cn'
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
type ColumnFiltersState,
|
|
21
|
-
flexRender,
|
|
22
|
-
getCoreRowModel,
|
|
23
|
-
getFilteredRowModel,
|
|
24
|
-
getPaginationRowModel,
|
|
25
|
-
getSortedRowModel,
|
|
26
|
-
type SortingState,
|
|
27
|
-
useReactTable
|
|
28
|
-
} from '@tanstack/react-table'
|
|
18
|
+
import { getUsersPage } from '@admin/utils/user/user'
|
|
19
|
+
import { type ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'
|
|
29
20
|
import { GripVertical } from 'lucide-react'
|
|
30
|
-
import { parseAsInteger,
|
|
21
|
+
import { parseAsInteger, parseAsString, useQueryStates } from 'nuqs'
|
|
31
22
|
import * as React from 'react'
|
|
32
23
|
|
|
33
|
-
interface UsersTableProps<TValue> {
|
|
24
|
+
interface UsersTableProps<TValue> extends React.HTMLAttributes<HTMLDivElement> {
|
|
34
25
|
columns: ColumnDef<User, TValue>[]
|
|
26
|
+
role?: string
|
|
27
|
+
search?: string
|
|
28
|
+
status?: string
|
|
35
29
|
}
|
|
36
30
|
|
|
37
|
-
export function UsersTable<TValue>({
|
|
31
|
+
export function UsersTable<TValue>({
|
|
32
|
+
columns,
|
|
33
|
+
role,
|
|
34
|
+
search,
|
|
35
|
+
status,
|
|
36
|
+
className
|
|
37
|
+
}: UsersTableProps<TValue>) {
|
|
38
|
+
const defaultPageSize = 20
|
|
38
39
|
const { data: session } = authClient.useSession()
|
|
39
40
|
const { data, error, isPending } = useUsers()
|
|
40
|
-
const [
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
const [{ dir: sortDir, page: pageIndex, size: pageSize, sort: sortField }, setTableQuery] =
|
|
42
|
+
useQueryStates({
|
|
43
|
+
dir: parseAsString.withDefault(''),
|
|
44
|
+
page: parseAsInteger.withDefault(0),
|
|
45
|
+
size: parseAsInteger.withDefault(defaultPageSize),
|
|
46
|
+
sort: parseAsString.withDefault('')
|
|
47
|
+
})
|
|
48
|
+
const safePageIndex = Number.isFinite(pageIndex) && pageIndex > 0 ? pageIndex : 0
|
|
49
|
+
const safePageSize =
|
|
50
|
+
pageSize === -1 || (Number.isFinite(pageSize) && pageSize > 0) ? pageSize : defaultPageSize
|
|
51
|
+
const tablePage = React.useMemo(
|
|
52
|
+
() =>
|
|
53
|
+
getUsersPage(data?.users ?? [], {
|
|
54
|
+
pageIndex: safePageIndex,
|
|
55
|
+
pageSize: safePageSize,
|
|
56
|
+
role,
|
|
57
|
+
search,
|
|
58
|
+
sortDir: sortDir === 'desc' ? 'desc' : sortDir === 'asc' ? 'asc' : undefined,
|
|
59
|
+
sortField: sortField || undefined,
|
|
60
|
+
status
|
|
61
|
+
}),
|
|
62
|
+
[data?.users, role, safePageIndex, safePageSize, search, sortDir, sortField, status]
|
|
63
|
+
)
|
|
64
|
+
const tableData = tablePage.users
|
|
65
|
+
const totalRows = tablePage.total
|
|
66
|
+
const effectivePageSize = safePageSize === -1 ? totalRows : safePageSize
|
|
67
|
+
const pageCount = effectivePageSize > 0 ? Math.ceil(totalRows / effectivePageSize) : 1
|
|
68
|
+
const shouldShowPagination = totalRows > defaultPageSize
|
|
45
69
|
const {
|
|
46
70
|
columnSizing,
|
|
47
71
|
handleColumnSizingChange,
|
|
48
72
|
handlePaginationChange,
|
|
73
|
+
handleSortingChange,
|
|
49
74
|
hoveredColumnId,
|
|
50
75
|
registerTable,
|
|
51
76
|
resetColumnSizingToContainer,
|
|
77
|
+
sorting,
|
|
52
78
|
setHoveredColumnId,
|
|
53
79
|
tableContainerRef
|
|
54
80
|
} = useTableUtils<User>({
|
|
55
|
-
pageIndex,
|
|
81
|
+
pageIndex: safePageIndex,
|
|
56
82
|
pageSize: effectivePageSize,
|
|
57
|
-
setPageIndex
|
|
83
|
+
setPageIndex: (nextPageIndex) => setTableQuery({ page: nextPageIndex }),
|
|
84
|
+
setSortingParams: ({
|
|
85
|
+
pageIndex: nextPageIndex,
|
|
86
|
+
sortDir: nextSortDir,
|
|
87
|
+
sortField: nextSortField
|
|
88
|
+
}) =>
|
|
89
|
+
setTableQuery({
|
|
90
|
+
dir: nextSortDir,
|
|
91
|
+
page: nextPageIndex,
|
|
92
|
+
sort: nextSortField
|
|
93
|
+
}),
|
|
94
|
+
sortDir,
|
|
95
|
+
sortField
|
|
58
96
|
})
|
|
59
97
|
|
|
60
98
|
const table = useReactTable({
|
|
61
|
-
data:
|
|
99
|
+
data: tableData,
|
|
62
100
|
columns,
|
|
63
101
|
columnResizeMode: 'onChange',
|
|
64
102
|
defaultColumn: {
|
|
@@ -66,11 +104,11 @@ export function UsersTable<TValue>({ columns }: UsersTableProps<TValue>) {
|
|
|
66
104
|
size: 220
|
|
67
105
|
},
|
|
68
106
|
getCoreRowModel: getCoreRowModel(),
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
107
|
+
manualPagination: true,
|
|
108
|
+
manualSorting: true,
|
|
109
|
+
pageCount,
|
|
110
|
+
rowCount: totalRows,
|
|
111
|
+
onSortingChange: handleSortingChange,
|
|
74
112
|
onColumnSizingChange: handleColumnSizingChange,
|
|
75
113
|
onPaginationChange: handlePaginationChange,
|
|
76
114
|
meta: {
|
|
@@ -86,33 +124,31 @@ export function UsersTable<TValue>({ columns }: UsersTableProps<TValue>) {
|
|
|
86
124
|
},
|
|
87
125
|
state: {
|
|
88
126
|
sorting,
|
|
89
|
-
columnFilters,
|
|
90
127
|
columnSizing,
|
|
91
128
|
pagination: {
|
|
92
|
-
pageIndex,
|
|
129
|
+
pageIndex: safePageIndex,
|
|
93
130
|
pageSize: effectivePageSize
|
|
94
131
|
}
|
|
95
132
|
}
|
|
96
133
|
})
|
|
97
134
|
registerTable(table)
|
|
135
|
+
const gridTemplateColumns = table
|
|
136
|
+
.getVisibleLeafColumns()
|
|
137
|
+
.map((column) => `${column.getSize()}px`)
|
|
138
|
+
.join(' ')
|
|
98
139
|
|
|
99
140
|
return (
|
|
100
|
-
<div className=
|
|
141
|
+
<div className={cn('flex flex-col gap-6 pt-px pb-2 h-full', className)}>
|
|
101
142
|
<Card className="min-h-0 flex-1 p-0">
|
|
102
143
|
<CardContent ref={tableContainerRef} className="flex min-h-0 flex-1 flex-col p-0">
|
|
103
|
-
<
|
|
144
|
+
<DataGrid
|
|
104
145
|
containerClassName="min-h-0 flex-1"
|
|
105
|
-
|
|
146
|
+
gridTemplateColumns={gridTemplateColumns}
|
|
106
147
|
style={{ width: table.getTotalSize() }}
|
|
107
148
|
>
|
|
108
|
-
<
|
|
109
|
-
{table.getVisibleLeafColumns().map((column) => (
|
|
110
|
-
<col key={column.id} style={{ width: column.getSize() }} />
|
|
111
|
-
))}
|
|
112
|
-
</colgroup>
|
|
113
|
-
<TableHeader>
|
|
149
|
+
<DataGridHeader>
|
|
114
150
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
115
|
-
<
|
|
151
|
+
<DataGridRow key={headerGroup.id}>
|
|
116
152
|
{headerGroup.headers.map((header, headerIndex) => {
|
|
117
153
|
const resizeLabel =
|
|
118
154
|
typeof header.column.columnDef.header === 'string'
|
|
@@ -122,7 +158,7 @@ export function UsersTable<TValue>({ columns }: UsersTableProps<TValue>) {
|
|
|
122
158
|
const canResizeColumn =
|
|
123
159
|
header.column.getCanResize() && nextHeader?.column.getCanResize() === true
|
|
124
160
|
return (
|
|
125
|
-
<
|
|
161
|
+
<DataGridHead
|
|
126
162
|
key={header.id}
|
|
127
163
|
className="group/resize-header relative overflow-hidden"
|
|
128
164
|
data-column-id={header.column.id}
|
|
@@ -132,7 +168,6 @@ export function UsersTable<TValue>({ columns }: UsersTableProps<TValue>) {
|
|
|
132
168
|
current === header.column.id ? null : current
|
|
133
169
|
)
|
|
134
170
|
}
|
|
135
|
-
style={{ width: header.getSize() }}
|
|
136
171
|
>
|
|
137
172
|
{header.isPlaceholder
|
|
138
173
|
? null
|
|
@@ -159,30 +194,36 @@ export function UsersTable<TValue>({ columns }: UsersTableProps<TValue>) {
|
|
|
159
194
|
/>
|
|
160
195
|
</button>
|
|
161
196
|
) : null}
|
|
162
|
-
</
|
|
197
|
+
</DataGridHead>
|
|
163
198
|
)
|
|
164
199
|
})}
|
|
165
|
-
</
|
|
200
|
+
</DataGridRow>
|
|
166
201
|
))}
|
|
167
|
-
</
|
|
168
|
-
<
|
|
202
|
+
</DataGridHeader>
|
|
203
|
+
<DataGridBody>
|
|
169
204
|
{isPending ? (
|
|
170
|
-
<
|
|
171
|
-
<
|
|
205
|
+
<DataGridRow>
|
|
206
|
+
<DataGridCell
|
|
207
|
+
className="flex h-24 items-center justify-center text-center"
|
|
208
|
+
style={{ gridColumn: '1 / -1' }}
|
|
209
|
+
>
|
|
172
210
|
<div className="text-muted-foreground">Loading users...</div>
|
|
173
|
-
</
|
|
174
|
-
</
|
|
211
|
+
</DataGridCell>
|
|
212
|
+
</DataGridRow>
|
|
175
213
|
) : error ? (
|
|
176
|
-
<
|
|
177
|
-
<
|
|
214
|
+
<DataGridRow>
|
|
215
|
+
<DataGridCell
|
|
216
|
+
className="flex h-24 items-center justify-center text-center"
|
|
217
|
+
style={{ gridColumn: '1 / -1' }}
|
|
218
|
+
>
|
|
178
219
|
<div className="text-destructive">Error: {error.message}</div>
|
|
179
|
-
</
|
|
180
|
-
</
|
|
220
|
+
</DataGridCell>
|
|
221
|
+
</DataGridRow>
|
|
181
222
|
) : table.getRowModel().rows?.length ? (
|
|
182
223
|
table.getRowModel().rows.map((row) => (
|
|
183
|
-
<
|
|
224
|
+
<DataGridRow key={row.id} className="hover:bg-muted/50">
|
|
184
225
|
{row.getVisibleCells().map((cell) => (
|
|
185
|
-
<
|
|
226
|
+
<DataGridCell
|
|
186
227
|
key={cell.id}
|
|
187
228
|
data-column-id={cell.column.id}
|
|
188
229
|
onMouseEnter={() => setHoveredColumnId(cell.column.id)}
|
|
@@ -191,31 +232,38 @@ export function UsersTable<TValue>({ columns }: UsersTableProps<TValue>) {
|
|
|
191
232
|
current === cell.column.id ? null : current
|
|
192
233
|
)
|
|
193
234
|
}
|
|
194
|
-
style={{ width: cell.column.getSize() }}
|
|
195
235
|
>
|
|
196
236
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
197
|
-
</
|
|
237
|
+
</DataGridCell>
|
|
198
238
|
))}
|
|
199
|
-
</
|
|
239
|
+
</DataGridRow>
|
|
200
240
|
))
|
|
201
241
|
) : (
|
|
202
|
-
<
|
|
203
|
-
<
|
|
242
|
+
<DataGridRow>
|
|
243
|
+
<DataGridCell
|
|
244
|
+
className="flex h-24 items-center justify-center text-center"
|
|
245
|
+
style={{ gridColumn: '1 / -1' }}
|
|
246
|
+
>
|
|
204
247
|
No users found.
|
|
205
|
-
</
|
|
206
|
-
</
|
|
248
|
+
</DataGridCell>
|
|
249
|
+
</DataGridRow>
|
|
207
250
|
)}
|
|
208
|
-
</
|
|
209
|
-
</
|
|
251
|
+
</DataGridBody>
|
|
252
|
+
</DataGrid>
|
|
210
253
|
</CardContent>
|
|
211
254
|
</Card>
|
|
212
255
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
256
|
+
{shouldShowPagination ? (
|
|
257
|
+
<DataTablePagination
|
|
258
|
+
table={table}
|
|
259
|
+
paginationMode="controlled"
|
|
260
|
+
pageSize={safePageSize}
|
|
261
|
+
pageIndex={safePageIndex}
|
|
262
|
+
pageCount={pageCount}
|
|
263
|
+
setPageIndex={(nextPageIndex) => setTableQuery({ page: nextPageIndex })}
|
|
264
|
+
setPageSize={(nextPageSize) => setTableQuery({ page: 0, size: nextPageSize })}
|
|
265
|
+
/>
|
|
266
|
+
) : null}
|
|
219
267
|
</div>
|
|
220
268
|
)
|
|
221
269
|
}
|
|
@@ -638,8 +638,6 @@ export function findNodePosition(props: {
|
|
|
638
638
|
let foundNode: PMNode | null = null
|
|
639
639
|
|
|
640
640
|
editor.state.doc.descendants((currentNode, pos) => {
|
|
641
|
-
// TODO: Needed?
|
|
642
|
-
// if (currentNode.type && currentNode.type.name === node!.type.name) {
|
|
643
641
|
if (currentNode === node) {
|
|
644
642
|
foundPos = pos
|
|
645
643
|
foundNode = currentNode
|
|
@@ -784,13 +782,14 @@ export function isAllowedUri(uri: string | undefined, protocols?: ProtocolConfig
|
|
|
784
782
|
|
|
785
783
|
return (
|
|
786
784
|
!uri ||
|
|
787
|
-
uri
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
785
|
+
uri
|
|
786
|
+
.replace(ATTR_WHITESPACE, '')
|
|
787
|
+
.match(
|
|
788
|
+
new RegExp(
|
|
789
|
+
`^(?:(?:${allowedProtocols.join('|')}):|[^a-z]|[a-z0-9+.-]+(?:[^a-z+.-:]|$))`,
|
|
790
|
+
'i'
|
|
791
|
+
)
|
|
792
792
|
)
|
|
793
|
-
)
|
|
794
793
|
)
|
|
795
794
|
}
|
|
796
795
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function cloneDefaultValue(value: unknown): unknown {
|
|
2
|
+
if (Array.isArray(value)) return value.map(cloneDefaultValue)
|
|
3
|
+
|
|
4
|
+
if (value && typeof value === 'object') {
|
|
5
|
+
const clonedEntries = Object.entries(value as Record<string, unknown>).map(
|
|
6
|
+
([key, childValue]) => [key, cloneDefaultValue(childValue)] as const
|
|
7
|
+
)
|
|
8
|
+
return Object.fromEntries(clonedEntries)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return value
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createListItemKey(): string {
|
|
15
|
+
const randomKey = Math.random().toString(36).slice(2)
|
|
16
|
+
return globalThis.crypto?.randomUUID?.() ?? randomKey
|
|
17
|
+
}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
import type { User, UsersPage } from '@admin/types/auth'
|
|
2
|
+
|
|
3
|
+
interface GetUsersPageOptions {
|
|
4
|
+
pageIndex: number
|
|
5
|
+
pageSize: number
|
|
6
|
+
role?: string
|
|
7
|
+
search?: string
|
|
8
|
+
sortDir?: 'asc' | 'desc'
|
|
9
|
+
sortField?: string
|
|
10
|
+
status?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const USER_SORT_FIELDS = new Set(['createdAt', 'email', 'emailVerified', 'role'])
|
|
14
|
+
|
|
1
15
|
export function getUserInitials(nameOrEmail: string): string {
|
|
2
16
|
const parts = nameOrEmail.includes('@')
|
|
3
17
|
? nameOrEmail.split('@')[0].split(/[._-]/)
|
|
@@ -9,3 +23,59 @@ export function getUserInitials(nameOrEmail: string): string {
|
|
|
9
23
|
.map((part) => part[0].toUpperCase())
|
|
10
24
|
.join('')
|
|
11
25
|
}
|
|
26
|
+
|
|
27
|
+
function normalizeUserFilterValue(value: string | null | undefined): string {
|
|
28
|
+
return (value ?? '').trim().toLowerCase()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getUserSortValue(user: User, sortField: string): number | string {
|
|
32
|
+
if (sortField === 'createdAt') return Date.parse(user.createdAt)
|
|
33
|
+
if (sortField === 'emailVerified') return user.emailVerified ? 1 : 0
|
|
34
|
+
if (sortField === 'role') return user.role.toLowerCase()
|
|
35
|
+
return user.email.toLowerCase()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function compareUserSortValues(a: number | string, b: number | string): number {
|
|
39
|
+
if (typeof a === 'number' && typeof b === 'number') return a - b
|
|
40
|
+
return String(a).localeCompare(String(b))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getUsersPage(users: User[], options: GetUsersPageOptions): UsersPage {
|
|
44
|
+
const search = normalizeUserFilterValue(options.search)
|
|
45
|
+
const role = normalizeUserFilterValue(options.role)
|
|
46
|
+
const status = normalizeUserFilterValue(options.status)
|
|
47
|
+
const sortField =
|
|
48
|
+
options.sortField && USER_SORT_FIELDS.has(options.sortField) ? options.sortField : ''
|
|
49
|
+
const sortDirection = options.sortDir === 'desc' ? -1 : 1
|
|
50
|
+
const filteredUsers = users.filter((user) => {
|
|
51
|
+
const matchesSearch =
|
|
52
|
+
!search ||
|
|
53
|
+
user.email.toLowerCase().includes(search) ||
|
|
54
|
+
user.name.toLowerCase().includes(search)
|
|
55
|
+
const matchesRole = !role || user.role.toLowerCase() === role
|
|
56
|
+
const matchesStatus =
|
|
57
|
+
!status ||
|
|
58
|
+
(status === 'verified' && user.emailVerified) ||
|
|
59
|
+
(status === 'unverified' && !user.emailVerified)
|
|
60
|
+
|
|
61
|
+
return matchesSearch && matchesRole && matchesStatus
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
if (sortField) {
|
|
65
|
+
filteredUsers.sort((a, b) => {
|
|
66
|
+
const comparison = compareUserSortValues(
|
|
67
|
+
getUserSortValue(a, sortField),
|
|
68
|
+
getUserSortValue(b, sortField)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return comparison * sortDirection
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const total = filteredUsers.length
|
|
76
|
+
const pageSize = options.pageSize === -1 ? total : Math.max(options.pageSize, 1)
|
|
77
|
+
const start = Math.max(options.pageIndex, 0) * pageSize
|
|
78
|
+
const pageUsers = pageSize > 0 ? filteredUsers.slice(start, start + pageSize) : filteredUsers
|
|
79
|
+
|
|
80
|
+
return { users: pageUsers, total }
|
|
81
|
+
}
|
|
@@ -63,7 +63,7 @@ export function MathBubbleMenu({ editor }: { editor: Editor }) {
|
|
|
63
63
|
role="toolbar"
|
|
64
64
|
aria-label="Math formatting toolbar"
|
|
65
65
|
className={cn(
|
|
66
|
-
'flex items-center gap-1 overflow-x-auto border-border rounded-
|
|
66
|
+
'flex items-center gap-1 overflow-x-auto border-border rounded-lg border bg-popover p-0 text-popover-foreground shadow-menu [scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
|
|
67
67
|
'relative z-50'
|
|
68
68
|
)}
|
|
69
69
|
>
|
package/dist/assets/shared-assets/react-admin/custom/content-editor/selection-bubble-menu.tsx
CHANGED
|
@@ -72,7 +72,7 @@ export function SelectionBubbleMenu({ editor }: { editor: Editor }) {
|
|
|
72
72
|
role="toolbar"
|
|
73
73
|
aria-label="Link formatting toolbar"
|
|
74
74
|
className={cn(
|
|
75
|
-
'flex items-center gap-1 overflow-x-auto border-border border bg-popover p-4 text-foreground shadow-2xl [scrollbar-width:none] [&::-webkit-scrollbar]:hidden rounded-
|
|
75
|
+
'flex items-center gap-1 overflow-x-auto border-border border bg-popover p-4 text-foreground shadow-2xl [scrollbar-width:none] [&::-webkit-scrollbar]:hidden rounded-lg',
|
|
76
76
|
'relative z-10'
|
|
77
77
|
)}
|
|
78
78
|
>
|
|
@@ -105,7 +105,6 @@ export function SourceMode({
|
|
|
105
105
|
isMarkdownLinkActive,
|
|
106
106
|
markdownLinkUrl,
|
|
107
107
|
openMediaGalleryAtCursor,
|
|
108
|
-
openMarkdownLink,
|
|
109
108
|
redoMarkdownChange,
|
|
110
109
|
removeHighlight,
|
|
111
110
|
removeMarkdownLink,
|
|
@@ -296,7 +295,6 @@ export function SourceMode({
|
|
|
296
295
|
disabled={disabled}
|
|
297
296
|
isActive={isMarkdownLinkActive}
|
|
298
297
|
onOpenChange={handleMarkdownLinkPopoverOpenChange}
|
|
299
|
-
openLink={openMarkdownLink}
|
|
300
298
|
removeLink={removeMarkdownLink}
|
|
301
299
|
setLink={setMarkdownLink}
|
|
302
300
|
setUrl={setMarkdownLinkUrl}
|