refactor: Setting redesign (#11124)

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
This commit is contained in:
Udit TakkarandPeer Richelsen authored and Alex van Andel committed 2023-09-29 15:10:13 +01:00
1 parent 85a1713897
commit 685be1663e
33 files changed
+1184 -782

No files matched your search

@@ -11,6 +11,7 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
showToast,
} from "@calcom/ui";
import { MoreHorizontal, Edit2, Trash } from "@calcom/ui/components/icon";
@@ -34,22 +35,31 @@ const ApiKeyListItem = ({
const deleteApiKey = trpc.viewer.apiKeys.delete.useMutation({
async onSuccess() {
await utils.viewer.apiKeys.list.invalidate();
showToast(t("api_key_deleted"), "success");
},
onError(err) {
console.log(err);
showToast(t("something_went_wrong"), "error");
},
});
return (
<div
key={apiKey.id}
className={classNames("flex w-full justify-between p-4", lastItem ? "" : "border-subtle border-b")}>
className={classNames(
"flex w-full justify-between px-4 py-4 sm:px-6",
lastItem ? "" : "border-subtle border-b"
)}>
<div>
<p className="font-medium"> {apiKey?.note ? apiKey.note : t("api_key_no_note")}</p>
<div className="flex items-center space-x-3.5">
<div className="flex gap-1">
<p className="text-sm font-semibold"> {apiKey?.note ? apiKey.note : t("api_key_no_note")}</p>
{!neverExpires && isExpired && <Badge variant="red">{t("expired")}</Badge>}
{!isExpired && <Badge variant="green">{t("active")}</Badge>}
<p className="text-default text-xs">
{" "}
</div>
<div className="mt-1 flex items-center space-x-3.5">
<p className="text-default text-sm">
{neverExpires ? (
<div className="text-subtle flex flex-row space-x-3">{t("api_key_never_expires")}</div>
<div className="flex flex-row space-x-3">{t("api_key_never_expires")}</div>
) : (
`${isExpired ? t("expired") : t("expires")} ${dayjs(apiKey?.expiresAt?.toString()).fromNow()}`
)}
@@ -71,6 +81,8 @@ const ApiKeyListItem = ({
<DropdownMenuItem>
<DropdownItem
type="button"
color="destructive"
disabled={deleteApiKey.isLoading}
onClick={() =>
deleteApiKey.mutate({
id: apiKey.id,
@@ -145,7 +145,7 @@ const CreateConnectionDialog = ({
)}
/>
</div>
<DialogFooter showDivider className="mt-10">
<DialogFooter showDivider className="relative mt-10">
<Button
type="button"
color="secondary"
@@ -6,7 +6,20 @@ import OIDCConnection from "@calcom/features/ee/sso/components/OIDCConnection";
import SAMLConnection from "@calcom/features/ee/sso/components/SAMLConnection";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { AppSkeletonLoader as SkeletonLoader, Meta, Alert } from "@calcom/ui";
import { Meta, Alert, SkeletonContainer, SkeletonText } from "@calcom/ui";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} borderInShellHeader={true} />
<div className="divide-subtle border-subtle space-y-6 rounded-b-xl border border-t-0 px-6 py-4">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
</div>
</SkeletonContainer>
);
};
export default function SSOConfiguration({ teamId }: { teamId: number | null }) {
const [errorMessage, setErrorMessage] = useState("");
@@ -22,14 +35,18 @@ export default function SSOConfiguration({ teamId }: { teamId: number | null })
);
if (isLoading) {
return <SkeletonLoader />;
return <SkeletonLoader title={t("sso_configuration")} description={t("sso_configuration_description")} />;
}
if (errorMessage) {
return (
<>
<Meta title={t("sso_configuration")} description={t("saml_description")} />
<Alert severity="warning" message={t(errorMessage)} className="mb-4 " />
<Meta
title={t("sso_configuration")}
description={t("sso_configuration_description")}
borderInShellHeader={true}
/>
<Alert severity="warning" message={t(errorMessage)} className="mt-4" />
</>
);
}
@@ -38,7 +55,7 @@ export default function SSOConfiguration({ teamId }: { teamId: number | null })
if (!connection) {
return (
<LicenseRequired>
<div className="flex flex-col space-y-10">
<div className="[&>*]:border-subtle flex flex-col [&>*:last-child]:rounded-b-xl [&>*]:border [&>*]:border-t-0 [&>*]:px-4 [&>*]:py-6 [&>*]:sm:px-6">
<SAMLConnection teamId={teamId} connection={null} />
<OIDCConnection teamId={teamId} connection={null} />
</div>
@@ -48,7 +65,7 @@ export default function SSOConfiguration({ teamId }: { teamId: number | null })
return (
<LicenseRequired>
<div className="flex flex-col space-y-6">
<div className="[&>*]:border-subtle flex flex-col [&>*:last-child]:rounded-b-xl [&>*]:border [&>*]:border-t-0 [&>*]:px-4 [&>*]:py-6 [&>*]:sm:px-6">
{connection.type === "saml" ? (
<SAMLConnection teamId={teamId} connection={connection} />
) : (
@@ -19,8 +19,12 @@ const SAMLSSO = () => {
}, []);
return (
<div className="bg-default w-full sm:mx-0 xl:mt-0">
<Meta title={t("sso_configuration")} description={t("sso_configuration_description")} />
<div className="bg-default w-full sm:mx-0">
<Meta
title={t("sso_configuration")}
description={t("sso_configuration_description")}
borderInShellHeader={true}
/>
<SSOConfiguration teamId={null} />
</div>
);
@@ -12,6 +12,8 @@ import { bookerLayoutOptions, type BookerLayoutSettings } from "@calcom/prisma/z
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { Label, CheckboxField, Button } from "@calcom/ui";
import SectionBottomActions from "./SectionBottomActions";
type BookerLayoutSelectorProps = {
title?: string;
description?: string;
@@ -30,6 +32,8 @@ type BookerLayoutSelectorProps = {
* to this boolean.
*/
isDark?: boolean;
isDisabled?: boolean;
};
const defaultFieldName = "metadata.bookerLayouts";
@@ -40,6 +44,7 @@ export const BookerLayoutSelector = ({
name,
fallbackToUserSettings,
isDark,
isDisabled = false,
}: BookerLayoutSelectorProps) => {
const { control, getValues } = useFormContext();
const { t } = useLocale();
@@ -51,10 +56,12 @@ export const BookerLayoutSelector = ({
return (
<>
<Label className="mb-0">{title ? title : t("layout")}</Label>
<p className="text-subtle max-w-full break-words py-1 text-sm">
{description ? description : t("bookerlayout_description")}
</p>
<div className="border-subtle rounded-t-xl border p-6">
<Label className="mb-0 text-base font-semibold">{title ? title : t("layout")}</Label>
<p className="text-subtle max-w-full break-words py-1 text-sm">
{description ? description : t("bookerlayout_description")}
</p>
</div>
<Controller
// If the event does not have any settings, we don't want to register this field in the form.
// That way the settings won't get saved into the event on save, but remain null. Thus keep using
@@ -62,12 +69,19 @@ export const BookerLayoutSelector = ({
control={shouldShowUserSettings ? undefined : control}
name={name || defaultFieldName}
render={({ field: { value, onChange } }) => (
<BookerLayoutFields
showUserSettings={shouldShowUserSettings}
settings={value}
onChange={onChange}
isDark={isDark}
/>
<>
<BookerLayoutFields
showUserSettings={shouldShowUserSettings}
settings={value}
onChange={onChange}
isDark={isDark}
/>
<SectionBottomActions align="end">
<Button type="submit" disabled={isDisabled} color="primary">
{t("update")}
</Button>
</SectionBottomActions>
</>
)}
/>
</>
@@ -141,7 +155,7 @@ const BookerLayoutFields = ({ settings, onChange, showUserSettings, isDark }: Bo
};
return (
<div className="my-4 space-y-5">
<div className="border-subtle space-y-5 border-x px-6 py-8">
<div
className={classNames(
"flex flex-col gap-5 transition-opacity sm:flex-row sm:gap-3",
@@ -0,0 +1,26 @@
import type { ReactNode } from "react";
import { classNames } from "@calcom/lib";
const SectionBottomActions = ({
align = "start",
children,
className,
}: {
align?: "start" | "end";
children: ReactNode;
className?: string;
}) => {
return (
<div
className={classNames(
"border-subtle bg-muted flex rounded-b-xl border px-6 py-4",
align === "end" && "justify-end",
className
)}>
{children}
</div>
);
};
export default SectionBottomActions;
@@ -633,7 +633,7 @@ export default function SettingsLayout({
<MobileSettingsContainer onSideContainerOpen={() => setSideContainerOpen(!sideContainerOpen)} />
}>
<div className="flex flex-1 [&>*]:flex-1">
<div className="mx-auto max-w-full justify-center md:max-w-4xl">
<div className="mx-auto max-w-full justify-center lg:max-w-4xl">
<ShellHeader />
<ErrorBoundary>
<Suspense fallback={<Loader />}>{children}</Suspense>
@@ -676,33 +676,40 @@ type SidebarContainerElementProps = {
export const getLayout = (page: React.ReactElement) => <SettingsLayout>{page}</SettingsLayout>;
function ShellHeader() {
export function ShellHeader() {
const { meta } = useMeta();
const { t, isLocaleReady } = useLocale();
return (
<header className="mx-auto block justify-between pt-8 sm:flex">
<div className="border-subtle mb-8 flex w-full items-center border-b pb-6">
{meta.backButton && (
<a href="javascript:history.back()">
<ArrowLeft className="mr-7" />
</a>
)}
<div>
{meta.title && isLocaleReady ? (
<h1 className="font-cal text-emphasis mb-1 text-xl font-bold leading-5 tracking-wide">
{t(meta.title)}
</h1>
) : (
<div className="bg-emphasis mb-1 h-5 w-24 animate-pulse rounded-md" />
)}
{meta.description && isLocaleReady ? (
<p className="text-default text-sm ltr:mr-4 rtl:ml-4">{t(meta.description)}</p>
) : (
<div className="bg-emphasis h-5 w-32 animate-pulse rounded-md" />
<>
<header
className={classNames(
"border-subtle mx-auto block justify-between sm:flex",
meta.borderInShellHeader && "rounded-t-xl border px-4 py-6 sm:px-6",
meta.borderInShellHeader === undefined && "mb-8 border-b pb-8"
)}>
<div className="flex w-full items-center">
{meta.backButton && (
<a href="javascript:history.back()">
<ArrowLeft className="mr-7" />
</a>
)}
<div>
{meta.title && isLocaleReady ? (
<h1 className="font-cal text-emphasis mb-1 text-xl font-bold leading-5 tracking-wide">
{t(meta.title)}
</h1>
) : (
<div className="bg-emphasis mb-1 h-5 w-24 animate-pulse rounded-md" />
)}
{meta.description && isLocaleReady ? (
<p className="text-default text-sm ltr:mr-4 rtl:ml-4">{t(meta.description)}</p>
) : (
<div className="bg-emphasis h-5 w-32 animate-pulse rounded-md" />
)}
</div>
<div className="ms-auto flex-shrink-0">{meta.CTA}</div>
</div>
<div className="ms-auto flex-shrink-0">{meta.CTA}</div>
</div>
</header>
</header>
</>
);
}
+1 -1
View File
@@ -1008,7 +1008,7 @@ function MainContainer({
<main className="bg-default relative z-0 flex-1 focus:outline-none">
{/* show top navigation for md and smaller (tablet and phones) */}
{TopNavContainerProp}
<div className="max-w-full px-4 py-4 md:py-8 lg:px-12">
<div className="max-w-full px-2 py-4 md:py-12 lg:px-6">
<ErrorBoundary>
{!props.withoutMain ? <ShellMain {...props}>{props.children}</ShellMain> : props.children}
</ErrorBoundary>
@@ -5,18 +5,9 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
import type { RouterOutputs } from "@calcom/trpc/react";
import {
Button,
Form,
Label,
Select,
Switch,
TextArea,
TextField,
ToggleGroup,
DialogFooter,
} from "@calcom/ui";
import { Button, Form, Label, Select, Switch, TextArea, TextField, ToggleGroup } from "@calcom/ui";
import SectionBottomActions from "../../settings/SectionBottomActions";
import customTemplate, { hasTemplateIntegration } from "../lib/integrationTemplate";
import WebhookTestDisclosure from "./WebhookTestDisclosure";
@@ -87,7 +78,7 @@ const WebhookForm = (props: {
const [useCustomTemplate, setUseCustomTemplate] = useState(false);
const [newSecret, setNewSecret] = useState("");
const [changeSecret, setChangeSecret] = useState(false);
const [changeSecret, setChangeSecret] = useState<boolean>(false);
const hasSecretKey = !!props?.webhook?.secret;
// const currentSecret = props?.webhook?.secret;
@@ -98,10 +89,10 @@ const WebhookForm = (props: {
}, [changeSecret, formMethods]);
return (
<>
<Form
form={formMethods}
handleSubmit={(values) => props.onSubmit({ ...values, changeSecret, newSecret })}>
<Form
form={formMethods}
handleSubmit={(values) => props.onSubmit({ ...values, changeSecret, newSecret })}>
<div className="border-subtle border-x p-6">
<Controller
name="subscriberUrl"
control={formMethods.control}
@@ -115,10 +106,12 @@ const WebhookForm = (props: {
required
type="url"
onChange={(e) => {
formMethods.setValue("subscriberUrl", e?.target.value);
formMethods.setValue("subscriberUrl", e?.target.value, { shouldDirty: true });
if (hasTemplateIntegration({ url: e.target.value })) {
setUseCustomTemplate(true);
formMethods.setValue("payloadTemplate", customTemplate({ url: e.target.value }));
formMethods.setValue("payloadTemplate", customTemplate({ url: e.target.value }), {
shouldDirty: true,
});
}
}}
/>
@@ -129,13 +122,13 @@ const WebhookForm = (props: {
name="active"
control={formMethods.control}
render={({ field: { value } }) => (
<div className="font-sm text-emphasis mt-8 font-medium">
<div className="font-sm text-emphasis mt-6 font-medium">
<Switch
label={t("enable_webhook")}
checked={value}
// defaultChecked={props?.webhook?.active ? props?.webhook?.active : true}
onCheckedChange={(value) => {
formMethods.setValue("active", value);
formMethods.setValue("active", value, { shouldDirty: true });
}}
/>
</div>
@@ -147,8 +140,8 @@ const WebhookForm = (props: {
render={({ field: { onChange, value } }) => {
const selectValue = translatedTriggerOptions.filter((option) => value.includes(option.value));
return (
<div className="mt-8">
<Label className="font-sm text-emphasis mt-8 font-medium">
<div className="mt-6">
<Label className="font-sm text-emphasis font-medium">
<>{t("event_triggers")}</>
</Label>
<Select
@@ -167,7 +160,7 @@ const WebhookForm = (props: {
name="secret"
control={formMethods.control}
render={({ field: { value } }) => (
<div className="mt-8 ">
<div className="mt-6">
{!!hasSecretKey && !changeSecret && (
<>
<Label className="font-sm text-emphasis font-medium">Secret</Label>
@@ -218,7 +211,7 @@ const WebhookForm = (props: {
labelClassName="font-medium text-emphasis font-sm"
value={value}
onChange={(e) => {
formMethods.setValue("secret", e?.target.value);
formMethods.setValue("secret", e?.target.value, { shouldDirty: true });
}}
/>
)}
@@ -231,7 +224,7 @@ const WebhookForm = (props: {
control={formMethods.control}
render={({ field: { value } }) => (
<>
<Label className="font-sm text-emphasis mt-8">
<Label className="font-sm text-emphasis mt-6">
<>{t("payload_template")}</>
</Label>
<div className="mb-2">
@@ -239,7 +232,7 @@ const WebhookForm = (props: {
onValueChange={(val) => {
if (val === "default") {
setUseCustomTemplate(false);
formMethods.setValue("payloadTemplate", undefined);
formMethods.setValue("payloadTemplate", undefined, { shouldDirty: true });
} else {
setUseCustomTemplate(true);
}
@@ -258,33 +251,34 @@ const WebhookForm = (props: {
rows={3}
value={value}
onChange={(e) => {
formMethods.setValue("payloadTemplate", e?.target.value);
formMethods.setValue("payloadTemplate", e?.target.value, { shouldDirty: true });
}}
/>
)}
</>
)}
/>
<div className="bg-subtle mt-8 rounded-md p-6">
<WebhookTestDisclosure />
</div>
</div>
<SectionBottomActions align="end">
<Button
type="button"
color="minimal"
onClick={props.onCancel}
{...(!props.onCancel ? { href: `${WEBAPP_URL}/settings/developer/webhooks` } : {})}>
{t("cancel")}
</Button>
<Button
type="submit"
disabled={!formMethods.formState.isDirty && !changeSecret}
loading={formMethods.formState.isSubmitting || formMethods.formState.isSubmitted}>
{props?.webhook?.id ? t("save") : t("create_webhook")}
</Button>
</SectionBottomActions>
<DialogFooter showDivider>
<Button
type="button"
color="minimal"
onClick={props.onCancel}
{...(!props.onCancel ? { href: `${WEBAPP_URL}/settings/developer/webhooks` } : {})}>
{t("cancel")}
</Button>
<Button
type="submit"
loading={formMethods.formState.isSubmitting || formMethods.formState.isSubmitted}>
{props?.webhook?.id ? t("save") : t("create_webhook")}
</Button>
</DialogFooter>
</Form>
</>
<div className="mt-6 rounded-md">
<WebhookTestDisclosure />
</div>
</Form>
);
};
@@ -17,10 +17,10 @@ export default function WebhookTestDisclosure() {
return (
<>
<div className="flex justify-between">
<div className="border-subtle flex justify-between rounded-t-xl border p-6">
<div>
<p className="text-emphasis text-sm font-semibold leading-5">{t("webhook_test")}</p>
<p className="text-default mb-4 text-sm">{t("test_webhook")}</p>
<p className="text-default text-sm">{t("test_webhook")}</p>
</div>
<Button
type="button"
@@ -31,8 +31,8 @@ export default function WebhookTestDisclosure() {
{t("ping_test")}
</Button>
</div>
<div className="border-subtle space-y-0 rounded-md border sm:mx-0">
<div className="flex justify-between border-b p-4">
<div className="border-subtle space-y-0 rounded-b-xl border border-t-0 px-6 py-8 sm:mx-0">
<div className="border-subtle flex justify-between rounded-t-xl border p-4">
<div className="flex items-center space-x-1">
<h3 className="text-emphasis self-center text-sm font-semibold leading-4">
{t("webhook_response")}
@@ -44,10 +44,10 @@ export default function WebhookTestDisclosure() {
)}
</div>
</div>
<div className="text-inverted bg-inverted rounded-b-md p-4 font-mono text-[13px] leading-4">
<div className="bg-muted border-subtle rounded-b-xl border border-t-0 p-4 font-mono text-[13px] leading-4 ">
{!mutation.data && <p>{t("no_data_yet")}</p>}
{mutation.status === "success" && (
<div className="text-inverted overflow-x-auto">{JSON.stringify(mutation.data, null, 4)}</div>
<div className="overflow-x-auto">{JSON.stringify(mutation.data, null, 4)}</div>
)}
</div>
</div>
@@ -60,7 +60,7 @@ function Component({ webhookId }: { webhookId: string }) {
<Meta
title={t("edit_webhook")}
description={t("add_webhook_description", { appName: APP_NAME })}
backButton
borderInShellHeader={true}
/>
<WebhookForm
noRoutingFormTriggers={false}
@@ -80,7 +80,7 @@ function Component({ webhookId }: { webhookId: string }) {
}
if (values.changeSecret) {
values.secret = values.newSecret.length ? values.newSecret : null;
values.secret = values.newSecret.trim().length ? values.newSecret : null;
}
if (!values.payloadTemplate) {
@@ -4,13 +4,24 @@ import { useRouter, useSearchParams } from "next/navigation";
import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Meta, showToast, SkeletonContainer } from "@calcom/ui";
import { Meta, showToast, SkeletonContainer, SkeletonText } from "@calcom/ui";
import { getLayout } from "../../settings/layouts/SettingsLayout";
import type { WebhookFormSubmitData } from "../components/WebhookForm";
import WebhookForm from "../components/WebhookForm";
import { subscriberUrlReserved } from "../lib/subscriberUrlReserved";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} borderInShellHeader={true} />
<div className="divide-subtle border-subtle space-y-6 rounded-b-xl border border-t-0 px-6 py-4">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
</div>
</SkeletonContainer>
);
};
const NewWebhookView = () => {
const searchParams = useSearchParams();
const { t } = useLocale();
@@ -71,7 +82,13 @@ const NewWebhookView = () => {
});
};
if (isLoading) return <SkeletonContainer />;
if (isLoading)
return (
<SkeletonLoader
title={t("add_webhook")}
description={t("add_webhook_description", { appName: APP_NAME })}
/>
);
return (
<>
@@ -79,6 +96,7 @@ const NewWebhookView = () => {
title={t("add_webhook")}
description={t("add_webhook_description", { appName: APP_NAME })}
backButton
borderInShellHeader={true}
/>
<WebhookForm
noRoutingFormTriggers={false}
@@ -1,36 +1,73 @@
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { Suspense } from "react";
import classNames from "@calcom/lib/classNames";
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import type { WebhooksByViewer } from "@calcom/trpc/server/routers/viewer/webhook/getByViewer.handler";
import { Avatar, CreateButtonWithTeamsList, EmptyScreen, Meta } from "@calcom/ui";
import {
Avatar,
CreateButtonWithTeamsList,
EmptyScreen,
Meta,
SkeletonContainer,
SkeletonText,
} from "@calcom/ui";
import { Link as LinkIcon } from "@calcom/ui/components/icon";
import { getLayout } from "../../settings/layouts/SettingsLayout";
import { WebhookListItem, WebhookListSkeleton } from "../components";
import { WebhookListItem } from "../components";
const SkeletonLoader = ({
title,
description,
borderInShellHeader,
}: {
title: string;
description: string;
borderInShellHeader: boolean;
}) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} borderInShellHeader={borderInShellHeader} />
<div className="divide-subtle border-subtle space-y-6 rounded-b-xl border border-t-0 px-6 py-4">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
</div>
</SkeletonContainer>
);
};
const WebhooksView = () => {
const { t } = useLocale();
const router = useRouter();
const session = useSession();
const { data } = trpc.viewer.webhook.getByViewer.useQuery(undefined, {
suspense: true,
const { data, isLoading } = trpc.viewer.webhook.getByViewer.useQuery(undefined, {
enabled: session.status === "authenticated",
});
if (isLoading || !data) {
return (
<SkeletonLoader
title={t("webhooks")}
description={t("add_webhook_description", { appName: APP_NAME })}
borderInShellHeader={true}
/>
);
}
return (
<>
<Meta
title="Webhooks"
title={t("webhooks")}
description={t("add_webhook_description", { appName: APP_NAME })}
CTA={
data && data.webhookGroups.length > 0 ? (
<CreateButtonWithTeamsList
color="secondary"
subtitle={t("create_for").toUpperCase()}
createFunction={(teamId?: number) => {
router.push(`webhooks/new${teamId ? `?teamId=${teamId}` : ""}`);
@@ -41,11 +78,10 @@ const WebhooksView = () => {
<></>
)
}
borderInShellHeader={!(data && data.webhookGroups.length > 0)}
/>
<div>
<Suspense fallback={<WebhookListSkeleton />}>
{data && <WebhooksList webhooksByViewer={data} />}
</Suspense>
<WebhooksList webhooksByViewer={data} />
</div>
</>
);
@@ -65,11 +101,11 @@ const WebhooksList = ({ webhooksByViewer }: { webhooksByViewer: WebhooksByViewer
{webhookGroups && (
<>
{!!webhookGroups.length && (
<>
<div className={classNames("mt-0", hasTeams && "mt-6")}>
{webhookGroups.map((group) => (
<div key={group.teamId}>
{hasTeams && (
<div className="items-centers flex ">
<div className="items-centers flex">
<Avatar
alt={group.profile.image || ""}
imageSrc={group.profile.image || `${bookerUrl}/${group.profile.name}/avatar.png`}
@@ -82,7 +118,11 @@ const WebhooksList = ({ webhooksByViewer }: { webhooksByViewer: WebhooksByViewer
</div>
)}
<div className="flex flex-col" key={group.profile.slug}>
<div className="border-subtle mb-8 mt-3 rounded-md border">
<div
className={classNames(
"border-subtle rounded-md rounded-t-none border border-t-0",
hasTeams && "mb-8 mt-3 rounded-t-md border-t"
)}>
{group.webhooks.map((webhook, index) => (
<WebhookListItem
key={webhook.id}
@@ -98,13 +138,14 @@ const WebhooksList = ({ webhooksByViewer }: { webhooksByViewer: WebhooksByViewer
</div>
</div>
))}
</>
</div>
)}
{!webhookGroups.length && (
<EmptyScreen
Icon={LinkIcon}
headline={t("create_your_first_webhook")}
description={t("create_your_first_webhook_description", { appName: APP_NAME })}
className="rounded-b-md rounded-t-none border-t-0"
buttonRaw={
<CreateButtonWithTeamsList
subtitle={t("create_for").toUpperCase()}