Skip to content

Commit 7b7c729

Browse files
Org Settings and Modal alert updates (#16883)
* simplify oidc client list view * making headers consistent * adjust alignment * adjust org setting pages headings * fixing alignment * updating modal footer alerts * animate modal footer alerts * handle non-closeable alerts * undo wrapper div for now * remove empty prop
1 parent 84e5640 commit 7b7c729

14 files changed

+173
-108
lines changed

components/dashboard/src/components/Modal.tsx

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
* See License.AGPL.txt in the project root for license information.
55
*/
66

7-
import { FC, ReactNode, useEffect, useMemo } from "react";
7+
import { FC, ReactNode, useEffect } from "react";
88
import cn from "classnames";
99
import { getGitpodService } from "../service/service";
1010
import { Heading2 } from "./typography/headings";
11-
import Alert from "./Alert";
11+
import Alert, { AlertProps } from "./Alert";
12+
import "./modal.css";
13+
import classNames from "classnames";
1214

1315
type CloseModalManner = "esc" | "enter" | "x";
1416

@@ -126,7 +128,7 @@ type ModalBodyProps = {
126128
export const ModalBody = ({ children, hideDivider = false, noScroll = false }: ModalBodyProps) => {
127129
return (
128130
<div
129-
className={cn("relative border-gray-200 dark:border-gray-800 -mx-6 px-6 pb-14", {
131+
className={cn("relative border-gray-200 dark:border-gray-800 -mx-6 px-6 pb-6", {
130132
"border-t border-b mt-2 py-4": !hideDivider,
131133
"overflow-y-auto": !noScroll,
132134
})}
@@ -137,47 +139,41 @@ export const ModalBody = ({ children, hideDivider = false, noScroll = false }: M
137139
};
138140

139141
type ModalFooterProps = {
140-
error?: string;
141-
warning?: string;
142+
alert?: ReactNode;
142143
children: ReactNode;
143144
};
144-
export const ModalFooter: FC<ModalFooterProps> = ({ error, warning, children }) => {
145-
// Inlining these to ensure error band covers modal left/right borders
146-
const alertStyles = useMemo(() => ({ marginLeft: "-25px", marginRight: "-25px" }), []);
147-
148-
const hasAlert = error || warning;
149-
145+
export const ModalFooter: FC<ModalFooterProps> = ({ alert, children }) => {
150146
return (
151-
<div className="relative">
152-
{hasAlert && (
153-
<div className="absolute bottom-12 left-0 right-0" style={alertStyles}>
154-
<ModalFooterAlert error={error} warning={warning} />
155-
</div>
156-
)}
157-
<div className="flex justify-end mt-6 space-x-2">{children}</div>
158-
</div>
147+
<>
148+
{alert}
149+
<div
150+
className={classNames(
151+
// causes footer to show up on top of alert
152+
"relative",
153+
// make as wide as the modal so it covers the alert
154+
"-mx-6 -mb-6 p-6",
155+
// apply the same bg and rounded corners as the modal
156+
"bg-white dark:bg-gray-900 rounded-b-xl",
157+
)}
158+
>
159+
<div className="flex justify-end space-x-2">{children}</div>
160+
</div>
161+
</>
159162
);
160163
};
161164

162-
type ModalFooterAlertProps = {
163-
error?: string;
164-
warning?: string;
165-
};
166-
const ModalFooterAlert: FC<ModalFooterAlertProps> = ({ error, warning }) => {
167-
if (error) {
168-
return (
169-
<Alert type="danger" rounded={false}>
170-
{error}
171-
</Alert>
172-
);
173-
}
174-
if (warning) {
175-
return (
176-
<Alert type="warning" rounded={false}>
177-
{warning}
165+
// Wrapper around Alert to ensure it's used correctly in a Modal
166+
export const ModalFooterAlert: FC<AlertProps> = ({ closable = true, children, ...alertProps }) => {
167+
return (
168+
<div
169+
className={classNames({
170+
"gp-modal-footer-alert border-b": !closable,
171+
"gp-modal-footer-alert_animate absolute": closable,
172+
})}
173+
>
174+
<Alert rounded={false} closable={closable} {...alertProps}>
175+
{children}
178176
</Alert>
179-
);
180-
}
181-
182-
return null;
177+
</div>
178+
);
183179
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
@layer components {
8+
.gp-modal-footer-alert {
9+
margin-top: -1px;
10+
margin-left: -25px;
11+
margin-right: -25px;
12+
}
13+
.gp-modal-footer-alert_animate {
14+
@apply bottom-2;
15+
margin-left: 0px;
16+
margin-right: 0px;
17+
left: -1px;
18+
right: -1px;
19+
/* Start w/ a fixed height to keep it smaller than the footer */
20+
max-height: 70px;
21+
overflow: hidden;
22+
animation: showModalFooterAlert 0.5s ease-out forwards;
23+
}
24+
25+
/* Animates alert from behind footer */
26+
@keyframes showModalFooterAlert {
27+
0% {
28+
@apply bottom-2;
29+
}
30+
31+
/* Swaps max-height and overflow so it's not fixed height anymore */
32+
50% {
33+
max-height: inherit;
34+
overflow: visible;
35+
}
36+
37+
100% {
38+
bottom: 85px;
39+
max-height: inherit;
40+
overflow: visible;
41+
}
42+
}
43+
}

components/dashboard/src/teams/GitIntegrationsPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { OrgSettingsPage } from "./OrgSettingsPage";
99

1010
export default function GitAuthPage() {
1111
return (
12-
<OrgSettingsPage title="Git Auth" subtitle="Configure Git Auth for GitLab or Github.">
12+
<OrgSettingsPage>
1313
<GitIntegrations />
1414
</OrgSettingsPage>
1515
);

components/dashboard/src/teams/OrgSettingsPage.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@ import { useCurrentOrg } from "../data/organizations/orgs-query";
1414
import { getTeamSettingsMenu } from "./TeamSettings";
1515

1616
export interface OrgSettingsPageProps {
17-
title: string;
18-
subtitle: string;
1917
children: React.ReactNode;
2018
}
2119

22-
export function OrgSettingsPage({ title, subtitle, children }: OrgSettingsPageProps) {
20+
export function OrgSettingsPage({ children }: OrgSettingsPageProps) {
2321
const org = useCurrentOrg();
2422
const { oidcServiceEnabled, orgGitAuthProviders } = useFeatureFlags();
2523

@@ -34,6 +32,9 @@ export function OrgSettingsPage({ title, subtitle, children }: OrgSettingsPagePr
3432
[oidcServiceEnabled, orgGitAuthProviders, org.data],
3533
);
3634

35+
const title = "Organization Settings";
36+
const subtitle = "Manage your organization's settings.";
37+
3738
// Render as much of the page as we can in a loading state to avoid content shift
3839
if (org.isLoading) {
3940
return (

components/dashboard/src/teams/SSO.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { OIDCClients } from "./sso/OIDCClients";
99

1010
export default function SSO() {
1111
return (
12-
<OrgSettingsPage title="SSO" subtitle="Configure OpenID Connect (OIDC) single sign-on.">
12+
<OrgSettingsPage>
1313
<OIDCClients />
1414
</OrgSettingsPage>
1515
);

components/dashboard/src/teams/TeamBilling.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type PendingPlan = Plan & { pendingSince: number };
2727

2828
export default function TeamBillingPage() {
2929
return (
30-
<OrgSettingsPage title={"Organization Billing"} subtitle="Manage billing for your organization.">
30+
<OrgSettingsPage>
3131
<TeamBilling />
3232
</OrgSettingsPage>
3333
);

components/dashboard/src/teams/TeamSettings.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export default function TeamSettings() {
109109

110110
return (
111111
<>
112-
<OrgSettingsPage title="Organization Settings" subtitle="Manage your organization's settings.">
112+
<OrgSettingsPage>
113113
<Heading2>Organization Name</Heading2>
114114
<Subheading className="max-w-2xl">
115115
This is your organization's visible name within Gitpod. For example, the name of your company.

components/dashboard/src/teams/git-integrations/GitIntegrationListItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export const GitIntegrationListItem: FunctionComponent<Props> = ({ provider }) =
4747
return (
4848
<>
4949
<Item className="h-16">
50-
<ItemFieldIcon className="w-1/12">
50+
<ItemFieldIcon>
5151
<div
5252
className={
5353
"rounded-full w-3 h-3 text-sm align-middle m-auto " +

components/dashboard/src/teams/git-integrations/GitIntegrationModal.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66

77
import { AuthProviderEntry } from "@gitpod/gitpod-protocol";
88
import { FunctionComponent, useCallback, useMemo, useState } from "react";
9+
import Alert from "../../components/Alert";
910
import { Button } from "../../components/Button";
1011
import { InputField } from "../../components/forms/InputField";
1112
import { SelectInputField } from "../../components/forms/SelectInputField";
1213
import { TextInputField } from "../../components/forms/TextInputField";
1314
import { InputWithCopy } from "../../components/InputWithCopy";
14-
import Modal, { ModalBody, ModalFooter, ModalHeader } from "../../components/Modal";
15+
import Modal, { ModalBody, ModalFooter, ModalFooterAlert, ModalHeader } from "../../components/Modal";
1516
import { useInvalidateOrgAuthProvidersQuery } from "../../data/auth-providers/org-auth-providers-query";
1617
import { useUpsertOrgAuthProviderMutation } from "../../data/auth-providers/upsert-org-auth-provider-mutation";
1718
import { useCurrentOrg } from "../../data/organizations/orgs-query";
@@ -238,8 +239,20 @@ export const GitIntegrationModal: FunctionComponent<Props> = (props) => {
238239
</div>
239240
</ModalBody>
240241
<ModalFooter
241-
error={errorMessage}
242-
warning={!isNew && savedProvider?.status !== "verified" ? "You need to activate this integration." : ""}
242+
alert={
243+
<>
244+
{errorMessage ? (
245+
<ModalFooterAlert type="danger">{errorMessage}</ModalFooterAlert>
246+
) : (
247+
!isNew &&
248+
savedProvider?.status !== "verified" && (
249+
<ModalFooterAlert type="warning" closable={false}>
250+
You need to activate this integration.
251+
</ModalFooterAlert>
252+
)
253+
)}
254+
</>
255+
}
243256
>
244257
<Button type="secondary" onClick={props.onClose}>
245258
Cancel

components/dashboard/src/teams/git-integrations/GitIntegrations.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import { FunctionComponent } from "react";
88
import { SpinnerLoader } from "../../components/Loader";
9-
import { Heading2, Subheading } from "../../components/typography/headings";
109
import { useOrgAuthProvidersQuery } from "../../data/auth-providers/org-auth-providers-query";
1110
import { GitIntegrationsList } from "./GitIntegrationsList";
1211

@@ -17,11 +16,5 @@ export const GitIntegrations: FunctionComponent = () => {
1716
return <SpinnerLoader />;
1817
}
1918

20-
return (
21-
<div>
22-
<Heading2>Git Auth configurations</Heading2>
23-
<Subheading>Configure Git Auth for your organization.</Subheading>
24-
<GitIntegrationsList providers={data || []} />
25-
</div>
26-
);
19+
return <GitIntegrationsList providers={data || []} />;
2720
};

components/dashboard/src/teams/git-integrations/GitIntegrationsList.tsx

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { AuthProviderEntry } from "@gitpod/gitpod-protocol";
88
import { FunctionComponent, useCallback, useState } from "react";
99
import { Button } from "../../components/Button";
1010
import { EmptyMessage } from "../../components/EmptyMessage";
11-
import { Item, ItemField, ItemsList } from "../../components/ItemsList";
11+
import { Item, ItemField, ItemFieldIcon, ItemsList } from "../../components/ItemsList";
12+
import { Heading2, Subheading } from "../../components/typography/headings";
1213
import { GitIntegrationListItem } from "./GitIntegrationListItem";
1314
import { GitIntegrationModal } from "./GitIntegrationModal";
1415

@@ -23,6 +24,21 @@ export const GitIntegrationsList: FunctionComponent<Props> = ({ providers }) =>
2324

2425
return (
2526
<>
27+
<div className="flex flex-col space-y-2 md:flex-row md:items-start md:justify-between md:space-y-0">
28+
<div>
29+
<Heading2>Git Auth configurations</Heading2>
30+
<Subheading>Configure Git Auth for your organization.</Subheading>
31+
</div>
32+
33+
{providers.length !== 0 ? (
34+
<div className="">
35+
<Button className="whitespace-nowrap" onClick={onCreate}>
36+
New Integration
37+
</Button>
38+
</div>
39+
) : null}
40+
</div>
41+
2642
{providers.length === 0 ? (
2743
<EmptyMessage
2844
title="No Git Auth configurations"
@@ -31,22 +47,16 @@ export const GitIntegrationsList: FunctionComponent<Props> = ({ providers }) =>
3147
onClick={onCreate}
3248
/>
3349
) : (
34-
<>
35-
<div className="mt-3">
36-
<Button onClick={onCreate}>New Integration</Button>
37-
</div>
38-
39-
<ItemsList className="pt-6">
40-
<Item header={true}>
41-
<ItemField className="w-1/12"> </ItemField>
42-
<ItemField className="w-5/12">Provider Type</ItemField>
43-
<ItemField className="w-6/12">Host Name</ItemField>
44-
</Item>
45-
{providers.map((p) => (
46-
<GitIntegrationListItem key={p.id} provider={p} />
47-
))}
48-
</ItemsList>
49-
</>
50+
<ItemsList className="pt-6">
51+
<Item header={true}>
52+
<ItemFieldIcon />
53+
<ItemField className="w-5/12">Provider Type</ItemField>
54+
<ItemField className="w-6/12">Host Name</ItemField>
55+
</Item>
56+
{providers.map((p) => (
57+
<GitIntegrationListItem key={p.id} provider={p} />
58+
))}
59+
</ItemsList>
5060
)}
5161
{showCreateModal && <GitIntegrationModal onClose={hideModal} />}
5262
</>

components/dashboard/src/teams/sso/OIDCClientConfigModal.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { Button } from "../../components/Button";
1111
import { InputField } from "../../components/forms/InputField";
1212
import { TextInputField } from "../../components/forms/TextInputField";
1313
import { InputWithCopy } from "../../components/InputWithCopy";
14-
import Modal, { ModalBody, ModalFooter, ModalHeader } from "../../components/Modal";
14+
import Modal, { ModalBody, ModalFooter, ModalFooterAlert, ModalHeader } from "../../components/Modal";
1515
import { useUpsertOIDCClientMutation } from "../../data/oidc-clients/upsert-oidc-client-mutation";
1616
import { useCurrentOrg } from "../../data/organizations/orgs-query";
1717
import { useOnBlurError } from "../../hooks/use-onblur-error";
@@ -89,8 +89,6 @@ export const OIDCClientConfigModal: FC<Props> = ({ clientConfig, onClose }) => {
8989
}
9090
}, [clientConfig?.id, clientId, clientSecret, isNew, isValid, issuer, onClose, org, upsertClientConfig]);
9191

92-
const errorMessage = upsertClientConfig.isError ? "There was a problem saving your configuration." : "";
93-
9492
return (
9593
<Modal
9694
visible
@@ -136,7 +134,9 @@ export const OIDCClientConfigModal: FC<Props> = ({ clientConfig, onClose }) => {
136134
onChange={setClientSecret}
137135
/>
138136
</ModalBody>
139-
<ModalFooter error={errorMessage}>
137+
<ModalFooter
138+
alert={upsertClientConfig.isError ? <SaveErrorAlert error={upsertClientConfig.error as Error} /> : null}
139+
>
140140
<Button type="secondary" onClick={onClose}>
141141
Cancel
142142
</Button>
@@ -147,3 +147,17 @@ export const OIDCClientConfigModal: FC<Props> = ({ clientConfig, onClose }) => {
147147
</Modal>
148148
);
149149
};
150+
151+
type SaveErrorMessageProps = {
152+
error?: Error;
153+
};
154+
const SaveErrorAlert: FC<SaveErrorMessageProps> = ({ error }) => {
155+
const message = error?.message || "";
156+
157+
return (
158+
<ModalFooterAlert type="danger">
159+
<span>There was a problem saving your configuration.</span>
160+
{message && <div className="leading-4 text-xs font-mono">{message}</div>}
161+
</ModalFooterAlert>
162+
);
163+
};

0 commit comments

Comments
 (0)