Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
828ef8580e | ||
|
|
a348c3a20a | ||
|
|
3c68c7f7cb | ||
|
|
820b2da16d | ||
|
|
6bdb2b94eb | ||
|
|
3c3db0b3a6 | ||
|
|
4748641c22 | ||
|
|
804787f2a3 | ||
|
|
b7c3b31e0c | ||
|
|
ab6781cd72 | ||
|
|
b070cfb227 | ||
|
|
5acb3a09e5 | ||
|
|
69c9f690cb | ||
|
|
d2a12ea834 | ||
|
|
16f24b4831 | ||
|
|
a3eeeab259 | ||
|
|
f69c254fbc | ||
|
|
f0f6bd4846 | ||
|
|
8816a18854 | ||
|
|
4e529e5c6c |
No files matched your search
@@ -2,7 +2,7 @@ import { CAL_URL } from "@calcom/lib/constants";
|
|||||||
|
|
||||||
export default function getCalLink(eventType: {
|
export default function getCalLink(eventType: {
|
||||||
team?: { slug: string | null } | null;
|
team?: { slug: string | null } | null;
|
||||||
owner: { username: string | null } | null;
|
owner?: { username: string | null } | null;
|
||||||
users?: { username: string | null }[];
|
users?: { username: string | null }[];
|
||||||
slug: string;
|
slug: string;
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
|
|||||||
{emptyStore ? (
|
{emptyStore ? (
|
||||||
<EmptyScreen
|
<EmptyScreen
|
||||||
Icon={AlertCircle}
|
Icon={AlertCircle}
|
||||||
headline={t("no_apps")}
|
headline={isAdmin ? t("no_apps") : t("no_apps_configured")}
|
||||||
description={isAdmin ? "You can enable apps in the settings" : ""}
|
description={isAdmin ? t("enable_in_settings") : t("please_contact_admin")}
|
||||||
buttonText={isAdmin ? t("apps_settings") : ""}
|
buttonText={isAdmin ? t("apps_settings") : ""}
|
||||||
buttonOnClick={() => router.push("/settings/admin/apps/calendar")}
|
buttonOnClick={() => router.push("/settings/admin/apps/calendar")}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const generateHashedLink = (id: number) => {
|
|||||||
|
|
||||||
export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps, "eventType" | "team">) => {
|
export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps, "eventType" | "team">) => {
|
||||||
const connectedCalendarsQuery = trpc.viewer.connectedCalendars.useQuery();
|
const connectedCalendarsQuery = trpc.viewer.connectedCalendars.useQuery();
|
||||||
|
const { data: user } = trpc.viewer.me.useQuery();
|
||||||
const formMethods = useFormContext<FormValues>();
|
const formMethods = useFormContext<FormValues>();
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
|
||||||
@@ -49,6 +50,9 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
const bookingFields: Prisma.JsonObject = {};
|
const bookingFields: Prisma.JsonObject = {};
|
||||||
|
|
||||||
const workflows = eventType.workflows.map((workflowOnEventType) => workflowOnEventType.workflow);
|
const workflows = eventType.workflows.map((workflowOnEventType) => workflowOnEventType.workflow);
|
||||||
|
const selectedThemeIsDark =
|
||||||
|
user?.theme === "dark" ||
|
||||||
|
(!user?.theme && typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
|
||||||
|
|
||||||
eventType.bookingFields.forEach(({ name }) => {
|
eventType.bookingFields.forEach(({ name }) => {
|
||||||
bookingFields[name] = name + " input";
|
bookingFields[name] = name + " input";
|
||||||
@@ -162,7 +166,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
</div>
|
</div>
|
||||||
<hr className="border-subtle [&:has(+div:empty)]:hidden" />
|
<hr className="border-subtle [&:has(+div:empty)]:hidden" />
|
||||||
<div>
|
<div>
|
||||||
<BookerLayoutSelector fallbackToUserSettings />
|
<BookerLayoutSelector fallbackToUserSettings isDark={selectedThemeIsDark} />
|
||||||
</div>
|
</div>
|
||||||
<hr className="border-subtle" />
|
<hr className="border-subtle" />
|
||||||
<FormBuilder
|
<FormBuilder
|
||||||
|
|||||||
+20
-23
@@ -1,10 +1,10 @@
|
|||||||
require("dotenv").config({ path: "../../.env" });
|
require("dotenv").config({ path: "../../.env" });
|
||||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
const os = require("os");
|
const os = require("os");
|
||||||
const glob = require("glob");
|
|
||||||
const englishTranslation = require("./public/static/locales/en/common.json");
|
const englishTranslation = require("./public/static/locales/en/common.json");
|
||||||
const { withAxiom } = require("next-axiom");
|
const { withAxiom } = require("next-axiom");
|
||||||
const { i18n } = require("./next-i18next.config");
|
const { i18n } = require("./next-i18next.config");
|
||||||
|
const { pages } = require("./pages");
|
||||||
|
|
||||||
if (!process.env.NEXTAUTH_SECRET) throw new Error("Please set NEXTAUTH_SECRET");
|
if (!process.env.NEXTAUTH_SECRET) throw new Error("Please set NEXTAUTH_SECRET");
|
||||||
if (!process.env.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY");
|
if (!process.env.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY");
|
||||||
@@ -82,16 +82,19 @@ if (process.env.ANALYZE === "true") {
|
|||||||
|
|
||||||
plugins.push(withAxiom);
|
plugins.push(withAxiom);
|
||||||
|
|
||||||
/** Needed to rewrite public booking page, gets all static pages but [user] */
|
// .* matches / as well(Note: *(i.e wildcard) doesn't match / but .*(i.e. RegExp) does)
|
||||||
const pages = glob
|
// It would match /free/30min but not /bookings/upcoming because 'bookings' is an item in pages
|
||||||
.sync("pages/**/[^_]*.{tsx,js,ts}", { cwd: __dirname })
|
// It would also not match /free/30min/embed because we are ensuring just two slashes
|
||||||
.map((filename) =>
|
// ?!book ensures it doesn't match /free/book page which doesn't have a corresponding new-booker page.
|
||||||
filename
|
// [^/]+ makes the RegExp match the full path, it seems like a partial match doesn't work.
|
||||||
.substr(6)
|
// book$ ensures that only /book is excluded from rewrite(which is at the end always) and not /booked
|
||||||
.replace(/(\.tsx|\.js|\.ts)/, "")
|
|
||||||
.replace(/\/.*/, "")
|
// Important Note: When modifying these RegExps update apps/web/test/lib/next-config.test.ts as well
|
||||||
)
|
const userTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type((?!book$)[^/]+)`;
|
||||||
.filter((v, i, self) => self.indexOf(v) === i && !v.startsWith("[user]"));
|
const teamTypeRouteRegExp = "/team/:slug/:type((?!book$)[^/]+)";
|
||||||
|
const privateLinkRouteRegExp = "/d/:link/:slug((?!book$)[^/]+)";
|
||||||
|
const embedUserTypeRouteRegExp = `/:user((?!${pages.join("|")}).*)/:type/embed`;
|
||||||
|
const embedTeamTypeRouteRegExp = "/team/:slug/:type/embed";
|
||||||
|
|
||||||
/** @type {import("next").NextConfig} */
|
/** @type {import("next").NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
@@ -183,12 +186,6 @@ const nextConfig = {
|
|||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
// .* matches / as well(Note: *(i.e wildcard) doesn't match / but .*(i.e. RegExp) does)
|
|
||||||
// It would match /free/30min but not /bookings/upcoming because 'bookings' is an item in pages
|
|
||||||
// It would also not match /free/30min/embed because we are ensuring just two slashes
|
|
||||||
// ?!book ensures it doesn't match /free/book page which doesn't have a corresponding new-booker page.
|
|
||||||
// [^/]+ makes the RegExp match the full path, it seems like a partial match doesn't work.
|
|
||||||
const userTypeRouteRegExp = `/:user((?!${pages.join("|")})[^/]*)/:type((?!book)[^/]+)`;
|
|
||||||
let rewrites = [
|
let rewrites = [
|
||||||
{
|
{
|
||||||
source: "/org/:slug",
|
source: "/org/:slug",
|
||||||
@@ -233,12 +230,12 @@ const nextConfig = {
|
|||||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: "/team/:slug/:type",
|
source: teamTypeRouteRegExp,
|
||||||
destination: "/new-booker/team/:slug/:type",
|
destination: "/new-booker/team/:slug/:type",
|
||||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: "/d/:link/:slug",
|
source: privateLinkRouteRegExp,
|
||||||
destination: "/new-booker/d/:link/:slug",
|
destination: "/new-booker/d/:link/:slug",
|
||||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||||
},
|
},
|
||||||
@@ -246,12 +243,12 @@ const nextConfig = {
|
|||||||
// Keep cookie based booker enabled to test new-booker embed in production
|
// Keep cookie based booker enabled to test new-booker embed in production
|
||||||
...[
|
...[
|
||||||
{
|
{
|
||||||
source: `/:user((?!${pages.join("|")}).*)/:type/embed`,
|
source: embedUserTypeRouteRegExp,
|
||||||
destination: "/new-booker/:user/:type/embed",
|
destination: "/new-booker/:user/:type/embed",
|
||||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: "/team/:slug/:type/embed",
|
source: embedTeamTypeRouteRegExp,
|
||||||
destination: "/new-booker/team/:slug/:type/embed",
|
destination: "/new-booker/team/:slug/:type/embed",
|
||||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||||
},
|
},
|
||||||
@@ -293,11 +290,11 @@ const nextConfig = {
|
|||||||
destination: "/new-booker/:user/:type",
|
destination: "/new-booker/:user/:type",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: "/team/:slug/:type",
|
source: teamTypeRouteRegExp,
|
||||||
destination: "/new-booker/team/:slug/:type",
|
destination: "/new-booker/team/:slug/:type",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: "/d/:link/:slug",
|
source: privateLinkRouteRegExp,
|
||||||
destination: "/new-booker/d/:link/:slug",
|
destination: "/new-booker/d/:link/:slug",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@calcom/web",
|
"name": "@calcom/web",
|
||||||
"version": "3.0.3",
|
"version": "3.0.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "ANALYZE=true next build",
|
"analyze": "ANALYZE=true next build",
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
const glob = require("glob");
|
||||||
|
|
||||||
|
/** Needed to rewrite public booking page, gets all static pages but [user] */
|
||||||
|
const pages = glob
|
||||||
|
.sync("pages/**/[^_]*.{tsx,js,ts}", { cwd: __dirname })
|
||||||
|
.map((filename) =>
|
||||||
|
filename
|
||||||
|
.substr(6)
|
||||||
|
.replace(/(\.tsx|\.js|\.ts)/, "")
|
||||||
|
.replace(/\/.*/, "")
|
||||||
|
)
|
||||||
|
.filter((v, i, self) => self.indexOf(v) === i && !v.startsWith("[user]"));
|
||||||
|
|
||||||
|
exports.pages = pages;
|
||||||
@@ -13,7 +13,8 @@ import useIntercom from "@calcom/features/ee/support/lib/intercom/useIntercom";
|
|||||||
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
|
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
|
||||||
import CreateEventTypeDialog from "@calcom/features/eventtypes/components/CreateEventTypeDialog";
|
import CreateEventTypeDialog from "@calcom/features/eventtypes/components/CreateEventTypeDialog";
|
||||||
import { DuplicateDialog } from "@calcom/features/eventtypes/components/DuplicateDialog";
|
import { DuplicateDialog } from "@calcom/features/eventtypes/components/DuplicateDialog";
|
||||||
import { OrganizationEventTypeFilter } from "@calcom/features/eventtypes/components/OrganizationEventTypeFilter";
|
import { TeamsFilter } from "@calcom/features/filters/components/TeamsFilter";
|
||||||
|
import { getTeamsFiltersFromQuery } from "@calcom/features/filters/lib/getTeamsFiltersFromQuery";
|
||||||
import Shell from "@calcom/features/shell/Shell";
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
@@ -47,7 +48,6 @@ import {
|
|||||||
HeadSeo,
|
HeadSeo,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Label,
|
Label,
|
||||||
VerticalDivider,
|
|
||||||
Alert,
|
Alert,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
import {
|
import {
|
||||||
@@ -420,7 +420,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
truncateAfter={4}
|
truncateAfter={4}
|
||||||
items={type.children
|
items={type.children
|
||||||
.flatMap((ch) => ch.users)
|
.flatMap((ch) => ch.users)
|
||||||
.map((user: User) => ({
|
.map((user: Pick<User, "name" | "username">) => ({
|
||||||
alt: user.name || "",
|
alt: user.name || "",
|
||||||
image: `${WEBAPP_URL}/${user.username}/avatar.png`,
|
image: `${WEBAPP_URL}/${user.username}/avatar.png`,
|
||||||
title: user.name || "",
|
title: user.name || "",
|
||||||
@@ -800,8 +800,7 @@ const CTA = () => {
|
|||||||
const Actions = () => {
|
const Actions = () => {
|
||||||
return (
|
return (
|
||||||
<div className="hidden items-center md:flex">
|
<div className="hidden items-center md:flex">
|
||||||
<OrganizationEventTypeFilter />
|
<TeamsFilter popoverTriggerClassNames="mb-0" showVerticalDivider={true} />
|
||||||
<VerticalDivider />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -834,9 +833,6 @@ const SetupProfileBanner = ({ closeAction }: { closeAction: () => void }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const WithQuery = withQuery(trpc.viewer.eventTypes.getByViewer as any);
|
|
||||||
|
|
||||||
const EventTypesPage = () => {
|
const EventTypesPage = () => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -862,6 +858,10 @@ const EventTypesPage = () => {
|
|||||||
);
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
const filters = getTeamsFiltersFromQuery(router.query);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const WithQuery = withQuery(trpc.viewer.eventTypes.getByViewer as any, { filters });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -879,9 +879,14 @@ const EventTypesPage = () => {
|
|||||||
CTA={<CTA />}>
|
CTA={<CTA />}>
|
||||||
<WithQuery
|
<WithQuery
|
||||||
customLoader={<SkeletonLoader />}
|
customLoader={<SkeletonLoader />}
|
||||||
success={({ data }) => (
|
success={({ data }) => {
|
||||||
|
const isFilteredByOnlyOneItem =
|
||||||
|
(filters?.teamIds?.length === 1 || filters?.userIds?.length === 1) &&
|
||||||
|
data.eventTypeGroups.length === 1;
|
||||||
|
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
{data.eventTypeGroups.length > 1 ? (
|
{data.eventTypeGroups.length > 1 || isFilteredByOnlyOneItem ? (
|
||||||
<>
|
<>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<MobileTeamsTab eventTypeGroups={data.eventTypeGroups} />
|
<MobileTeamsTab eventTypeGroups={data.eventTypeGroups} />
|
||||||
@@ -905,21 +910,24 @@ const EventTypesPage = () => {
|
|||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : data.eventTypeGroups.length === 1 ? (
|
) : (
|
||||||
|
data.eventTypeGroups.length === 1 && (
|
||||||
<EventTypeList
|
<EventTypeList
|
||||||
types={data.eventTypeGroups[0].eventTypes}
|
types={data.eventTypeGroups[0].eventTypes}
|
||||||
group={data.eventTypeGroups[0]}
|
group={data.eventTypeGroups[0]}
|
||||||
groupIndex={0}
|
groupIndex={0}
|
||||||
readOnly={data.eventTypeGroups[0].metadata.readOnly}
|
readOnly={data.eventTypeGroups[0].metadata.readOnly}
|
||||||
/>
|
/>
|
||||||
) : (
|
)
|
||||||
<CreateFirstEventTypeView />
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{data.eventTypeGroups.length === 0 && <CreateFirstEventTypeView />}
|
||||||
|
|
||||||
<EmbedDialog />
|
<EmbedDialog />
|
||||||
{router.query.dialog === "duplicate" && <DuplicateDialog />}
|
{router.query.dialog === "duplicate" && <DuplicateDialog />}
|
||||||
</>
|
</>
|
||||||
)}
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Shell>
|
</Shell>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import PageWrapper from "@components/PageWrapper";
|
|||||||
|
|
||||||
type PageProps = inferSSRProps<typeof getServerSideProps>;
|
type PageProps = inferSSRProps<typeof getServerSideProps>;
|
||||||
|
|
||||||
export default function Type({ slug, user, booking, away, isBrandingHidden }: PageProps) {
|
export default function Type({ slug, user, booking, away, isBrandingHidden, isTeamEvent }: PageProps) {
|
||||||
return (
|
return (
|
||||||
<main className="flex h-full min-h-[100dvh] items-center justify-center">
|
<main className="flex h-full min-h-[100dvh] items-center justify-center">
|
||||||
<BookerSeo
|
<BookerSeo
|
||||||
@@ -29,6 +29,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden }: Pa
|
|||||||
rescheduleBooking={booking}
|
rescheduleBooking={booking}
|
||||||
isAway={away}
|
isAway={away}
|
||||||
hideBranding={isBrandingHidden}
|
hideBranding={isBrandingHidden}
|
||||||
|
isTeamEvent={isTeamEvent}
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
@@ -57,6 +58,11 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
|||||||
username: true,
|
username: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
team: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -96,9 +102,11 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
|||||||
booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
|
booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isTeamEvent = !!hashedLink.eventType?.team?.id;
|
||||||
|
|
||||||
// We use this to both prefetch the query on the server,
|
// We use this to both prefetch the query on the server,
|
||||||
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
||||||
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug });
|
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug, isTeamEvent });
|
||||||
|
|
||||||
if (!eventData) {
|
if (!eventData) {
|
||||||
return {
|
return {
|
||||||
@@ -114,6 +122,9 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
|||||||
slug,
|
slug,
|
||||||
trpcState: ssr.dehydrate(),
|
trpcState: ssr.dehydrate(),
|
||||||
isBrandingHidden: user?.hideBranding,
|
isBrandingHidden: user?.hideBranding,
|
||||||
|
// Sending the team event from the server, because this template file
|
||||||
|
// is reused for both team and user events.
|
||||||
|
isTeamEvent,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden }: Pa
|
|||||||
rescheduleBooking={booking}
|
rescheduleBooking={booking}
|
||||||
isAway={away}
|
isAway={away}
|
||||||
hideBranding={isBrandingHidden}
|
hideBranding={isBrandingHidden}
|
||||||
|
isTeamEvent
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
@@ -71,7 +72,11 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
|
|
||||||
// We use this to both prefetch the query on the server,
|
// We use this to both prefetch the query on the server,
|
||||||
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
||||||
const eventData = await ssr.viewer.public.event.fetch({ username: teamSlug, eventSlug: meetingSlug });
|
const eventData = await ssr.viewer.public.event.fetch({
|
||||||
|
username: teamSlug,
|
||||||
|
eventSlug: meetingSlug,
|
||||||
|
isTeamEvent: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (!eventData) {
|
if (!eventData) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -70,6 +70,13 @@ const AppearanceView = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const selectedTheme = formMethods.watch("theme");
|
||||||
|
const selectedThemeIsDark =
|
||||||
|
selectedTheme === "dark" ||
|
||||||
|
(selectedTheme === "" &&
|
||||||
|
typeof document !== "undefined" &&
|
||||||
|
document.documentElement.classList.contains("dark"));
|
||||||
|
|
||||||
const {
|
const {
|
||||||
formState: { isSubmitting, isDirty },
|
formState: { isSubmitting, isDirty },
|
||||||
reset,
|
reset,
|
||||||
@@ -144,6 +151,7 @@ const AppearanceView = () => {
|
|||||||
|
|
||||||
<hr className="border-subtle my-8 border [&:has(+hr)]:hidden" />
|
<hr className="border-subtle my-8 border [&:has(+hr)]:hidden" />
|
||||||
<BookerLayoutSelector
|
<BookerLayoutSelector
|
||||||
|
isDark={selectedThemeIsDark}
|
||||||
name="metadata.defaultBookerLayouts"
|
name="metadata.defaultBookerLayouts"
|
||||||
title={t("bookerlayout_user_settings_title")}
|
title={t("bookerlayout_user_settings_title")}
|
||||||
description={t("bookerlayout_user_settings_description")}
|
description={t("bookerlayout_user_settings_description")}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<svg width="208" height="120" viewBox="0 0 208 120" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g clip-path="url(#clip0_5291_194335)">
|
||||||
|
<rect width="208" height="120" rx="6" fill="url(#paint0_radial_5291_194335)"/>
|
||||||
|
<rect x="25.4497" y="14.6206" width="157.101" height="90.7591" rx="5.35267" fill="#1A1A1A"/>
|
||||||
|
<circle cx="35.5909" cy="24.7618" r="2.67634" fill="#2B2B2B"/>
|
||||||
|
<rect x="40.0518" y="23.8696" width="14.2738" height="1.78422" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="32.9146" y="31.0068" width="28.5476" height="2.67634" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="32.9146" y="37.2515" width="21.4107" height="1.78422" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="32.9146" y="42.604" width="21.4107" height="1.78422" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="32.9146" y="47.957" width="21.4107" height="1.78422" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="32.9146" y="58.6621" width="5.35267" height="1.78422" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="40.0518" y="58.6621" width="5.35267" height="1.78422" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="47.1885" y="58.6621" width="5.35267" height="1.78422" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="54.3252" y="58.6621" width="5.35267" height="1.78422" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="61.4619" y="58.6621" width="5.35267" height="1.78422" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="68.5991" y="58.6621" width="5.35267" height="1.78422" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="75.7363" y="58.6621" width="5.35267" height="1.78422" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="47.1885" y="64.0146" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="54.3252" y="64.0146" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="61.4619" y="64.0146" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="68.5991" y="64.0146" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="75.7363" y="64.0146" width="5.35267" height="5.35267" rx="0.892112" fill="#1C1C1C"/>
|
||||||
|
<rect x="32.9146" y="72.936" width="5.35267" height="5.35267" rx="0.892112" fill="#1C1C1C"/>
|
||||||
|
<rect x="40.0518" y="72.936" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="47.1885" y="72.936" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="54.3252" y="72.936" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="61.4619" y="72.936" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="68.5991" y="72.936" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="75.7363" y="72.936" width="5.35267" height="5.35267" rx="0.892112" fill="#1C1C1C"/>
|
||||||
|
<rect x="32.9146" y="81.8569" width="5.35267" height="5.35267" rx="0.892112" fill="#1C1C1C"/>
|
||||||
|
<rect x="40.0518" y="81.8569" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="47.1885" y="81.8569" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="54.3252" y="81.8569" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="61.4619" y="81.8569" width="5.35267" height="5.35267" rx="0.892112" fill="#575757"/>
|
||||||
|
<rect x="68.5991" y="81.8569" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="75.7363" y="81.8569" width="5.35267" height="5.35267" rx="0.892112" fill="#1C1C1C"/>
|
||||||
|
<rect x="32.9146" y="90.7783" width="5.35267" height="5.35267" rx="0.892112" fill="#1C1C1C"/>
|
||||||
|
<rect x="40.0518" y="90.7783" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="47.1885" y="90.7783" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="54.3252" y="90.7783" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<path d="M89.1177 18.5171V101.484H90.9019V18.5171H89.1177Z" fill="#1C1C1C" mask="url(#path-3-inside-1_5291_194335)"/>
|
||||||
|
<rect x="96.5796" y="22.0854" width="21.4107" height="3.56845" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="96.5796" y="29.2227" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="96.5796" y="38.1436" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="96.5796" y="47.0645" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="96.5796" y="55.9858" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="96.5796" y="64.9067" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="96.5796" y="73.8281" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="125.127" y="22.0854" width="21.4107" height="3.56845" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="125.127" y="29.2227" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="125.127" y="38.1436" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="125.127" y="47.0649" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="125.127" y="55.9858" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="125.127" y="64.9067" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="125.127" y="73.8281" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="153.675" y="22.0854" width="21.4107" height="3.56845" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="153.675" y="29.2227" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="153.675" y="38.1436" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="153.675" y="47.0645" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="153.675" y="55.9858" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="153.675" y="64.9067" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="153.675" y="73.8281" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect opacity="0.2" x="0.666992" y="-1" width="208" height="120" fill="url(#pattern0)"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||||
|
<use xlink:href="#image0_5291_194335" transform="scale(0.00480769 0.00833333)"/>
|
||||||
|
</pattern>
|
||||||
|
<radialGradient id="paint0_radial_5291_194335" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(207.334 -1.47576e-05) rotate(145.398) scale(465.773 828.954)">
|
||||||
|
<stop stop-color="#282E39"/>
|
||||||
|
<stop offset="1" stop-color="#D4D4D5"/>
|
||||||
|
</radialGradient>
|
||||||
|
<clipPath id="clip0_5291_194335">
|
||||||
|
<rect width="208" height="120" rx="6" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
<image id="image0_5291_194335" width="208" height="120" xlink:href="data:image/png;base64,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 truncated
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,67 @@
|
|||||||
|
<svg width="208" height="120" viewBox="0 0 208 120" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g clip-path="url(#clip0_5291_194117)">
|
||||||
|
<rect width="208" height="120" rx="6" fill="url(#paint0_radial_5291_194117)"/>
|
||||||
|
<rect x="22" y="24" width="164" height="72" rx="6" fill="#1A1A1A"/>
|
||||||
|
<circle cx="33" cy="35" r="3" fill="#2B2B2B"/>
|
||||||
|
<rect x="38" y="34" width="16" height="2" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="30" y="42" width="24" height="3" rx="1" fill="#444444"/>
|
||||||
|
<rect x="30" y="49" width="24" height="2" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="30" y="55" width="24" height="2" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="30" y="61" width="24" height="2" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="30" y="67" width="24" height="2" rx="1" fill="#2B2B2B"/>
|
||||||
|
<path d="M61 28V92H63V28H61Z" fill="#1C1C1C" mask="url(#path-4-inside-1_5291_194117)"/>
|
||||||
|
<rect x="70" y="32" width="24" height="2" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="70" y="38" width="8" height="2" rx="1" fill="#444444"/>
|
||||||
|
<rect x="80" y="38" width="8" height="2" rx="1" fill="#444444"/>
|
||||||
|
<rect x="90" y="38" width="8" height="2" rx="1" fill="#444444"/>
|
||||||
|
<rect x="100" y="38" width="8" height="2" rx="1" fill="#444444"/>
|
||||||
|
<rect x="110" y="38" width="8" height="2" rx="1" fill="#444444"/>
|
||||||
|
<rect x="120" y="38" width="8" height="2" rx="1" fill="#444444"/>
|
||||||
|
<rect x="130" y="38" width="8" height="2" rx="1" fill="#444444"/>
|
||||||
|
<rect x="90" y="44" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="100" y="44" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="110" y="44" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="120" y="44" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="130" y="44" width="8" height="8" rx="1" fill="#1C1C1C"/>
|
||||||
|
<rect x="70" y="56" width="8" height="8" rx="1" fill="#1C1C1C"/>
|
||||||
|
<rect x="80" y="56" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="90" y="56" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="100" y="56" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="110" y="56" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="120" y="56" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="130" y="56" width="8" height="8" rx="1" fill="#1C1C1C"/>
|
||||||
|
<rect x="70" y="68" width="8" height="8" rx="1" fill="#1C1C1C"/>
|
||||||
|
<rect x="80" y="68" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="90" y="68" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="100" y="68" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="110" y="68" width="8" height="8" rx="1" fill="#575757"/>
|
||||||
|
<rect x="120" y="68" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="130" y="68" width="8" height="8" rx="1" fill="#1C1C1C"/>
|
||||||
|
<rect x="70" y="80" width="8" height="8" rx="1" fill="#1C1C1C"/>
|
||||||
|
<rect x="80" y="80" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="90" y="80" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="100" y="80" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<path d="M145 28V92H147V28H145Z" fill="#1C1C1C" mask="url(#path-13-inside-2_5291_194117)"/>
|
||||||
|
<rect x="154" y="32" width="24" height="2" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="154" y="38" width="24" height="5" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="154" y="47" width="24" height="5" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="154" y="56" width="24" height="5" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="154" y="65" width="24" height="5" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="154" y="74" width="24" height="5" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="154" y="83" width="24" height="5" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect opacity="0.2" width="208" height="120" fill="url(#pattern0)"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||||
|
<use xlink:href="#image0_5291_194117" transform="scale(0.00480769 0.00833333)"/>
|
||||||
|
</pattern>
|
||||||
|
<radialGradient id="paint0_radial_5291_194117" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(207.334 -1.47576e-05) rotate(145.398) scale(465.773 828.954)">
|
||||||
|
<stop stop-color="#282E39"/>
|
||||||
|
<stop offset="1" stop-color="#D4D4D5"/>
|
||||||
|
</radialGradient>
|
||||||
|
<clipPath id="clip0_5291_194117">
|
||||||
|
<rect width="208" height="120" rx="6" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
<image id="image0_5291_194117" width="208" height="120" xlink:href="data:image/png;base64,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 truncated
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,149 @@
|
|||||||
|
<svg width="208" height="120" viewBox="0 0 208 120" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g clip-path="url(#clip0_5291_194186)">
|
||||||
|
<rect width="208" height="120" rx="6" fill="url(#paint0_radial_5291_194186)"/>
|
||||||
|
<rect x="26.7329" y="13.4136" width="154.535" height="93.1733" rx="5.49505" fill="#1A1A1A"/>
|
||||||
|
<circle cx="37.1435" cy="23.8247" r="2.74752" fill="#2B2B2B"/>
|
||||||
|
<rect x="41.7227" y="22.9087" width="14.6535" height="1.83168" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="34.396" y="30.2354" width="29.3069" height="2.74752" rx="0.915842" fill="#444444"/>
|
||||||
|
<rect x="34.396" y="36.6465" width="21.9802" height="1.83168" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="34.396" y="42.1411" width="21.9802" height="1.83168" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="34.396" y="47.6362" width="21.9802" height="1.83168" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="34.396" y="58.6265" width="5.49505" height="1.83168" rx="0.915842" fill="#444444"/>
|
||||||
|
<rect x="41.7227" y="58.6265" width="5.49505" height="1.83168" rx="0.915842" fill="#444444"/>
|
||||||
|
<rect x="49.0493" y="58.6265" width="5.49505" height="1.83168" rx="0.915842" fill="#444444"/>
|
||||||
|
<rect x="56.3765" y="58.6265" width="5.49505" height="1.83168" rx="0.915842" fill="#444444"/>
|
||||||
|
<rect x="63.7031" y="58.6265" width="5.49505" height="1.83168" rx="0.915842" fill="#444444"/>
|
||||||
|
<rect x="71.0298" y="58.6265" width="5.49505" height="1.83168" rx="0.915842" fill="#444444"/>
|
||||||
|
<rect x="78.3564" y="58.6265" width="5.49505" height="1.83168" rx="0.915842" fill="#444444"/>
|
||||||
|
<rect x="49.0493" y="64.1216" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="56.3765" y="64.1216" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="63.7031" y="64.1216" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="71.0298" y="64.1216" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="78.3564" y="64.1216" width="5.49505" height="5.49505" rx="0.915842" fill="#1C1C1C"/>
|
||||||
|
<rect x="34.396" y="73.2798" width="5.49505" height="5.49505" rx="0.915842" fill="#1C1C1C"/>
|
||||||
|
<rect x="41.7227" y="73.2798" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="49.0493" y="73.2798" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="56.3765" y="73.2798" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="63.7031" y="73.2798" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="71.0298" y="73.2798" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="78.3564" y="73.2798" width="5.49505" height="5.49505" rx="0.915842" fill="#1C1C1C"/>
|
||||||
|
<rect x="34.396" y="82.4385" width="5.49505" height="5.49505" rx="0.915842" fill="#1C1C1C"/>
|
||||||
|
<rect x="41.7227" y="82.4385" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="49.0493" y="82.4385" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="56.3765" y="82.4385" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="63.7031" y="82.4385" width="5.49505" height="5.49505" rx="0.915842" fill="#575757"/>
|
||||||
|
<rect x="71.0298" y="82.4385" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="78.3564" y="82.4385" width="5.49505" height="5.49505" rx="0.915842" fill="#1C1C1C"/>
|
||||||
|
<rect x="34.396" y="91.5967" width="5.49505" height="5.49505" rx="0.915842" fill="#1C1C1C"/>
|
||||||
|
<rect x="41.7227" y="91.5967" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="49.0493" y="91.5967" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<rect x="56.3765" y="91.5967" width="5.49505" height="5.49505" rx="0.915842" fill="#2B2B2B"/>
|
||||||
|
<path d="M92.0943 17.4136V102.587H93.926V17.4136H92.0943Z" fill="#1C1C1C" mask="url(#path-3-inside-1_5291_194186)"/>
|
||||||
|
<g clip-path="url(#clip1_5291_194186)">
|
||||||
|
<rect x="100.337" y="21.0771" width="10.9901" height="1.83168" rx="0.915842" fill="#444444"/>
|
||||||
|
<rect x="114.99" y="21.0771" width="10.9901" height="1.83168" rx="0.915842" fill="#444444"/>
|
||||||
|
<rect x="129.644" y="21.0771" width="10.9901" height="1.83168" rx="0.915842" fill="#444444"/>
|
||||||
|
<rect x="144.297" y="21.0771" width="10.9901" height="1.83168" rx="0.915842" fill="#444444"/>
|
||||||
|
<rect x="158.951" y="21.0771" width="10.9901" height="1.83168" rx="0.915842" fill="#444444"/>
|
||||||
|
<rect x="98.5059" y="26.5723" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<path d="M98.5059 28.4039C98.5059 27.3923 99.3259 26.5723 100.338 26.5723H113.159V32.0673H98.5059V28.4039Z" stroke="#2B2B2B" stroke-width="0.457921"/>
|
||||||
|
<rect x="113.159" y="26.5723" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="113.159" y="26.5723" width="14.6535" height="5.49505" stroke="#2B2B2B" stroke-width="0.457921"/>
|
||||||
|
<rect x="127.812" y="26.5723" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="127.812" y="26.5723" width="14.6535" height="5.49505" stroke="#2B2B2B" stroke-width="0.457921"/>
|
||||||
|
<rect x="142.466" y="26.5723" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="142.466" y="26.5723" width="14.6535" height="5.49505" stroke="#2B2B2B" stroke-width="0.457921"/>
|
||||||
|
<rect x="157.12" y="26.5723" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<path d="M157.12 26.5723H169.941C170.953 26.5723 171.773 27.3923 171.773 28.4039V32.0673H157.12V26.5723Z" stroke="#2B2B2B" stroke-width="0.457921"/>
|
||||||
|
<rect x="98.5054" y="32.0669" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="113.159" y="32.0669" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="127.812" y="32.0669" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="142.465" y="32.0669" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="157.119" y="32.0669" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="98.5054" y="37.562" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="113.159" y="37.562" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="127.812" y="37.562" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="142.465" y="37.562" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="157.119" y="37.562" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="98.5054" y="43.0571" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="113.159" y="43.0571" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="127.812" y="43.0571" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="142.465" y="43.0571" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="157.119" y="43.0571" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="98.5054" y="48.5522" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="113.159" y="48.5522" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="127.812" y="48.5522" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="142.465" y="48.5522" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="157.119" y="48.5522" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="98.5054" y="54.0474" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="113.159" y="54.0474" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="127.812" y="54.0474" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="142.465" y="54.0474" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="157.119" y="54.0474" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="98.5054" y="59.5425" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="113.159" y="59.5425" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="127.812" y="59.5425" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="142.465" y="59.5425" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="157.119" y="59.5425" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="98.5054" y="65.0371" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="113.159" y="65.0371" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="127.812" y="65.0371" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="142.465" y="65.0371" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="157.119" y="65.0371" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="98.5054" y="70.5322" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="113.159" y="70.5322" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="127.812" y="70.5322" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="142.465" y="70.5322" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="157.119" y="70.5322" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="98.5054" y="76.0273" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="113.159" y="76.0273" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="127.812" y="76.0273" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="142.465" y="76.0273" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="157.119" y="76.0273" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="98.5054" y="81.5225" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="113.159" y="81.5225" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="127.812" y="81.5225" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="142.465" y="81.5225" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="157.119" y="81.5225" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="98.5054" y="87.0176" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="113.159" y="87.0176" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="127.812" y="87.0176" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="142.465" y="87.0176" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="157.119" y="87.0176" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="98.5054" y="92.5127" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<path d="M98.5054 92.5127H113.159V98.0077H100.337C99.3254 98.0077 98.5054 97.1877 98.5054 96.1761V92.5127Z" stroke="#2B2B2B" stroke-width="0.457921"/>
|
||||||
|
<rect x="113.159" y="92.5127" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="113.159" y="92.5127" width="14.6535" height="5.49505" stroke="#2B2B2B" stroke-width="0.457921"/>
|
||||||
|
<rect x="127.812" y="92.5127" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="127.812" y="92.5127" width="14.6535" height="5.49505" stroke="#2B2B2B" stroke-width="0.457921"/>
|
||||||
|
<rect x="142.465" y="92.5127" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<rect x="142.465" y="92.5127" width="14.6535" height="5.49505" stroke="#2B2B2B" stroke-width="0.457921"/>
|
||||||
|
<rect x="157.119" y="92.5127" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
<path d="M157.119 92.5127H171.773V96.1761C171.773 97.1877 170.953 98.0077 169.941 98.0077H157.119V92.5127Z" stroke="#2B2B2B" stroke-width="0.457921"/>
|
||||||
|
<rect x="98.5054" y="59.5425" width="14.6535" height="10.9901" stroke="#2B2B2B"/>
|
||||||
|
<rect opacity="0.6" x="127.813" y="75.2798" width="14.6535" height="11.9901" fill="#525252" stroke="#2B2B2B"/>
|
||||||
|
<rect opacity="0.6" x="157.12" y="59.7104" width="14.6535" height="26.4851" fill="#525252" stroke="#2B2B2B"/>
|
||||||
|
<rect opacity="0.6" x="99.1196" y="81.5229" width="13.6535" height="15.6726" fill="#525252" stroke="#2B2B2B"/>
|
||||||
|
<rect opacity="0.6" x="99.1196" y="26.7104" width="13.6535" height="10.4851" fill="#525252" stroke="#2B2B2B"/>
|
||||||
|
<rect opacity="0.6" x="142.466" y="43.0571" width="14.6535" height="2.74752" fill="#525252" stroke="#2B2B2B"/>
|
||||||
|
<rect x="113.159" y="26.5718" width="14.6535" height="5.49505" stroke="#2B2B2B"/>
|
||||||
|
</g>
|
||||||
|
<rect opacity="0.2" x="0.333008" y="-1" width="208" height="120" fill="url(#pattern0)"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||||
|
<use xlink:href="#image0_5291_194186" transform="scale(0.00480769 0.00833333)"/>
|
||||||
|
</pattern>
|
||||||
|
<radialGradient id="paint0_radial_5291_194186" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(207.334 -1.47576e-05) rotate(145.398) scale(465.773 828.954)">
|
||||||
|
<stop stop-color="#282E39"/>
|
||||||
|
<stop offset="1" stop-color="#D4D4D5"/>
|
||||||
|
</radialGradient>
|
||||||
|
<clipPath id="clip0_5291_194186">
|
||||||
|
<rect width="208" height="120" rx="6" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip1_5291_194186">
|
||||||
|
<rect width="84.2574" height="84.2574" fill="white" transform="translate(93.0103 17.4136)"/>
|
||||||
|
</clipPath>
|
||||||
|
<image id="image0_5291_194186" width="208" height="120" xlink:href="data:image/png;base64,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 truncated
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 24 KiB |
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "يجب إدخال عنوان URL للفريق",
|
"team_url_required": "يجب إدخال عنوان URL للفريق",
|
||||||
"team_url_taken": "عنوان URL هذا مأخوذ بالفعل",
|
"team_url_taken": "عنوان URL هذا مأخوذ بالفعل",
|
||||||
"team_publish": "نشر فريق",
|
"team_publish": "نشر فريق",
|
||||||
"number_sms_notifications": "رقم الهاتف (إشعارات الرسائل النصية)",
|
|
||||||
"attendee_email_variable": "اسم الحاضر",
|
"attendee_email_variable": "اسم الحاضر",
|
||||||
"attendee_email_info": "البريد الإلكتروني للشخص الحجز",
|
"attendee_email_info": "البريد الإلكتروني للشخص الحجز",
|
||||||
"kbar_search_placeholder": "اكتب أمرًا أو بحثًا...",
|
"kbar_search_placeholder": "اكتب أمرًا أو بحثًا...",
|
||||||
|
|||||||
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "Musíte zadat adresu URL týmu",
|
"team_url_required": "Musíte zadat adresu URL týmu",
|
||||||
"team_url_taken": "Tato adresa URL je už obsazená",
|
"team_url_taken": "Tato adresa URL je už obsazená",
|
||||||
"team_publish": "Zveřejnit tým",
|
"team_publish": "Zveřejnit tým",
|
||||||
"number_sms_notifications": "Číslo telefonu (SMS oznámení)",
|
|
||||||
"attendee_email_variable": "E-mail účastníka",
|
"attendee_email_variable": "E-mail účastníka",
|
||||||
"attendee_email_info": "E-mail osoby provádějící rezervaci",
|
"attendee_email_info": "E-mail osoby provádějící rezervaci",
|
||||||
"kbar_search_placeholder": "Zadejte příkaz nebo vyhledejte...",
|
"kbar_search_placeholder": "Zadejte příkaz nebo vyhledejte...",
|
||||||
|
|||||||
@@ -1344,7 +1344,6 @@
|
|||||||
"team_url_required": "Der skal indtastes et team URL",
|
"team_url_required": "Der skal indtastes et team URL",
|
||||||
"team_url_taken": "Denne URL er allerede i brug",
|
"team_url_taken": "Denne URL er allerede i brug",
|
||||||
"team_publish": "Publicer team",
|
"team_publish": "Publicer team",
|
||||||
"number_sms_notifications": "Telefonnummer (SMS notifikationer)",
|
|
||||||
"attendee_email_variable": "Deltagers e-mail",
|
"attendee_email_variable": "Deltagers e-mail",
|
||||||
"attendee_email_info": "Personen der booker's e-mail",
|
"attendee_email_info": "Personen der booker's e-mail",
|
||||||
"kbar_search_placeholder": "Indtast en kommando eller søgning...",
|
"kbar_search_placeholder": "Indtast en kommando eller søgning...",
|
||||||
|
|||||||
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "Team-URL muss eingegeben werden",
|
"team_url_required": "Team-URL muss eingegeben werden",
|
||||||
"team_url_taken": "Diese URL ist bereits vergeben",
|
"team_url_taken": "Diese URL ist bereits vergeben",
|
||||||
"team_publish": "Team veröffentlichen",
|
"team_publish": "Team veröffentlichen",
|
||||||
"number_sms_notifications": "Telefonnummer (SMS-Benachrichtigungen)",
|
|
||||||
"attendee_email_variable": "Teilnehmer E-Mail",
|
"attendee_email_variable": "Teilnehmer E-Mail",
|
||||||
"attendee_email_info": "Die E-Mail-Adresse der buchenden Person",
|
"attendee_email_info": "Die E-Mail-Adresse der buchenden Person",
|
||||||
"kbar_search_placeholder": "Geben Sie einen Befehl ein oder suchen Sie ...",
|
"kbar_search_placeholder": "Geben Sie einen Befehl ein oder suchen Sie ...",
|
||||||
|
|||||||
@@ -1466,7 +1466,7 @@
|
|||||||
"team_url_required": "Must enter a team URL",
|
"team_url_required": "Must enter a team URL",
|
||||||
"team_url_taken": "This URL is already taken",
|
"team_url_taken": "This URL is already taken",
|
||||||
"team_publish": "Publish team",
|
"team_publish": "Publish team",
|
||||||
"number_sms_notifications": "Phone number (SMS notifications)",
|
"number_sms_notifications": "Phone number (SMS notifications)",
|
||||||
"attendee_email_variable": "Attendee email",
|
"attendee_email_variable": "Attendee email",
|
||||||
"attendee_email_info": "The person booking's email",
|
"attendee_email_info": "The person booking's email",
|
||||||
"kbar_search_placeholder": "Type a command or search...",
|
"kbar_search_placeholder": "Type a command or search...",
|
||||||
@@ -1523,6 +1523,9 @@
|
|||||||
"no_available_apps": "There are no available apps",
|
"no_available_apps": "There are no available apps",
|
||||||
"no_available_apps_description": "Please ensure there are apps in your deployment under 'packages/app-store'",
|
"no_available_apps_description": "Please ensure there are apps in your deployment under 'packages/app-store'",
|
||||||
"no_apps": "There are no apps enabled in this instance of Cal",
|
"no_apps": "There are no apps enabled in this instance of Cal",
|
||||||
|
"no_apps_configured": "No app has been configured yet",
|
||||||
|
"enable_in_settings": "You can enable apps in the settings",
|
||||||
|
"please_contact_admin": "Please contact your admin",
|
||||||
"apps_settings": "Apps settings",
|
"apps_settings": "Apps settings",
|
||||||
"fill_this_field": "Please fill in this field",
|
"fill_this_field": "Please fill in this field",
|
||||||
"options": "Options",
|
"options": "Options",
|
||||||
|
|||||||
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "Debe introducir una URL del equipo",
|
"team_url_required": "Debe introducir una URL del equipo",
|
||||||
"team_url_taken": "Esta URL ya está en uso",
|
"team_url_taken": "Esta URL ya está en uso",
|
||||||
"team_publish": "Publicar equipo",
|
"team_publish": "Publicar equipo",
|
||||||
"number_sms_notifications": "Número de teléfono (notificaciones por SMS)",
|
|
||||||
"attendee_email_variable": "Correo electrónico del asistente",
|
"attendee_email_variable": "Correo electrónico del asistente",
|
||||||
"attendee_email_info": "El correo electrónico de la persona que reserva",
|
"attendee_email_info": "El correo electrónico de la persona que reserva",
|
||||||
"kbar_search_placeholder": "Escriba un comando o búsqueda...",
|
"kbar_search_placeholder": "Escriba un comando o búsqueda...",
|
||||||
|
|||||||
@@ -1523,6 +1523,9 @@
|
|||||||
"no_available_apps": "Il n'y a aucune application disponible",
|
"no_available_apps": "Il n'y a aucune application disponible",
|
||||||
"no_available_apps_description": "Veuillez vous assurer qu'il y a des applications dans votre déploiement sous \"packages/app-store\"",
|
"no_available_apps_description": "Veuillez vous assurer qu'il y a des applications dans votre déploiement sous \"packages/app-store\"",
|
||||||
"no_apps": "Aucune application n'est activée dans cette instance de Cal",
|
"no_apps": "Aucune application n'est activée dans cette instance de Cal",
|
||||||
|
"no_apps_configured": "Aucune application n'a encore été configurée",
|
||||||
|
"enable_in_settings": "Vous pouvez activer les applications dans les paramètres",
|
||||||
|
"please_contact_admin": "Veuillez contacter votre administrateur",
|
||||||
"apps_settings": "Paramètres des applications",
|
"apps_settings": "Paramètres des applications",
|
||||||
"fill_this_field": "Veuillez remplir ce champ",
|
"fill_this_field": "Veuillez remplir ce champ",
|
||||||
"options": "Options",
|
"options": "Options",
|
||||||
|
|||||||
@@ -1421,7 +1421,6 @@
|
|||||||
"team_url_required": "יש להזין כתובת URL של הצוות",
|
"team_url_required": "יש להזין כתובת URL של הצוות",
|
||||||
"team_url_taken": "כתובת ה-URL הזו כבר תפוסה",
|
"team_url_taken": "כתובת ה-URL הזו כבר תפוסה",
|
||||||
"team_publish": "פרסום צוות",
|
"team_publish": "פרסום צוות",
|
||||||
"number_sms_notifications": "מספר טלפון (התראות SMS)",
|
|
||||||
"attendee_email_variable": "כתובת הדוא\"ל של המשתתף",
|
"attendee_email_variable": "כתובת הדוא\"ל של המשתתף",
|
||||||
"attendee_email_info": "כתובת הדוא\"ל של האדם שביצע את ההזמנה",
|
"attendee_email_info": "כתובת הדוא\"ל של האדם שביצע את ההזמנה",
|
||||||
"kbar_search_placeholder": "הקלד/י פקודה או חפש/י...",
|
"kbar_search_placeholder": "הקלד/י פקודה או חפש/י...",
|
||||||
|
|||||||
@@ -1438,7 +1438,6 @@
|
|||||||
"team_url_required": "Immettere un URL per il team",
|
"team_url_required": "Immettere un URL per il team",
|
||||||
"team_url_taken": "URL già occupato",
|
"team_url_taken": "URL già occupato",
|
||||||
"team_publish": "Pubblica team",
|
"team_publish": "Pubblica team",
|
||||||
"number_sms_notifications": "Numero di telefono (notifiche SMS)",
|
|
||||||
"attendee_email_variable": "E-mail partecipante",
|
"attendee_email_variable": "E-mail partecipante",
|
||||||
"attendee_email_info": "E-mail della persona che prenota",
|
"attendee_email_info": "E-mail della persona che prenota",
|
||||||
"kbar_search_placeholder": "Digita un comando o esegui una ricerca...",
|
"kbar_search_placeholder": "Digita un comando o esegui una ricerca...",
|
||||||
|
|||||||
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "チームの URL の入力は必須です",
|
"team_url_required": "チームの URL の入力は必須です",
|
||||||
"team_url_taken": "この URL は既に使用されています",
|
"team_url_taken": "この URL は既に使用されています",
|
||||||
"team_publish": "チームを公開",
|
"team_publish": "チームを公開",
|
||||||
"number_sms_notifications": "電話番号(SMS 通知)",
|
|
||||||
"attendee_email_variable": "出席者のメールアドレス",
|
"attendee_email_variable": "出席者のメールアドレス",
|
||||||
"attendee_email_info": "予約者のメールアドレス",
|
"attendee_email_info": "予約者のメールアドレス",
|
||||||
"kbar_search_placeholder": "コマンドを入力するか、検索してください...",
|
"kbar_search_placeholder": "コマンドを入力するか、検索してください...",
|
||||||
|
|||||||
@@ -1409,7 +1409,6 @@
|
|||||||
"team_url_required": "팀 URL을 입력하세요",
|
"team_url_required": "팀 URL을 입력하세요",
|
||||||
"team_url_taken": "이미 사용 중인 URL입니다",
|
"team_url_taken": "이미 사용 중인 URL입니다",
|
||||||
"team_publish": "팀 게시",
|
"team_publish": "팀 게시",
|
||||||
"number_sms_notifications": "전화번호 (SMS notifications)",
|
|
||||||
"attendee_email_variable": "참석자 이메일",
|
"attendee_email_variable": "참석자 이메일",
|
||||||
"attendee_email_info": "예약자 이메일",
|
"attendee_email_info": "예약자 이메일",
|
||||||
"kbar_search_placeholder": "명령어를 입력하거나 검색하세요...",
|
"kbar_search_placeholder": "명령어를 입력하거나 검색하세요...",
|
||||||
|
|||||||
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "Moet een team-URL invoeren",
|
"team_url_required": "Moet een team-URL invoeren",
|
||||||
"team_url_taken": "Deze URL is al in gebruik",
|
"team_url_taken": "Deze URL is al in gebruik",
|
||||||
"team_publish": "Team publiceren",
|
"team_publish": "Team publiceren",
|
||||||
"number_sms_notifications": "Telefoonnummer (sms-meldingen)",
|
|
||||||
"attendee_email_variable": "E-mailadres deelnemer",
|
"attendee_email_variable": "E-mailadres deelnemer",
|
||||||
"attendee_email_info": "Het e-mailadres van de persoon die boekt",
|
"attendee_email_info": "Het e-mailadres van de persoon die boekt",
|
||||||
"kbar_search_placeholder": "Typ een opdracht of zoek...",
|
"kbar_search_placeholder": "Typ een opdracht of zoek...",
|
||||||
|
|||||||
@@ -1318,7 +1318,6 @@
|
|||||||
"team_url_required": "Må angi en team URL",
|
"team_url_required": "Må angi en team URL",
|
||||||
"team_url_taken": "Denne URL'en er allerede tatt",
|
"team_url_taken": "Denne URL'en er allerede tatt",
|
||||||
"team_publish": "Publiser team",
|
"team_publish": "Publiser team",
|
||||||
"number_sms_notifications": "Telefonnummer (SMS varsler)",
|
|
||||||
"attendee_email_variable": "Deltakerens e-post",
|
"attendee_email_variable": "Deltakerens e-post",
|
||||||
"attendee_email_info": "Den som booker sin e-post",
|
"attendee_email_info": "Den som booker sin e-post",
|
||||||
"kbar_search_placeholder": "Skriv inn en kommando eller søk...",
|
"kbar_search_placeholder": "Skriv inn en kommando eller søk...",
|
||||||
|
|||||||
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "Musisz wprowadzić adres URL zespołu",
|
"team_url_required": "Musisz wprowadzić adres URL zespołu",
|
||||||
"team_url_taken": "Ten adres URL jest już zajęty",
|
"team_url_taken": "Ten adres URL jest już zajęty",
|
||||||
"team_publish": "Opublikuj zespół",
|
"team_publish": "Opublikuj zespół",
|
||||||
"number_sms_notifications": "Numer telefonu (powiadomienia SMS)",
|
|
||||||
"attendee_email_variable": "Adres e-mail uczestnika",
|
"attendee_email_variable": "Adres e-mail uczestnika",
|
||||||
"attendee_email_info": "Adres e-mail osoby rezerwującej",
|
"attendee_email_info": "Adres e-mail osoby rezerwującej",
|
||||||
"kbar_search_placeholder": "Wpisz polecenie lub wyszukaj...",
|
"kbar_search_placeholder": "Wpisz polecenie lub wyszukaj...",
|
||||||
|
|||||||
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "É preciso inserir uma URL de equipe",
|
"team_url_required": "É preciso inserir uma URL de equipe",
|
||||||
"team_url_taken": "Este URL já está em uso",
|
"team_url_taken": "Este URL já está em uso",
|
||||||
"team_publish": "Publicar equipe",
|
"team_publish": "Publicar equipe",
|
||||||
"number_sms_notifications": "Número de telefone (notificações de SMS)",
|
|
||||||
"attendee_email_variable": "E-mail do participante",
|
"attendee_email_variable": "E-mail do participante",
|
||||||
"attendee_email_info": "O e-mail da pessoa que fez a reserva",
|
"attendee_email_info": "O e-mail da pessoa que fez a reserva",
|
||||||
"kbar_search_placeholder": "Digite um comando ou procure...",
|
"kbar_search_placeholder": "Digite um comando ou procure...",
|
||||||
|
|||||||
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "Deve especificar um endereço para a equipa",
|
"team_url_required": "Deve especificar um endereço para a equipa",
|
||||||
"team_url_taken": "Este endereço já está a ser utilizado",
|
"team_url_taken": "Este endereço já está a ser utilizado",
|
||||||
"team_publish": "Publicar equipa",
|
"team_publish": "Publicar equipa",
|
||||||
"number_sms_notifications": "Número de telefone (notificações SMS)",
|
|
||||||
"attendee_email_variable": "E-mail do participante",
|
"attendee_email_variable": "E-mail do participante",
|
||||||
"attendee_email_info": "O e-mail do responsável pela reserva",
|
"attendee_email_info": "O e-mail do responsável pela reserva",
|
||||||
"kbar_search_placeholder": "Digite um comando ou pesquise...",
|
"kbar_search_placeholder": "Digite um comando ou pesquise...",
|
||||||
|
|||||||
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "Trebuie să introduceți un URL de echipă",
|
"team_url_required": "Trebuie să introduceți un URL de echipă",
|
||||||
"team_url_taken": "Acest URL este folosit deja",
|
"team_url_taken": "Acest URL este folosit deja",
|
||||||
"team_publish": "Publicați echipa",
|
"team_publish": "Publicați echipa",
|
||||||
"number_sms_notifications": "Număr de telefon (notificări SMS)",
|
|
||||||
"attendee_email_variable": "E-mail participant",
|
"attendee_email_variable": "E-mail participant",
|
||||||
"attendee_email_info": "E-mailul persoanei care efectuează rezervarea",
|
"attendee_email_info": "E-mailul persoanei care efectuează rezervarea",
|
||||||
"kbar_search_placeholder": "Scrie o comandă sau caută...",
|
"kbar_search_placeholder": "Scrie o comandă sau caută...",
|
||||||
|
|||||||
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "Необходимо ввести URL-адрес команды",
|
"team_url_required": "Необходимо ввести URL-адрес команды",
|
||||||
"team_url_taken": "Этот URL-адрес уже занят",
|
"team_url_taken": "Этот URL-адрес уже занят",
|
||||||
"team_publish": "Опубликовать команду",
|
"team_publish": "Опубликовать команду",
|
||||||
"number_sms_notifications": "Номер телефона (SMS-уведомления)",
|
|
||||||
"attendee_email_variable": "Электронная почта участника",
|
"attendee_email_variable": "Электронная почта участника",
|
||||||
"attendee_email_info": "Электронная почта участника, на которого оформляется бронирование",
|
"attendee_email_info": "Электронная почта участника, на которого оформляется бронирование",
|
||||||
"kbar_search_placeholder": "Введите команду или начните поиск...",
|
"kbar_search_placeholder": "Введите команду или начните поиск...",
|
||||||
|
|||||||
@@ -1435,7 +1435,6 @@
|
|||||||
"team_url_required": "Morate da unesete URL tima",
|
"team_url_required": "Morate da unesete URL tima",
|
||||||
"team_url_taken": "Ovaj URL je zauzet",
|
"team_url_taken": "Ovaj URL je zauzet",
|
||||||
"team_publish": "Objavite tim",
|
"team_publish": "Objavite tim",
|
||||||
"number_sms_notifications": "Broj telefona (obaveštenja putem SMS-a)",
|
|
||||||
"attendee_email_variable": "Imejl polaznika",
|
"attendee_email_variable": "Imejl polaznika",
|
||||||
"attendee_email_info": "Imejl osobe koja rezerviše",
|
"attendee_email_info": "Imejl osobe koja rezerviše",
|
||||||
"kbar_search_placeholder": "Unesite komandu ili pretražite...",
|
"kbar_search_placeholder": "Unesite komandu ili pretražite...",
|
||||||
|
|||||||
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "Team-URL måste anges",
|
"team_url_required": "Team-URL måste anges",
|
||||||
"team_url_taken": "Denna URL används redan",
|
"team_url_taken": "Denna URL används redan",
|
||||||
"team_publish": "Publicera team",
|
"team_publish": "Publicera team",
|
||||||
"number_sms_notifications": "Telefonnummer (SMS-notiser)",
|
|
||||||
"attendee_email_variable": "Deltagarens e-postadress",
|
"attendee_email_variable": "Deltagarens e-postadress",
|
||||||
"attendee_email_info": "Personens e-postadress för bokning",
|
"attendee_email_info": "Personens e-postadress för bokning",
|
||||||
"kbar_search_placeholder": "Ange ett kommando eller sök ...",
|
"kbar_search_placeholder": "Ange ett kommando eller sök ...",
|
||||||
|
|||||||
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "Ekip URL'si girilmelidir",
|
"team_url_required": "Ekip URL'si girilmelidir",
|
||||||
"team_url_taken": "Bu URL zaten kullanılıyor",
|
"team_url_taken": "Bu URL zaten kullanılıyor",
|
||||||
"team_publish": "Ekibi yayınla",
|
"team_publish": "Ekibi yayınla",
|
||||||
"number_sms_notifications": "Telefon numarası (SMS bildirimleri)",
|
|
||||||
"attendee_email_variable": "Katılımcı e-postası",
|
"attendee_email_variable": "Katılımcı e-postası",
|
||||||
"attendee_email_info": "Rezervasyon yaptıran kişinin e-postası",
|
"attendee_email_info": "Rezervasyon yaptıran kişinin e-postası",
|
||||||
"kbar_search_placeholder": "Komut girin veya arayın...",
|
"kbar_search_placeholder": "Komut girin veya arayın...",
|
||||||
|
|||||||
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "Потрібно ввести URL-адресу команди",
|
"team_url_required": "Потрібно ввести URL-адресу команди",
|
||||||
"team_url_taken": "Цю URL-адресу вже зайнято",
|
"team_url_taken": "Цю URL-адресу вже зайнято",
|
||||||
"team_publish": "Опублікувати команду",
|
"team_publish": "Опублікувати команду",
|
||||||
"number_sms_notifications": "Номер телефону (SMS-сповіщення)",
|
|
||||||
"attendee_email_variable": "Адреса ел. пошти учасника",
|
"attendee_email_variable": "Адреса ел. пошти учасника",
|
||||||
"attendee_email_info": "Адреса ел. пошти особи, яка бронює",
|
"attendee_email_info": "Адреса ел. пошти особи, яка бронює",
|
||||||
"kbar_search_placeholder": "Введіть команду або пошуковий запит…",
|
"kbar_search_placeholder": "Введіть команду або пошуковий запит…",
|
||||||
|
|||||||
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "Cần phải nhập một URL của nhóm",
|
"team_url_required": "Cần phải nhập một URL của nhóm",
|
||||||
"team_url_taken": "URL này đã có người lấy rồi",
|
"team_url_taken": "URL này đã có người lấy rồi",
|
||||||
"team_publish": "Đăng nhóm",
|
"team_publish": "Đăng nhóm",
|
||||||
"number_sms_notifications": "Số điện thoại (thông báo SMS)",
|
|
||||||
"attendee_email_variable": "Email người tham dự",
|
"attendee_email_variable": "Email người tham dự",
|
||||||
"attendee_email_info": "Email người tham gia lịch hẹn",
|
"attendee_email_info": "Email người tham gia lịch hẹn",
|
||||||
"kbar_search_placeholder": "Nhập một lệnh hoặc tìm kiếm...",
|
"kbar_search_placeholder": "Nhập một lệnh hoặc tìm kiếm...",
|
||||||
|
|||||||
@@ -1421,7 +1421,6 @@
|
|||||||
"team_url_required": "必须输入团队链接",
|
"team_url_required": "必须输入团队链接",
|
||||||
"team_url_taken": "此链接已被占用",
|
"team_url_taken": "此链接已被占用",
|
||||||
"team_publish": "发布团队",
|
"team_publish": "发布团队",
|
||||||
"number_sms_notifications": "电话号码(短信通知)",
|
|
||||||
"attendee_email_variable": "参与者电子邮件",
|
"attendee_email_variable": "参与者电子邮件",
|
||||||
"attendee_email_info": "预约人的电子邮件",
|
"attendee_email_info": "预约人的电子邮件",
|
||||||
"kbar_search_placeholder": "输入命令或搜索...",
|
"kbar_search_placeholder": "输入命令或搜索...",
|
||||||
|
|||||||
@@ -1408,7 +1408,6 @@
|
|||||||
"team_url_required": "團隊網址為必填",
|
"team_url_required": "團隊網址為必填",
|
||||||
"team_url_taken": "已有人使用此網址",
|
"team_url_taken": "已有人使用此網址",
|
||||||
"team_publish": "發佈團隊",
|
"team_publish": "發佈團隊",
|
||||||
"number_sms_notifications": "電話號碼 (簡訊通知)",
|
|
||||||
"attendee_email_variable": "與會者電子郵件",
|
"attendee_email_variable": "與會者電子郵件",
|
||||||
"attendee_email_info": "預約人電子郵件",
|
"attendee_email_info": "預約人電子郵件",
|
||||||
"kbar_search_placeholder": "輸入指令或搜尋…",
|
"kbar_search_placeholder": "輸入指令或搜尋…",
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
|
||||||
|
import { it, expect, describe, beforeAll, afterAll } from "vitest";
|
||||||
|
let userTypeRouteRegExp: RegExp;
|
||||||
|
let teamTypeRouteRegExp:RegExp;
|
||||||
|
let privateLinkRouteRegExp:RegExp
|
||||||
|
let embedUserTypeRouteRegExp:RegExp
|
||||||
|
let embedTeamTypeRouteRegExp:RegExp
|
||||||
|
|
||||||
|
|
||||||
|
const getRegExpFromNextJsRewriteRegExp = (nextJsRegExp:string) => {
|
||||||
|
// const parts = nextJsRegExp.split(':');
|
||||||
|
|
||||||
|
// const validNamedGroupRegExp = parts.map((part, index)=>{
|
||||||
|
// if (index === 0) {
|
||||||
|
// return part;
|
||||||
|
// }
|
||||||
|
// if (part.match(/^[a-zA-Z0-9]+$/)) {
|
||||||
|
// return `(?<${part}>[^/]+)`
|
||||||
|
// }
|
||||||
|
// part = part.replace(new RegExp('([^(]+)(.*)'), '(?<$1>$2)');
|
||||||
|
// return part
|
||||||
|
// }).join('');
|
||||||
|
|
||||||
|
// TODO: If we can easily convert the exported rewrite regexes from next.config.js to a valid named capturing group regex, it would be best
|
||||||
|
// Next.js does an exact match as per my testing.
|
||||||
|
return new RegExp(`^${nextJsRegExp}$`)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('next.config.js - RegExp', ()=>{
|
||||||
|
beforeAll(async()=>{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
// process.env.NEXTAUTH_SECRET = process.env.NEXTAUTH_URL = process.env.CALENDSO_ENCRYPTION_KEY = 1
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const pages = require("../../pages").pages
|
||||||
|
|
||||||
|
// How to convert a Next.js rewrite RegExp/wildcard to a valid JS named capturing Group RegExp?
|
||||||
|
// - /:user/ -> (?<user>[^/]+)
|
||||||
|
// - /:user(?!404)[^/]+/ -> (?<user>((?!404)[^/]+))
|
||||||
|
|
||||||
|
// userTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type((?!book$)[^/]+)`;
|
||||||
|
userTypeRouteRegExp = getRegExpFromNextJsRewriteRegExp(`/(?<user>((?!${pages.join("/|")})[^/]*))/(?<type>((?!book$)[^/]+))`);
|
||||||
|
|
||||||
|
// teamTypeRouteRegExp = "/team/:slug/:type((?!book$)[^/]+)";
|
||||||
|
teamTypeRouteRegExp = getRegExpFromNextJsRewriteRegExp("/team/(?<slug>[^/]+)/(?<type>((?!book$)[^/]+))");
|
||||||
|
|
||||||
|
// privateLinkRouteRegExp = "/d/:link/:slug((?!book$)[^/]+)";
|
||||||
|
privateLinkRouteRegExp = getRegExpFromNextJsRewriteRegExp("/d/(?<link>[^/]+)/(?<slug>((?!book$)[^/]+))");
|
||||||
|
|
||||||
|
// embedUserTypeRouteRegExp = `/:user((?!${pages.join("|")}).*)/:type/embed`;
|
||||||
|
embedUserTypeRouteRegExp = getRegExpFromNextJsRewriteRegExp(`/(?<user>((?!${pages.join("|")}).*))/(?<type>[^/]+)/embed`);
|
||||||
|
|
||||||
|
// embedTeamTypeRouteRegExp = "/team/:slug/:type/embed";
|
||||||
|
embedTeamTypeRouteRegExp = getRegExpFromNextJsRewriteRegExp("/team/(?<slug>[^/]+)/(?<type>[^/]+)/embed");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(()=>{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
//@ts-ignore
|
||||||
|
process.env.NEXTAUTH_SECRET = process.env.NEXTAUTH_URL = process.env.CALENDSO_ENCRYPTION_KEY = undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Booking Urls", async () => {
|
||||||
|
expect(userTypeRouteRegExp.exec('/free/30')?.groups).toContain({
|
||||||
|
user: 'free',
|
||||||
|
type: '30'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Edgecase of username starting with team also works
|
||||||
|
expect(userTypeRouteRegExp.exec('/teampro/30')?.groups).toContain({
|
||||||
|
user: 'teampro',
|
||||||
|
type: '30'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/teampro+pro/30')?.groups).toContain({
|
||||||
|
user: 'teampro+pro',
|
||||||
|
type: '30'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/teampro+pro/book')).toEqual(null)
|
||||||
|
|
||||||
|
// Because /book doesn't have a corresponding new-booker route.
|
||||||
|
expect(userTypeRouteRegExp.exec('/free/book')).toEqual(null)
|
||||||
|
|
||||||
|
// Because /booked is a normal event name
|
||||||
|
expect(userTypeRouteRegExp.exec('/free/booked')?.groups).toEqual({
|
||||||
|
user: 'free',
|
||||||
|
type: 'booked'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
expect(embedUserTypeRouteRegExp.exec('/free/30/embed')?.groups).toContain({
|
||||||
|
user: 'free',
|
||||||
|
type:'30'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(teamTypeRouteRegExp.exec('/team/seeded/30')?.groups).toContain({
|
||||||
|
slug: 'seeded',
|
||||||
|
type: '30'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Because /book doesn't have a corresponding new-booker route.
|
||||||
|
expect(teamTypeRouteRegExp.exec('/team/seeded/book')).toEqual(null)
|
||||||
|
|
||||||
|
expect(teamTypeRouteRegExp.exec('/team/seeded/30/embed')).toEqual(null)
|
||||||
|
|
||||||
|
expect(embedTeamTypeRouteRegExp.exec('/team/seeded/30/embed')?.groups).toContain({
|
||||||
|
slug: 'seeded',
|
||||||
|
type:'30'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(privateLinkRouteRegExp.exec('/d/3v4s321CXRJZx5TFxkpPvd/30min')?.groups).toContain({
|
||||||
|
link: '3v4s321CXRJZx5TFxkpPvd',
|
||||||
|
slug: '30min'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(privateLinkRouteRegExp.exec('/d/3v4s321CXRJZx5TFxkpPvd/30min')?.groups).toContain({
|
||||||
|
link: '3v4s321CXRJZx5TFxkpPvd',
|
||||||
|
slug: '30min'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Because /book doesn't have a corresponding new-booker route.
|
||||||
|
expect(privateLinkRouteRegExp.exec('/d/3v4s321CXRJZx5TFxkpPvd/book')).toEqual(null)
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Non booking Urls', ()=>{
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/404')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/404')).toEqual(null)
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/404/30')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/404/30')).toEqual(null)
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/api')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/api')).toEqual(null)
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/api/30')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/api/30')).toEqual(null)
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/workflows/30')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/workflows/30')).toEqual(null)
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/event-types/30')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/event-types/30')).toEqual(null)
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/teams/1')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/teams/1')).toEqual(null)
|
||||||
|
|
||||||
|
expect(userTypeRouteRegExp.exec('/teams')).toEqual(null)
|
||||||
|
expect(teamTypeRouteRegExp.exec('/teams')).toEqual(null)
|
||||||
|
|
||||||
|
// Note that even though it matches /embed/embed.js, but it's served from /public and the regexes are in afterEach, it won't hit the flow.
|
||||||
|
// expect(userTypeRouteRegExp.exec('/embed/embed.js')).toEqual(null)
|
||||||
|
// expect(teamTypeRouteRegExp.exec('/embed/embed.js')).toEqual(null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
@@ -4,6 +4,7 @@ import { useEffect, useRef, useMemo } from "react";
|
|||||||
import StickyBox from "react-sticky-box";
|
import StickyBox from "react-sticky-box";
|
||||||
import { shallow } from "zustand/shallow";
|
import { shallow } from "zustand/shallow";
|
||||||
|
|
||||||
|
import { useEmbedUiConfig } from "@calcom/embed-core/embed-iframe";
|
||||||
import classNames from "@calcom/lib/classNames";
|
import classNames from "@calcom/lib/classNames";
|
||||||
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
||||||
import { BookerLayouts, defaultBookerLayoutSettings } from "@calcom/prisma/zod-utils";
|
import { BookerLayouts, defaultBookerLayoutSettings } from "@calcom/prisma/zod-utils";
|
||||||
@@ -35,6 +36,7 @@ const BookerComponent = ({
|
|||||||
month,
|
month,
|
||||||
rescheduleBooking,
|
rescheduleBooking,
|
||||||
hideBranding = false,
|
hideBranding = false,
|
||||||
|
isTeamEvent,
|
||||||
}: BookerProps) => {
|
}: BookerProps) => {
|
||||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||||
const isTablet = useMediaQuery("(max-width: 1024px)");
|
const isTablet = useMediaQuery("(max-width: 1024px)");
|
||||||
@@ -80,6 +82,7 @@ const BookerComponent = ({
|
|||||||
rescheduleUid,
|
rescheduleUid,
|
||||||
rescheduleBooking,
|
rescheduleBooking,
|
||||||
layout: defaultLayout,
|
layout: defaultLayout,
|
||||||
|
isTeamEvent,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -103,6 +106,9 @@ const BookerComponent = ({
|
|||||||
}
|
}
|
||||||
}, [layout]);
|
}, [layout]);
|
||||||
|
|
||||||
|
const embedUiConfig = useEmbedUiConfig();
|
||||||
|
const hideEventTypeDetails = isEmbed ? embedUiConfig.hideEventTypeDetails : false;
|
||||||
|
|
||||||
if (event.isSuccess && !event.data) {
|
if (event.isSuccess && !event.data) {
|
||||||
return <NotFound />;
|
return <NotFound />;
|
||||||
}
|
}
|
||||||
@@ -114,7 +120,7 @@ const BookerComponent = ({
|
|||||||
ref={animationScope}
|
ref={animationScope}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
// Sets booker size css variables for the size of all the columns.
|
// Sets booker size css variables for the size of all the columns.
|
||||||
...getBookerSizeClassNames(layout, bookerState),
|
...getBookerSizeClassNames(layout, bookerState, hideEventTypeDetails),
|
||||||
"bg-default dark:bg-muted grid max-w-full items-start dark:[color-scheme:dark] sm:transition-[width] sm:duration-300 sm:motion-reduce:transition-none md:flex-row",
|
"bg-default dark:bg-muted grid max-w-full items-start dark:[color-scheme:dark] sm:transition-[width] sm:duration-300 sm:motion-reduce:transition-none md:flex-row",
|
||||||
layout === BookerLayouts.MONTH_VIEW && "border-subtle rounded-md border",
|
layout === BookerLayouts.MONTH_VIEW && "border-subtle rounded-md border",
|
||||||
!isEmbed && "sm:transition-[width] sm:duration-300",
|
!isEmbed && "sm:transition-[width] sm:duration-300",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { m } from "framer-motion";
|
import { m } from "framer-motion";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
import { useEmbedUiConfig, useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||||
import { EventDetails, EventMembers, EventMetaSkeleton, EventTitle } from "@calcom/features/bookings";
|
import { EventDetails, EventMembers, EventMetaSkeleton, EventTitle } from "@calcom/features/bookings";
|
||||||
import { EventMetaBlock } from "@calcom/features/bookings/components/event-meta/Details";
|
import { EventMetaBlock } from "@calcom/features/bookings/components/event-meta/Details";
|
||||||
import { useTimePreferences } from "@calcom/features/bookings/lib";
|
import { useTimePreferences } from "@calcom/features/bookings/lib";
|
||||||
@@ -25,6 +26,13 @@ export const EventMeta = () => {
|
|||||||
const rescheduleBooking = useBookerStore((state) => state.rescheduleBooking);
|
const rescheduleBooking = useBookerStore((state) => state.rescheduleBooking);
|
||||||
const { i18n, t } = useLocale();
|
const { i18n, t } = useLocale();
|
||||||
const { data: event, isLoading } = useEvent();
|
const { data: event, isLoading } = useEvent();
|
||||||
|
const embedUiConfig = useEmbedUiConfig();
|
||||||
|
const isEmbed = useIsEmbed();
|
||||||
|
const hideEventTypeDetails = isEmbed ? embedUiConfig.hideEventTypeDetails : false;
|
||||||
|
|
||||||
|
if (hideEventTypeDetails) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative z-10 p-6">
|
<div className="relative z-10 p-6">
|
||||||
|
|||||||
@@ -112,25 +112,38 @@ export const resizeAnimationConfig: ResizeAnimationConfig = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBookerSizeClassNames = (layout: BookerLayout, bookerState: BookerState) => {
|
export const getBookerSizeClassNames = (
|
||||||
|
layout: BookerLayout,
|
||||||
|
bookerState: BookerState,
|
||||||
|
hideEventTypeDetails = false
|
||||||
|
) => {
|
||||||
|
const getBookerMetaClass = (className: string) => {
|
||||||
|
if (hideEventTypeDetails) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return className;
|
||||||
|
};
|
||||||
|
|
||||||
return [
|
return [
|
||||||
// Size settings are abstracted on their own lines purely for readbility.
|
// Size settings are abstracted on their own lines purely for readability.
|
||||||
// General sizes, used always
|
// General sizes, used always
|
||||||
"[--booker-timeslots-width:240px] lg:[--booker-timeslots-width:280px]",
|
"[--booker-timeslots-width:240px] lg:[--booker-timeslots-width:280px]",
|
||||||
// Small calendar defaults
|
// Small calendar defaults
|
||||||
layout === BookerLayouts.MONTH_VIEW && "[--booker-meta-width:240px]",
|
layout === BookerLayouts.MONTH_VIEW && getBookerMetaClass("[--booker-meta-width:240px]"),
|
||||||
// Meta column get's wider in booking view to fit the full date on a single row in case
|
// Meta column get's wider in booking view to fit the full date on a single row in case
|
||||||
// of a multi occurance event. Also makes form less wide, which also looks better.
|
// of a multi occurance event. Also makes form less wide, which also looks better.
|
||||||
layout === BookerLayouts.MONTH_VIEW &&
|
layout === BookerLayouts.MONTH_VIEW &&
|
||||||
bookerState === "booking" &&
|
bookerState === "booking" &&
|
||||||
"[--booker-main-width:420px] lg:[--booker-meta-width:340px]",
|
`[--booker-main-width:420px] ${getBookerMetaClass("lg:[--booker-meta-width:340px]")}`,
|
||||||
// Smaller meta when not in booking view.
|
// Smaller meta when not in booking view.
|
||||||
layout === BookerLayouts.MONTH_VIEW &&
|
layout === BookerLayouts.MONTH_VIEW &&
|
||||||
bookerState !== "booking" &&
|
bookerState !== "booking" &&
|
||||||
"[--booker-main-width:480px] lg:[--booker-meta-width:280px]",
|
`[--booker-main-width:480px] ${getBookerMetaClass("lg:[--booker-meta-width:280px]")}`,
|
||||||
// Fullscreen view settings.
|
// Fullscreen view settings.
|
||||||
layout !== BookerLayouts.MONTH_VIEW &&
|
layout !== BookerLayouts.MONTH_VIEW &&
|
||||||
"[--booker-main-width:480px] [--booker-meta-width:340px] lg:[--booker-meta-width:424px]",
|
`[--booker-main-width:480px] [--booker-meta-width:340px] ${getBookerMetaClass(
|
||||||
|
"lg:[--booker-meta-width:424px]"
|
||||||
|
)}`,
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ type StoreInitializeType = {
|
|||||||
rescheduleUid: string | null;
|
rescheduleUid: string | null;
|
||||||
rescheduleBooking: GetBookingType | null | undefined;
|
rescheduleBooking: GetBookingType | null | undefined;
|
||||||
layout: BookerLayout;
|
layout: BookerLayout;
|
||||||
|
isTeamEvent?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BookerStore = {
|
export type BookerStore = {
|
||||||
@@ -89,6 +90,12 @@ export type BookerStore = {
|
|||||||
*/
|
*/
|
||||||
formValues: Record<string, any>;
|
formValues: Record<string, any>;
|
||||||
setFormValues: (values: Record<string, any>) => void;
|
setFormValues: (values: Record<string, any>) => void;
|
||||||
|
/**
|
||||||
|
* Force event being a team event, so we only query for team events instead
|
||||||
|
* of also include 'user' events and return the first event that matches with
|
||||||
|
* both the slug and the event slug.
|
||||||
|
*/
|
||||||
|
isTeamEvent: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,6 +144,7 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
|||||||
updateQueryParam("month", month ?? "");
|
updateQueryParam("month", month ?? "");
|
||||||
get().setSelectedDate(null);
|
get().setSelectedDate(null);
|
||||||
},
|
},
|
||||||
|
isTeamEvent: false,
|
||||||
initialize: ({
|
initialize: ({
|
||||||
username,
|
username,
|
||||||
eventSlug,
|
eventSlug,
|
||||||
@@ -145,6 +153,7 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
|||||||
rescheduleUid = null,
|
rescheduleUid = null,
|
||||||
rescheduleBooking = null,
|
rescheduleBooking = null,
|
||||||
layout,
|
layout,
|
||||||
|
isTeamEvent,
|
||||||
}: StoreInitializeType) => {
|
}: StoreInitializeType) => {
|
||||||
const selectedDateInStore = get().selectedDate;
|
const selectedDateInStore = get().selectedDate;
|
||||||
|
|
||||||
@@ -165,6 +174,7 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
|||||||
rescheduleUid,
|
rescheduleUid,
|
||||||
rescheduleBooking,
|
rescheduleBooking,
|
||||||
layout: layout || BookerLayouts.MONTH_VIEW,
|
layout: layout || BookerLayouts.MONTH_VIEW,
|
||||||
|
isTeamEvent: isTeamEvent || false,
|
||||||
// Preselect today's date in week / column view, since they use this to show the week title.
|
// Preselect today's date in week / column view, since they use this to show the week title.
|
||||||
selectedDate:
|
selectedDate:
|
||||||
selectedDateInStore ||
|
selectedDateInStore ||
|
||||||
@@ -207,9 +217,29 @@ export const useInitializeBookerStore = ({
|
|||||||
rescheduleUid = null,
|
rescheduleUid = null,
|
||||||
rescheduleBooking = null,
|
rescheduleBooking = null,
|
||||||
layout,
|
layout,
|
||||||
|
isTeamEvent,
|
||||||
}: StoreInitializeType) => {
|
}: StoreInitializeType) => {
|
||||||
const initializeStore = useBookerStore((state) => state.initialize);
|
const initializeStore = useBookerStore((state) => state.initialize);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeStore({ username, eventSlug, month, eventId, rescheduleUid, rescheduleBooking, layout });
|
initializeStore({
|
||||||
}, [initializeStore, username, eventSlug, month, eventId, rescheduleUid, rescheduleBooking, layout]);
|
username,
|
||||||
|
eventSlug,
|
||||||
|
month,
|
||||||
|
eventId,
|
||||||
|
rescheduleUid,
|
||||||
|
rescheduleBooking,
|
||||||
|
layout,
|
||||||
|
isTeamEvent,
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
initializeStore,
|
||||||
|
username,
|
||||||
|
eventSlug,
|
||||||
|
month,
|
||||||
|
eventId,
|
||||||
|
rescheduleUid,
|
||||||
|
rescheduleBooking,
|
||||||
|
layout,
|
||||||
|
isTeamEvent,
|
||||||
|
]);
|
||||||
};
|
};
|
||||||
@@ -41,6 +41,13 @@ export interface BookerProps {
|
|||||||
* within the atom (i.e. without a server side component).
|
* within the atom (i.e. without a server side component).
|
||||||
*/
|
*/
|
||||||
rescheduleBooking?: GetBookingType;
|
rescheduleBooking?: GetBookingType;
|
||||||
|
/**
|
||||||
|
* If this boolean is passed, we will only check team events with this slug and event slug.
|
||||||
|
* If it's not passed, we will first query a generic user event, and only if that doesn't exist
|
||||||
|
* fetch the team event. In case there's both a team + user with the same slug AND same event slug,
|
||||||
|
* that will always result in the user event being returned.
|
||||||
|
*/
|
||||||
|
isTeamEvent?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BookerState = "loading" | "selecting_date" | "selecting_time" | "booking";
|
export type BookerState = "loading" | "selecting_date" | "selecting_time" | "booking";
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ import { useBookerStore } from "../store";
|
|||||||
*/
|
*/
|
||||||
export const useEvent = () => {
|
export const useEvent = () => {
|
||||||
const [username, eventSlug] = useBookerStore((state) => [state.username, state.eventSlug], shallow);
|
const [username, eventSlug] = useBookerStore((state) => [state.username, state.eventSlug], shallow);
|
||||||
|
const isTeamEvent = useBookerStore((state) => state.isTeamEvent);
|
||||||
|
|
||||||
return trpc.viewer.public.event.useQuery(
|
return trpc.viewer.public.event.useQuery(
|
||||||
{ username: username ?? "", eventSlug: eventSlug ?? "" },
|
{ username: username ?? "", eventSlug: eventSlug ?? "", isTeamEvent },
|
||||||
{ refetchOnWindowFocus: false, enabled: Boolean(username) && Boolean(eventSlug) }
|
{ refetchOnWindowFocus: false, enabled: Boolean(username) && Boolean(eventSlug) }
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import type { Session } from "next-auth";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { UserPermissionRole } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
|
import {
|
||||||
|
parseTeamId,
|
||||||
|
checkSelfImpersonation,
|
||||||
|
checkUserIdentifier,
|
||||||
|
checkPermission,
|
||||||
|
} from "./ImpersonationProvider";
|
||||||
|
|
||||||
|
const session: Session = {
|
||||||
|
expires: "2021-08-31T15:00:00.000Z",
|
||||||
|
hasValidLicense: true,
|
||||||
|
user: {
|
||||||
|
id: 123,
|
||||||
|
username: "test",
|
||||||
|
role: UserPermissionRole.USER,
|
||||||
|
email: "test@example.com",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("parseTeamId", () => {
|
||||||
|
it("should return undefined if no teamId is provided", () => {
|
||||||
|
expect(parseTeamId(undefined)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the parsed teamId if a teamId is provided", () => {
|
||||||
|
expect(parseTeamId({ username: "test", teamId: "123" })).toBe(123);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error if the provided teamId is not a positive number", () => {
|
||||||
|
expect(() => parseTeamId({ username: "test", teamId: "-123" })).toThrow();
|
||||||
|
});
|
||||||
|
it("should throw an error if the provided teamId is not a number", () => {
|
||||||
|
expect(() => parseTeamId({ username: "test", teamId: "notanumber" })).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("checkSelfImpersonation", () => {
|
||||||
|
it("should throw an error if the provided username is the same as the session user's username", () => {
|
||||||
|
expect(() => checkSelfImpersonation(session, { username: "test" })).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error if the provided username is the same as the session user's email", () => {
|
||||||
|
expect(() => checkSelfImpersonation(session, { username: "test@example.com" })).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not throw an error if the provided username is different from the session user's username and email", () => {
|
||||||
|
expect(() => checkSelfImpersonation(session, { username: "other" })).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("checkUserIdentifier", () => {
|
||||||
|
it("should throw an error if no username is provided", () => {
|
||||||
|
expect(() => checkUserIdentifier(undefined)).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not throw an error if a username is provided", () => {
|
||||||
|
expect(() => checkUserIdentifier({ username: "test" })).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("checkPermission", () => {
|
||||||
|
it("should throw an error if the user is not an admin and team impersonation is disabled", () => {
|
||||||
|
process.env.NEXT_PUBLIC_TEAM_IMPERSONATION = "false";
|
||||||
|
expect(() => checkPermission(session)).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not throw an error if the user is an admin and team impersonation is disabled", () => {
|
||||||
|
const modifiedSession = { ...session, user: { ...session.user, role: UserPermissionRole.ADMIN } };
|
||||||
|
process.env.NEXT_PUBLIC_TEAM_IMPERSONATION = "false";
|
||||||
|
expect(() => checkPermission(modifiedSession)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not throw an error if the user is not an admin but team impersonation is enabled", () => {
|
||||||
|
process.env.NEXT_PUBLIC_TEAM_IMPERSONATION = "true";
|
||||||
|
expect(() => checkPermission(session)).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { User } from "@prisma/client";
|
import type { User } from "@prisma/client";
|
||||||
|
import type { Session } from "next-auth";
|
||||||
import CredentialsProvider from "next-auth/providers/credentials";
|
import CredentialsProvider from "next-auth/providers/credentials";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -44,6 +45,28 @@ const auditAndReturnNextUser = async (
|
|||||||
return obj;
|
return obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Credentials = Record<"username" | "teamId", string> | undefined;
|
||||||
|
|
||||||
|
export function parseTeamId(creds: Partial<Credentials>) {
|
||||||
|
return creds?.teamId ? teamIdschema.parse({ teamId: creds.teamId }).teamId : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkSelfImpersonation(session: Session | null, creds: Partial<Credentials>) {
|
||||||
|
if (session?.user.username === creds?.username || session?.user.email === creds?.username) {
|
||||||
|
throw new Error("You cannot impersonate yourself.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkUserIdentifier(creds: Partial<Credentials>) {
|
||||||
|
if (!creds?.username) throw new Error("User identifier must be present");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkPermission(session: Session | null) {
|
||||||
|
if (session?.user.role !== "ADMIN" && process.env.NEXT_PUBLIC_TEAM_IMPERSONATION === "false") {
|
||||||
|
throw new Error("You do not have permission to do this.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ImpersonationProvider = CredentialsProvider({
|
const ImpersonationProvider = CredentialsProvider({
|
||||||
id: "impersonation-auth",
|
id: "impersonation-auth",
|
||||||
name: "Impersonation",
|
name: "Impersonation",
|
||||||
@@ -56,18 +79,10 @@ const ImpersonationProvider = CredentialsProvider({
|
|||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore need to figure out how to correctly type this
|
// @ts-ignore need to figure out how to correctly type this
|
||||||
const session = await getSession({ req });
|
const session = await getSession({ req });
|
||||||
// If teamId is present -> parse the teamId and throw error itn ot number. If not present teamId is set to undefined
|
const teamId = parseTeamId(creds);
|
||||||
const teamId = creds?.teamId ? teamIdschema.parse({ teamId: creds.teamId }).teamId : undefined;
|
checkSelfImpersonation(session, creds);
|
||||||
|
checkUserIdentifier(creds);
|
||||||
if (session?.user.username === creds?.username || session?.user.email === creds?.username) {
|
checkPermission(session);
|
||||||
throw new Error("You cannot impersonate yourself.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!creds?.username) throw new Error("User identifier must be present");
|
|
||||||
// If you are an ADMIN we return way before team impersonation logic is executed, so NEXT_PUBLIC_TEAM_IMPERSONATION certainly true
|
|
||||||
if (session?.user.role !== "ADMIN" && process.env.NEXT_PUBLIC_TEAM_IMPERSONATION === "false") {
|
|
||||||
throw new Error("You do not have permission to do this.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user who is being impersonated
|
// Get user who is being impersonated
|
||||||
const impersonatedUser = await prisma.user.findFirst({
|
const impersonatedUser = await prisma.user.findFirst({
|
||||||
|
|||||||
@@ -1,118 +0,0 @@
|
|||||||
import { useSession } from "next-auth/react";
|
|
||||||
import type { ReactNode, InputHTMLAttributes } from "react";
|
|
||||||
import { useState, forwardRef, Fragment } from "react";
|
|
||||||
|
|
||||||
import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery";
|
|
||||||
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
||||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
|
||||||
import { trpc } from "@calcom/trpc/react";
|
|
||||||
import { AnimatedPopover, Avatar } from "@calcom/ui";
|
|
||||||
import { Layers, User } from "@calcom/ui/components/icon";
|
|
||||||
|
|
||||||
export type IEventTypesFilters = RouterOutputs["viewer"]["eventTypes"]["listWithTeam"];
|
|
||||||
export type IEventTypeFilter = IEventTypesFilters[0];
|
|
||||||
|
|
||||||
export const OrganizationEventTypeFilter = () => {
|
|
||||||
const { t } = useLocale();
|
|
||||||
const session = useSession();
|
|
||||||
const { data: query, pushItemToKey, removeItemByKeyAndValue, removeAllQueryParams } = useFilterQuery();
|
|
||||||
const [dropdownTitle, setDropdownTitle] = useState<string>(t("all_apps"));
|
|
||||||
|
|
||||||
const { data: teams, status } = trpc.viewer.teams.list.useQuery();
|
|
||||||
const isNotEmpty = !!teams?.length;
|
|
||||||
|
|
||||||
return status === "success" ? (
|
|
||||||
<AnimatedPopover text={dropdownTitle} popoverTriggerClassNames="!mb-0">
|
|
||||||
<CheckboxFieldContainer>
|
|
||||||
<CheckboxField
|
|
||||||
id="all-eventtypes-checkbox"
|
|
||||||
icon={<Layers className="h-4 w-4" />}
|
|
||||||
checked={dropdownTitle === t("all_apps")}
|
|
||||||
onChange={(e) => {
|
|
||||||
removeAllQueryParams();
|
|
||||||
setDropdownTitle(t("all_apps"));
|
|
||||||
// TODO: What to do when all event types is unchecked
|
|
||||||
}}
|
|
||||||
label={t("all_apps")}
|
|
||||||
/>
|
|
||||||
</CheckboxFieldContainer>
|
|
||||||
<CheckboxFieldContainer>
|
|
||||||
<CheckboxField
|
|
||||||
id="all-eventtypes-checkbox"
|
|
||||||
icon={<User className="h-4 w-4" />}
|
|
||||||
checked={query.userIds?.includes(session.data?.user.id || 0)}
|
|
||||||
onChange={(e) => {
|
|
||||||
setDropdownTitle(t("yours"));
|
|
||||||
if (e.target.checked) {
|
|
||||||
pushItemToKey("userIds", session.data?.user.id || 0);
|
|
||||||
} else if (!e.target.checked) {
|
|
||||||
removeItemByKeyAndValue("userIds", session.data?.user.id || 0);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
label={t("yours")}
|
|
||||||
/>
|
|
||||||
</CheckboxFieldContainer>
|
|
||||||
|
|
||||||
{isNotEmpty && (
|
|
||||||
<Fragment>
|
|
||||||
<div className="text-subtle px-4 py-2.5 text-xs font-medium uppercase leading-none">TEAMS</div>
|
|
||||||
{teams?.map((team) => (
|
|
||||||
<CheckboxFieldContainer key={team.id}>
|
|
||||||
<CheckboxField
|
|
||||||
id={team.name}
|
|
||||||
label={team.name}
|
|
||||||
icon={
|
|
||||||
<Avatar
|
|
||||||
alt={team?.name}
|
|
||||||
imageSrc={getPlaceholderAvatar(team.logo, team?.name as string)}
|
|
||||||
size="xs"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
checked={query.teamIds?.includes(team.id)}
|
|
||||||
onChange={(e) => {
|
|
||||||
setDropdownTitle(team.name);
|
|
||||||
if (e.target.checked) {
|
|
||||||
pushItemToKey("teamIds", team.id);
|
|
||||||
} else if (!e.target.checked) {
|
|
||||||
removeItemByKeyAndValue("teamIds", team.id);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</CheckboxFieldContainer>
|
|
||||||
))}
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</AnimatedPopover>
|
|
||||||
) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = InputHTMLAttributes<HTMLInputElement> & {
|
|
||||||
label: string;
|
|
||||||
icon: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const CheckboxField = forwardRef<HTMLInputElement, Props>(({ label, icon, ...rest }, ref) => {
|
|
||||||
return (
|
|
||||||
<label className="flex w-full items-center justify-between">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className="text-default flex h-6 w-6 items-center justify-center ltr:mr-2 rtl:ml-2">{icon}</div>
|
|
||||||
<span className="text-sm">{label}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex h-5 items-center">
|
|
||||||
<input
|
|
||||||
{...rest}
|
|
||||||
ref={ref}
|
|
||||||
type="checkbox"
|
|
||||||
className="text-primary-600 focus:ring-primary-500 border-default bg-default h-4 w-4 rounded hover:cursor-pointer"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const CheckboxFieldContainer = ({ children }: { children: ReactNode }) => {
|
|
||||||
return <div className="flex items-center px-3 py-2">{children}</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckboxField.displayName = "CheckboxField";
|
|
||||||
@@ -7,7 +7,7 @@ import { getAppFromSlug } from "@calcom/app-store/utils";
|
|||||||
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
|
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
|
||||||
import { isRecurringEvent, parseRecurringEvent } from "@calcom/lib";
|
import { isRecurringEvent, parseRecurringEvent } from "@calcom/lib";
|
||||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
|
import { getDefaultEvent, getUsernameList } from "@calcom/lib/defaultEvents";
|
||||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||||
import type { PrismaClient } from "@calcom/prisma/client";
|
import type { PrismaClient } from "@calcom/prisma/client";
|
||||||
import type { BookerLayoutSettings } from "@calcom/prisma/zod-utils";
|
import type { BookerLayoutSettings } from "@calcom/prisma/zod-utils";
|
||||||
@@ -71,13 +71,20 @@ const publicEventSelect = Prisma.validator<Prisma.EventTypeSelect>()({
|
|||||||
name: true,
|
name: true,
|
||||||
theme: true,
|
theme: true,
|
||||||
metadata: true,
|
metadata: true,
|
||||||
|
brandColor: true,
|
||||||
|
darkBrandColor: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
hidden: true,
|
hidden: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getPublicEvent = async (username: string, eventSlug: string, prisma: PrismaClient) => {
|
export const getPublicEvent = async (
|
||||||
const usernameList = username.split("+");
|
username: string,
|
||||||
|
eventSlug: string,
|
||||||
|
isTeamEvent: boolean | undefined,
|
||||||
|
prisma: PrismaClient
|
||||||
|
) => {
|
||||||
|
const usernameList = getUsernameList(username);
|
||||||
|
|
||||||
// In case of dynamic group event, we fetch user's data and use the default event.
|
// In case of dynamic group event, we fetch user's data and use the default event.
|
||||||
if (usernameList.length > 1) {
|
if (usernameList.length > 1) {
|
||||||
@@ -141,24 +148,26 @@ export const getPublicEvent = async (username: string, eventSlug: string, prisma
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case it's not a group event, it's either a single user or a team, and we query that data.
|
const usersOrTeamQuery = isTeamEvent
|
||||||
const event = await prisma.eventType.findFirst({
|
? {
|
||||||
where: {
|
team: {
|
||||||
slug: eventSlug,
|
slug: username,
|
||||||
OR: [
|
},
|
||||||
{
|
}
|
||||||
|
: {
|
||||||
users: {
|
users: {
|
||||||
some: {
|
some: {
|
||||||
username,
|
username,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
team: null,
|
||||||
{
|
};
|
||||||
team: {
|
|
||||||
slug: username,
|
// In case it's not a group event, it's either a single user or a team, and we query that data.
|
||||||
},
|
const event = await prisma.eventType.findFirst({
|
||||||
},
|
where: {
|
||||||
],
|
slug: eventSlug,
|
||||||
|
...usersOrTeamQuery,
|
||||||
},
|
},
|
||||||
select: publicEventSelect,
|
select: publicEventSelect,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
|
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||||
import { AnimatedPopover, Avatar, Divider } from "@calcom/ui";
|
import { AnimatedPopover, Avatar, Divider, VerticalDivider } from "@calcom/ui";
|
||||||
import { Layers, User } from "@calcom/ui/components/icon";
|
import { Layers, User } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import { filterQuerySchema } from "../lib/getTeamsFiltersFromQuery";
|
import { filterQuerySchema } from "../lib/getTeamsFiltersFromQuery";
|
||||||
@@ -20,7 +20,13 @@ function useFilterQuery() {
|
|||||||
return useTypedQuery(filterQuerySchema.passthrough());
|
return useTypedQuery(filterQuerySchema.passthrough());
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TeamsFilter = () => {
|
export const TeamsFilter = ({
|
||||||
|
popoverTriggerClassNames,
|
||||||
|
showVerticalDivider = false,
|
||||||
|
}: {
|
||||||
|
popoverTriggerClassNames?: string;
|
||||||
|
showVerticalDivider?: boolean;
|
||||||
|
}) => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const { data: query, pushItemToKey, removeItemByKeyAndValue, removeAllQueryParams } = useFilterQuery();
|
const { data: query, pushItemToKey, removeItemByKeyAndValue, removeAllQueryParams } = useFilterQuery();
|
||||||
@@ -40,7 +46,7 @@ export const TeamsFilter = () => {
|
|||||||
if (selectedTeamsNames) {
|
if (selectedTeamsNames) {
|
||||||
checkedOptions.push(...selectedTeamsNames);
|
checkedOptions.push(...selectedTeamsNames);
|
||||||
}
|
}
|
||||||
return checkedOptions.join(",");
|
return `${t("team")}: ${checkedOptions.join(",")}`;
|
||||||
}
|
}
|
||||||
if (query.userIds) {
|
if (query.userIds) {
|
||||||
return t("yours");
|
return t("yours");
|
||||||
@@ -51,7 +57,8 @@ export const TeamsFilter = () => {
|
|||||||
if (!teams || !teams.length) return null;
|
if (!teams || !teams.length) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatedPopover text={getCheckedOptionsNames()}>
|
<div className="flex items-center">
|
||||||
|
<AnimatedPopover text={getCheckedOptionsNames()} popoverTriggerClassNames={popoverTriggerClassNames}>
|
||||||
<FilterCheckboxFieldsContainer>
|
<FilterCheckboxFieldsContainer>
|
||||||
<FilterCheckboxField
|
<FilterCheckboxField
|
||||||
id="all"
|
id="all"
|
||||||
@@ -101,6 +108,8 @@ export const TeamsFilter = () => {
|
|||||||
))}
|
))}
|
||||||
</FilterCheckboxFieldsContainer>
|
</FilterCheckboxFieldsContainer>
|
||||||
</AnimatedPopover>
|
</AnimatedPopover>
|
||||||
|
{showVerticalDivider && <VerticalDivider />}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
|
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
|
|
||||||
type UseScheduleWithCacheArgs = {
|
type UseScheduleWithCacheArgs = {
|
||||||
@@ -22,13 +23,12 @@ export const useSchedule = ({
|
|||||||
}: UseScheduleWithCacheArgs) => {
|
}: UseScheduleWithCacheArgs) => {
|
||||||
const monthDayjs = month ? dayjs(month) : dayjs();
|
const monthDayjs = month ? dayjs(month) : dayjs();
|
||||||
const nextMonthDayjs = monthDayjs.add(1, "month");
|
const nextMonthDayjs = monthDayjs.add(1, "month");
|
||||||
|
|
||||||
// Why the non-null assertions? All of these arguments are checked in the enabled condition,
|
// Why the non-null assertions? All of these arguments are checked in the enabled condition,
|
||||||
// and the query will not run if they are null. However, the check in `enabled` does
|
// and the query will not run if they are null. However, the check in `enabled` does
|
||||||
// no satisfy typscript.
|
// no satisfy typscript.
|
||||||
return trpc.viewer.public.slots.getSchedule.useQuery(
|
return trpc.viewer.public.slots.getSchedule.useQuery(
|
||||||
{
|
{
|
||||||
usernameList: username && username.indexOf("+") > -1 ? username.split("+") : [username!],
|
usernameList: getUsernameList(username ?? ""),
|
||||||
eventTypeSlug: eventSlug!,
|
eventTypeSlug: eventSlug!,
|
||||||
// @TODO: Old code fetched 2 days ago if we were fetching the current month.
|
// @TODO: Old code fetched 2 days ago if we were fetching the current month.
|
||||||
// Do we want / need to keep that behavior?
|
// Do we want / need to keep that behavior?
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ type BookerLayoutSelectorProps = {
|
|||||||
* settings.
|
* settings.
|
||||||
*/
|
*/
|
||||||
fallbackToUserSettings?: boolean;
|
fallbackToUserSettings?: boolean;
|
||||||
|
/**
|
||||||
|
* isDark boolean should be passed in when the user selected 'dark mode' in the theme settings in profile/appearance.
|
||||||
|
* So it's not based on the user's system settings, but on the user's preference for the booker.
|
||||||
|
* This boolean is then used to show a dark version of the layout image. It's only easthetic, no functionality is attached
|
||||||
|
* to this boolean.
|
||||||
|
*/
|
||||||
|
isDark?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultFieldName = "metadata.bookerLayouts";
|
const defaultFieldName = "metadata.bookerLayouts";
|
||||||
@@ -32,6 +39,7 @@ export const BookerLayoutSelector = ({
|
|||||||
description,
|
description,
|
||||||
name,
|
name,
|
||||||
fallbackToUserSettings,
|
fallbackToUserSettings,
|
||||||
|
isDark,
|
||||||
}: BookerLayoutSelectorProps) => {
|
}: BookerLayoutSelectorProps) => {
|
||||||
const { control, getValues } = useFormContext();
|
const { control, getValues } = useFormContext();
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
@@ -58,6 +66,7 @@ export const BookerLayoutSelector = ({
|
|||||||
showUserSettings={shouldShowUserSettings}
|
showUserSettings={shouldShowUserSettings}
|
||||||
settings={value}
|
settings={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
isDark={isDark}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -69,11 +78,12 @@ type BookerLayoutFieldsProps = {
|
|||||||
settings: BookerLayoutSettings;
|
settings: BookerLayoutSettings;
|
||||||
onChange: (settings: BookerLayoutSettings) => void;
|
onChange: (settings: BookerLayoutSettings) => void;
|
||||||
showUserSettings: boolean;
|
showUserSettings: boolean;
|
||||||
|
isDark?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type BookerLayoutState = { [key in BookerLayouts]: boolean };
|
type BookerLayoutState = { [key in BookerLayouts]: boolean };
|
||||||
|
|
||||||
const BookerLayoutFields = ({ settings, onChange, showUserSettings }: BookerLayoutFieldsProps) => {
|
const BookerLayoutFields = ({ settings, onChange, showUserSettings, isDark }: BookerLayoutFieldsProps) => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const { isLoading: isUserLoading, data: user } = useMeQuery();
|
const { isLoading: isUserLoading, data: user } = useMeQuery();
|
||||||
const [isOverridingSettings, setIsOverridingSettings] = useState(false);
|
const [isOverridingSettings, setIsOverridingSettings] = useState(false);
|
||||||
@@ -143,7 +153,7 @@ const BookerLayoutFields = ({ settings, onChange, showUserSettings }: BookerLayo
|
|||||||
<label>
|
<label>
|
||||||
<img
|
<img
|
||||||
className="mb-3 w-full max-w-none cursor-pointer"
|
className="mb-3 w-full max-w-none cursor-pointer"
|
||||||
src={`/bookerlayout_${layout}.svg`}
|
src={`/bookerlayout_${layout}${isDark ? "_dark" : ""}.svg`}
|
||||||
alt="Layout preview"
|
alt="Layout preview"
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|||||||
@@ -24,9 +24,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@calcom/lib": "*",
|
"@calcom/lib": "*",
|
||||||
"@prisma/client": "^4.13.0",
|
"@prisma/client": "^4.16.0",
|
||||||
"@prisma/generator-helper": "^4.13.0",
|
"@prisma/generator-helper": "^4.13.0",
|
||||||
"prisma": "^4.13.0",
|
"prisma": "^4.16.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"zod": "^3.20.2",
|
"zod": "^3.20.2",
|
||||||
"zod-prisma": "^0.5.4"
|
"zod-prisma": "^0.5.4"
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ interface EventHandlerOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const eventHandler = async ({ ctx, input }: EventHandlerOptions) => {
|
export const eventHandler = async ({ ctx, input }: EventHandlerOptions) => {
|
||||||
const event = await getPublicEvent(input.username, input.eventSlug, ctx.prisma);
|
const event = await getPublicEvent(input.username, input.eventSlug, input.isTeamEvent, ctx.prisma);
|
||||||
return event;
|
return event;
|
||||||
};
|
};
|
||||||
@@ -3,6 +3,7 @@ import z from "zod";
|
|||||||
export const ZEventInputSchema = z.object({
|
export const ZEventInputSchema = z.object({
|
||||||
username: z.string(),
|
username: z.string(),
|
||||||
eventSlug: z.string(),
|
eventSlug: z.string(),
|
||||||
|
isTeamEvent: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TEventInputSchema = z.infer<typeof ZEventInputSchema>;
|
export type TEventInputSchema = z.infer<typeof ZEventInputSchema>;
|
||||||
@@ -8,6 +8,7 @@ import { ZCreateInputSchema } from "./create.schema";
|
|||||||
import { ZDeleteInputSchema } from "./delete.schema";
|
import { ZDeleteInputSchema } from "./delete.schema";
|
||||||
import { ZDuplicateInputSchema } from "./duplicate.schema";
|
import { ZDuplicateInputSchema } from "./duplicate.schema";
|
||||||
import { ZGetInputSchema } from "./get.schema";
|
import { ZGetInputSchema } from "./get.schema";
|
||||||
|
import { ZEventTypeInputSchema } from "./getByViewer.schema";
|
||||||
import { ZUpdateInputSchema } from "./update.schema";
|
import { ZUpdateInputSchema } from "./update.schema";
|
||||||
import { eventOwnerProcedure } from "./util";
|
import { eventOwnerProcedure } from "./util";
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ const UNSTABLE_HANDLER_CACHE: BookingsRouterHandlerCache = {};
|
|||||||
|
|
||||||
export const eventTypesRouter = router({
|
export const eventTypesRouter = router({
|
||||||
// REVIEW: What should we name this procedure?
|
// REVIEW: What should we name this procedure?
|
||||||
getByViewer: authedProcedure.query(async ({ ctx }) => {
|
getByViewer: authedProcedure.input(ZEventTypeInputSchema).query(async ({ ctx, input }) => {
|
||||||
if (!UNSTABLE_HANDLER_CACHE.getByViewer) {
|
if (!UNSTABLE_HANDLER_CACHE.getByViewer) {
|
||||||
UNSTABLE_HANDLER_CACHE.getByViewer = await import("./getByViewer.handler").then(
|
UNSTABLE_HANDLER_CACHE.getByViewer = await import("./getByViewer.handler").then(
|
||||||
(mod) => mod.getByViewerHandler
|
(mod) => mod.getByViewerHandler
|
||||||
@@ -44,6 +45,7 @@ export const eventTypesRouter = router({
|
|||||||
|
|
||||||
const result = await UNSTABLE_HANDLER_CACHE.getByViewer({
|
const result = await UNSTABLE_HANDLER_CACHE.getByViewer({
|
||||||
ctx,
|
ctx,
|
||||||
|
input,
|
||||||
});
|
});
|
||||||
|
|
||||||
timer();
|
timer();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { type PrismaClient, Prisma } from "@prisma/client";
|
import { type PrismaClient, Prisma } from "@prisma/client";
|
||||||
import { orderBy } from "lodash";
|
import { orderBy } from "lodash";
|
||||||
|
|
||||||
|
import { hasFilter } from "@calcom/features/filters/lib/hasFilter";
|
||||||
import { CAL_URL } from "@calcom/lib/constants";
|
import { CAL_URL } from "@calcom/lib/constants";
|
||||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||||
import { baseEventTypeSelect, baseUserSelect } from "@calcom/prisma";
|
import { baseEventTypeSelect, baseUserSelect } from "@calcom/prisma";
|
||||||
@@ -11,16 +12,16 @@ import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
|
|
||||||
import type { TrpcSessionUser } from "../../../trpc";
|
import type { TrpcSessionUser } from "../../../trpc";
|
||||||
|
import type { TEventTypeInputSchema } from "./getByViewer.schema";
|
||||||
|
|
||||||
type GetByViewerOptions = {
|
type GetByViewerOptions = {
|
||||||
ctx: {
|
ctx: {
|
||||||
user: NonNullable<TrpcSessionUser>;
|
user: NonNullable<TrpcSessionUser>;
|
||||||
prisma: PrismaClient;
|
prisma: PrismaClient;
|
||||||
};
|
};
|
||||||
|
input: TEventTypeInputSchema;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
|
|
||||||
const { prisma } = ctx;
|
|
||||||
const eventTypeSelect = Prisma.validator<Prisma.EventTypeSelect>()({
|
const eventTypeSelect = Prisma.validator<Prisma.EventTypeSelect>()({
|
||||||
// Position is required by lodash to sort on it. Don't remove it, TS won't complain but it would silently break reordering
|
// Position is required by lodash to sort on it. Don't remove it, TS won't complain but it would silently break reordering
|
||||||
position: true,
|
position: true,
|
||||||
@@ -44,7 +45,9 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
|
|||||||
},
|
},
|
||||||
children: {
|
children: {
|
||||||
include: {
|
include: {
|
||||||
users: true,
|
users: {
|
||||||
|
select: baseUserSelect,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
parentId: true,
|
parentId: true,
|
||||||
@@ -59,6 +62,9 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
|
|||||||
...baseEventTypeSelect,
|
...baseEventTypeSelect,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) => {
|
||||||
|
const { prisma } = ctx;
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: ctx.user.id,
|
id: ctx.user.id,
|
||||||
@@ -107,6 +113,7 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
|
|||||||
eventTypes: {
|
eventTypes: {
|
||||||
where: {
|
where: {
|
||||||
team: null,
|
team: null,
|
||||||
|
userId: getPrismaWhereUserIdFromFilter(ctx.user.id, input?.filters),
|
||||||
},
|
},
|
||||||
select: eventTypeSelect,
|
select: eventTypeSelect,
|
||||||
orderBy: [
|
orderBy: [
|
||||||
@@ -137,7 +144,8 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
|
|||||||
const typesRaw = (
|
const typesRaw = (
|
||||||
await prisma.eventType.findMany({
|
await prisma.eventType.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId: ctx.user.id,
|
userId: getPrismaWhereUserIdFromFilter(ctx.user.id, input?.filters),
|
||||||
|
team: null,
|
||||||
},
|
},
|
||||||
select: eventTypeSelect,
|
select: eventTypeSelect,
|
||||||
orderBy: [
|
orderBy: [
|
||||||
@@ -167,11 +175,13 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let eventTypeGroups: EventTypeGroup[] = [];
|
let eventTypeGroups: EventTypeGroup[] = [];
|
||||||
|
|
||||||
const eventTypesHashMap = userEventTypes.concat(typesRaw).reduce((hashMap, newItem) => {
|
const eventTypesHashMap = userEventTypes.concat(typesRaw).reduce((hashMap, newItem) => {
|
||||||
const oldItem = hashMap[newItem.id];
|
const oldItem = hashMap[newItem.id];
|
||||||
hashMap[newItem.id] = { ...oldItem, ...newItem };
|
hashMap[newItem.id] = { ...oldItem, ...newItem };
|
||||||
return hashMap;
|
return hashMap;
|
||||||
}, {} as Record<number, EventTypeGroup["eventTypes"][number]>);
|
}, {} as Record<number, EventTypeGroup["eventTypes"][number]>);
|
||||||
|
|
||||||
const mergedEventTypes = Object.values(eventTypesHashMap)
|
const mergedEventTypes = Object.values(eventTypesHashMap)
|
||||||
.map((eventType) => eventType)
|
.map((eventType) => eventType)
|
||||||
.filter((evType) => evType.schedulingType !== SchedulingType.MANAGED);
|
.filter((evType) => evType.schedulingType !== SchedulingType.MANAGED);
|
||||||
@@ -204,6 +214,13 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
|
|||||||
return mshipToNumber(mship1) > mshipToNumber(mship2);
|
return mshipToNumber(mship1) > mshipToNumber(mship2);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filterTeamsEventTypesBasedOnInput = (eventType: ReturnType<typeof mapEventType>) => {
|
||||||
|
if (!input?.filters || !hasFilter(input?.filters)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return input?.filters?.teamIds?.includes(eventType?.team?.id || 0) ?? false;
|
||||||
|
};
|
||||||
|
|
||||||
eventTypeGroups = ([] as EventTypeGroup[]).concat(
|
eventTypeGroups = ([] as EventTypeGroup[]).concat(
|
||||||
eventTypeGroups,
|
eventTypeGroups,
|
||||||
user.teams
|
user.teams
|
||||||
@@ -242,6 +259,7 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
|
|||||||
},
|
},
|
||||||
eventTypes: membership.team.eventTypes
|
eventTypes: membership.team.eventTypes
|
||||||
.map(mapEventType)
|
.map(mapEventType)
|
||||||
|
.filter(filterTeamsEventTypesBasedOnInput)
|
||||||
.filter((evType) => evType.userId === null || evType.userId === ctx.user.id)
|
.filter((evType) => evType.userId === null || evType.userId === ctx.user.id)
|
||||||
.filter((evType) =>
|
.filter((evType) =>
|
||||||
membership.role === MembershipRole.MEMBER
|
membership.role === MembershipRole.MEMBER
|
||||||
@@ -251,6 +269,7 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// don't display event teams without event types,
|
// don't display event teams without event types,
|
||||||
eventTypeGroups: eventTypeGroups.filter((groupBy) => !!groupBy.eventTypes?.length),
|
eventTypeGroups: eventTypeGroups.filter((groupBy) => !!groupBy.eventTypes?.length),
|
||||||
@@ -264,3 +283,15 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
|
|||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getPrismaWhereUserIdFromFilter(
|
||||||
|
userId: number,
|
||||||
|
filters: NonNullable<TEventTypeInputSchema>["filters"]
|
||||||
|
) {
|
||||||
|
if (!filters || !hasFilter(filters)) {
|
||||||
|
return userId;
|
||||||
|
} else if (filters.userIds?.[0] === userId) {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -1 +1,15 @@
|
|||||||
export {};
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const filterQuerySchemaStrict = z.object({
|
||||||
|
teamIds: z.number().array().optional(),
|
||||||
|
// A user can only filter by only his userId
|
||||||
|
userIds: z.number().array().max(1).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZEventTypeInputSchema = z
|
||||||
|
.object({
|
||||||
|
filters: filterQuerySchemaStrict.optional(),
|
||||||
|
})
|
||||||
|
.nullish();
|
||||||
|
|
||||||
|
export type TEventTypeInputSchema = z.infer<typeof ZEventTypeInputSchema>;
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import { subdomainSuffix } from "@calcom/ee/organizations/lib/orgDomains";
|
||||||
import { cancelTeamSubscriptionFromStripe } from "@calcom/features/ee/teams/lib/payments";
|
import { cancelTeamSubscriptionFromStripe } from "@calcom/features/ee/teams/lib/payments";
|
||||||
import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
|
import { IS_PRODUCTION, IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
|
||||||
import { isTeamOwner } from "@calcom/lib/server/queries/teams";
|
import { isTeamOwner } from "@calcom/lib/server/queries/teams";
|
||||||
import { closeComDeleteTeam } from "@calcom/lib/sync/SyncServiceManager";
|
import { closeComDeleteTeam } from "@calcom/lib/sync/SyncServiceManager";
|
||||||
import { prisma } from "@calcom/prisma";
|
import { prisma } from "@calcom/prisma";
|
||||||
|
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||||
|
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
|
|
||||||
@@ -16,6 +18,41 @@ type DeleteOptions = {
|
|||||||
input: TDeleteInputSchema;
|
input: TDeleteInputSchema;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteVercelDomain = async ({
|
||||||
|
slug,
|
||||||
|
isOrganization,
|
||||||
|
}: {
|
||||||
|
slug?: string | null;
|
||||||
|
isOrganization?: boolean | null;
|
||||||
|
}) => {
|
||||||
|
if (!isOrganization || !slug) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullDomain = `${slug}.${subdomainSuffix()}`;
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains/${fullDomain}?teamId=${process.env.TEAM_ID_VERCEL}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`,
|
||||||
|
},
|
||||||
|
method: "DELETE",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Domain is already owned by another team but you can request delegation to access it
|
||||||
|
if (data.error?.code === "forbidden")
|
||||||
|
throw new TRPCError({ code: "CONFLICT", message: "domain_taken_team" });
|
||||||
|
|
||||||
|
// Domain is already being used by a different project
|
||||||
|
if (data.error?.code === "domain_taken")
|
||||||
|
throw new TRPCError({ code: "CONFLICT", message: "domain_taken_project" });
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
export const deleteHandler = async ({ ctx, input }: DeleteOptions) => {
|
export const deleteHandler = async ({ ctx, input }: DeleteOptions) => {
|
||||||
if (!(await isTeamOwner(ctx.user?.id, input.teamId))) throw new TRPCError({ code: "UNAUTHORIZED" });
|
if (!(await isTeamOwner(ctx.user?.id, input.teamId))) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
|
||||||
@@ -34,6 +71,14 @@ export const deleteHandler = async ({ ctx, input }: DeleteOptions) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const deletedTeamMetadata = teamMetadataSchema.parse(deletedTeam.metadata);
|
||||||
|
|
||||||
|
if (IS_PRODUCTION)
|
||||||
|
deleteVercelDomain({
|
||||||
|
slug: deletedTeam.slug,
|
||||||
|
isOrganization: deletedTeamMetadata?.isOrganization,
|
||||||
|
});
|
||||||
|
|
||||||
// Sync Services: Close.cm
|
// Sync Services: Close.cm
|
||||||
closeComDeleteTeam(deletedTeam);
|
closeComDeleteTeam(deletedTeam);
|
||||||
};
|
};
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
// @TODO: turn this into a more generic component that has the same Props API as MUI https://mui.com/material-ui/react-card/
|
// @TODO: turn this into a more generic component that has the same Props API as MUI https://mui.com/material-ui/react-card/
|
||||||
|
import type { VariantProps } from "class-variance-authority";
|
||||||
|
import { cva } from "class-variance-authority";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@@ -8,10 +10,96 @@ import { ArrowRight } from "@calcom/ui/components/icon";
|
|||||||
|
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
|
|
||||||
export type BaseCardProps = {
|
const cvaCardTypeByVariant = cva("", {
|
||||||
|
// Variants won't have any style by default. Style will only be applied if the variants are combined.
|
||||||
|
// So, style is defined in compoundVariants.
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
basic: "",
|
||||||
|
ProfileCard: "",
|
||||||
|
SidebarCard: "",
|
||||||
|
},
|
||||||
|
structure: {
|
||||||
|
image: "",
|
||||||
|
card: "",
|
||||||
|
title: "",
|
||||||
|
description: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
compoundVariants: [
|
||||||
|
// Style for Basic Variants types
|
||||||
|
{
|
||||||
|
variant: "basic",
|
||||||
|
structure: "image",
|
||||||
|
className: "w-10 h-auto",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: "basic",
|
||||||
|
structure: "card",
|
||||||
|
className: "p-5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: "basic",
|
||||||
|
structure: "title",
|
||||||
|
className: "text-base mt-4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: "basic",
|
||||||
|
structure: "description",
|
||||||
|
className: "text-sm leading-[18px] text-subtle font-normal",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Style for ProfileCard Variant Types
|
||||||
|
{
|
||||||
|
variant: "ProfileCard",
|
||||||
|
structure: "image",
|
||||||
|
className: "w-9 h-auto rounded-full mb-4s",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: "ProfileCard",
|
||||||
|
structure: "card",
|
||||||
|
className: "w-80 p-4 hover:bg-subtle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: "ProfileCard",
|
||||||
|
structure: "title",
|
||||||
|
className: "text-base",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: "ProfileCard",
|
||||||
|
structure: "description",
|
||||||
|
className: "text-sm leading-[18px] text-subtle font-normal",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Style for SidebarCard Variant Types
|
||||||
|
{
|
||||||
|
variant: "SidebarCard",
|
||||||
|
structure: "image",
|
||||||
|
className: "w-9 h-auto rounded-full mb-4s",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: "SidebarCard",
|
||||||
|
structure: "card",
|
||||||
|
className: "w-full p-3 border border-subtle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: "SidebarCard",
|
||||||
|
structure: "title",
|
||||||
|
className: "text-sm font-cal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: "SidebarCard",
|
||||||
|
structure: "description",
|
||||||
|
className: "text-xs text-default line-clamp-2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
type CVACardType = Required<Pick<VariantProps<typeof cvaCardTypeByVariant>, "variant">>;
|
||||||
|
|
||||||
|
export interface BaseCardProps extends CVACardType {
|
||||||
image?: string;
|
image?: string;
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
variant: keyof typeof cardTypeByVariant;
|
|
||||||
imageProps?: JSX.IntrinsicElements["img"];
|
imageProps?: JSX.IntrinsicElements["img"];
|
||||||
title: string;
|
title: string;
|
||||||
description: ReactNode;
|
description: ReactNode;
|
||||||
@@ -27,30 +115,7 @@ export type BaseCardProps = {
|
|||||||
};
|
};
|
||||||
mediaLink?: string;
|
mediaLink?: string;
|
||||||
thumbnailUrl?: string;
|
thumbnailUrl?: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
// @TODO: use CVA
|
|
||||||
|
|
||||||
const cardTypeByVariant = {
|
|
||||||
basic: {
|
|
||||||
image: "w-10 h-auto",
|
|
||||||
card: "p-5",
|
|
||||||
title: "text-base mt-4",
|
|
||||||
description: "text-sm leading-[18px] text-subtle font-normal",
|
|
||||||
},
|
|
||||||
ProfileCard: {
|
|
||||||
image: "w-9 h-auto rounded-full mb-4s",
|
|
||||||
card: "w-80 p-4 hover:bg-subtle",
|
|
||||||
title: "text-base",
|
|
||||||
description: "text-sm leading-[18px] text-subtle font-normal",
|
|
||||||
},
|
|
||||||
SidebarCard: {
|
|
||||||
image: "w-9 h-auto rounded-full mb-4s",
|
|
||||||
card: "w-full p-3 border border-subtle",
|
|
||||||
title: "text-sm font-cal",
|
|
||||||
description: "text-xs text-default line-clamp-2",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Card({
|
export function Card({
|
||||||
image,
|
image,
|
||||||
@@ -70,7 +135,7 @@ export function Card({
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
containerProps?.className,
|
containerProps?.className,
|
||||||
cardTypeByVariant[variant].card,
|
cvaCardTypeByVariant({ variant, structure: "card" }),
|
||||||
"bg-default border-subtle text-default flex flex-col justify-between rounded-md border"
|
"bg-default border-subtle text-default flex flex-col justify-between rounded-md border"
|
||||||
)}
|
)}
|
||||||
{...containerProps}>
|
{...containerProps}>
|
||||||
@@ -81,14 +146,17 @@ export function Card({
|
|||||||
src={image}
|
src={image}
|
||||||
// Stops eslint complaining - not smart enough to realise it comes from ...imageProps
|
// Stops eslint complaining - not smart enough to realise it comes from ...imageProps
|
||||||
alt={imageProps?.alt}
|
alt={imageProps?.alt}
|
||||||
className={classNames(imageProps?.className, cardTypeByVariant[variant].image)}
|
className={classNames(
|
||||||
|
imageProps?.className,
|
||||||
|
cvaCardTypeByVariant({ variant, structure: "image" })
|
||||||
|
)}
|
||||||
{...imageProps}
|
{...imageProps}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<h5
|
<h5
|
||||||
title={title}
|
title={title}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
cardTypeByVariant[variant].title,
|
cvaCardTypeByVariant({ variant, structure: "title" }),
|
||||||
"line-clamp-1 text-emphasis font-bold leading-5"
|
"line-clamp-1 text-emphasis font-bold leading-5"
|
||||||
)}>
|
)}>
|
||||||
{title}
|
{title}
|
||||||
@@ -96,7 +164,7 @@ export function Card({
|
|||||||
{description && (
|
{description && (
|
||||||
<p
|
<p
|
||||||
title={description.toString()}
|
title={description.toString()}
|
||||||
className={classNames(cardTypeByVariant[variant].description, "pt-1")}>
|
className={classNames(cvaCardTypeByVariant({ variant, structure: "description" }), "pt-1")}>
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Examples,
|
||||||
|
Example,
|
||||||
|
Note,
|
||||||
|
Title,
|
||||||
|
VariantsTable,
|
||||||
|
VariantColumn,
|
||||||
|
RowTitles,
|
||||||
|
CustomArgsTable,
|
||||||
|
} from "@calcom/storybook/components";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Sheet,
|
||||||
|
SheetClose,
|
||||||
|
SheetContent,
|
||||||
|
SheetDescription,
|
||||||
|
SheetFooter,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
SheetTrigger,
|
||||||
|
} from "./sheet";
|
||||||
|
|
||||||
|
import {Button} from "../button";
|
||||||
|
|
||||||
|
|
||||||
|
<Meta title="UI/Sheet" />
|
||||||
|
|
||||||
|
<Title title="Sheet" suffix="Brief" subtitle="Version 2.0 — Last Update: 21 June 2023" />
|
||||||
|
|
||||||
|
## Definition
|
||||||
|
|
||||||
|
A sheet is a container that can be used to display content on differnt sides of the current page.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
<Examples title="Spacing">
|
||||||
|
<Sheet>
|
||||||
|
<SheetTrigger asChild>
|
||||||
|
<Button>Open default sheet</Button>
|
||||||
|
</SheetTrigger>
|
||||||
|
<SheetContent position="right" size={"default"}>
|
||||||
|
<SheetHeader>
|
||||||
|
<SheetTitle>Edit profile</SheetTitle>
|
||||||
|
<SheetDescription>
|
||||||
|
Make changes to your profile here. Click save when you're done.
|
||||||
|
</SheetDescription>
|
||||||
|
</SheetHeader>
|
||||||
|
<SheetFooter>
|
||||||
|
<SheetClose asChild>
|
||||||
|
<Button type="submit">Save changes</Button>
|
||||||
|
</SheetClose>
|
||||||
|
</SheetFooter>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
</Examples>
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import classNames from "@calcom/lib/classNames";
|
||||||
|
|
||||||
|
const Sheet = SheetPrimitive.Root;
|
||||||
|
|
||||||
|
const SheetTrigger = SheetPrimitive.Trigger;
|
||||||
|
|
||||||
|
const SheetClose = SheetPrimitive.Close;
|
||||||
|
|
||||||
|
const portalVariants = cva("fixed inset-0 z-50 flex", {
|
||||||
|
variants: {
|
||||||
|
position: {
|
||||||
|
top: "items-start",
|
||||||
|
bottom: "items-end",
|
||||||
|
left: "justify-start",
|
||||||
|
right: "justify-end",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: { position: "right" },
|
||||||
|
});
|
||||||
|
|
||||||
|
interface SheetPortalProps extends SheetPrimitive.DialogPortalProps, VariantProps<typeof portalVariants> {}
|
||||||
|
|
||||||
|
const SheetPortal = ({ position, className, children, ...props }: SheetPortalProps) => (
|
||||||
|
<SheetPrimitive.Portal className={classNames(className)} {...props}>
|
||||||
|
<div className={portalVariants({ position })}>{children}</div>
|
||||||
|
</SheetPrimitive.Portal>
|
||||||
|
);
|
||||||
|
SheetPortal.displayName = SheetPrimitive.Portal.displayName;
|
||||||
|
|
||||||
|
const SheetOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Overlay
|
||||||
|
className={classNames(
|
||||||
|
"bg-inverted data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in fixed inset-0 z-50 opacity-60 backdrop-blur-sm transition-all duration-100",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
||||||
|
|
||||||
|
const sheetVariants = cva(
|
||||||
|
"fixed z-50 scale-100 gap-4 bg-default m-4 rounded-xl p-6 opacity-100 shadow-lg border",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
position: {
|
||||||
|
top: "animate-in slide-in-from-top w-full duration-300",
|
||||||
|
bottom: "animate-in slide-in-from-bottom w-full duration-300",
|
||||||
|
left: "animate-in slide-in-from-left h-full duration-300",
|
||||||
|
right: "animate-in slide-in-from-right h-full duration-300",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
content: "",
|
||||||
|
default: "",
|
||||||
|
sm: "",
|
||||||
|
lg: "",
|
||||||
|
xl: "",
|
||||||
|
full: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
compoundVariants: [
|
||||||
|
{
|
||||||
|
position: ["top", "bottom"],
|
||||||
|
size: "content",
|
||||||
|
class: "max-h-screen",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: ["top", "bottom"],
|
||||||
|
size: "default",
|
||||||
|
class: "h-1/3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: ["top", "bottom"],
|
||||||
|
size: "sm",
|
||||||
|
class: "h-1/4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: ["top", "bottom"],
|
||||||
|
size: "lg",
|
||||||
|
class: "h-1/2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: ["top", "bottom"],
|
||||||
|
size: "xl",
|
||||||
|
class: "h-5/6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: ["top", "bottom"],
|
||||||
|
size: "full",
|
||||||
|
class: "h-screen",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: ["right", "left"],
|
||||||
|
size: "content",
|
||||||
|
class: "max-w-screen",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: ["right", "left"],
|
||||||
|
size: "default",
|
||||||
|
class: "w-1/3 h-[calc(100vh-2rem)]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: ["right", "left"],
|
||||||
|
size: "sm",
|
||||||
|
class: "w-1/4 h-[calc(100vh-2rem)]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: ["right", "left"],
|
||||||
|
size: "lg",
|
||||||
|
class: "w-1/2 h-[calc(100vh-2rem)]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: ["right", "left"],
|
||||||
|
size: "xl",
|
||||||
|
class: "w-5/6 h-[calc(100vh-2rem)]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: ["right", "left"],
|
||||||
|
size: "full",
|
||||||
|
class: "w-screen h-[calc(100vh-2rem)]",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultVariants: {
|
||||||
|
position: "right",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface DialogContentProps
|
||||||
|
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||||
|
VariantProps<typeof sheetVariants> {}
|
||||||
|
|
||||||
|
const SheetContent = React.forwardRef<React.ElementRef<typeof SheetPrimitive.Content>, DialogContentProps>(
|
||||||
|
({ position, size, className, children, ...props }, ref) => (
|
||||||
|
<SheetPortal position={position}>
|
||||||
|
<SheetOverlay />
|
||||||
|
<SheetPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={classNames(sheetVariants({ position, size }), className)}
|
||||||
|
{...props}>
|
||||||
|
{children}
|
||||||
|
<SheetPrimitive.Close className="focus:ring-emphasis data-[state=open]:bg-deafult absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none">
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</SheetPrimitive.Close>
|
||||||
|
</SheetPrimitive.Content>
|
||||||
|
</SheetPortal>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
SheetContent.displayName = SheetPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div className={classNames("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
|
||||||
|
);
|
||||||
|
SheetHeader.displayName = "SheetHeader";
|
||||||
|
|
||||||
|
const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={classNames("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
SheetFooter.displayName = "SheetFooter";
|
||||||
|
|
||||||
|
const SheetTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={classNames("text-foreground text-lg font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
SheetTitle.displayName = SheetPrimitive.Title.displayName;
|
||||||
|
|
||||||
|
const SheetDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={classNames("text-muted-foreground text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
SheetDescription.displayName = SheetPrimitive.Description.displayName;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Sheet,
|
||||||
|
SheetTrigger,
|
||||||
|
SheetClose,
|
||||||
|
SheetContent,
|
||||||
|
SheetHeader,
|
||||||
|
SheetFooter,
|
||||||
|
SheetTitle,
|
||||||
|
SheetDescription,
|
||||||
|
};
|
||||||
@@ -4062,7 +4062,7 @@ __metadata:
|
|||||||
"@calcom/ui": "*"
|
"@calcom/ui": "*"
|
||||||
"@headlessui/react": ^1.5.0
|
"@headlessui/react": ^1.5.0
|
||||||
"@heroicons/react": ^1.0.6
|
"@heroicons/react": ^1.0.6
|
||||||
"@prisma/client": ^4.13.0
|
"@prisma/client": ^4.16.0
|
||||||
"@tailwindcss/forms": ^0.5.2
|
"@tailwindcss/forms": ^0.5.2
|
||||||
"@types/node": 16.9.1
|
"@types/node": 16.9.1
|
||||||
"@types/react": 18.0.26
|
"@types/react": 18.0.26
|
||||||
@@ -4074,7 +4074,7 @@ __metadata:
|
|||||||
next-auth: ^4.20.1
|
next-auth: ^4.20.1
|
||||||
next-i18next: ^11.3.0
|
next-i18next: ^11.3.0
|
||||||
postcss: ^8.4.18
|
postcss: ^8.4.18
|
||||||
prisma: ^4.13.0
|
prisma: ^4.16.0
|
||||||
prisma-field-encryption: ^1.4.0
|
prisma-field-encryption: ^1.4.0
|
||||||
react: ^18.2.0
|
react: ^18.2.0
|
||||||
react-chartjs-2: ^4.0.1
|
react-chartjs-2: ^4.0.1
|
||||||
@@ -4525,10 +4525,10 @@ __metadata:
|
|||||||
resolution: "@calcom/prisma@workspace:packages/prisma"
|
resolution: "@calcom/prisma@workspace:packages/prisma"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@calcom/lib": "*"
|
"@calcom/lib": "*"
|
||||||
"@prisma/client": ^4.13.0
|
"@prisma/client": ^4.16.0
|
||||||
"@prisma/generator-helper": ^4.13.0
|
"@prisma/generator-helper": ^4.13.0
|
||||||
npm-run-all: ^4.1.5
|
npm-run-all: ^4.1.5
|
||||||
prisma: ^4.13.0
|
prisma: ^4.16.0
|
||||||
ts-node: ^10.9.1
|
ts-node: ^10.9.1
|
||||||
zod: ^3.20.2
|
zod: ^3.20.2
|
||||||
zod-prisma: ^0.5.4
|
zod-prisma: ^0.5.4
|
||||||
@@ -7835,17 +7835,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@prisma/client@npm:^4.13.0":
|
"@prisma/client@npm:^4.16.0":
|
||||||
version: 4.13.0
|
version: 4.16.0
|
||||||
resolution: "@prisma/client@npm:4.13.0"
|
resolution: "@prisma/client@npm:4.16.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@prisma/engines-version": 4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a
|
"@prisma/engines-version": 4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
prisma: "*"
|
prisma: "*"
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
prisma:
|
prisma:
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 74e2ede6308d6f70dc6244c34d13cf5f6c8da4f1a1ffe42a26192675ecac02733cb5482518d2a37fa057f160873fe8d42f5e3a08fc45d52b1eb386522bdbec0b
|
checksum: 67fbf8e0bd672d597433b61b65a39c58cbb2eb82e7bcac33f31215d8ca5ba48df790a20aa990c47d6dde53e0d84d1a45d07d872f772d3dd8271613abe879b5d0
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -7882,17 +7882,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@prisma/engines-version@npm:4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a":
|
"@prisma/engines-version@npm:4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c":
|
||||||
version: 4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a
|
version: 4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c
|
||||||
resolution: "@prisma/engines-version@npm:4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a"
|
resolution: "@prisma/engines-version@npm:4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c"
|
||||||
checksum: cc583fe956b65fd4548082917f14ccf1c3c54d6b26623583f5e63298a8b4b5a492ba90597123af1fc272115621cb7340e2d708d5b5e252f59139b35f0df00abe
|
checksum: 23183cab33f95e0711cd6cee7642f4beeeb77b8b0aa54b419f55097bfb96863a11a28f6509ab27fef0ea794522e188044ce850993a25c341333f8305711a255f
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@prisma/engines@npm:4.13.0":
|
"@prisma/engines@npm:4.16.0":
|
||||||
version: 4.13.0
|
version: 4.16.0
|
||||||
resolution: "@prisma/engines@npm:4.13.0"
|
resolution: "@prisma/engines@npm:4.16.0"
|
||||||
checksum: 6c2315dc1e1a8a07f02a074ee6898a66a96a3bbbb4341224cb1dc966da3959c2ee7557131a2ce37fd559c53d210b904b72c4559474e017438e6753d480c3cfe1
|
checksum: 60ef1c3720720cf5a9d23f52c668669736716afd1862c4ac61e8ebcd3ca896e3453f80dfab77f3b68b001c5d499a895f28e54cc5025eae61878b40c1ec5d99c3
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -29665,15 +29665,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"prisma@npm:^4.13.0":
|
"prisma@npm:^4.16.0":
|
||||||
version: 4.13.0
|
version: 4.16.0
|
||||||
resolution: "prisma@npm:4.13.0"
|
resolution: "prisma@npm:4.16.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@prisma/engines": 4.13.0
|
"@prisma/engines": 4.16.0
|
||||||
bin:
|
bin:
|
||||||
prisma: build/index.js
|
prisma: build/index.js
|
||||||
prisma2: build/index.js
|
prisma2: build/index.js
|
||||||
checksum: b735dd19842da1dfe5b3411551665db5f2a419ee5e67136dfc2c8cae3c7fe0f885b472b0f9decb3adb4af83260c51a605ecca8f2194805804e020c382096a75a
|
checksum: 831a4d14a48474fa144ae0648ed5e3766756b991f1997ecca813a044ef9bd7f2a5794c603adc9d23d867d8f4a0f51cedadaebfd8a841e6923ef9b804ed8bcbbb
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user