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:
parent
e91b908f2a
commit
2655e1f1c2
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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")}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue
Block a user