Skip to content

Commit 6b9e1d4

Browse files
authored
feat: create idp sync page skeleton (#14543)
* feat: initial commit for idp skeleton page * feat: add optional tooltip icon to settings header * feat: add help tooltip * feat: add mock data and update pageview for mock data * feat: initial stories * feat: error circle * feat: cleanup * feat: update StatusIndicator for outlined variant * feat: use StatusIndicator instead of Circle * chore: cleanup * fix: remove ternaries in css * fix: updates for PR review comments * chore: add story for compact empty state * feat: extract IdpField and improve field spacing
1 parent 84d312c commit 6b9e1d4

File tree

12 files changed

+550
-68
lines changed

12 files changed

+550
-68
lines changed

site/src/components/EmptyState/EmptyState.stories.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,17 @@ const meta: Meta<typeof EmptyState> = {
1313
export default meta;
1414
type Story = StoryObj<typeof EmptyState>;
1515

16-
const Example: Story = {
16+
export const Example: Story = {
1717
args: {
1818
description: "It is easy, just click the button below",
1919
cta: <Button>Create workspace</Button>,
2020
},
2121
};
2222

23-
export { Example as EmptyState };
23+
export const Compact: Story = {
24+
args: {
25+
description: "It is easy, just click the button below",
26+
cta: <Button>Create workspace</Button>,
27+
isCompact: true,
28+
},
29+
};

site/src/components/EmptyState/EmptyState.tsx

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface EmptyStateProps extends HTMLAttributes<HTMLDivElement> {
77
description?: string | ReactNode;
88
cta?: ReactNode;
99
image?: ReactNode;
10+
isCompact?: boolean;
1011
}
1112

1213
/**
@@ -19,21 +20,28 @@ export const EmptyState: FC<EmptyStateProps> = ({
1920
description,
2021
cta,
2122
image,
23+
isCompact,
2224
...attrs
2325
}) => {
2426
return (
2527
<div
26-
css={{
27-
overflow: "hidden",
28-
display: "flex",
29-
flexDirection: "column",
30-
justifyContent: "center",
31-
alignItems: "center",
32-
textAlign: "center",
33-
minHeight: 360,
34-
padding: "80px 40px",
35-
position: "relative",
36-
}}
28+
css={[
29+
{
30+
overflow: "hidden",
31+
display: "flex",
32+
flexDirection: "column",
33+
justifyContent: "center",
34+
alignItems: "center",
35+
textAlign: "center",
36+
minHeight: 360,
37+
padding: "80px 40px",
38+
position: "relative",
39+
},
40+
isCompact && {
41+
minHeight: 180,
42+
padding: "10px 40px",
43+
},
44+
]}
3745
{...attrs}
3846
>
3947
<h5 css={{ fontSize: 24, fontWeight: 500, margin: 0 }}>{message}</h5>

site/src/components/SettingsHeader/SettingsHeader.tsx

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,44 @@ interface HeaderProps {
99
description?: ReactNode;
1010
secondary?: boolean;
1111
docsHref?: string;
12+
tooltip?: ReactNode;
1213
}
1314

1415
export const SettingsHeader: FC<HeaderProps> = ({
1516
title,
1617
description,
1718
docsHref,
1819
secondary,
20+
tooltip,
1921
}) => {
2022
const theme = useTheme();
2123

2224
return (
2325
<Stack alignItems="baseline" direction="row" justifyContent="space-between">
2426
<div css={{ maxWidth: 420, marginBottom: 24 }}>
25-
<h1
26-
css={[
27-
{
28-
fontSize: 32,
29-
fontWeight: 700,
30-
display: "flex",
31-
alignItems: "center",
32-
lineHeight: "initial",
33-
margin: 0,
34-
marginBottom: 4,
35-
gap: 8,
36-
},
37-
secondary && {
38-
fontSize: 24,
39-
fontWeight: 500,
40-
},
41-
]}
42-
>
43-
{title}
44-
</h1>
27+
<Stack direction="row" spacing={1} alignItems="center">
28+
<h1
29+
css={[
30+
{
31+
fontSize: 32,
32+
fontWeight: 700,
33+
display: "flex",
34+
alignItems: "center",
35+
lineHeight: "initial",
36+
margin: 0,
37+
marginBottom: 4,
38+
gap: 8,
39+
},
40+
secondary && {
41+
fontSize: 24,
42+
fontWeight: 500,
43+
},
44+
]}
45+
>
46+
{title}
47+
</h1>
48+
{tooltip}
49+
</Stack>
4550
{description && (
4651
<span
4752
css={{

site/src/components/StatusIndicator/StatusIndicator.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,30 @@ import type { ThemeRole } from "theme/roles";
44

55
interface StatusIndicatorProps {
66
color: ThemeRole;
7+
variant?: "solid" | "outlined";
78
}
89

9-
export const StatusIndicator: FC<StatusIndicatorProps> = ({ color }) => {
10+
export const StatusIndicator: FC<StatusIndicatorProps> = ({
11+
color,
12+
variant = "solid",
13+
}) => {
1014
const theme = useTheme();
1115

1216
return (
1317
<div
14-
css={{
15-
height: 8,
16-
width: 8,
17-
borderRadius: 4,
18-
backgroundColor: theme.roles[color].fill.solid,
19-
}}
18+
css={[
19+
{
20+
height: 8,
21+
width: 8,
22+
borderRadius: 4,
23+
},
24+
variant === "solid" && {
25+
backgroundColor: theme.roles[color].fill.solid,
26+
},
27+
variant === "outlined" && {
28+
border: `1px solid ${theme.roles[color].outline}`,
29+
},
30+
]}
2031
/>
2132
);
2233
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {
2+
HelpTooltip,
3+
HelpTooltipContent,
4+
HelpTooltipLink,
5+
HelpTooltipLinksGroup,
6+
HelpTooltipText,
7+
HelpTooltipTitle,
8+
HelpTooltipTrigger,
9+
} from "components/HelpTooltip/HelpTooltip";
10+
import type { FC } from "react";
11+
import { docs } from "utils/docs";
12+
13+
export const IdpSyncHelpTooltip: FC = () => {
14+
return (
15+
<HelpTooltip>
16+
<HelpTooltipTrigger />
17+
<HelpTooltipContent>
18+
<HelpTooltipTitle>What is IdP Sync?</HelpTooltipTitle>
19+
<HelpTooltipText>
20+
View the current mappings between your external OIDC provider and
21+
Coder. Use the Coder CLI to configure these mappings.
22+
</HelpTooltipText>
23+
<HelpTooltipLinksGroup>
24+
<HelpTooltipLink href={docs("/admin/auth#group-sync-enterprise")}>
25+
Configure IdP Sync
26+
</HelpTooltipLink>
27+
</HelpTooltipLinksGroup>
28+
</HelpTooltipContent>
29+
</HelpTooltip>
30+
);
31+
};
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import AddIcon from "@mui/icons-material/AddOutlined";
2+
import LaunchOutlined from "@mui/icons-material/LaunchOutlined";
3+
import Button from "@mui/material/Button";
4+
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
5+
import { Stack } from "components/Stack/Stack";
6+
import type { FC } from "react";
7+
import { Helmet } from "react-helmet-async";
8+
import { Link as RouterLink } from "react-router-dom";
9+
import { docs } from "utils/docs";
10+
import { pageTitle } from "utils/page";
11+
import { IdpSyncHelpTooltip } from "./IdpSyncHelpTooltip";
12+
import IdpSyncPageView from "./IdpSyncPageView";
13+
14+
const mockOIDCConfig = {
15+
allow_signups: true,
16+
client_id: "test",
17+
client_secret: "test",
18+
client_key_file: "test",
19+
client_cert_file: "test",
20+
email_domain: [],
21+
issuer_url: "test",
22+
scopes: [],
23+
ignore_email_verified: true,
24+
username_field: "",
25+
name_field: "",
26+
email_field: "",
27+
auth_url_params: {},
28+
ignore_user_info: true,
29+
organization_field: "",
30+
organization_mapping: {},
31+
organization_assign_default: true,
32+
group_auto_create: false,
33+
group_regex_filter: "^Coder-.*$",
34+
group_allow_list: [],
35+
groups_field: "groups",
36+
group_mapping: { group1: "developers", group2: "admin", group3: "auditors" },
37+
user_role_field: "roles",
38+
user_role_mapping: { role1: ["role1", "role2"] },
39+
user_roles_default: [],
40+
sign_in_text: "",
41+
icon_url: "",
42+
signups_disabled_text: "string",
43+
skip_issuer_checks: true,
44+
};
45+
46+
export const IdpSyncPage: FC = () => {
47+
// feature visibility and permissions to be implemented when integrating with backend
48+
// const feats = useFeatureVisibility();
49+
// const { organization: organizationName } = useParams() as {
50+
// organization: string;
51+
// };
52+
// const { organizations } = useOrganizationSettings();
53+
// const organization = organizations?.find((o) => o.name === organizationName);
54+
// const permissionsQuery = useQuery(organizationPermissions(organization?.id));
55+
// const permissions = permissionsQuery.data;
56+
57+
// if (!permissions) {
58+
// return <Loader />;
59+
// }
60+
61+
return (
62+
<>
63+
<Helmet>
64+
<title>{pageTitle("IdP Sync")}</title>
65+
</Helmet>
66+
67+
<Stack
68+
alignItems="baseline"
69+
direction="row"
70+
justifyContent="space-between"
71+
>
72+
<SettingsHeader
73+
title="IdP Sync"
74+
description="Group and role sync mappings (configured outside Coder)."
75+
tooltip={<IdpSyncHelpTooltip />}
76+
/>
77+
<Stack direction="row" spacing={2}>
78+
<Button
79+
startIcon={<LaunchOutlined />}
80+
component="a"
81+
href={docs("/admin/auth#group-sync-enterprise")}
82+
target="_blank"
83+
>
84+
Setup IdP Sync
85+
</Button>
86+
<Button component={RouterLink} startIcon={<AddIcon />} to="export">
87+
Export Policy
88+
</Button>
89+
</Stack>
90+
</Stack>
91+
92+
<IdpSyncPageView oidcConfig={mockOIDCConfig} />
93+
</>
94+
);
95+
};
96+
97+
export default IdpSyncPage;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { MockOIDCConfig } from "testHelpers/entities";
3+
import { IdpSyncPageView } from "./IdpSyncPageView";
4+
5+
const meta: Meta<typeof IdpSyncPageView> = {
6+
title: "pages/OrganizationIdpSyncPage",
7+
component: IdpSyncPageView,
8+
};
9+
10+
export default meta;
11+
type Story = StoryObj<typeof IdpSyncPageView>;
12+
13+
export const Empty: Story = {
14+
args: { oidcConfig: undefined },
15+
};
16+
17+
export const Default: Story = {
18+
args: { oidcConfig: MockOIDCConfig },
19+
};

0 commit comments

Comments
 (0)