From 8f833388eb7fbf85d3073b652fda2d357f122803 Mon Sep 17 00:00:00 2001 From: DarkSky <25152247+darkskygit@users.noreply.github.com> Date: Tue, 17 Feb 2026 17:40:29 +0800 Subject: [PATCH] feat: improve admin panel design (#14464) --- .docker/selfhost/schema.json | 4 +- .github/workflows/release.yml | 2 +- .../backend/server/src/core/mail/config.ts | 4 +- .../src/core/workspaces/resolvers/admin.ts | 73 ++- packages/backend/server/src/env.ts | 1 + .../server/src/models/workspace-analytics.ts | 120 ++--- .../backend/server/src/plugins/indexer/job.ts | 4 + packages/frontend/admin/package.json | 4 +- packages/frontend/admin/src/app.tsx | 94 ++-- .../src/components/shared/confirm-dialog.tsx | 10 +- .../src/components/shared/data-table.spec.tsx | 85 ++++ .../src/components/shared/data-table.tsx | 19 +- .../components/shared/feature-toggle-list.tsx | 12 +- .../src/components/theme-provider.spec.tsx | 35 ++ .../admin/src/components/theme-provider.tsx | 16 + .../admin/src/components/ui/button.spec.tsx | 36 ++ .../admin/src/components/ui/button.tsx | 22 +- .../frontend/admin/src/components/ui/card.tsx | 2 +- .../admin/src/components/ui/carousel.tsx | 79 +-- .../admin/src/components/ui/chart.tsx | 112 ++--- .../admin/src/components/ui/dialog.tsx | 2 +- .../frontend/admin/src/components/ui/form.tsx | 36 +- .../admin/src/components/ui/input.tsx | 2 +- .../admin/src/components/ui/select.tsx | 6 +- .../admin/src/components/ui/sheet.tsx | 2 +- .../admin/src/components/ui/skeleton.tsx | 2 +- .../admin/src/components/ui/sonner.tsx | 2 +- .../admin/src/components/ui/switch.tsx | 4 +- .../admin/src/components/ui/table.tsx | 2 +- .../admin/src/components/ui/textarea.tsx | 2 +- .../admin/src/components/ui/toggle-group.tsx | 45 +- .../admin/src/components/ui/tooltip.tsx | 2 +- packages/frontend/admin/src/config.json | 4 +- packages/frontend/admin/src/global.css | 169 ++++--- packages/frontend/admin/src/global.spec.ts | 27 ++ .../admin/src/modules/about/about.tsx | 2 +- .../admin/src/modules/about/index.tsx | 2 +- .../modules/accounts/components/columns.tsx | 64 +-- .../components/data-table-row-actions.tsx | 9 +- .../import-users/csv-format-guidance.tsx | 13 +- .../import-users/file-upload-area.tsx | 24 +- .../import-users/import-content.tsx | 5 +- .../src/modules/accounts/components/logo.tsx | 2 +- .../modules/accounts/components/user-form.tsx | 33 +- .../accounts/components/user-table.tsx | 52 +- .../admin/src/modules/accounts/index.tsx | 2 +- .../admin/src/modules/ai/edit-prompt.tsx | 12 +- .../frontend/admin/src/modules/ai/index.tsx | 4 +- .../frontend/admin/src/modules/ai/keys.tsx | 2 +- .../frontend/admin/src/modules/auth/index.tsx | 2 +- .../src/modules/dashboard/index.spec.tsx | 125 +++++ .../admin/src/modules/dashboard/index.tsx | 459 +++++++++++++----- .../frontend/admin/src/modules/header.tsx | 24 +- .../frontend/admin/src/modules/layout.tsx | 154 +++--- .../src/modules/nav/collapsible-item.tsx | 44 -- .../frontend/admin/src/modules/nav/context.ts | 21 - .../admin/src/modules/nav/nav-item.spec.tsx | 42 ++ .../admin/src/modules/nav/nav-item.tsx | 58 +-- .../frontend/admin/src/modules/nav/nav.tsx | 68 +-- .../admin/src/modules/nav/server-version.tsx | 10 +- .../admin/src/modules/nav/settings-item.tsx | 199 +------- .../admin/src/modules/nav/user-dropdown.tsx | 87 ++-- .../admin/src/modules/queue/index.tsx | 3 +- .../settings/config-input-row.spec.tsx | 107 ++++ .../src/modules/settings/config-input-row.tsx | 99 +++- .../admin/src/modules/settings/index.spec.tsx | 215 ++++++++ .../admin/src/modules/settings/index.tsx | 285 ++++++++--- .../modules/settings/use-app-config.spec.ts | 193 ++++++++ .../src/modules/settings/use-app-config.ts | 217 ++++++++- .../admin/src/modules/setup/create-admin.tsx | 4 +- .../admin/src/modules/setup/index.tsx | 2 +- .../frontend/admin/src/modules/setup/logo.svg | 2 +- .../modules/workspaces/components/columns.tsx | 56 +-- .../workspaces/components/workspace-panel.tsx | 52 +- .../workspace-shared-links-panel.tsx | 23 +- .../admin/src/modules/workspaces/index.tsx | 2 +- packages/frontend/admin/tailwind.config.js | 93 +++- .../frontend/apps/ios/src/modal-config.tsx | 12 +- .../component/src/ui/menu/mobile/root.tsx | 22 +- .../component/src/ui/modal/confirm-modal.tsx | 19 +- .../component/src/ui/modal/prompt-modal.tsx | 24 +- .../navigation/nodes/tag/dialog.tsx | 8 +- .../mobile/dialogs/setting/swipe-dialog.tsx | 13 +- .../detail/journal-date-picker/index.tsx | 31 +- .../src/modules/workbench/view/view-root.tsx | 12 +- yarn.lock | 10 +- 86 files changed, 2633 insertions(+), 1431 deletions(-) create mode 100644 packages/frontend/admin/src/components/shared/data-table.spec.tsx create mode 100644 packages/frontend/admin/src/components/theme-provider.spec.tsx create mode 100644 packages/frontend/admin/src/components/theme-provider.tsx create mode 100644 packages/frontend/admin/src/components/ui/button.spec.tsx create mode 100644 packages/frontend/admin/src/global.spec.ts create mode 100644 packages/frontend/admin/src/modules/dashboard/index.spec.tsx delete mode 100644 packages/frontend/admin/src/modules/nav/collapsible-item.tsx delete mode 100644 packages/frontend/admin/src/modules/nav/context.ts create mode 100644 packages/frontend/admin/src/modules/nav/nav-item.spec.tsx create mode 100644 packages/frontend/admin/src/modules/settings/config-input-row.spec.tsx create mode 100644 packages/frontend/admin/src/modules/settings/index.spec.tsx create mode 100644 packages/frontend/admin/src/modules/settings/use-app-config.spec.ts diff --git a/.docker/selfhost/schema.json b/.docker/selfhost/schema.json index f9eece72ad..ef819a6089 100644 --- a/.docker/selfhost/schema.json +++ b/.docker/selfhost/schema.json @@ -222,7 +222,7 @@ }, "SMTP.sender": { "type": "string", - "description": "Sender of all the emails (e.g. \"AFFiNE Self Hosted \")\n@default \"AFFiNE Self Hosted \"\n@environment `MAILER_SENDER`", + "description": "Sender of all the emails (e.g. \"AFFiNE Self Hosted <noreply@example.com>\")\n@default \"AFFiNE Self Hosted \"\n@environment `MAILER_SENDER`", "default": "AFFiNE Self Hosted " }, "SMTP.ignoreTLS": { @@ -262,7 +262,7 @@ }, "fallbackSMTP.sender": { "type": "string", - "description": "Sender of all the emails (e.g. \"AFFiNE Self Hosted \")\n@default \"\"", + "description": "Sender of all the emails (e.g. \"AFFiNE Self Hosted <noreply@example.com>\")\n@default \"\"", "default": "" }, "fallbackSMTP.ignoreTLS": { diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 822c50c828..98c0db20f9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -148,7 +148,7 @@ jobs: name: Wait for approval with: secret: ${{ secrets.GITHUB_TOKEN }} - approvers: darkskygit,pengx17,L-Sun,EYHN + approvers: darkskygit minimum-approvals: 1 fail-on-denial: true issue-title: Please confirm to release docker image diff --git a/packages/backend/server/src/core/mail/config.ts b/packages/backend/server/src/core/mail/config.ts index 88b73b5050..97c61f41a7 100644 --- a/packages/backend/server/src/core/mail/config.ts +++ b/packages/backend/server/src/core/mail/config.ts @@ -56,7 +56,7 @@ defineModuleConfig('mailer', { env: 'MAILER_PASSWORD', }, 'SMTP.sender': { - desc: 'Sender of all the emails (e.g. "AFFiNE Self Hosted ")', + desc: 'Sender of all the emails (e.g. "AFFiNE Self Hosted <noreply@example.com>")', default: 'AFFiNE Self Hosted ', env: 'MAILER_SENDER', }, @@ -92,7 +92,7 @@ defineModuleConfig('mailer', { default: '', }, 'fallbackSMTP.sender': { - desc: 'Sender of all the emails (e.g. "AFFiNE Self Hosted ")', + desc: 'Sender of all the emails (e.g. "AFFiNE Self Hosted <noreply@example.com>")', default: '', }, 'fallbackSMTP.ignoreTLS': { diff --git a/packages/backend/server/src/core/workspaces/resolvers/admin.ts b/packages/backend/server/src/core/workspaces/resolvers/admin.ts index d5b76d2df9..1a59780545 100644 --- a/packages/backend/server/src/core/workspaces/resolvers/admin.ts +++ b/packages/backend/server/src/core/workspaces/resolvers/admin.ts @@ -2,6 +2,7 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { Args, Field, + Info, InputType, Int, Mutation, @@ -14,6 +15,12 @@ import { ResolveField, Resolver, } from '@nestjs/graphql'; +import { + type FragmentDefinitionNode, + type GraphQLResolveInfo, + Kind, + type SelectionNode, +} from 'graphql'; import { SafeIntResolver } from 'graphql-scalars'; import { PaginationInput, URLHelper } from '../../../base'; @@ -53,6 +60,44 @@ registerEnumType(AdminSharedLinksOrder, { name: 'AdminSharedLinksOrder', }); +function hasSelectedField( + selections: readonly SelectionNode[], + fieldName: string, + fragments: Record +): boolean { + for (const selection of selections) { + if (selection.kind === Kind.FIELD) { + if (selection.name.value === fieldName) { + return true; + } + continue; + } + + if (selection.kind === Kind.INLINE_FRAGMENT) { + if ( + hasSelectedField( + selection.selectionSet.selections, + fieldName, + fragments + ) + ) { + return true; + } + continue; + } + + const fragment = fragments[selection.name.value]; + if ( + fragment && + hasSelectedField(fragment.selectionSet.selections, fieldName, fragments) + ) { + return true; + } + } + + return false; +} + @InputType() class ListWorkspaceInput { @Field(() => Int, { defaultValue: 20 }) @@ -471,22 +516,40 @@ export class AdminWorkspaceResolver { }) async adminDashboard( @Args('input', { nullable: true, type: () => AdminDashboardInput }) - input?: AdminDashboardInput + input?: AdminDashboardInput, + @Info() info?: GraphQLResolveInfo ) { this.assertCloudOnly(); + const includeTopSharedLinks = Boolean( + info?.fieldNodes.some( + node => + node.selectionSet && + hasSelectedField( + node.selectionSet.selections, + 'topSharedLinks', + info.fragments + ) + ) + ); + const dashboard = await this.models.workspaceAnalytics.adminGetDashboard({ timezone: input?.timezone, storageHistoryDays: input?.storageHistoryDays, syncHistoryHours: input?.syncHistoryHours, sharedLinkWindowDays: input?.sharedLinkWindowDays, + includeTopSharedLinks, }); return { ...dashboard, - topSharedLinks: dashboard.topSharedLinks.map(link => ({ - ...link, - shareUrl: this.url.link(`/workspace/${link.workspaceId}/${link.docId}`), - })), + topSharedLinks: includeTopSharedLinks + ? dashboard.topSharedLinks.map(link => ({ + ...link, + shareUrl: this.url.link( + `/workspace/${link.workspaceId}/${link.docId}` + ), + })) + : [], }; } diff --git a/packages/backend/server/src/env.ts b/packages/backend/server/src/env.ts index 21a2125886..a0e44cd9d8 100644 --- a/packages/backend/server/src/env.ts +++ b/packages/backend/server/src/env.ts @@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url'; import pkg from '../package.json' with { type: 'json' }; declare global { + // oxlint-disable-next-line no-shadow-restricted-names namespace globalThis { // oxlint-disable-next-line no-var var env: Readonly; diff --git a/packages/backend/server/src/models/workspace-analytics.ts b/packages/backend/server/src/models/workspace-analytics.ts index 253d165344..8041346797 100644 --- a/packages/backend/server/src/models/workspace-analytics.ts +++ b/packages/backend/server/src/models/workspace-analytics.ts @@ -51,6 +51,7 @@ export type AdminDashboardOptions = { storageHistoryDays?: number; syncHistoryHours?: number; sharedLinkWindowDays?: number; + includeTopSharedLinks?: boolean; }; export type AdminAllSharedLinksOptions = { @@ -262,6 +263,7 @@ export class WorkspaceAnalyticsModel extends BaseModel { 90, DEFAULT_SHARED_LINK_WINDOW_DAYS ); + const includeTopSharedLinks = options.includeTopSharedLinks ?? true; const now = new Date(); @@ -274,6 +276,66 @@ export class WorkspaceAnalyticsModel extends BaseModel { const storageFrom = addUtcDays(currentDay, -(storageHistoryDays - 1)); const sharedFrom = addUtcDays(currentDay, -(sharedLinkWindowDays - 1)); + const topSharedLinksPromise = includeTopSharedLinks + ? this.db.$queryRaw< + { + workspaceId: string; + docId: string; + title: string | null; + publishedAt: Date | null; + docUpdatedAt: Date | null; + workspaceOwnerId: string | null; + lastUpdaterId: string | null; + views: bigint | number; + uniqueViews: bigint | number; + guestViews: bigint | number; + lastAccessedAt: Date | null; + }[] + >` + WITH view_agg AS ( + SELECT + workspace_id, + doc_id, + COALESCE(SUM(total_views), 0) AS views, + COALESCE(SUM(unique_views), 0) AS unique_views, + COALESCE(SUM(guest_views), 0) AS guest_views, + MAX(last_accessed_at) AS last_accessed_at + FROM workspace_doc_view_daily + WHERE date BETWEEN ${sharedFrom}::date AND ${currentDay}::date + GROUP BY workspace_id, doc_id + ) + SELECT + wp.workspace_id AS "workspaceId", + wp.page_id AS "docId", + wp.title AS title, + wp.published_at AS "publishedAt", + sn.updated_at AS "docUpdatedAt", + owner.user_id AS "workspaceOwnerId", + sn.updated_by AS "lastUpdaterId", + COALESCE(v.views, 0) AS views, + COALESCE(v.unique_views, 0) AS "uniqueViews", + COALESCE(v.guest_views, 0) AS "guestViews", + v.last_accessed_at AS "lastAccessedAt" + FROM workspace_pages wp + LEFT JOIN snapshots sn + ON sn.workspace_id = wp.workspace_id AND sn.guid = wp.page_id + LEFT JOIN view_agg v + ON v.workspace_id = wp.workspace_id AND v.doc_id = wp.page_id + LEFT JOIN LATERAL ( + SELECT user_id + FROM workspace_user_permissions + WHERE workspace_id = wp.workspace_id + AND type = ${WorkspaceRole.Owner} + AND status = 'Accepted'::"WorkspaceMemberStatus" + ORDER BY created_at ASC + LIMIT 1 + ) owner ON TRUE + WHERE wp.public = TRUE + ORDER BY views DESC, "uniqueViews" DESC, "workspaceId" ASC, "docId" ASC + LIMIT 10 + ` + : Promise.resolve([]); + const [ syncCurrent, syncTimeline, @@ -350,63 +412,7 @@ export class WorkspaceAnalyticsModel extends BaseModel { AND created_at >= ${sharedFrom} AND created_at <= ${now} `, - this.db.$queryRaw< - { - workspaceId: string; - docId: string; - title: string | null; - publishedAt: Date | null; - docUpdatedAt: Date | null; - workspaceOwnerId: string | null; - lastUpdaterId: string | null; - views: bigint | number; - uniqueViews: bigint | number; - guestViews: bigint | number; - lastAccessedAt: Date | null; - }[] - >` - WITH view_agg AS ( - SELECT - workspace_id, - doc_id, - COALESCE(SUM(total_views), 0) AS views, - COALESCE(SUM(unique_views), 0) AS unique_views, - COALESCE(SUM(guest_views), 0) AS guest_views, - MAX(last_accessed_at) AS last_accessed_at - FROM workspace_doc_view_daily - WHERE date BETWEEN ${sharedFrom}::date AND ${currentDay}::date - GROUP BY workspace_id, doc_id - ) - SELECT - wp.workspace_id AS "workspaceId", - wp.page_id AS "docId", - wp.title AS title, - wp.published_at AS "publishedAt", - sn.updated_at AS "docUpdatedAt", - owner.user_id AS "workspaceOwnerId", - sn.updated_by AS "lastUpdaterId", - COALESCE(v.views, 0) AS views, - COALESCE(v.unique_views, 0) AS "uniqueViews", - COALESCE(v.guest_views, 0) AS "guestViews", - v.last_accessed_at AS "lastAccessedAt" - FROM workspace_pages wp - LEFT JOIN snapshots sn - ON sn.workspace_id = wp.workspace_id AND sn.guid = wp.page_id - LEFT JOIN view_agg v - ON v.workspace_id = wp.workspace_id AND v.doc_id = wp.page_id - LEFT JOIN LATERAL ( - SELECT user_id - FROM workspace_user_permissions - WHERE workspace_id = wp.workspace_id - AND type = ${WorkspaceRole.Owner} - AND status = 'Accepted'::"WorkspaceMemberStatus" - ORDER BY created_at ASC - LIMIT 1 - ) owner ON TRUE - WHERE wp.public = TRUE - ORDER BY views DESC, "uniqueViews" DESC, "workspaceId" ASC, "docId" ASC - LIMIT 10 - `, + topSharedLinksPromise, ]); const storageHistorySeries = storageHistory.map(row => ({ diff --git a/packages/backend/server/src/plugins/indexer/job.ts b/packages/backend/server/src/plugins/indexer/job.ts index ff21ecba2c..9d1be70fbc 100644 --- a/packages/backend/server/src/plugins/indexer/job.ts +++ b/packages/backend/server/src/plugins/indexer/job.ts @@ -132,6 +132,10 @@ export class IndexerJob { indexed: true, }); } + if (!missingDocIds.length && !deletedDocIds.length) { + this.logger.verbose(`workspace ${workspaceId} is already indexed`); + return; + } this.logger.log( `indexed workspace ${workspaceId} with ${missingDocIds.length} missing docs and ${deletedDocIds.length} deleted docs` ); diff --git a/packages/frontend/admin/package.json b/packages/frontend/admin/package.json index 0cf2bb693c..df71544e5d 100644 --- a/packages/frontend/admin/package.json +++ b/packages/frontend/admin/package.json @@ -60,6 +60,7 @@ "zod": "^3.25.76" }, "devDependencies": { + "@testing-library/react": "^16.3.2", "@types/lodash-es": "^4.17.12", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -67,7 +68,8 @@ "shadcn-ui": "^0.9.5", "tailwind-merge": "^3.4.0", "tailwindcss": "^4.1.17", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "vitest": "^3.2.4" }, "scripts": { "build": "affine bundle", diff --git a/packages/frontend/admin/src/app.tsx b/packages/frontend/admin/src/app.tsx index c9d487f02e..36a7ef7a68 100644 --- a/packages/frontend/admin/src/app.tsx +++ b/packages/frontend/admin/src/app.tsx @@ -13,6 +13,7 @@ import { import { toast } from 'sonner'; import { SWRConfig } from 'swr'; +import { ThemeProvider } from './components/theme-provider'; import { TooltipProvider } from './components/ui/tooltip'; import { isAdmin, useCurrentUser, useServerConfig } from './modules/common'; import { Layout } from './modules/layout'; @@ -94,58 +95,55 @@ function RootRoutes() { export const App = () => { return ( - - - - - }> - } /> - } /> - }> - - ) : ( - - ) - } - /> - } /> - - ) : ( - - ) - } - /> - } /> - } /> - } /> - } - > + + + + + + }> + } /> + } /> + }> + ) : ( + + ) + } + /> + } /> + + ) : ( + + ) + } + /> + } /> + } /> + } /> + } /> - - - - - - + + + + + + ); }; diff --git a/packages/frontend/admin/src/components/shared/confirm-dialog.tsx b/packages/frontend/admin/src/components/shared/confirm-dialog.tsx index 5d35dc9ec0..a83ea694c7 100644 --- a/packages/frontend/admin/src/components/shared/confirm-dialog.tsx +++ b/packages/frontend/admin/src/components/shared/confirm-dialog.tsx @@ -39,7 +39,7 @@ export const ConfirmDialog = ({ return ( - + {title} @@ -48,13 +48,19 @@ export const ConfirmDialog = ({
- diff --git a/packages/frontend/admin/src/components/shared/data-table.spec.tsx b/packages/frontend/admin/src/components/shared/data-table.spec.tsx new file mode 100644 index 0000000000..5091f332bd --- /dev/null +++ b/packages/frontend/admin/src/components/shared/data-table.spec.tsx @@ -0,0 +1,85 @@ +/** + * @vitest-environment happy-dom + */ +import type { ColumnDef } from '@tanstack/react-table'; +import { cleanup, render, screen } from '@testing-library/react'; +import { afterEach, describe, expect, test, vi } from 'vitest'; + +import { SharedDataTable } from './data-table'; + +const { DataTablePaginationMock } = vi.hoisted(() => ({ + DataTablePaginationMock: vi.fn(({ disabled }: { disabled?: boolean }) => ( +
+ )), +})); + +vi.mock('./data-table-pagination', () => ({ + DataTablePagination: DataTablePaginationMock, +})); + +type Row = { id: string; name: string }; + +const columns: ColumnDef[] = [ + { + accessorKey: 'name', + header: 'Name', + cell: ({ row }) => row.original.name, + }, +]; + +describe('SharedDataTable', () => { + afterEach(() => { + cleanup(); + DataTablePaginationMock.mockClear(); + }); + + test('renders token-aligned table shell and row data', () => { + const { container } = render( + + ); + + expect(screen.queryByText('Alice')).not.toBeNull(); + + const shell = container.querySelector('.rounded-xl'); + expect(shell).not.toBeNull(); + expect(shell?.className).toContain('border-border'); + expect(shell?.className).toContain('bg-card'); + expect(shell?.className).toContain('shadow-1'); + }); + + test('shows loading overlay and disables pagination while loading', () => { + render( + + ); + + expect(screen.queryByText('Loading...')).not.toBeNull(); + expect(screen.getByTestId('pagination').dataset.disabled).toBe('true'); + }); + + test('renders empty state when there is no data', () => { + render( + + ); + + expect(screen.queryByText('No results.')).not.toBeNull(); + }); +}); diff --git a/packages/frontend/admin/src/components/shared/data-table.tsx b/packages/frontend/admin/src/components/shared/data-table.tsx index 8906cf5165..b5facf7fd9 100644 --- a/packages/frontend/admin/src/components/shared/data-table.tsx +++ b/packages/frontend/admin/src/components/shared/data-table.tsx @@ -21,6 +21,8 @@ import { type ReactNode, useEffect, useState } from 'react'; import { DataTablePagination } from './data-table-pagination'; +const DEFAULT_RESET_FILTERS_DEPS: unknown[] = []; + interface DataTableProps { columns: ColumnDef[]; data: TData[]; @@ -58,7 +60,7 @@ export function SharedDataTable({ rowSelection, onRowSelectionChange, renderToolbar, - resetFiltersDeps = [], + resetFiltersDeps = DEFAULT_RESET_FILTERS_DEPS, }: DataTableProps) { const [columnFilters, setColumnFilters] = useState([]); @@ -88,13 +90,13 @@ export function SharedDataTable({ }); return ( -
+
{renderToolbar?.(table)} -
+
{loading ? ( -
+
({ {table.getHeaderGroups().map(headerGroup => ( - + {headerGroup.headers.map(header => { // Use meta.className if available, otherwise default to flex-1 const meta = header.column.columnDef.meta as @@ -154,7 +159,7 @@ export function SharedDataTable({ {row.getVisibleCells().map(cell => { const meta = cell.column.columnDef.meta as diff --git a/packages/frontend/admin/src/components/shared/feature-toggle-list.tsx b/packages/frontend/admin/src/components/shared/feature-toggle-list.tsx index 3cf4c0c519..1667f2459c 100644 --- a/packages/frontend/admin/src/components/shared/feature-toggle-list.tsx +++ b/packages/frontend/admin/src/components/shared/feature-toggle-list.tsx @@ -3,7 +3,6 @@ import { Label } from '@affine/admin/components/ui/label'; import { Separator } from '@affine/admin/components/ui/separator'; import { Switch } from '@affine/admin/components/ui/switch'; import type { FeatureType } from '@affine/graphql'; -import { cssVarV2 } from '@toeverything/theme/v2'; import { useCallback } from 'react'; import { cn } from '../../utils'; @@ -42,10 +41,7 @@ export const FeatureToggleList = ({ if (!features.length) { return ( -
+
No configurable features.
); @@ -57,10 +53,10 @@ export const FeatureToggleList = ({
( return (