chore: front-end-avatars (#12716)
* Update UserAvatar and remove org avatar * Update Imports * Fix imports to use calcom/ui * type: fix imports * fix: use testId on profile * test: use image src instead of innerHTML * fix: Allow alt on useravatar * test: add testId to org profile --------- Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
parent
0dddc2224a
commit
698d8ae4bd
|
@ -3,7 +3,6 @@ import type { FormEvent } from "react";
|
|||
import { useRef, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import OrganizationMemberAvatar from "@calcom/features/ee/organizations/components/OrganizationMemberAvatar";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { md } from "@calcom/lib/markdownIt";
|
||||
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
|
@ -11,6 +10,7 @@ import turndown from "@calcom/lib/turndownService";
|
|||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { Ensure } from "@calcom/types/utils";
|
||||
import { Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui";
|
||||
import { UserAvatar } from "@calcom/ui";
|
||||
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||
|
||||
type FormData = {
|
||||
|
@ -108,9 +108,7 @@ const UserProfile = () => {
|
|||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className="flex flex-row items-center justify-start rtl:justify-end">
|
||||
{user && (
|
||||
<OrganizationMemberAvatar size="lg" user={user} previewSrc={imageSrc} organization={organization} />
|
||||
)}
|
||||
{user && <UserAvatar size="lg" user={user} previewSrc={imageSrc} organization={organization} />}
|
||||
<input
|
||||
ref={avatarRef}
|
||||
type="hidden"
|
||||
|
|
|
@ -5,8 +5,7 @@ import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
|||
import { md } from "@calcom/lib/markdownIt";
|
||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||
import type { TeamWithMembers } from "@calcom/lib/server/queries/teams";
|
||||
|
||||
import { UserAvatar } from "@components/ui/avatar/UserAvatar";
|
||||
import { UserAvatar } from "@calcom/ui";
|
||||
|
||||
type TeamType = Omit<NonNullable<TeamWithMembers>, "inviteToken">;
|
||||
type MembersType = TeamType["members"];
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
import { Avatar } from "@calcom/ui";
|
||||
|
||||
type UserAvatarProps = Omit<React.ComponentProps<typeof Avatar>, "alt" | "imageSrc"> & {
|
||||
user: Pick<User, "organizationId" | "name" | "username">;
|
||||
/**
|
||||
* Useful when allowing the user to upload their own avatar and showing the avatar before it's uploaded
|
||||
*/
|
||||
previewSrc?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* It is aware of the user's organization to correctly show the avatar from the correct URL
|
||||
*/
|
||||
export function UserAvatar(props: UserAvatarProps) {
|
||||
const { user, previewSrc = getUserAvatarUrl(user), ...rest } = props;
|
||||
return <Avatar {...rest} alt={user.name || "Nameless User"} imageSrc={previewSrc} />;
|
||||
}
|
|
@ -11,7 +11,6 @@ import {
|
|||
useEmbedStyles,
|
||||
useIsEmbed,
|
||||
} from "@calcom/embed-core/embed-iframe";
|
||||
import OrganizationMemberAvatar from "@calcom/features/ee/organizations/components/OrganizationMemberAvatar";
|
||||
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
|
||||
|
@ -28,6 +27,7 @@ import { RedirectType, type EventType, type User } from "@calcom/prisma/client";
|
|||
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
||||
import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { HeadSeo, UnpublishedEntity } from "@calcom/ui";
|
||||
import { UserAvatar } from "@calcom/ui";
|
||||
import { Verified, ArrowRight } from "@calcom/ui/components/icon";
|
||||
|
||||
import type { EmbedProps } from "@lib/withEmbedSsr";
|
||||
|
@ -101,7 +101,7 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
|
|||
"max-w-3xl px-4 py-24"
|
||||
)}>
|
||||
<div className="mb-8 text-center">
|
||||
<OrganizationMemberAvatar
|
||||
<UserAvatar
|
||||
size="xl"
|
||||
user={{
|
||||
organizationId: profile.organization?.id,
|
||||
|
|
|
@ -6,7 +6,6 @@ import { Controller, useForm } from "react-hook-form";
|
|||
import { z } from "zod";
|
||||
|
||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||
import OrganizationMemberAvatar from "@calcom/features/ee/organizations/components/OrganizationMemberAvatar";
|
||||
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
import { APP_NAME, FULL_NAME_LENGTH_MAX_LIMIT } from "@calcom/lib/constants";
|
||||
|
@ -41,6 +40,7 @@ import {
|
|||
SkeletonText,
|
||||
TextField,
|
||||
} from "@calcom/ui";
|
||||
import { UserAvatar } from "@calcom/ui";
|
||||
import { AlertTriangle, Trash2 } from "@calcom/ui/components/icon";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
@ -448,7 +448,8 @@ const ProfileForm = ({
|
|||
: null;
|
||||
return (
|
||||
<>
|
||||
<OrganizationMemberAvatar
|
||||
<UserAvatar
|
||||
data-testid="profile-upload-avatar"
|
||||
previewSrc={value}
|
||||
size="lg"
|
||||
user={user}
|
||||
|
|
|
@ -43,8 +43,11 @@ test.describe("UploadAvatar", async () => {
|
|||
// todo: remove this; ideally the organization-avatar is updated the moment
|
||||
// 'Settings updated succesfully' is saved.
|
||||
await page.waitForLoadState("networkidle");
|
||||
const avatar = page.getByTestId("profile-upload-avatar").locator("img");
|
||||
|
||||
await expect(await page.getByTestId("organization-avatar").innerHTML()).toContain(response.objectKey);
|
||||
const src = await avatar.getAttribute("src");
|
||||
|
||||
await expect(src).toContain(response.objectKey);
|
||||
|
||||
const urlResponse = await page.request.get(`/api/avatar/${response.objectKey}.png`, {
|
||||
maxRedirects: 0,
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
import classNames from "@calcom/lib/classNames";
|
||||
import { getOrgAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
// import { Avatar } from "@calcom/ui";
|
||||
import { UserAvatar } from "@calcom/web/components/ui/avatar/UserAvatar";
|
||||
|
||||
type OrganizationMemberAvatarProps = React.ComponentProps<typeof UserAvatar> & {
|
||||
organization: {
|
||||
id: number;
|
||||
slug: string | null;
|
||||
requestedSlug: string | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows the user's avatar along with a small organization's avatar
|
||||
*/
|
||||
const OrganizationMemberAvatar = ({
|
||||
size,
|
||||
user,
|
||||
organization,
|
||||
previewSrc,
|
||||
...rest
|
||||
}: OrganizationMemberAvatarProps) => {
|
||||
return (
|
||||
<UserAvatar
|
||||
data-testid="organization-avatar"
|
||||
size={size}
|
||||
user={user}
|
||||
previewSrc={previewSrc}
|
||||
indicator={
|
||||
organization ? (
|
||||
<div
|
||||
className={classNames("absolute bottom-0 right-0 z-10", size === "lg" ? "h-6 w-6" : "h-10 w-10")}>
|
||||
<img
|
||||
src={getOrgAvatarUrl(organization)}
|
||||
alt={user.username || ""}
|
||||
className="flex h-full items-center justify-center rounded-full"
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrganizationMemberAvatar;
|
|
@ -12,11 +12,10 @@ import {
|
|||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
Tooltip,
|
||||
UserAvatar,
|
||||
} from "@calcom/ui";
|
||||
import { ExternalLink, MoreHorizontal } from "@calcom/ui/components/icon";
|
||||
|
||||
import { UserAvatar } from "@components/ui/avatar/UserAvatar";
|
||||
|
||||
interface Props {
|
||||
member: RouterOutputs["viewer"]["organizations"]["listOtherTeamMembers"]["rows"][number];
|
||||
}
|
||||
|
|
|
@ -205,6 +205,7 @@ const OrgProfileForm = ({ defaultValues }: { defaultValues: FormValues }) => {
|
|||
return (
|
||||
<>
|
||||
<Avatar
|
||||
data-testid="profile-upload-avatar"
|
||||
alt={defaultValues.name || ""}
|
||||
imageSrc={getPlaceholderAvatar(value, defaultValues.name as string)}
|
||||
size="lg"
|
||||
|
|
|
@ -13,9 +13,16 @@ import { useTelemetry, telemetryEventTypes } from "@calcom/lib/telemetry";
|
|||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Badge, Button, showToast, SkeletonButton, SkeletonContainer, SkeletonText } from "@calcom/ui";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
showToast,
|
||||
SkeletonButton,
|
||||
SkeletonContainer,
|
||||
SkeletonText,
|
||||
UserAvatar,
|
||||
} from "@calcom/ui";
|
||||
import { ArrowRight, Plus, Trash2 } from "@calcom/ui/components/icon";
|
||||
import { UserAvatar } from "@calcom/web/components/ui/avatar/UserAvatar";
|
||||
|
||||
type TeamMember = RouterOutputs["viewer"]["teams"]["get"]["members"][number];
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ import {
|
|||
showToast,
|
||||
Tooltip,
|
||||
} from "@calcom/ui";
|
||||
import { UserAvatar } from "@calcom/ui";
|
||||
import { ExternalLink, MoreHorizontal, Edit2, Lock, UserX } from "@calcom/ui/components/icon";
|
||||
import { UserAvatar } from "@calcom/web/components/ui/avatar/UserAvatar";
|
||||
|
||||
import MemberChangeRoleModal from "./MemberChangeRoleModal";
|
||||
import TeamAvailabilityModal from "./TeamAvailabilityModal";
|
||||
|
|
|
@ -9,7 +9,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||
import type { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { trpc } from "@calcom/trpc";
|
||||
import { Button, ButtonGroup, DataTable } from "@calcom/ui";
|
||||
import { UserAvatar } from "@calcom/web/components/ui/avatar/UserAvatar";
|
||||
import { UserAvatar } from "@calcom/ui";
|
||||
|
||||
import { UpgradeTip } from "../../tips/UpgradeTip";
|
||||
import { TBContext, createTimezoneBuddyStore } from "../store";
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/* eslint-disable playwright/missing-playwright-await */
|
||||
import { render } from "@testing-library/react";
|
||||
|
||||
import { AVATAR_FALLBACK } from "@calcom/lib/constants";
|
||||
|
||||
import { UserAvatar } from "./UserAvatar";
|
||||
|
||||
const mockUser = {
|
||||
name: "John Doe",
|
||||
username: "pro",
|
||||
organizationId: null,
|
||||
};
|
||||
|
||||
describe("tests for UserAvatar component", () => {
|
||||
test("Should render the UsersAvatar Correctly", () => {
|
||||
const { getByTestId } = render(<UserAvatar user={mockUser} data-testid="user-avatar-test" />);
|
||||
const avatar = getByTestId("user-avatar-test");
|
||||
|
||||
expect(avatar).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("It should render the organization logo if a organization is passed in", () => {
|
||||
const { getByTestId } = render(
|
||||
<UserAvatar
|
||||
user={mockUser}
|
||||
organization={{ id: -1, requestedSlug: "steve", slug: "steve", logoUrl: AVATAR_FALLBACK }}
|
||||
data-testid="user-avatar-test"
|
||||
/>
|
||||
);
|
||||
|
||||
const avatar = getByTestId("user-avatar-test");
|
||||
const organizationLogo = getByTestId("organization-logo");
|
||||
|
||||
expect(avatar).toBeInTheDocument();
|
||||
expect(organizationLogo).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
import { classNames } from "@calcom/lib";
|
||||
import { getOrgAvatarUrl, getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
import { Avatar } from "@calcom/ui";
|
||||
|
||||
type Organization = {
|
||||
id: number;
|
||||
slug: string | null;
|
||||
requestedSlug: string | null;
|
||||
logoUrl?: string;
|
||||
};
|
||||
|
||||
type UserAvatarProps = Omit<React.ComponentProps<typeof Avatar>, "alt" | "imageSrc"> & {
|
||||
user: Pick<User, "organizationId" | "name" | "username">;
|
||||
/**
|
||||
* Useful when allowing the user to upload their own avatar and showing the avatar before it's uploaded
|
||||
*/
|
||||
previewSrc?: string | null;
|
||||
organization?: Organization | null;
|
||||
alt?: string | null;
|
||||
};
|
||||
|
||||
function OrganizationIndicator({
|
||||
size,
|
||||
organization,
|
||||
user,
|
||||
}: Pick<UserAvatarProps, "size" | "user"> & { organization: Organization }) {
|
||||
const organizationUrl = organization.logoUrl ?? getOrgAvatarUrl(organization);
|
||||
return (
|
||||
<div className={classNames("absolute bottom-0 right-0 z-10", size === "lg" ? "h-6 w-6" : "h-10 w-10")}>
|
||||
<img
|
||||
data-testId="organization-logo"
|
||||
src={organizationUrl}
|
||||
alt={user.username || ""}
|
||||
className="flex h-full items-center justify-center rounded-full"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* It is aware of the user's organization to correctly show the avatar from the correct URL
|
||||
*/
|
||||
export function UserAvatar(props: UserAvatarProps) {
|
||||
const { user, previewSrc = getUserAvatarUrl(user), ...rest } = props;
|
||||
|
||||
const indicator = props.organization ? (
|
||||
<OrganizationIndicator size={props.size} organization={props.organization} user={props.user} />
|
||||
) : (
|
||||
props.indicator
|
||||
);
|
||||
|
||||
return <Avatar {...rest} alt={user.name || "Nameless User"} imageSrc={previewSrc} indicator={indicator} />;
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
export { Avatar } from "./Avatar";
|
||||
export { UserAvatar } from "./UserAvatar";
|
||||
export type { AvatarProps } from "./Avatar";
|
||||
export { AvatarGroup } from "./AvatarGroup";
|
||||
export type { AvatarGroupProps } from "./AvatarGroup";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export { Avatar, AvatarGroup } from "./components/avatar";
|
||||
export { Avatar, AvatarGroup, UserAvatar } from "./components/avatar";
|
||||
export type { AvatarProps, AvatarGroupProps } from "./components/avatar";
|
||||
export { ArrowButton } from "./components/arrow-button";
|
||||
export type { ArrowButtonProps } from "./components/arrow-button";
|
||||
|
|
Loading…
Reference in New Issue
Block a user