mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore(site): use <TableEmpty /> for table empty states (#27583)
## Summary Standardize table empty and error rows on shared `TableEmpty` so they share the same large empty treatment instead of one-off markup or manual `EmptyState` wrappers. - Replace hand-rolled `text-center` empties (External Auth, OAuth2 apps/secrets/authorized apps, Tasks) - Swap `TableRow`/`TableCell` + `EmptyState` wrappers to `TableEmpty` across audit/connection logs, groups, roles, IdP sync, provisioners, users, versions, permissions, banners, and workspace sharing - Drop redundant padding wrappers on Users and Versions tables - Add an Empty story for External Auth settings
This commit is contained in:
@@ -20,7 +20,6 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "#/components/DropdownMenu/DropdownMenu";
|
||||
import { EmptyState } from "#/components/EmptyState/EmptyState";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -37,6 +36,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import { TableLoader } from "#/components/TableLoader/TableLoader";
|
||||
import { getGroupSubtitle } from "#/modules/groups";
|
||||
|
||||
@@ -198,15 +198,11 @@ export const WorkspaceSharingForm: FC<WorkspaceSharingFormProps> = ({
|
||||
if (sharingSettingsQuery.data.sharing_disabled) {
|
||||
return (
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message="This workspace cannot be shared"
|
||||
description="Workspace sharing has been disabled for this organization."
|
||||
isCompact={isCompact}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty
|
||||
message="This workspace cannot be shared"
|
||||
description="Workspace sharing has been disabled for this organization."
|
||||
isCompact={isCompact}
|
||||
/>
|
||||
</TableBody>
|
||||
);
|
||||
}
|
||||
@@ -232,15 +228,11 @@ export const WorkspaceSharingForm: FC<WorkspaceSharingFormProps> = ({
|
||||
{!workspaceACL ? (
|
||||
<TableLoader />
|
||||
) : isEmpty ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message="No shared members or groups yet"
|
||||
description="Add a member or group using the controls above."
|
||||
isCompact={isCompact}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty
|
||||
message="No shared members or groups yet"
|
||||
description="Add a member or group using the controls above."
|
||||
isCompact={isCompact}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{workspaceACL.group.map((group) => (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ComponentProps, FC } from "react";
|
||||
import type { AuditLog } from "#/api/typesGenerated";
|
||||
import { EmptyState } from "#/components/EmptyState/EmptyState";
|
||||
import { Margins } from "#/components/Margins/Margins";
|
||||
import {
|
||||
PageHeader,
|
||||
@@ -12,12 +11,8 @@ import {
|
||||
type PaginationResult,
|
||||
} from "#/components/PaginationWidget/PaginationContainer";
|
||||
import { PaywallPremium } from "#/components/Paywall/PaywallPremium";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { Table, TableBody } from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import { TableLoader } from "#/components/TableLoader/TableLoader";
|
||||
import { Timeline } from "#/components/Timeline/Timeline";
|
||||
import { docs } from "#/utils/docs";
|
||||
@@ -114,13 +109,7 @@ const AuditTableBody: FC<AuditTableBodyProps> = ({
|
||||
}) => {
|
||||
// An error renders as an empty table.
|
||||
if (error) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState message="An error occurred while loading audit logs" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
return <TableEmpty message="An error occurred while loading audit logs" />;
|
||||
}
|
||||
if (isLoading) {
|
||||
return <TableLoader />;
|
||||
@@ -129,13 +118,7 @@ const AuditTableBody: FC<AuditTableBodyProps> = ({
|
||||
const emptyMessage = isNonInitialPage
|
||||
? "No audit logs available on this page"
|
||||
: "No audit logs available";
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState message={emptyMessage} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
return <TableEmpty message={emptyMessage} />;
|
||||
}
|
||||
if (!auditLogs) {
|
||||
return null;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ComponentProps, FC } from "react";
|
||||
import type { ConnectionLog } from "#/api/typesGenerated";
|
||||
import { EmptyState } from "#/components/EmptyState/EmptyState";
|
||||
import { Margins } from "#/components/Margins/Margins";
|
||||
import {
|
||||
PageHeader,
|
||||
@@ -12,12 +11,8 @@ import {
|
||||
type PaginationResult,
|
||||
} from "#/components/PaginationWidget/PaginationContainer";
|
||||
import { PaywallPremium } from "#/components/Paywall/PaywallPremium";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { Table, TableBody } from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import { TableLoader } from "#/components/TableLoader/TableLoader";
|
||||
import { Timeline } from "#/components/Timeline/Timeline";
|
||||
import { docs } from "#/utils/docs";
|
||||
@@ -113,11 +108,7 @@ const ConnectionLogTableBody: FC<ConnectionLogTableBodyProps> = ({
|
||||
// An error renders as an empty table.
|
||||
if (error) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState message="An error occurred while loading connection logs" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty message="An error occurred while loading connection logs" />
|
||||
);
|
||||
}
|
||||
if (isLoading) {
|
||||
@@ -127,13 +118,7 @@ const ConnectionLogTableBody: FC<ConnectionLogTableBodyProps> = ({
|
||||
const emptyMessage = isNonInitialPage
|
||||
? "No connection logs available on this page"
|
||||
: "No connection logs available";
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState message={emptyMessage} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
return <TableEmpty message={emptyMessage} />;
|
||||
}
|
||||
if (!connectionLogs) {
|
||||
return null;
|
||||
|
||||
+5
-8
@@ -3,16 +3,15 @@ import { type FC, useState } from "react";
|
||||
import type { BannerConfig } from "#/api/typesGenerated";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
import { ConfirmDialog } from "#/components/Dialog/ConfirmDialog/ConfirmDialog";
|
||||
import { EmptyState } from "#/components/EmptyState/EmptyState";
|
||||
import { Link } from "#/components/Link/Link";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import { AnnouncementBannerDialog } from "./AnnouncementBannerDialog";
|
||||
import { AnnouncementBannerItem } from "./AnnouncementBannerItem";
|
||||
|
||||
@@ -91,12 +90,10 @@ export const AnnouncementBannerSettings: FC<
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{!isEntitled || banners.length < 1 ? (
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
className="min-h-[160px]"
|
||||
message="No announcement banners"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableEmpty
|
||||
className="min-h-[160px]"
|
||||
message="No announcement banners"
|
||||
/>
|
||||
) : (
|
||||
banners.map((banner, i) => (
|
||||
<AnnouncementBannerItem
|
||||
|
||||
+8
@@ -39,3 +39,11 @@ export default meta;
|
||||
type Story = StoryObj<typeof ExternalAuthSettingsPageView>;
|
||||
|
||||
export const Page: Story = {};
|
||||
|
||||
export const Empty: Story = {
|
||||
args: {
|
||||
config: {
|
||||
external_auth: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
+7
-11
@@ -19,6 +19,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import { docs } from "#/utils/docs";
|
||||
|
||||
type ExternalAuthSettingsPageViewProps = {
|
||||
@@ -68,16 +69,10 @@ export const ExternalAuthSettingsPageView: FC<
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{((config.external_auth === null ||
|
||||
config.external_auth?.length === 0) && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<div className="text-center">
|
||||
No providers have been configured!
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)) ||
|
||||
{config.external_auth === null ||
|
||||
config.external_auth?.length === 0 ? (
|
||||
<TableEmpty message="No providers have been configured!" />
|
||||
) : (
|
||||
config.external_auth?.map((git: ExternalAuthConfig) => {
|
||||
const name = git.id || git.type;
|
||||
return (
|
||||
@@ -87,7 +82,8 @@ export const ExternalAuthSettingsPageView: FC<
|
||||
<TableCell>{git.regex || "Not Set"}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
})
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</>
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "#/components/Dialog/Dialog";
|
||||
import { EmptyState } from "#/components/EmptyState/EmptyState";
|
||||
import {
|
||||
HelpPopover,
|
||||
HelpPopoverContent,
|
||||
@@ -49,6 +48,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -408,19 +408,15 @@ const IdpMappingTable: FC<IdpMappingTableProps> = ({ isEmpty, children }) => {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{isEmpty ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message="No organization mappings"
|
||||
isCompact
|
||||
cta={
|
||||
<Link href={docs("/admin/users/idp-sync#organization-sync")}>
|
||||
How to set up IdP organization sync
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty
|
||||
message="No organization mappings"
|
||||
isCompact
|
||||
cta={
|
||||
<Link href={docs("/admin/users/idp-sync#organization-sync")}>
|
||||
How to set up IdP organization sync
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
|
||||
+22
@@ -67,6 +67,28 @@ export const Default: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const EmptySecrets: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{ key: oauth2ProviderAppKey(appId), data: mockApp },
|
||||
{
|
||||
key: oauth2ProviderAppSecretsKey(appId),
|
||||
data: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await expect(await canvas.findByText(mockApp.name)).toBeVisible();
|
||||
await expect(
|
||||
canvas.getByRole("table", { name: "OAuth2 client secrets" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
canvas.getByText("No client secrets have been generated."),
|
||||
).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const Loading: Story = {
|
||||
parameters: {
|
||||
queries: [],
|
||||
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "#/components/DropdownMenu/DropdownMenu";
|
||||
import { EmptyState } from "#/components/EmptyState/EmptyState";
|
||||
import { UsersFilter } from "#/components/Filter/UsersFilter";
|
||||
import { LastSeen } from "#/components/LastSeen/LastSeen";
|
||||
import { MultiMemberSelect } from "#/components/MultiUserSelect/MultiUserSelect";
|
||||
@@ -47,6 +46,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import { useAuthenticated } from "#/hooks/useAuthenticated";
|
||||
import { useFeatureVisibility } from "#/modules/dashboard/useFeatureVisibility";
|
||||
import { isEveryoneGroup } from "#/modules/groups";
|
||||
@@ -198,11 +198,7 @@ const GroupMembersPage: FC = () => {
|
||||
|
||||
<TableBody>
|
||||
{members.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState message="No members found" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty message="No members found" />
|
||||
) : (
|
||||
membersWithSpend.map((member) => (
|
||||
<GroupMemberRow
|
||||
|
||||
@@ -8,7 +8,6 @@ import { AvatarData } from "#/components/Avatar/AvatarData";
|
||||
import { AvatarDataSkeleton } from "#/components/Avatar/AvatarDataSkeleton";
|
||||
import { Badge } from "#/components/Badge/Badge";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
import { EmptyState } from "#/components/EmptyState/EmptyState";
|
||||
import { PaywallPremium } from "#/components/Paywall/PaywallPremium";
|
||||
import { Skeleton } from "#/components/Skeleton/Skeleton";
|
||||
import {
|
||||
@@ -19,6 +18,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import {
|
||||
TableLoaderSkeleton,
|
||||
TableRowSkeleton,
|
||||
@@ -137,28 +137,24 @@ const GroupsTableBody: FC<GroupsTableBodyProps> = ({
|
||||
}
|
||||
if (groups.length === 0) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message="No groups yet"
|
||||
description={
|
||||
canCreateGroup
|
||||
? "Create your first group"
|
||||
: "You don't have permission to create a group"
|
||||
}
|
||||
cta={
|
||||
canCreateGroup && (
|
||||
<Button asChild>
|
||||
<RouterLink to="create">
|
||||
<PlusIcon className="size-icon-sm" />
|
||||
Create group
|
||||
</RouterLink>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty
|
||||
message="No groups yet"
|
||||
description={
|
||||
canCreateGroup
|
||||
? "Create your first group"
|
||||
: "You don't have permission to create a group"
|
||||
}
|
||||
cta={
|
||||
canCreateGroup && (
|
||||
<Button asChild>
|
||||
<RouterLink to="create">
|
||||
<PlusIcon className="size-icon-sm" />
|
||||
Create group
|
||||
</RouterLink>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "#/components/DropdownMenu/DropdownMenu";
|
||||
import { EmptyState } from "#/components/EmptyState/EmptyState";
|
||||
import { PaywallPremium } from "#/components/Paywall/PaywallPremium";
|
||||
import { Skeleton } from "#/components/Skeleton/Skeleton";
|
||||
import {
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import {
|
||||
TableLoaderSkeleton,
|
||||
TableRowSkeleton,
|
||||
@@ -282,31 +282,27 @@ const RoleTableBody: FC<RoleTableProps> = ({
|
||||
}
|
||||
if (roles.length === 0) {
|
||||
return (
|
||||
<TableRow className="h-14">
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message="No custom roles yet"
|
||||
description={
|
||||
canCreateOrgRole && isCustomRolesEnabled
|
||||
? "Create your first custom role"
|
||||
: !isCustomRolesEnabled
|
||||
? "Upgrade to a premium license to create a custom role"
|
||||
: "You don't have permission to create a custom role"
|
||||
}
|
||||
cta={
|
||||
canCreateOrgRole &&
|
||||
isCustomRolesEnabled && (
|
||||
<Button asChild>
|
||||
<RouterLink to="create">
|
||||
<PlusIcon />
|
||||
Create custom role
|
||||
</RouterLink>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty
|
||||
message="No custom roles yet"
|
||||
description={
|
||||
canCreateOrgRole && isCustomRolesEnabled
|
||||
? "Create your first custom role"
|
||||
: !isCustomRolesEnabled
|
||||
? "Upgrade to a premium license to create a custom role"
|
||||
: "You don't have permission to create a custom role"
|
||||
}
|
||||
cta={
|
||||
canCreateOrgRole &&
|
||||
isCustomRolesEnabled && (
|
||||
<Button asChild>
|
||||
<RouterLink to="create">
|
||||
<PlusIcon />
|
||||
Create custom role
|
||||
</RouterLink>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { FC } from "react";
|
||||
import { EmptyState } from "#/components/EmptyState/EmptyState";
|
||||
import { Link } from "#/components/Link/Link";
|
||||
import {
|
||||
Table,
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import { docs } from "#/utils/docs";
|
||||
|
||||
interface IdpMappingTableProps {
|
||||
@@ -37,23 +37,19 @@ export const IdpMappingTable: FC<IdpMappingTableProps> = ({
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{rowCount === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message={`No ${type.toLocaleLowerCase()} mappings`}
|
||||
isCompact
|
||||
cta={
|
||||
<Link
|
||||
href={docs(
|
||||
`/admin/users/idp-sync#${type.toLocaleLowerCase()}-sync`,
|
||||
)}
|
||||
>
|
||||
How to setup IdP {type.toLocaleLowerCase()} sync
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty
|
||||
message={`No ${type.toLocaleLowerCase()} mappings`}
|
||||
isCompact
|
||||
cta={
|
||||
<Link
|
||||
href={docs(
|
||||
`/admin/users/idp-sync#${type.toLocaleLowerCase()}-sync`,
|
||||
)}
|
||||
>
|
||||
How to setup IdP {type.toLocaleLowerCase()} sync
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
|
||||
+10
-17
@@ -36,6 +36,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -191,25 +192,17 @@ const OrganizationProvisionerJobsPageView: FC<
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState message="No provisioner jobs found" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty message="No provisioner jobs found" />
|
||||
)
|
||||
) : error ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message="Error loading the provisioner jobs"
|
||||
cta={
|
||||
<Button size="sm" onClick={onRetry}>
|
||||
Retry
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty
|
||||
message="Error loading the provisioner jobs"
|
||||
cta={
|
||||
<Button size="sm" onClick={onRetry}>
|
||||
Retry
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
|
||||
+13
-21
@@ -6,7 +6,6 @@ import {
|
||||
ProvisionerKeyIDUserAuth,
|
||||
} from "#/api/typesGenerated";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
import { EmptyState } from "#/components/EmptyState/EmptyState";
|
||||
import { Link } from "#/components/Link/Link";
|
||||
import { Loader } from "#/components/Loader/Loader";
|
||||
import { PaywallPremium } from "#/components/Paywall/PaywallPremium";
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import { docs } from "#/utils/docs";
|
||||
import { ProvisionerKeyRow } from "./ProvisionerKeyRow";
|
||||
|
||||
@@ -77,14 +77,10 @@ export const OrganizationProvisionerKeysPageView: FC<
|
||||
<TableBody>
|
||||
{filteredProvisionerKeyDaemons ? (
|
||||
filteredProvisionerKeyDaemons.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5}>
|
||||
<EmptyState
|
||||
message="No provisioner keys"
|
||||
description="Create your first provisioner key to authenticate external provisioner daemons."
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty
|
||||
message="No provisioner keys"
|
||||
description="Create your first provisioner key to authenticate external provisioner daemons."
|
||||
/>
|
||||
) : (
|
||||
filteredProvisionerKeyDaemons.map((pkd) => (
|
||||
<ProvisionerKeyRow
|
||||
@@ -96,18 +92,14 @@ export const OrganizationProvisionerKeysPageView: FC<
|
||||
))
|
||||
)
|
||||
) : error ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5}>
|
||||
<EmptyState
|
||||
message="Error loading provisioner keys"
|
||||
cta={
|
||||
<Button onClick={onRetry} size="sm">
|
||||
Retry
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty
|
||||
message="Error loading provisioner keys"
|
||||
cta={
|
||||
<Button onClick={onRetry} size="sm">
|
||||
Retry
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
|
||||
+20
-28
@@ -4,7 +4,6 @@ import type { ProvisionerDaemon } from "#/api/typesGenerated";
|
||||
import { Badge } from "#/components/Badge/Badge";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
import { Checkbox } from "#/components/Checkbox/Checkbox";
|
||||
import { EmptyState } from "#/components/EmptyState/EmptyState";
|
||||
import { Link } from "#/components/Link/Link";
|
||||
import { Loader } from "#/components/Loader/Loader";
|
||||
import { PaywallPremium } from "#/components/Paywall/PaywallPremium";
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -145,35 +145,27 @@ export const OrganizationProvisionersPageView: FC<
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message="No provisioners found"
|
||||
description="A provisioner is required before you can create templates and workspaces. You can connect your first provisioner by following our documentation."
|
||||
cta={
|
||||
<Button size="sm" asChild>
|
||||
<Link href={docs("/admin/provisioners")}>
|
||||
Create a provisioner
|
||||
</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty
|
||||
message="No provisioners found"
|
||||
description="A provisioner is required before you can create templates and workspaces. You can connect your first provisioner by following our documentation."
|
||||
cta={
|
||||
<Button size="sm" asChild>
|
||||
<Link href={docs("/admin/provisioners")}>
|
||||
Create a provisioner
|
||||
</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)
|
||||
) : error ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message="Error loading the provisioner jobs"
|
||||
cta={
|
||||
<Button onClick={onRetry} size="sm">
|
||||
Retry
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty
|
||||
message="Error loading the provisioner jobs"
|
||||
cta={
|
||||
<Button onClick={onRetry} size="sm">
|
||||
Retry
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import {
|
||||
TableLoaderSkeleton,
|
||||
TableRowSkeleton,
|
||||
@@ -131,43 +132,25 @@ type TasksErrorBodyProps = {
|
||||
|
||||
const TasksErrorBody: FC<TasksErrorBodyProps> = ({ error, onRetry }) => {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999} className="text-center">
|
||||
<div className="rounded-lg w-full min-h-80 flex items-center justify-center">
|
||||
<div className="flex flex-col items-center">
|
||||
<h3 className="m-0 font-medium text-content-primary text-base">
|
||||
{getErrorMessage(error, "Error loading tasks")}
|
||||
</h3>
|
||||
<span className="text-content-secondary text-sm">
|
||||
{getErrorDetail(error) ?? "Please try again"}
|
||||
</span>
|
||||
<Button size="sm" onClick={onRetry} className="mt-4">
|
||||
<RotateCcwIcon />
|
||||
Try again
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty
|
||||
message={getErrorMessage(error, "Error loading tasks")}
|
||||
description={getErrorDetail(error) ?? "Please try again"}
|
||||
cta={
|
||||
<Button size="sm" onClick={onRetry}>
|
||||
<RotateCcwIcon />
|
||||
Try again
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TasksEmpty: FC = () => {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="text-center">
|
||||
<div className="w-full min-h-80 p-4 flex items-center justify-center">
|
||||
<div className="flex flex-col items-center">
|
||||
<h3 className="m-0 font-medium text-content-primary text-base">
|
||||
No tasks found
|
||||
</h3>
|
||||
<span className="text-content-secondary text-sm">
|
||||
Use the form above to run a task
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty
|
||||
message="No tasks found"
|
||||
description="Use the form above to run a task"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import type { FC } from "react";
|
||||
import type * as TypesGen from "#/api/typesGenerated";
|
||||
import { EmptyState } from "#/components/EmptyState/EmptyState";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { Table, TableBody } from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import { TableLoader } from "#/components/TableLoader/TableLoader";
|
||||
import { Timeline } from "#/components/Timeline/Timeline";
|
||||
import { VersionRow } from "./VersionRow";
|
||||
@@ -65,13 +60,7 @@ export const VersionsTable: FC<VersionsTableProps> = ({
|
||||
)}
|
||||
|
||||
{versions && versions.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<div className="p-8">
|
||||
<EmptyState message="No versions found" />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty message="No versions found" />
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
+5
-9
@@ -17,7 +17,6 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "#/components/DropdownMenu/DropdownMenu";
|
||||
import { EmptyState } from "#/components/EmptyState/EmptyState";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -39,6 +38,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import { TableLoader } from "#/components/TableLoader/TableLoader";
|
||||
import { getGroupSubtitle } from "#/modules/groups";
|
||||
import {
|
||||
@@ -302,14 +302,10 @@ const MembersTableBody: FC<MembersTableBodyProps> = ({
|
||||
templateACL.users.length === 0 && templateACL.group.length === 0;
|
||||
if (isEmpty) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message="No members yet"
|
||||
description="Add a member using the controls above"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty
|
||||
message="No members yet"
|
||||
description="Add a member using the controls above"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,3 +34,12 @@ export const Apps: Story = {
|
||||
revoke: () => undefined,
|
||||
},
|
||||
};
|
||||
|
||||
export const Empty: Story = {
|
||||
args: {
|
||||
isLoading: false,
|
||||
error: undefined,
|
||||
apps: [],
|
||||
revoke: () => undefined,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import { TableLoader } from "#/components/TableLoader/TableLoader";
|
||||
|
||||
type OAuth2ProviderPageViewProps = {
|
||||
@@ -43,13 +44,7 @@ const OAuth2ProviderPageView: FC<OAuth2ProviderPageViewProps> = ({
|
||||
<OAuth2AppRow key={app.id} app={app} revoke={revoke} />
|
||||
))}
|
||||
{apps?.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<div className="text-center">
|
||||
No OAuth2 applications have been authorized.
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty message="No OAuth2 applications have been authorized." />
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "#/components/DropdownMenu/DropdownMenu";
|
||||
import { EmptyState } from "#/components/EmptyState/EmptyState";
|
||||
import { LastSeen } from "#/components/LastSeen/LastSeen";
|
||||
import { Skeleton } from "#/components/Skeleton/Skeleton";
|
||||
import {
|
||||
@@ -26,6 +25,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "#/components/Table/Table";
|
||||
import { TableEmpty } from "#/components/TableEmpty/TableEmpty";
|
||||
import {
|
||||
TableLoaderSkeleton,
|
||||
TableRowSkeleton,
|
||||
@@ -136,15 +136,7 @@ const UsersTableBody: React.FC<UsersTableProps> = ({
|
||||
}
|
||||
|
||||
if (!users || users.length === 0) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<div className="p-8">
|
||||
<EmptyState message="No users found" />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
return <TableEmpty message="No users found" />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user