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")}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
Reference in new issue
Block a user