Skip to content

Org Settings Page Header updates & cleanup #16850

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions components/dashboard/src/components/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ReactComponent as Exclamation2 } from "../images/exclamation2.svg";
import { ReactComponent as InfoSvg } from "../images/info.svg";
import { ReactComponent as XSvg } from "../images/x.svg";
import { ReactComponent as Check } from "../images/check-circle.svg";
import classNames from "classnames";

export type AlertType =
// Green
Expand All @@ -20,6 +21,8 @@ export type AlertType =
| "info"
// Red
| "error"
// Dark Red
| "danger"
// Blue
| "message";

Expand All @@ -32,6 +35,7 @@ export interface AlertProps {
onClose?: () => void;
showIcon?: boolean;
icon?: React.ReactNode;
rounded?: boolean;
children?: React.ReactNode;
}

Expand Down Expand Up @@ -73,6 +77,12 @@ const infoMap: Record<AlertType, AlertInfo> = {
icon: <Exclamation className="w-4 h-4"></Exclamation>,
iconColor: "text-red-400",
},
danger: {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wanted the alert w/ a darker red background for modals. Open to alternatives here.

bgCls: "bg-red-600 dark:bg-red-600",
txtCls: "text-white",
icon: <Exclamation className="w-4 h-4"></Exclamation>,
iconColor: "filter-brightness-10",
},
};

