Co-authored-by: Peer Richelsen <peeroke@gmail.com>
This commit is contained in:
sean-brydonandPeer Richelsen authored and GitHub committed 2024-08-06 01:35:15 +01:00
1 parent e7816cfb0c
commit d33681eddd
6 files changed
+123 -146

No files matched your search

@@ -1,68 +1,43 @@
import { classNames } from "@calcom/lib";
import { useCopy } from "@calcom/lib/hooks/useCopy";
import type { BadgeProps } from "@calcom/ui";
import { Badge, Button, Label } from "@calcom/ui";
import type { IconName } from "@calcom/ui";
import { Badge, Icon } from "@calcom/ui";
type DisplayInfoType<T extends boolean> = {
type DisplayInfoType = {
label: string;
value: T extends true ? string[] : string;
asBadge?: boolean;
isArray?: T;
displayCopy?: boolean;
badgeColor?: BadgeProps["variant"];
} & (T extends false
? { displayCopy?: boolean; displayCount?: never }
: { displayCopy?: never; displayCount?: number }); // Only show displayCopy if its not an array is false
icon?: IconName;
value: string | string[];
coloredBadges?: boolean;
};
export function DisplayInfo<T extends boolean>({
label,
value,
asBadge,
isArray,
displayCopy,
displayCount,
badgeColor,
}: DisplayInfoType<T>) {
const { copyToClipboard, isCopied } = useCopy();
const values = (isArray ? value : [value]) as string[];
const badgeColors = ["warning", "success", "green", "gray", "blue", "red", "error"] as const;
export function DisplayInfo({ label, icon, value, coloredBadges }: DisplayInfoType) {
const displayAsBadges = Array.isArray(value);
return (
<div className="flex flex-col">
<Label className="text-subtle mb-1 text-xs font-semibold uppercase leading-none">
{label} {displayCount && `(${displayCount})`}
</Label>
<div className={classNames(asBadge ? "mt-0.5 flex space-x-2" : "flex flex-col")}>
<>
{values.map((v) => {
const content = (
<span
className={classNames(
"text-emphasis inline-flex items-center gap-1 font-normal leading-5",
asBadge ? "text-xs" : "text-sm"
)}>
{v}
{displayCopy && (
<Button
size="sm"
variant="icon"
onClick={() => copyToClipboard(v)}
color="minimal"
className="text-subtle rounded-md"
StartIcon={isCopied ? "clipboard-check" : "clipboard"}
/>
)}
</span>
);
return asBadge ? (
<Badge variant={badgeColor} size="sm">
{content}
</Badge>
) : (
content
);
})}
</>
<div className="flex items-center gap-6">
<div className="flex w-[110px] items-center gap-2">
{icon ? <Icon className="text-subtle h-4 w-4" name={icon} /> : null}
<label className="text-subtle text-sm font-medium">{label}</label>
</div>
<div className="flex flex-1">
{displayAsBadges ? (
<div className="flex flex-wrap gap-2">
{value.map((v, idx) => {
return coloredBadges ? (
<Badge variant={badgeColors[idx % badgeColors.length]} key={v}>
{v}
</Badge>
) : (
<Badge variant="gray">{v}</Badge>
);
})}
</div>
) : (
<span className={classNames("text-emphasis inline-flex items-center gap-1 font-medium leading-5")}>
{value}
</span>
)}
</div>
</div>
);
@@ -5,26 +5,19 @@ import { useOrgBranding } from "@calcom/ee/organizations/context/provider";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import {
Avatar,
Label,
Loader,
Sheet,
SheetContent,
SheetBody,
Skeleton,
SheetHeader,
SheetDescription,
SheetTitle,
SheetFooter,
} from "@calcom/ui";
import { Avatar, Loader, Sheet, SheetContent, SheetBody, SheetHeader, SheetFooter } from "@calcom/ui";
import type { Action, State } from "../UserListTable";
import { DisplayInfo } from "./DisplayInfo";
import { EditForm } from "./EditUserForm";
import { OrganizationBanner } from "./OrganizationBanner";
import { SheetFooterControls } from "./SheetFooterControls";
import { useEditMode } from "./store";
function removeProtocol(url: string) {
return url.replace(/^(https?:\/\/)/, "");
}
export function EditUserSheet({ state, dispatch }: { state: State; dispatch: Dispatch<Action> }) {
const { t } = useLocale();
const { user: selectedUser } = state.editSheet;
@@ -47,67 +40,55 @@ export function EditUserSheet({ state, dispatch }: { state: State; dispatch: Dis
setEditMode(false);
dispatch({ type: "CLOSE_MODAL" });
}}>
<SheetContent>
<SheetContent className="bg-muted">
{!isPending && loadedUser ? (
<>
{!editMode ? (
<>
<SheetHeader>
<Avatar
asChild
className="h-[36px] w-[36px]"
alt={`${loadedUser?.name} avatar`}
imageSrc={loadedUser.avatarUrl}
/>
<SheetTitle>
<Skeleton loading={isPending} as="p" waitForTranslation={false}>
<span className="text-emphasis text-lg font-semibold">
{loadedUser?.name ?? "Nameless User"}
</span>
</Skeleton>
</SheetTitle>
<SheetDescription>
<Skeleton loading={isPending} as="p" waitForTranslation={false}>
<p className="subtle text-sm font-normal">
{orgBranding?.fullDomain ?? WEBAPP_URL}/{loadedUser?.username}
</p>
</Skeleton>
</SheetDescription>
</SheetHeader>
<SheetBody className="flex flex-col space-y-5">
<DisplayInfo label={t("email")} value={loadedUser?.email ?? ""} displayCopy />
<DisplayInfo
label={t("bio")}
badgeColor="gray"
value={loadedUser?.bio ? loadedUser?.bio : t("user_has_no_bio")}
/>
<DisplayInfo label={t("role")} value={loadedUser?.role ?? ""} asBadge badgeColor="blue" />
<DisplayInfo label={t("timezone")} value={loadedUser?.timeZone ?? ""} />
<div className="flex flex-col">
<Label className="text-subtle mb-1 text-xs font-semibold uppercase leading-none">
{t("availability_schedules")}
</Label>
<div className="flex flex-col">
{schedulesNames
? schedulesNames.map((scheduleName) => (
<span
key={scheduleName}
className="text-emphasis inline-flex items-center gap-1 text-sm font-normal leading-5">
{scheduleName}
</span>
))
: t("user_has_no_schedules")}
<SheetHeader showCloseButton={false}>
<div className="border-sublte bg-default w-full rounded-xl border p-4">
<OrganizationBanner />
<div className="bg-default ml-3 w-fit translate-y-[-50%] rounded-full p-1 ring-1 ring-[#0000000F]">
<Avatar
asChild
size="lg"
alt={`${loadedUser?.name} avatar`}
imageSrc={loadedUser.avatarUrl}
/>
</div>
<h2 className="text-emphasis font-sans text-2xl font-semibold">
{loadedUser?.name || "Nameless User"}
</h2>
<p className="text-subtle max-h-[3em] overflow-hidden text-ellipsis text-sm font-normal">
{loadedUser?.bio || "This user does not have a bio..."}
</p>
</div>
</SheetHeader>
<SheetBody className="flex flex-col space-y-8 p-4">
<div className="flex flex-col space-y-4">
<h3 className="text-emphasis mb-1 text-base font-semibold">{t("profile")}</h3>
<DisplayInfo
label="Cal"
value={removeProtocol(
`${orgBranding?.fullDomain ?? WEBAPP_URL}/${loadedUser?.username}`
)}
icon="external-link"
/>
<DisplayInfo label={t("email")} value={loadedUser?.email ?? ""} icon="at-sign" />
<DisplayInfo label={t("role")} value={[loadedUser?.role ?? ""]} icon="fingerprint" />
<DisplayInfo label={t("timezone")} value={loadedUser?.timeZone ?? ""} icon="clock" />
<DisplayInfo
label={t("teams")}
value={!teamNames || teamNames.length === 0 ? "" : teamNames}
icon="users"
coloredBadges
/>
<DisplayInfo
label={t("availability")}
value={!schedulesNames || schedulesNames.length === 0 ? "" : schedulesNames}
icon="calendar"
/>
</div>
<DisplayInfo
label={t("teams")}
displayCount={teamNames?.length ?? 0}
value={
teamNames && teamNames?.length === 0 ? [t("user_isnt_in_any_teams")] : teamNames ?? "" // TS wtf
}
asBadge={teamNames && teamNames?.length > 0}
/>
</SheetBody>
<SheetFooter>
<SheetFooterControls />
@@ -0,0 +1,24 @@
import { trpc } from "@calcom/trpc/react";
export function OrganizationBanner() {
const {
data: currentOrganisation,
isPending: isPendingOrg,
error,
} = trpc.viewer.organizations.listCurrent.useQuery(undefined, {});
return (
<>
<div
className="block w-full rounded-lg ring-1 ring-[#0000000F]"
style={{
background: "linear-gradient(to top right, var(--cal-bg-emphasis), var(--cal-bg))",
height: currentOrganisation && currentOrganisation?.bannerUrl ? "auto" : "110px",
}}>
{currentOrganisation && currentOrganisation?.bannerUrl && (
<img src={currentOrganisation?.bannerUrl} alt="Organization Banner" className="rounded-lg" />
)}
</div>
</>
);
}
@@ -13,14 +13,13 @@ function EditModeFooter() {
<Button
color="secondary"
type="button"
className="justify-center md:w-1/5"
onClick={() => {
setEditMode(false);
}}>
{t("cancel")}
</Button>
<Button type="submit" className="w-full justify-center" form="edit-user-form" loading={isPending}>
<Button type="submit" className="w-fit" form="edit-user-form" loading={isPending}>
{t("update")}
</Button>
</>
@@ -34,7 +33,7 @@ function MoreInfoFooter() {
return (
<>
<SheetClose asChild>
<Button color="secondary" type="button" className="w-full justify-center lg:w-1/5">
<Button color="secondary" type="button" className="justify-center">
{t("close")}
</Button>
</SheetClose>
@@ -43,8 +42,6 @@ function MoreInfoFooter() {
onClick={() => {
setEditMode(true);
}}
className="w-full justify-center gap-2"
variant="icon"
key="EDIT_BUTTON"
StartIcon="pencil">
{t("edit")}
+1 -1
View File
@@ -14,7 +14,7 @@ export const badgeStyles = cva("font-medium inline-flex items-center justify-cen
orange: "bg-attention text-attention",
success: "bg-success text-success",
green: "bg-success text-success",
gray: "bg-subtle text-emphasis",
gray: "bg-emphasis text-emphasis",
blue: "bg-info text-info",
red: "bg-error text-error",
error: "bg-error text-error",
+13 -13
View File
@@ -68,7 +68,7 @@ const SheetContent = React.forwardRef<
ref={forwardedRef}
className={classNames(
// base
"fixed inset-y-2 mx-auto flex w-[95vw] flex-1 flex-col overflow-y-auto rounded-md border p-4 shadow-lg focus:outline-none max-sm:inset-x-2 sm:inset-y-2 sm:right-2 sm:max-w-lg sm:p-6",
"fixed inset-y-2 mx-auto flex w-[95vw] flex-1 flex-col overflow-y-auto rounded-xl border p-4 shadow-lg focus:outline-none max-sm:inset-x-2 sm:inset-y-2 sm:right-2 sm:max-w-lg sm:p-6",
// border color
"border-subtle",
// background color
@@ -86,21 +86,21 @@ const SheetContent = React.forwardRef<
SheetContent.displayName = "SheetContent";
const SheetHeader = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(
({ children, className, ...props }, ref) => {
return (
<div
ref={ref}
className="border-subtle flex items-start justify-between gap-x-4 border-b pb-2"
{...props}>
<div className={classNames("mt-1 flex flex-col gap-y-1", className)}>{children}</div>
const SheetHeader = React.forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<"div"> & { showCloseButton?: boolean }
>(({ children, className, showCloseButton = true, ...props }, ref) => {
return (
<div ref={ref} className="flex items-start justify-between gap-x-4 pb-2" {...props}>
<div className={classNames("mt-1 flex flex-col gap-y-1", className)}>{children}</div>
{showCloseButton && (
<SheetPrimitives.Close asChild>
<Button variant="icon" StartIcon="x" color="minimal" className="aspect-square p-1" />
</SheetPrimitives.Close>
</div>
);
}
);
)}
</div>
);
});
SheetHeader.displayName = "Sheet.Header";