feat: Organization support for event types page (#9449)

* Desktop first banner, mobile pending

* Removing dead code and img

* WIP

* Adds Email verification template+translations for organizations (#9202)

* First step done

* Merge branch 'feat/organizations-onboarding' of github.com:calcom/cal.com into feat/organizations-onboarding

* Step 2 done, avatar not working

* Covering null on unique clauses

* Onboarding admins step

* Last step to create teams

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Org branding provider used in shell sidebar

* Using meta component for head/descr

* Missing i18n strings

* Feedback

* Making an org avatar (temp)

* Using org avatar (temp)

* Not showing org logo if not set

* User onboarding with org branding (slug)

* Check for subteams slug clashes with usernames

* Fixing create teams onsuccess

* feedback

* Feedback

* Org public profile

* Public profiles for team event types

* Added setup profile alert

* Using org avatar on subteams avatar

* Processing orgs and children as profile options

* Reverting change not belonging to this PR

* Making sure we show the set up profile on org only

* Removing console.log

* Comparing memberships to choose the highest one

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
This commit is contained in:
Leo Giovanetti 2023-06-12 15:21:50 -03:00 committed by GitHub
parent e91b908f2a
commit 2655e1f1c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 75 additions and 26 deletions

View File

@ -81,6 +81,7 @@ interface EventTypeListHeadingProps {
profile: EventTypeGroupProfile;
membershipCount: number;
teamId?: number | null;
orgSlug?: string;
}
type EventTypeGroup = EventTypeGroups[number];
@ -697,6 +698,7 @@ const EventTypeListHeading = ({
profile,
membershipCount,
teamId,
orgSlug,
}: EventTypeListHeadingProps): JSX.Element => {
const { t } = useLocale();
const router = useRouter();
@ -737,7 +739,9 @@ const EventTypeListHeading = ({
)}
{profile?.slug && (
<Link href={`${CAL_URL}/${profile.slug}`} className="text-subtle block text-xs">
{`${CAL_URL?.replace("https://", "")}/${profile.slug}`}
{orgSlug
? `${orgSlug}.${subdomainSuffix()}/${profile.slug}`
: `${CAL_URL?.replace("https://", "")}/${profile.slug}`}
</Link>
)}
</div>
@ -887,6 +891,7 @@ const EventTypesPage = () => {
profile={group.profile}
membershipCount={group.metadata.membershipCount}
teamId={group.teamId}
orgSlug={orgBranding?.slug}
/>
<EventTypeList

View File

@ -38,6 +38,7 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
const router = useRouter();
const teamName = team.name || "Nameless Team";
const isBioEmpty = !team.bio || !team.bio.replace("<p><br></p>", "").length;
const metadata = teamMetadataSchema.parse(team.metadata);
useEffect(() => {
telemetry.event(

View File

@ -6,6 +6,8 @@ import { useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useOrgBrandingValues } from "@calcom/features/ee/organizations/hooks";
import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
import { useFlagMap } from "@calcom/features/flags/context/provider";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@ -81,6 +83,7 @@ export default function CreateEventTypeDialog({
const { t } = useLocale();
const router = useRouter();
const [firstRender, setFirstRender] = useState(true);
const orgBranding = useOrgBrandingValues();
const {
data: { teamId, eventPage: pageSlug },
@ -136,6 +139,9 @@ export default function CreateEventTypeDialog({
});
const flags = useFlagMap();
const urlPrefix = orgBranding
? `${orgBranding.slug}.${subdomainSuffix()}`
: process.env.NEXT_PUBLIC_WEBSITE_URL;
return (
<Dialog
@ -181,11 +187,10 @@ export default function CreateEventTypeDialog({
}}
/>
{process.env.NEXT_PUBLIC_WEBSITE_URL !== undefined &&
process.env.NEXT_PUBLIC_WEBSITE_URL?.length >= 21 ? (
{urlPrefix && urlPrefix.length >= 21 ? (
<div>
<TextField
label={`${t("url")}: ${process.env.NEXT_PUBLIC_WEBSITE_URL}`}
label={`${t("url")}: ${urlPrefix}`}
required
addOnLeading={<>/{!isManagedEventType ? pageSlug : t("username_placeholder")}/</>}
{...register("slug")}
@ -205,8 +210,7 @@ export default function CreateEventTypeDialog({
required
addOnLeading={
<>
{process.env.NEXT_PUBLIC_WEBSITE_URL}/
{!isManagedEventType ? pageSlug : t("username_placeholder")}/
{urlPrefix}/{!isManagedEventType ? pageSlug : t("username_placeholder")}/
</>
}
{...register("slug")}

View File

@ -23,7 +23,7 @@ export const OrganizationEventTypeFilter = () => {
const isNotEmpty = !!teams?.length;
return status === "success" ? (
<AnimatedPopover text={dropdownTitle} popoverTriggerClassNames="mb-0">
<AnimatedPopover text={dropdownTitle} popoverTriggerClassNames="!mb-0">
<CheckboxFieldContainer>
<CheckboxField
id="all-eventtypes-checkbox"

View File

@ -5,6 +5,7 @@ import { CAL_URL } from "@calcom/lib/constants";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import { baseEventTypeSelect, baseUserSelect } from "@calcom/prisma";
import { MembershipRole, SchedulingType } from "@calcom/prisma/enums";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { TRPCError } from "@trpc/server";
@ -81,6 +82,8 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
id: true,
name: true,
slug: true,
parentId: true,
metadata: true,
members: {
select: {
userId: true,
@ -190,27 +193,63 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
},
});
const teamMemberships = user.teams.map((membership) => ({
teamId: membership.team.id,
membershipRole: membership.role,
}));
const compareMembership = (mship1: MembershipRole, mship2: MembershipRole) => {
const mshipToNumber = (mship: MembershipRole) =>
Object.keys(MembershipRole).findIndex((mmship) => mmship === mship);
return mshipToNumber(mship1) > mshipToNumber(mship2);
};
eventTypeGroups = ([] as EventTypeGroup[]).concat(
eventTypeGroups,
user.teams.map((membership) => ({
teamId: membership.team.id,
membershipRole: membership.role,
profile: {
name: membership.team.name,
image: `${CAL_URL}/team/${membership.team.slug}/avatar.png`,
slug: membership.team.slug ? "team/" + membership.team.slug : null,
},
metadata: {
membershipCount: membership.team.members.length,
readOnly: membership.role === MembershipRole.MEMBER,
},
eventTypes: membership.team.eventTypes
.map(mapEventType)
.filter((evType) => evType.userId === null || evType.userId === ctx.user.id)
.filter((evType) =>
membership.role === MembershipRole.MEMBER ? evType.schedulingType !== SchedulingType.MANAGED : true
),
}))
user.teams
.filter((mmship) => {
const metadata = teamMetadataSchema.parse(mmship.team.metadata);
return !metadata?.isOrganization;
})
.map((membership) => {
const orgMembership = teamMemberships.find(
(teamM) => teamM.teamId === membership.team.parentId
)?.membershipRole;
return {
teamId: membership.team.id,
membershipRole:
orgMembership && compareMembership(orgMembership, membership.role)
? orgMembership
: membership.role,
profile: {
name: membership.team.name,
image: `${CAL_URL}/team/${membership.team.slug}/avatar.png`,
slug: membership.team.slug
? !membership.team.parentId
? `/team`
: "" + membership.team.slug
: null,
},
metadata: {
membershipCount: membership.team.members.length,
readOnly:
membership.role ===
(membership.team.parentId
? orgMembership && compareMembership(orgMembership, membership.role)
? orgMembership
: MembershipRole.MEMBER
: MembershipRole.MEMBER),
},
eventTypes: membership.team.eventTypes
.map(mapEventType)
.filter((evType) => evType.userId === null || evType.userId === ctx.user.id)
.filter((evType) =>
membership.role === MembershipRole.MEMBER
? evType.schedulingType !== SchedulingType.MANAGED
: true
),
};
})
);
return {
// don't display event teams without event types,