From 356117feaf3a48a3dd76075f641ce6a174a06475 Mon Sep 17 00:00:00 2001 From: Leo Giovanetti Date: Wed, 30 Aug 2023 17:42:34 -0300 Subject: [PATCH] fix: other reported issues (#11015) * fix: weird margin top in avatar * fix: pending users are shown on booking page * fix: avatar and naming issues * fix: toast alignment and removing unneeded titles * missing changes from toast improvements * feat: empty state for teams without event types * Removing console.log --- .../web/components/eventtype/EventTeamTab.tsx | 6 ++-- apps/web/pages/event-types/[type]/index.tsx | 7 +--- apps/web/pages/event-types/index.tsx | 35 +++++++++++++++---- apps/web/playwright/booking-seats.e2e.ts | 2 +- apps/web/public/static/locales/en/common.json | 7 ++-- .../bookings/Booker/components/DatePicker.tsx | 1 - packages/features/bookings/Booker/store.ts | 2 +- .../components/event-meta/Members.tsx | 4 +-- .../components/AboutOrganizationForm.tsx | 1 - packages/features/embed/Embed.tsx | 1 - .../components/ChildrenEventTypeSelect.tsx | 8 +++-- .../components/CreateEventTypeDialog.tsx | 2 +- .../eventtypes/components/DuplicateDialog.tsx | 2 +- .../features/eventtypes/lib/getPublicEvent.ts | 2 +- packages/lib/getEventTypeById.ts | 16 +++++++-- packages/prisma/zod-utils.ts | 1 - .../routers/loggedInViewer/me.handler.ts | 5 ++- .../viewer/eventTypes/getByViewer.handler.ts | 4 ++- packages/ui/components/avatar/Avatar.tsx | 7 ++-- .../components/empty-screen/EmptyScreen.tsx | 10 +++--- packages/ui/components/toast/showToast.tsx | 32 ++++++++++------- 21 files changed, 102 insertions(+), 53 deletions(-) diff --git a/apps/web/components/eventtype/EventTeamTab.tsx b/apps/web/components/eventtype/EventTeamTab.tsx index 7d170115ae..49917235a3 100644 --- a/apps/web/components/eventtype/EventTeamTab.tsx +++ b/apps/web/components/eventtype/EventTeamTab.tsx @@ -9,7 +9,6 @@ import type { Options } from "react-select"; import type { CheckedSelectOption } from "@calcom/features/eventtypes/components/CheckedTeamSelect"; import CheckedTeamSelect from "@calcom/features/eventtypes/components/CheckedTeamSelect"; import ChildrenEventTypeSelect from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect"; -import { WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { SchedulingType } from "@calcom/prisma/enums"; import { Label, Select } from "@calcom/ui"; @@ -18,13 +17,14 @@ interface IUserToValue { id: number | null; name: string | null; username: string | null; + avatar: string; email: string; } -const mapUserToValue = ({ id, name, username, email }: IUserToValue, pendingString: string) => ({ +const mapUserToValue = ({ id, name, username, avatar, email }: IUserToValue, pendingString: string) => ({ value: `${id || ""}`, label: `${name || email || ""}${!username ? ` (${pendingString})` : ""}`, - avatar: `${WEBAPP_URL}/${username}/avatar.png`, + avatar, email, }); diff --git a/apps/web/pages/event-types/[type]/index.tsx b/apps/web/pages/event-types/[type]/index.tsx index 521355f036..3fa0c17df4 100644 --- a/apps/web/pages/event-types/[type]/index.tsx +++ b/apps/web/pages/event-types/[type]/index.tsx @@ -183,12 +183,7 @@ const EventTypePage = (props: EventTypeSetupProps) => { created: true, })) ); - showToast( - t("event_type_updated_successfully", { - eventTypeTitle: eventType.title, - }), - "success" - ); + showToast(t("event_type_updated_successfully"), "success"); }, async onSettled() { await utils.viewer.eventTypes.get.invalidate(); diff --git a/apps/web/pages/event-types/index.tsx b/apps/web/pages/event-types/index.tsx index 2ab2a6cfd6..e84fd54e86 100644 --- a/apps/web/pages/event-types/index.tsx +++ b/apps/web/pages/event-types/index.tsx @@ -830,6 +830,25 @@ const SetupProfileBanner = ({ closeAction }: { closeAction: () => void }) => { ); }; +const EmptyEventTypeList = ({ group }: { group: EventTypeGroup }) => { + const { t } = useLocale(); + return ( + <> + + {t("create")} + + } + /> + + ); +}; + const Main = ({ status, errorMessage, @@ -871,12 +890,16 @@ const Main = ({ orgSlug={orgBranding?.slug} /> - + {group.eventTypes.length ? ( + + ) : ( + + )} )) )} diff --git a/apps/web/playwright/booking-seats.e2e.ts b/apps/web/playwright/booking-seats.e2e.ts index 8a93bec558..d173992d00 100644 --- a/apps/web/playwright/booking-seats.e2e.ts +++ b/apps/web/playwright/booking-seats.e2e.ts @@ -64,7 +64,7 @@ test.describe("Booking with Seats", () => { await page.waitForSelector('[data-testid="event-types"]'); const eventTitle = "My 2-seated event"; await createNewSeatedEventType(page, { eventTitle }); - await expect(page.locator(`text=${eventTitle} event type updated successfully`)).toBeVisible(); + await expect(page.locator(`text=Event type updated successfully`)).toBeVisible(); }); test("Multiple Attendees can book a seated event time slot", async ({ users, page }) => { diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index a246e37968..31489764ea 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -678,8 +678,8 @@ "new_event_type_btn": "New event type", "new_event_type_heading": "Create your first event type", "new_event_type_description": "Event types enable you to share links that show available times on your calendar and allow people to make bookings with you.", - "event_type_created_successfully": "{{eventTypeTitle}} event type created successfully", - "event_type_updated_successfully": "{{eventTypeTitle}} event type updated successfully", + "event_type_created_successfully": "Event type created successfully", + "event_type_updated_successfully": "Event type updated successfully", "event_type_deleted_successfully": "Event type deleted successfully", "hours": "Hours", "people": "People", @@ -2032,7 +2032,8 @@ "mark_dns_configured": "Mark as DNS configured", "value": "Value", "your_organization_updated_sucessfully": "Your organization updated successfully", - "seat_options_doesnt_multiple_durations": "Seat option doesn't support multiple durations", + "team_no_event_types": "This team has no event types", + "seat_options_doesnt_multiple_durations": "Seat option doesn't support multiple durations", "include_calendar_event": "Include calendar event", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" } diff --git a/packages/features/bookings/Booker/components/DatePicker.tsx b/packages/features/bookings/Booker/components/DatePicker.tsx index 36bed16909..d3970073db 100644 --- a/packages/features/bookings/Booker/components/DatePicker.tsx +++ b/packages/features/bookings/Booker/components/DatePicker.tsx @@ -1,6 +1,5 @@ import { shallow } from "zustand/shallow"; -import type { Dayjs } from "@calcom/dayjs"; import dayjs from "@calcom/dayjs"; import { default as DatePickerComponent } from "@calcom/features/calendars/DatePicker"; import { useNonEmptyScheduleDays } from "@calcom/features/schedules"; diff --git a/packages/features/bookings/Booker/store.ts b/packages/features/bookings/Booker/store.ts index 8644e575db..85c1cd5f31 100644 --- a/packages/features/bookings/Booker/store.ts +++ b/packages/features/bookings/Booker/store.ts @@ -161,7 +161,7 @@ export const useBookerStore = create((set, get) => ({ // Setting month make sure small calendar in fullscreen layouts also updates. // If selectedDate is null, prevents setting month to Invalid-Date - if (selectedDate && newSelection.month() !== currentSelection.month() ) { + if (selectedDate && newSelection.month() !== currentSelection.month()) { set({ month: newSelection.format("YYYY-MM") }); updateQueryParam("month", newSelection.format("YYYY-MM")); } diff --git a/packages/features/bookings/components/event-meta/Members.tsx b/packages/features/bookings/components/event-meta/Members.tsx index 8b134c7e5e..aaf10154b7 100644 --- a/packages/features/bookings/components/event-meta/Members.tsx +++ b/packages/features/bookings/components/event-meta/Members.tsx @@ -36,7 +36,7 @@ export const EventMembers = ({ schedulingType, users, profile, entity }: EventMe (profile.name !== users[0].name && schedulingType === SchedulingType.COLLECTIVE); const avatars: Avatar[] = shownUsers.map((user) => ({ - title: `${user.name}`, + title: `${user.name || user.username}`, image: "image" in user ? `${user.image}` : `/${user.username}/avatar.png`, alt: user.name || undefined, href: `/${user.username}`, @@ -54,7 +54,7 @@ export const EventMembers = ({ schedulingType, users, profile, entity }: EventMe // Add profile later since we don't want to force creating an avatar for this if it doesn't exist. avatars.unshift({ - title: `${profile.name}`, + title: `${profile.name || profile.username}`, image: "logo" in profile && profile.logo ? `${profile.logo}` : undefined, alt: profile.name || undefined, href: profile.username ? `${CAL_URL}/${profile.username}` : undefined, diff --git a/packages/features/ee/organizations/components/AboutOrganizationForm.tsx b/packages/features/ee/organizations/components/AboutOrganizationForm.tsx index 57500159fc..bf74b232f1 100644 --- a/packages/features/ee/organizations/components/AboutOrganizationForm.tsx +++ b/packages/features/ee/organizations/components/AboutOrganizationForm.tsx @@ -65,7 +65,6 @@ export const AboutOrganizationForm = () => { } - asChild className="items-center" imageSrc={image} size="lg" diff --git a/packages/features/embed/Embed.tsx b/packages/features/embed/Embed.tsx index 9154d4b56d..444abbbdf2 100644 --- a/packages/features/embed/Embed.tsx +++ b/packages/features/embed/Embed.tsx @@ -8,7 +8,6 @@ import type { ControlProps } from "react-select"; import { components } from "react-select"; import { shallow } from "zustand/shallow"; -import type { Dayjs } from "@calcom/dayjs"; import dayjs from "@calcom/dayjs"; import { AvailableTimes } from "@calcom/features/bookings"; import { useBookerStore, useInitializeBookerStore } from "@calcom/features/bookings/Booker/store"; diff --git a/packages/features/eventtypes/components/ChildrenEventTypeSelect.tsx b/packages/features/eventtypes/components/ChildrenEventTypeSelect.tsx index e5eac2311a..d6cc851900 100644 --- a/packages/features/eventtypes/components/ChildrenEventTypeSelect.tsx +++ b/packages/features/eventtypes/components/ChildrenEventTypeSelect.tsx @@ -1,6 +1,8 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import type { Props } from "react-select"; +import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; +import { getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains"; import { classNames } from "@calcom/lib"; import { CAL_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -34,7 +36,7 @@ export const ChildrenEventTypeSelect = ({ onChange: (value: readonly ChildrenEventType[]) => void; }) => { const { t } = useLocale(); - + const orgBranding = useOrgBranding(); const [animationRef] = useAutoAnimate(); return ( @@ -61,7 +63,9 @@ export const ChildrenEventTypeSelect = ({
diff --git a/packages/features/eventtypes/components/CreateEventTypeDialog.tsx b/packages/features/eventtypes/components/CreateEventTypeDialog.tsx index 36d8a4efdc..4e192b92fc 100644 --- a/packages/features/eventtypes/components/CreateEventTypeDialog.tsx +++ b/packages/features/eventtypes/components/CreateEventTypeDialog.tsx @@ -117,7 +117,7 @@ export default function CreateEventTypeDialog({ const createMutation = trpc.viewer.eventTypes.create.useMutation({ onSuccess: async ({ eventType }) => { await router.replace("/event-types/" + eventType.id); - showToast(t("event_type_created_successfully", { eventTypeTitle: eventType.title }), "success"); + showToast(t("event_type_created_successfully"), "success"); }, onError: (err) => { if (err instanceof HttpError) { diff --git a/packages/features/eventtypes/components/DuplicateDialog.tsx b/packages/features/eventtypes/components/DuplicateDialog.tsx index 053079d970..de31accf97 100644 --- a/packages/features/eventtypes/components/DuplicateDialog.tsx +++ b/packages/features/eventtypes/components/DuplicateDialog.tsx @@ -66,7 +66,7 @@ const DuplicateDialog = () => { const duplicateMutation = trpc.viewer.eventTypes.duplicate.useMutation({ onSuccess: async ({ eventType }) => { await router.replace("/event-types/" + eventType.id); - showToast(t("event_type_created_successfully", { eventTypeTitle: eventType.title }), "success"); + showToast(t("event_type_created_successfully"), "success"); }, onError: (err) => { if (err instanceof HttpError) { diff --git a/packages/features/eventtypes/lib/getPublicEvent.ts b/packages/features/eventtypes/lib/getPublicEvent.ts index 2883e03435..9132b96fac 100644 --- a/packages/features/eventtypes/lib/getPublicEvent.ts +++ b/packages/features/eventtypes/lib/getPublicEvent.ts @@ -285,7 +285,7 @@ function getProfileFromEvent(event: Event) { function getUsersFromEvent(event: Event) { const { team, hosts, owner } = event; if (team) { - return (hosts || []).map(mapHostsToUsers); + return (hosts || []).filter((host) => host.user.username).map(mapHostsToUsers); } if (!owner) { return null; diff --git a/packages/lib/getEventTypeById.ts b/packages/lib/getEventTypeById.ts index 32989b1711..aea3336d42 100644 --- a/packages/lib/getEventTypeById.ts +++ b/packages/lib/getEventTypeById.ts @@ -4,6 +4,7 @@ import { getLocationGroupedOptions } from "@calcom/app-store/server"; import type { StripeData } from "@calcom/app-store/stripepayment/lib/server"; import { getEventTypeAppData } from "@calcom/app-store/utils"; import type { LocationObject } from "@calcom/core/location"; +import { getOrgFullDomain } from "@calcom/ee/organizations/lib/orgDomains"; import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields"; import { parseBookingLimit, parseDurationLimit, parseRecurringEvent } from "@calcom/lib"; import { CAL_URL } from "@calcom/lib/constants"; @@ -113,6 +114,11 @@ export default async function getEventTypeById({ name: true, slug: true, parentId: true, + parent: { + select: { + slug: true, + }, + }, members: { select: { role: true, @@ -319,7 +325,9 @@ export default async function getEventTypeById({ const eventTypeUsers: ((typeof eventType.users)[number] & { avatar: string })[] = eventType.users.map( (user) => ({ ...user, - avatar: `${CAL_URL}/${user.username}/avatar.png`, + avatar: `${eventType.team?.parent?.slug ? getOrgFullDomain(eventType.team?.parent?.slug) : CAL_URL}/${ + user.username + }/avatar.png`, }) ); @@ -365,7 +373,11 @@ export default async function getEventTypeById({ .map((member) => { const user: typeof member.user & { avatar: string } = { ...member.user, - avatar: `${CAL_URL}/${member.user.username}/avatar.png`, + avatar: `${ + eventTypeObject.team?.parent?.slug + ? getOrgFullDomain(eventTypeObject.team?.parent?.slug) + : CAL_URL + }/${member.user.username}/avatar.png`, }; return { ...user, diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts index d8d67a5525..61ef2c6fa0 100644 --- a/packages/prisma/zod-utils.ts +++ b/packages/prisma/zod-utils.ts @@ -633,4 +633,3 @@ export const ZVerifyCodeInputSchema = z.object({ export type ZVerifyCodeInputSchema = z.infer; export const coerceToDate = z.coerce.date(); - diff --git a/packages/trpc/server/routers/loggedInViewer/me.handler.ts b/packages/trpc/server/routers/loggedInViewer/me.handler.ts index 54f55551a1..cbf06009d5 100644 --- a/packages/trpc/server/routers/loggedInViewer/me.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/me.handler.ts @@ -1,3 +1,4 @@ +import { getOrgFullDomain } from "@calcom/ee/organizations/lib/orgDomains"; import { WEBAPP_URL } from "@calcom/lib/constants"; import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; @@ -24,7 +25,9 @@ export const meHandler = async ({ ctx }: MeOptions) => { locale: user.locale, timeFormat: user.timeFormat, timeZone: user.timeZone, - avatar: `${WEBAPP_URL}/${user.username}/avatar.png`, + avatar: `${user.organization?.slug ? getOrgFullDomain(user.organization.slug) : WEBAPP_URL}/${ + user.username + }/avatar.png`, createdDate: user.createdDate, trialEndsAt: user.trialEndsAt, defaultScheduleId: user.defaultScheduleId, diff --git a/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts b/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts index 3741aaa080..13e5abd9ae 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts @@ -165,6 +165,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) => type EventTypeGroup = { teamId?: number | null; + parentId?: number | null; membershipRole?: MembershipRole | null; profile: { slug: (typeof user)["username"]; @@ -226,6 +227,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) => )?.membershipRole; return { teamId: membership.team.id, + parentId: membership.team.parentId, membershipRole: orgMembership && compareMembership(orgMembership, membership.role) ? orgMembership @@ -265,7 +267,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) => const bookerUrl = await getBookerUrl(user); return { // don't display event teams without event types, - eventTypeGroups: eventTypeGroups.filter((groupBy) => !!groupBy.eventTypes?.length), + eventTypeGroups: eventTypeGroups.filter((groupBy) => groupBy.parentId || !!groupBy.eventTypes?.length), // so we can show a dropdown when the user has teams profiles: eventTypeGroups.map((group) => ({ ...group.profile, diff --git a/packages/ui/components/avatar/Avatar.tsx b/packages/ui/components/avatar/Avatar.tsx index 7b1550612c..572e9a2f19 100644 --- a/packages/ui/components/avatar/Avatar.tsx +++ b/packages/ui/components/avatar/Avatar.tsx @@ -40,7 +40,7 @@ export function Avatar(props: AvatarProps) { let avatar = ( @@ -50,7 +50,10 @@ export function Avatar(props: AvatarProps) { alt={alt} className={classNames("aspect-square rounded-full", sizesPropsBySize[size])} /> - + <> {props.fallback ? ( props.fallback diff --git a/packages/ui/components/empty-screen/EmptyScreen.tsx b/packages/ui/components/empty-screen/EmptyScreen.tsx index b3e987b5f0..1b898b8dc5 100644 --- a/packages/ui/components/empty-screen/EmptyScreen.tsx +++ b/packages/ui/components/empty-screen/EmptyScreen.tsx @@ -22,7 +22,7 @@ export function EmptyScreen({ Icon?: SVGComponent | IconType; avatar?: React.ReactElement; headline: string | React.ReactElement; - description: string | React.ReactElement; + description?: string | React.ReactElement; buttonText?: string; buttonOnClick?: (event: React.MouseEvent) => void; buttonRaw?: ReactNode; // Used incase you want to provide your own button. @@ -49,9 +49,11 @@ export function EmptyScreen({ )}

{headline}

-
- {description} -
+ {description && ( +
+ {description} +
+ )} {buttonOnClick && buttonText && } {buttonRaw}
diff --git a/packages/ui/components/toast/showToast.tsx b/packages/ui/components/toast/showToast.tsx index ad5c353864..6d0cb41bca 100644 --- a/packages/ui/components/toast/showToast.tsx +++ b/packages/ui/components/toast/showToast.tsx @@ -13,56 +13,64 @@ type IToast = { export const SuccessToast = ({ message, toastVisible, onClose, toastId }: IToast) => ( ); export const ErrorToast = ({ message, toastVisible, onClose, toastId }: IToast) => ( ); export const WarningToast = ({ message, toastVisible, onClose, toastId }: IToast) => ( ); export const DefaultToast = ({ message, toastVisible, onClose, toastId }: IToast) => ( );