export default function Alert(props: AlertProps) {
Expand All @@ -84,11 +94,16 @@ export default function Alert(props: AlertProps) {
const info = infoMap[type];
const showIcon = props.showIcon ?? true;
const light = props.light ?? false;
const rounded = props.rounded ?? true;
return (
<div
className={`flex relative whitespace-pre-wrap rounded p-4 ${info.txtCls} ${props.className || ""} ${
light ? "" : info.bgCls
}`}
className={classNames(
"flex relative whitespace-pre-wrap p-4",
info.txtCls,
props.className,
light ? "" : info.bgCls,
rounded ? "rounded" : "",
)}
>
{showIcon && <span className={`mt-1 mr-4 h-4 w-4 ${info.iconColor}`}>{props.icon ?? info.icon}</span>}
<span className="flex-1 text-left">{props.children}</span>
Expand Down
37 changes: 37 additions & 0 deletions components/dashboard/src/components/EmptyMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License.AGPL.txt in the project root for license information.
*/

import classNames from "classnames";
import { FC, useCallback } from "react";
import { Button } from "./Button";
import { Heading2, Subheading } from "./typography/headings";

type Props = {
title: string;
subtitle?: string;
buttonText?: string;
onClick?: () => void;
className?: string;
};
export const EmptyMessage: FC<Props> = ({ title, subtitle, buttonText, onClick, className }) => {
const handleClick = useCallback(() => {
onClick && onClick();
}, [onClick]);
return (
<div
className={classNames(
"w-full flex justify-center mt-2 rounded-xl bg-gray-100 dark:bg-gray-900 px-4 py-14",
className,
)}
>
<div className="flex flex-col justify-center items-center text-center space-y-4">
<Heading2 color="light">{title}</Heading2>
<Subheading className="max-w-md">{subtitle}</Subheading>
{buttonText && <Button onClick={handleClick}>{buttonText}</Button>}
</div>
</div>
);
};
48 changes: 44 additions & 4 deletions components/dashboard/src/components/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
* See License.AGPL.txt in the project root for license information.
*/

import { ReactNode, useEffect } from "react";
import { FC, ReactNode, useEffect, useMemo } from "react";
import cn from "classnames";
import { getGitpodService } from "../service/service";
import { Heading2 } from "./typography/headings";
import Alert from "./Alert";

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

Expand Down Expand Up @@ -125,7 +126,7 @@ type ModalBodyProps = {
export const ModalBody = ({ children, hideDivider = false, noScroll = false }: ModalBodyProps) => {
return (
<div
className={cn("border-gray-200 dark:border-gray-800 -mx-6 px-6 ", {
className={cn("relative border-gray-200 dark:border-gray-800 -mx-6 px-6 pb-14", {
"border-t border-b mt-2 py-4": !hideDivider,
"overflow-y-auto": !noScroll,
})}
Expand All @@ -136,8 +137,47 @@ export const ModalBody = ({ children, hideDivider = false, noScroll = false }: M
};

type ModalFooterProps = {
error?: string;
warning?: string;
children: ReactNode;
};
export const ModalFooter = ({ children }: ModalFooterProps) => {
return <div className="flex justify-end mt-6 space-x-2">{children}</div>;
export const ModalFooter: FC<ModalFooterProps> = ({ error, warning, children }) => {
// Inlining these to ensure error band covers modal left/right borders
const alertStyles = useMemo(() => ({ marginLeft: "-25px", marginRight: "-25px" }), []);

const hasAlert = error || warning;

return (
<div className="relative">
{hasAlert && (
<div className="absolute -top-12 left-0 right-0" style={alertStyles}>
<ModalFooterAlert error={error} warning={warning} />
</div>
)}
<div className="flex justify-end mt-6 space-x-2">{children}</div>
</div>
);
};

type ModalFooterAlertProps = {
error?: string;
warning?: string;
};
const ModalFooterAlert: FC<ModalFooterAlertProps> = ({ error, warning }) => {
if (error) {
return (
<Alert type="danger" rounded={false}>
{error}
</Alert>
);
}
if (warning) {
return (
<Alert type="warning" rounded={false}>
{warning}
</Alert>
);
}

return null;
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ type PendingStripeSubscription = { pendingSince: number };

interface Props {
attributionId?: string;
hideSubheading?: boolean;
}

export default function UsageBasedBillingConfig({ attributionId }: Props) {
export default function UsageBasedBillingConfig({ attributionId, hideSubheading = false }: Props) {
const location = useLocation();
const currentOrg = useCurrentOrg().data;
const attrId = attributionId ? AttributionId.parse(attributionId) : undefined;
Expand Down Expand Up @@ -170,11 +171,13 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {

return (
<div className="mb-16">
<Subheading>
{attributionId && AttributionId.parse(attributionId)?.kind === "user"
? "Manage billing for your personal account."
: "Manage billing for your organization."}
</Subheading>
{!hideSubheading && (
<Subheading>
{attributionId && AttributionId.parse(attributionId)?.kind === "user"
? "Manage billing for your personal account."
: "Manage billing for your organization."}
</Subheading>
)}
<div className="max-w-xl flex flex-col">
{errorMessage && (
<Alert className="max-w-xl mt-2" closable={false} showIcon={true} type="error">
Expand Down
11 changes: 9 additions & 2 deletions components/dashboard/src/teams/GitIntegrationsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ export default function GitAuth() {
export const OrgSettingsPageWrapper: FunctionComponent = ({ children }) => {
const currentOrg = useCurrentOrg();

const title = "Git Auth";
const subtitle = "Configure Git Auth for GitLab, or Github.";

// Render as much of the page as we can in a loading state to avoid content shift
if (currentOrg.isLoading) {
return (
<div className="w-full">
<Header title="Organization Settings" subtitle="Manage your organization's settings." />
<Header title={title} subtitle={subtitle} />
<div className="w-full">
<SpinnerLoader />
</div>
Expand All @@ -40,5 +43,9 @@ export const OrgSettingsPageWrapper: FunctionComponent = ({ children }) => {
return <Redirect to={"/"} />;
}

return <OrgSettingsPage>{children}</OrgSettingsPage>;
return (
<OrgSettingsPage title={title} subtitle={subtitle}>
{children}
</OrgSettingsPage>
);
};
7 changes: 3 additions & 4 deletions components/dashboard/src/teams/OrgSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import { useCurrentOrg } from "../data/organizations/orgs-query";
import { getTeamSettingsMenu } from "./TeamSettings";

export interface OrgSettingsPageProps {
title: string;
subtitle: string;
children: React.ReactNode;
}

export function OrgSettingsPage({ children }: OrgSettingsPageProps) {
export function OrgSettingsPage({ title, subtitle, children }: OrgSettingsPageProps) {
const org = useCurrentOrg();
const { oidcServiceEnabled, orgGitAuthProviders } = useFeatureFlags();

Expand All @@ -32,9 +34,6 @@ export function OrgSettingsPage({ children }: OrgSettingsPageProps) {
[oidcServiceEnabled, orgGitAuthProviders, org.data],
);

const title = "Organization Settings";
const subtitle = "Manage your organization's settings.";

// Render as much of the page as we can in a loading state to avoid content shift
if (org.isLoading) {
return (
Expand Down
32 changes: 15 additions & 17 deletions components/dashboard/src/teams/SSO.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@ import copy from "../images/copy.svg";
import exclamation from "../images/exclamation.svg";
import { OrgSettingsPage } from "./OrgSettingsPage";
import { Heading2, Subheading } from "../components/typography/headings";
import { EmptyMessage } from "../components/EmptyMessage";

export default function SSO() {
const currentOrg = useCurrentOrg();

return <OrgSettingsPage>{currentOrg.data && <OIDCClients organizationId={currentOrg.data?.id} />}</OrgSettingsPage>;
return (
<OrgSettingsPage title="SSO" subtitle="Configure OpenID Connect (OIDC) single sign-on.">
{currentOrg.data && <OIDCClients organizationId={currentOrg.data?.id} />}
</OrgSettingsPage>
);
}

function OIDCClients(props: { organizationId: string }) {
Expand Down Expand Up @@ -85,11 +90,10 @@ function OIDCClients(props: { organizationId: string }) {
{modal?.mode === "edit" && <></>}
{modal?.mode === "delete" && <></>}

<Heading2>OpenID Connect clients</Heading2>
<Subheading>Configure single sign-on for your organization.</Subheading>

<div className="flex items-start sm:justify-between mb-2">
<div>
<Heading2>Single sign sign-on with OIDC</Heading2>
<Subheading>Setup SSO for your organization.</Subheading>
</div>
{clientConfigs.length !== 0 ? (
<div className="mt-3 flex mt-0">
<button onClick={() => setModal({ mode: "new" })} className="ml-2">
Expand All @@ -100,18 +104,12 @@ function OIDCClients(props: { organizationId: string }) {
</div>

{clientConfigs.length === 0 && (
<div className="w-full flex h-80 mt-2 rounded-xl bg-gray-100 dark:bg-gray-900">
<div className="m-auto text-center">
<h3 className="self-center text-gray-500 dark:text-gray-400 mb-4">No OIDC Clients</h3>
<div className="text-gray-500 mb-6">
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor
invidunt ut labore et dolore magna aliquyam
</div>
<button className="self-center" onClick={() => setModal({ mode: "new" })}>
New OIDC Client
</button>
</div>
</div>
<EmptyMessage
title="No OIDC providers"
subtitle="Enable single sign-on for your organization using an external identity provider (IdP) service that supports the OpenID Connect (OIDC) standard, such as Google."
buttonText="New OIDC Client"
onClick={() => setModal({ mode: "new" })}
/>
)}

<ItemsList className="pt-6">
Expand Down
2 changes: 1 addition & 1 deletion components/dashboard/src/teams/TeamBilling.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type PendingPlan = Plan & { pendingSince: number };

export default function TeamBillingPage() {
return (
<OrgSettingsPage>
<OrgSettingsPage title={"Organization Billing"} subtitle="Manage billing for your organization.">
<TeamBilling />
</OrgSettingsPage>
);
Expand Down
4 changes: 2 additions & 2 deletions components/dashboard/src/teams/TeamSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function getTeamSettingsMenu(params: {
}
if (orgGitAuthProviders) {
result.push({
title: "Git Integrations",
title: "Git Auth",
link: [`/settings/git`],
});
}
Expand Down Expand Up @@ -109,7 +109,7 @@ export default function TeamSettings() {

return (
<>
<OrgSettingsPage>
<OrgSettingsPage title="Organization Settings" subtitle="Manage your organization's settings.">
<Heading2>Organization Name</Heading2>
<Subheading className="max-w-2xl">
This is your organization's visible name within Gitpod. For example, the name of your company.
Expand Down
7 changes: 4 additions & 3 deletions components/dashboard/src/teams/TeamUsageBasedBilling.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
*/

import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
import { Heading2 } from "../components/typography/headings";
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
import UsageBasedBillingConfig from "../components/UsageBasedBillingConfig";
import { useCurrentOrg } from "../data/organizations/orgs-query";
Expand All @@ -19,8 +18,10 @@ export default function TeamUsageBasedBilling() {

return (
<>
<Heading2>Organization Billing</Heading2>
<UsageBasedBillingConfig attributionId={org && AttributionId.render({ kind: "team", teamId: org.id })} />
<UsageBasedBillingConfig
hideSubheading
attributionId={org && AttributionId.render({ kind: "team", teamId: org.id })}
/>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const GitIntegrationListItem: FunctionComponent<Props> = ({ provider }) =
return (
<>
<Item className="h-16">
<ItemFieldIcon>
<ItemFieldIcon className="w-1/12">
<div
className={
"rounded-full w-3 h-3 text-sm align-middle m-auto " +
Expand All @@ -57,13 +57,13 @@ export const GitIntegrationListItem: FunctionComponent<Props> = ({ provider }) =
&nbsp;
</div>
</ItemFieldIcon>
<ItemField className="w-3/12 flex flex-col my-auto">
<ItemField className="w-5/12 flex items-center">
<span className="font-medium truncate overflow-ellipsis">{provider.type}</span>
</ItemField>
<ItemField className="w-7/12 flex flex-col my-auto">
<ItemField className="w-5/12 flex items-center">
<span className="my-auto truncate text-gray-500 overflow-ellipsis">{provider.host}</span>
</ItemField>
<ItemFieldContextMenu menuEntries={menuEntries} />
<ItemFieldContextMenu className="w-1/12" menuEntries={menuEntries} />
</Item>
{showDeleteConfirmation && (
<ConfirmationModal
Expand Down
Loading