From 5c0da23b971b43336cd24b671557969152150c1e Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Date: Thu, 7 Sep 2023 11:26:40 -0400 Subject: [PATCH] feat: User Avatar and Org Logo (#10700) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Pass organization name & logo * Overflow hidden * Show org icon on public page * Add org logo to large user avatars * Clean up * Add org name and logo to context * Get org logo from /avatar.png endpoint * Do not query for logo * Remove name and logo from session middleware * Type fix * Set user onboarding org logo * feat: organization avatar component (#10788) Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> * Type fixes * Type fix * Transition to org slug for organization avatar * Address feedback * Clean up * Clean up * Type fix * fix: set avatar cache control (#11163) * test: Integration tests for handleNewBooking (#11044) Co-authored-by: Shivam Kalra Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> * fix: booking_paid webhook and added new payment metadata (#11093) * app store improvements, logos, dark mode, added screenshots, fixed author names (#11164) * fix: mobile event types and avatars (#11184) * New Crowdin translations by Github Action * fix: updateProfile metadata overwrite (#11188) Co-authored-by: alannnc * New Crowdin translations by Github Action --------- Co-authored-by: Sean Brydon Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Co-authored-by: Omar López Co-authored-by: Hariom Balhara Co-authored-by: Shivam Kalra Co-authored-by: alannnc Co-authored-by: Peer Richelsen Co-authored-by: Leo Giovanetti Co-authored-by: Crowdin Bot --- .../steps-views/UserProfile.tsx | 12 +++++-- apps/web/next.config.js | 4 +++ apps/web/pages/[user].tsx | 12 +++++-- apps/web/pages/api/user/avatar.ts | 21 +++++++++++-- .../web/pages/settings/my-account/profile.tsx | 13 ++++++-- .../components/OrganizationAvatar.tsx | 31 +++++++++++++++++++ .../server/middlewares/sessionMiddleware.ts | 1 + packages/ui/components/avatar/Avatar.tsx | 19 +++--------- packages/ui/components/avatar/AvatarGroup.tsx | 2 -- 9 files changed, 91 insertions(+), 24 deletions(-) create mode 100644 packages/features/ee/organizations/components/OrganizationAvatar.tsx diff --git a/apps/web/components/getting-started/steps-views/UserProfile.tsx b/apps/web/components/getting-started/steps-views/UserProfile.tsx index aae5e10f7d..f197ff4461 100644 --- a/apps/web/components/getting-started/steps-views/UserProfile.tsx +++ b/apps/web/components/getting-started/steps-views/UserProfile.tsx @@ -3,12 +3,13 @@ import type { FormEvent } from "react"; import { useRef, useState } from "react"; import { useForm } from "react-hook-form"; +import OrganizationAvatar from "@calcom/features/ee/organizations/components/OrganizationAvatar"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { md } from "@calcom/lib/markdownIt"; import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import turndown from "@calcom/lib/turndownService"; import { trpc } from "@calcom/trpc/react"; -import { Avatar, Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui"; +import { Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui"; import { ArrowRight } from "@calcom/ui/components/icon"; type FormData = { @@ -98,7 +99,14 @@ const UserProfile = () => { return (
- {user && } + {user && ( + + )}
- +

{profile.name} {user.verified && ( @@ -218,6 +224,7 @@ export type UserPageProps = { theme: string | null; brandColor: string; darkBrandColor: string; + organizationSlug: string | null; allowSEOIndexing: boolean; }; users: Pick[]; @@ -321,6 +328,7 @@ export const getServerSideProps: GetServerSideProps = async (cont theme: user.theme, brandColor: user.brandColor, darkBrandColor: user.darkBrandColor, + organizationSlug: user.organization?.slug ?? null, allowSEOIndexing: user.allowSEOIndexing ?? true, }; diff --git a/apps/web/pages/api/user/avatar.ts b/apps/web/pages/api/user/avatar.ts index edb6fb24b2..43cd00f0e1 100644 --- a/apps/web/pages/api/user/avatar.ts +++ b/apps/web/pages/api/user/avatar.ts @@ -10,6 +10,7 @@ const querySchema = z .object({ username: z.string(), teamname: z.string(), + orgSlug: z.string(), /** * Allow fetching avatar of a particular organization * Avatars being public, we need not worry about others accessing it. @@ -19,7 +20,7 @@ const querySchema = z .partial(); async function getIdentityData(req: NextApiRequest) { - const { username, teamname, orgId } = querySchema.parse(req.query); + const { username, teamname, orgId, orgSlug } = querySchema.parse(req.query); const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(req.headers.host ?? ""); const org = isValidOrgDomain ? currentOrgDomain : null; @@ -59,7 +60,23 @@ async function getIdentityData(req: NextApiRequest) { org, name: teamname, email: null, - avatar: team?.logo || getPlaceholderAvatar(null, teamname), + avatar: getPlaceholderAvatar(team?.logo, teamname), + }; + } + if (orgSlug) { + const org = await prisma.team.findFirst({ + where: getSlugOrRequestedSlug(orgSlug), + select: { + slug: true, + logo: true, + name: true, + }, + }); + return { + org: org?.slug, + name: org?.name, + email: null, + avatar: getPlaceholderAvatar(org?.logo, org?.name), }; } } diff --git a/apps/web/pages/settings/my-account/profile.tsx b/apps/web/pages/settings/my-account/profile.tsx index c83fe55106..4f36f5a933 100644 --- a/apps/web/pages/settings/my-account/profile.tsx +++ b/apps/web/pages/settings/my-account/profile.tsx @@ -6,6 +6,7 @@ import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode"; +import OrganizationAvatar from "@calcom/features/ee/organizations/components/OrganizationAvatar"; import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout"; import { APP_NAME, FULL_NAME_LENGTH_MAX_LIMIT } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -14,10 +15,10 @@ import turndown from "@calcom/lib/turndownService"; import { IdentityProvider } from "@calcom/prisma/enums"; import type { TRPCClientErrorLike } from "@calcom/trpc/client"; import { trpc } from "@calcom/trpc/react"; +import type { RouterOutputs } from "@calcom/trpc/react"; import type { AppRouter } from "@calcom/trpc/server/routers/_app"; import { Alert, - Avatar, Button, Dialog, DialogClose, @@ -223,6 +224,7 @@ const ProfileView = () => { key={JSON.stringify(defaultValues)} defaultValues={defaultValues} isLoading={updateProfileMutation.isLoading} + userOrganization={user.organization} onSubmit={(values) => { if (values.email !== user.email && isCALIdentityProvider) { setTempFormValues(values); @@ -364,11 +366,13 @@ const ProfileForm = ({ onSubmit, extraField, isLoading = false, + userOrganization, }: { defaultValues: FormValues; onSubmit: (values: FormValues) => void; extraField?: React.ReactNode; isLoading: boolean; + userOrganization: RouterOutputs["viewer"]["me"]["organization"]; }) => { const { t } = useLocale(); const [firstRender, setFirstRender] = useState(true); @@ -406,7 +410,12 @@ const ProfileForm = ({ name="avatar" render={({ field: { value } }) => ( <> - +
{ + return ( + + {alt} +
+ ) : null + } + /> + ); +}; + +export default OrganizationAvatar; diff --git a/packages/trpc/server/middlewares/sessionMiddleware.ts b/packages/trpc/server/middlewares/sessionMiddleware.ts index 9b14f37c66..8edddd8df5 100644 --- a/packages/trpc/server/middlewares/sessionMiddleware.ts +++ b/packages/trpc/server/middlewares/sessionMiddleware.ts @@ -64,6 +64,7 @@ export async function getUserFromSession(ctx: TRPCContextInner, session: Maybe @@ -57,17 +58,7 @@ export function Avatar(props: AvatarProps) { {props.fallback ? props.fallback : {alt}} - {props.accepted && ( -
-
- {size === "lg" && } -
-
- )} + {indicator} ); diff --git a/packages/ui/components/avatar/AvatarGroup.tsx b/packages/ui/components/avatar/AvatarGroup.tsx index 8f94a31f38..ca8cb95606 100644 --- a/packages/ui/components/avatar/AvatarGroup.tsx +++ b/packages/ui/components/avatar/AvatarGroup.tsx @@ -11,7 +11,6 @@ export type AvatarGroupProps = { href?: string; }[]; className?: string; - accepted?: boolean; truncateAfter?: number; }; @@ -36,7 +35,6 @@ export const AvatarGroup = function AvatarGroup(props: AvatarGroupProps) { imageSrc={item.image} title={item.title} alt={item.alt || ""} - accepted={props.accepted} size={props.size} href={item.href} />