Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b51cc9aef | ||
|
|
50c8959cfa | ||
|
|
c96d258f5e | ||
|
|
6e45b1c383 | ||
|
|
551ebe863b | ||
|
|
fa7b94bfe9 | ||
|
|
56c5d39cd3 | ||
|
|
7574fb6f44 | ||
|
|
901d1f0e01 | ||
|
|
828fcbe1d9 | ||
|
|
34db46869e | ||
|
|
1f1cab2be1 |
No files matched your search
@@ -13,7 +13,7 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
e2e:
|
e2e:
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
runs-on: ubuntu-latest
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DATABASE_URL: postgresql://postgres:@localhost:5432/calendso
|
DATABASE_URL: postgresql://postgres:@localhost:5432/calendso
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ import { Controller, useForm, useFormContext } from "react-hook-form";
|
|||||||
import { MultiValue } from "react-select";
|
import { MultiValue } from "react-select";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { EventLocationType, getEventLocationType, MeetLocationType } from "@calcom/app-store/locations";
|
import {
|
||||||
|
EventLocationType,
|
||||||
|
getEventLocationType,
|
||||||
|
MeetLocationType,
|
||||||
|
LocationType,
|
||||||
|
} from "@calcom/app-store/locations";
|
||||||
import { CAL_URL } from "@calcom/lib/constants";
|
import { CAL_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { slugify } from "@calcom/lib/slugify";
|
import { slugify } from "@calcom/lib/slugify";
|
||||||
@@ -34,6 +39,16 @@ const getLocationFromType = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDefaultLocationValue = (options: EventTypeSetupProps["locationOptions"], type: string) => {
|
||||||
|
for (const locationType of options) {
|
||||||
|
for (const location of locationType.options) {
|
||||||
|
if (location.value === type && location.disabled === false) {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const EventSetupTab = (
|
export const EventSetupTab = (
|
||||||
props: Pick<EventTypeSetupProps, "eventType" | "locationOptions" | "team" | "teamMembers">
|
props: Pick<EventTypeSetupProps, "eventType" | "locationOptions" | "team" | "teamMembers">
|
||||||
) => {
|
) => {
|
||||||
@@ -69,7 +84,12 @@ export const EventSetupTab = (
|
|||||||
const removeLocation = (selectedLocation: typeof eventType.locations[number]) => {
|
const removeLocation = (selectedLocation: typeof eventType.locations[number]) => {
|
||||||
formMethods.setValue(
|
formMethods.setValue(
|
||||||
"locations",
|
"locations",
|
||||||
formMethods.getValues("locations").filter((location) => location.type !== selectedLocation.type),
|
formMethods.getValues("locations").filter((location) => {
|
||||||
|
if (location.type === LocationType.InPerson) {
|
||||||
|
return location.address !== selectedLocation.address;
|
||||||
|
}
|
||||||
|
return location.type !== selectedLocation.type;
|
||||||
|
}),
|
||||||
{ shouldValidate: true }
|
{ shouldValidate: true }
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -84,14 +104,14 @@ export const EventSetupTab = (
|
|||||||
...details,
|
...details,
|
||||||
type: newLocationType,
|
type: newLocationType,
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
copy[existingIdx] = {
|
|
||||||
...formMethods.getValues("locations")[existingIdx],
|
|
||||||
...details,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
formMethods.setValue("locations", copy);
|
formMethods.setValue("locations", [
|
||||||
|
...copy,
|
||||||
|
...(newLocationType === LocationType.InPerson && editingLocationType === ""
|
||||||
|
? [{ ...details, type: newLocationType }]
|
||||||
|
: []),
|
||||||
|
]);
|
||||||
} else {
|
} else {
|
||||||
formMethods.setValue(
|
formMethods.setValue(
|
||||||
"locations",
|
"locations",
|
||||||
@@ -138,7 +158,8 @@ export const EventSetupTab = (
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultValue = locationOptions.find((item) => item.label === "video")?.options;
|
const defaultValue = getDefaultLocationValue(locationOptions, "integrations:daily");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{validLocations.length === 0 && (
|
{validLocations.length === 0 && (
|
||||||
@@ -176,7 +197,9 @@ export const EventSetupTab = (
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<li key={location.type} className="mb-2 rounded-md border border-gray-300 py-1.5 px-2">
|
<li
|
||||||
|
key={`${location.type}${index}`}
|
||||||
|
className="mb-2 rounded-md border border-gray-300 py-1.5 px-2">
|
||||||
<div className="flex max-w-full justify-between">
|
<div className="flex max-w-full justify-between">
|
||||||
<div key={index} className="flex flex-grow items-center">
|
<div key={index} className="flex flex-grow items-center">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@calcom/web",
|
"name": "@calcom/web",
|
||||||
"version": "2.5.7",
|
"version": "2.5.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "ANALYZE=true next build",
|
"analyze": "ANALYZE=true next build",
|
||||||
@@ -74,7 +74,6 @@
|
|||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"ical.js": "^1.4.0",
|
"ical.js": "^1.4.0",
|
||||||
"ics": "^2.37.0",
|
"ics": "^2.37.0",
|
||||||
"jose": "^4.11.1",
|
|
||||||
"kbar": "^0.1.0-beta.36",
|
"kbar": "^0.1.0-beta.36",
|
||||||
"libphonenumber-js": "^1.10.12",
|
"libphonenumber-js": "^1.10.12",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { IdentityProvider, UserPermissionRole } from "@prisma/client";
|
import { IdentityProvider, UserPermissionRole } from "@prisma/client";
|
||||||
import { BinaryLike, hkdfSync, KeyObject } from "crypto";
|
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import Handlebars from "handlebars";
|
import Handlebars from "handlebars";
|
||||||
import * as jose from "jose";
|
|
||||||
import NextAuth, { Session } from "next-auth";
|
import NextAuth, { Session } from "next-auth";
|
||||||
|
import { encode } from "next-auth/jwt";
|
||||||
import { Provider } from "next-auth/providers";
|
import { Provider } from "next-auth/providers";
|
||||||
import CredentialsProvider from "next-auth/providers/credentials";
|
import CredentialsProvider from "next-auth/providers/credentials";
|
||||||
import EmailProvider from "next-auth/providers/email";
|
import EmailProvider from "next-auth/providers/email";
|
||||||
@@ -11,7 +10,6 @@ import GoogleProvider from "next-auth/providers/google";
|
|||||||
import nodemailer, { TransportOptions } from "nodemailer";
|
import nodemailer, { TransportOptions } from "nodemailer";
|
||||||
import { authenticator } from "otplib";
|
import { authenticator } from "otplib";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
|
||||||
|
|
||||||
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
|
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
|
||||||
import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider";
|
import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider";
|
||||||
@@ -238,10 +236,13 @@ if (true) {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isNumber(n: string) {
|
||||||
|
return !isNaN(parseFloat(n)) && !isNaN(+n);
|
||||||
|
}
|
||||||
|
|
||||||
const calcomAdapter = CalComAdapter(prisma);
|
const calcomAdapter = CalComAdapter(prisma);
|
||||||
const getDerivedEncryptionKey = async (secret: BinaryLike | KeyObject) => {
|
|
||||||
return await hkdfSync("sha256", secret, "", "NextAuth.js Generated Encryption Key", 32);
|
|
||||||
};
|
|
||||||
export default NextAuth({
|
export default NextAuth({
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -250,34 +251,23 @@ export default NextAuth({
|
|||||||
strategy: "jwt",
|
strategy: "jwt",
|
||||||
},
|
},
|
||||||
jwt: {
|
jwt: {
|
||||||
encode: async ({ secret, token }) => {
|
// decorate the native JWT encode function
|
||||||
if (!token || token.sub === undefined) throw new Error("Not valid token");
|
// Impl. detail: We don't pass through as this function is called with encode/decode functions.
|
||||||
const user = await prisma.user.findFirst({
|
encode: async ({ token, maxAge, secret }) => {
|
||||||
where: { id: Number(token.sub) },
|
if (token?.sub && isNumber(token.sub)) {
|
||||||
select: { metadata: true },
|
const user = await prisma.user.findFirst({
|
||||||
});
|
where: { id: Number(token.sub) },
|
||||||
const encryptionSecret = await getDerivedEncryptionKey(secret);
|
select: { metadata: true },
|
||||||
const metadata = userMetadata.parse(user?.metadata);
|
});
|
||||||
return await new jose.EncryptJWT({
|
// if no user is found, we still don't want to crash here.
|
||||||
sub: token?.sub,
|
if (user) {
|
||||||
name: token?.name,
|
const metadata = userMetadata.parse(user.metadata);
|
||||||
email: token?.email,
|
if (metadata?.sessionTimeout) {
|
||||||
})
|
maxAge = metadata.sessionTimeout / 60;
|
||||||
.setProtectedHeader({
|
}
|
||||||
alg: "dir",
|
}
|
||||||
enc: "A256GCM",
|
}
|
||||||
})
|
return encode({ secret, token, maxAge });
|
||||||
.setIssuedAt()
|
|
||||||
.setExpirationTime(metadata?.sessionTimeout ? `${metadata.sessionTimeout}m` : "30d")
|
|
||||||
.setJti(uuidv4())
|
|
||||||
.encrypt(new Uint8Array(encryptionSecret));
|
|
||||||
},
|
|
||||||
decode: async ({ secret, token }) => {
|
|
||||||
const encryptionSecret = await getDerivedEncryptionKey(secret);
|
|
||||||
const { payload } = await jose.jwtDecrypt(token || "", new Uint8Array(encryptionSecret), {
|
|
||||||
clockTolerance: 15,
|
|
||||||
});
|
|
||||||
return payload;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
cookies: defaultCookies(WEBAPP_URL?.startsWith("https://")),
|
cookies: defaultCookies(WEBAPP_URL?.startsWith("https://")),
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
|
|||||||
});
|
});
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
return (
|
return (
|
||||||
<div className="px-4 py-5 sm:p-6">
|
<div className="p-6">
|
||||||
<h3 className="font-medium leading-6 text-gray-900">
|
<h3 className="font-medium leading-6 text-gray-900">
|
||||||
{t("date_overrides")}{" "}
|
{t("date_overrides")}{" "}
|
||||||
<Tooltip content={t("date_overrides_info")}>
|
<Tooltip content={t("date_overrides_info")}>
|
||||||
@@ -60,8 +60,8 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
|
|||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mb-4 text-sm text-gray-500 ltr:mr-4 rtl:ml-4">{t("date_overrides_subtitle")}</p>
|
<p className="mb-4 text-sm text-gray-500">{t("date_overrides_subtitle")}</p>
|
||||||
<div className="mt-1 space-y-2">
|
<div className="space-y-2">
|
||||||
<DateOverrideList
|
<DateOverrideList
|
||||||
excludedDates={fields.map((field) => yyyymmdd(field.ranges[0].start))}
|
excludedDates={fields.map((field) => yyyymmdd(field.ranges[0].start))}
|
||||||
remove={remove}
|
remove={remove}
|
||||||
@@ -148,7 +148,7 @@ export default function Availability({ schedule }: { schedule: number }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
CTA={
|
CTA={
|
||||||
<div className="flex items-center justify-end">
|
<div className="mr-10 flex items-center justify-end xl:mr-36">
|
||||||
<div className="flex items-center rounded-md px-2 sm:hover:bg-gray-100">
|
<div className="flex items-center rounded-md px-2 sm:hover:bg-gray-100">
|
||||||
<Skeleton
|
<Skeleton
|
||||||
as={Label}
|
as={Label}
|
||||||
@@ -185,25 +185,29 @@ export default function Availability({ schedule }: { schedule: number }) {
|
|||||||
...values,
|
...values,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className="flex flex-col pb-16 sm:mx-0 xl:flex-row xl:space-x-6">
|
className="flex flex-col sm:mx-0 xl:flex-row xl:space-x-6">
|
||||||
<div className="flex-1 divide-y divide-gray-200 rounded-md border">
|
<div className="mx-10 flex-1 flex-row xl:mr-0 xl:ml-36">
|
||||||
<div className=" py-5 sm:p-6">
|
<div className="mb-6 rounded-md border">
|
||||||
{typeof me.data?.weekStart === "string" && (
|
<div>
|
||||||
<Schedule
|
{typeof me.data?.weekStart === "string" && (
|
||||||
control={control}
|
<Schedule
|
||||||
name="schedule"
|
control={control}
|
||||||
weekStart={
|
name="schedule"
|
||||||
["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"].indexOf(
|
weekStart={
|
||||||
me.data?.weekStart
|
["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"].indexOf(
|
||||||
) as 0 | 1 | 2 | 3 | 4 | 5 | 6
|
me.data?.weekStart
|
||||||
}
|
) as 0 | 1 | 2 | 3 | 4 | 5 | 6
|
||||||
/>
|
}
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="my-6 rounded-md border">
|
||||||
|
{data?.workingHours && <DateOverride workingHours={data.workingHours} />}
|
||||||
</div>
|
</div>
|
||||||
{data?.workingHours && <DateOverride workingHours={data.workingHours} />}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-40 col-span-3 space-y-2 lg:col-span-1">
|
<div className="min-w-40 col-span-3 ml-10 space-y-2 lg:col-span-1 xl:ml-36">
|
||||||
<div className="xl:max-w-80 mt-4 w-full pr-4 sm:p-0">
|
<div className="xl:max-w-80 w-full pr-4 sm:ml-0 sm:mr-36 sm:p-0">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
|
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
|
||||||
{t("timezone")}
|
{t("timezone")}
|
||||||
@@ -223,7 +227,7 @@ export default function Availability({ schedule }: { schedule: number }) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<hr className="my-8" />
|
<hr className="my-6 mr-8" />
|
||||||
<div className="rounded-md">
|
<div className="rounded-md">
|
||||||
<h3 className="text-sm font-medium text-gray-900">{t("something_doesnt_look_right")}</h3>
|
<h3 className="text-sm font-medium text-gray-900">{t("something_doesnt_look_right")}</h3>
|
||||||
<div className="mt-3 flex">
|
<div className="mt-3 flex">
|
||||||
|
|||||||
@@ -1562,6 +1562,7 @@
|
|||||||
"recommended_next_steps": "Recommended next steps",
|
"recommended_next_steps": "Recommended next steps",
|
||||||
"create_a_managed_event": "Create a managed event type",
|
"create_a_managed_event": "Create a managed event type",
|
||||||
"meetings_are_better_with_the_right": "Meetings are better with the right team members there. Invite them now.",
|
"meetings_are_better_with_the_right": "Meetings are better with the right team members there. Invite them now.",
|
||||||
|
"create_a_one_one_template": "Create a one-one one template for an event type and distribute it to multiple members.",
|
||||||
"collective_or_roundrobin": "Collective or round-robin",
|
"collective_or_roundrobin": "Collective or round-robin",
|
||||||
"book_your_team_members": "Book your team members together with collective events or cycle through to get the right person with round-robin."
|
"book_your_team_members": "Book your team members together with collective events or cycle through to get the right person with round-robin."
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@ export default function BookingPageTagManager({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const parseValue = <T extends string | undefined>(val: T): T =>
|
const parseValue = <T extends string | undefined>(val: T): T =>
|
||||||
|
//TODO: Support more template variables.
|
||||||
val ? (val.replace(/\{TRACKING_ID\}/g, trackingId) as T) : val;
|
val ? (val.replace(/\{TRACKING_ID\}/g, trackingId) as T) : val;
|
||||||
|
|
||||||
return tag.scripts.map((script, index) => {
|
return tag.scripts.map((script, index) => {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export const InstallAppButtonMap = {
|
|||||||
larkcalendar: dynamic(() => import("./larkcalendar/components/InstallAppButton")),
|
larkcalendar: dynamic(() => import("./larkcalendar/components/InstallAppButton")),
|
||||||
office365calendar: dynamic(() => import("./office365calendar/components/InstallAppButton")),
|
office365calendar: dynamic(() => import("./office365calendar/components/InstallAppButton")),
|
||||||
office365video: dynamic(() => import("./office365video/components/InstallAppButton")),
|
office365video: dynamic(() => import("./office365video/components/InstallAppButton")),
|
||||||
plausible: dynamic(() => import("./plausible/components/InstallAppButton")),
|
|
||||||
riverside: dynamic(() => import("./riverside/components/InstallAppButton")),
|
riverside: dynamic(() => import("./riverside/components/InstallAppButton")),
|
||||||
tandemvideo: dynamic(() => import("./tandemvideo/components/InstallAppButton")),
|
tandemvideo: dynamic(() => import("./tandemvideo/components/InstallAppButton")),
|
||||||
vital: dynamic(() => import("./vital/components/InstallAppButton")),
|
vital: dynamic(() => import("./vital/components/InstallAppButton")),
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { appDataSchema } from "../zod";
|
|||||||
|
|
||||||
const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app }) {
|
const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app }) {
|
||||||
const [getAppData, setAppData] = useAppContextWithSchema<typeof appDataSchema>();
|
const [getAppData, setAppData] = useAppContextWithSchema<typeof appDataSchema>();
|
||||||
const trackedDomain = getAppData("trackedDomain");
|
const trackingId = getAppData("trackingId");
|
||||||
const [enabled, setEnabled] = useState(getAppData("enabled"));
|
const [enabled, setEnabled] = useState(getAppData("enabled"));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -26,9 +26,9 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
|||||||
switchChecked={enabled}>
|
switchChecked={enabled}>
|
||||||
<TextField
|
<TextField
|
||||||
name="Tracked Domain"
|
name="Tracked Domain"
|
||||||
value={trackedDomain}
|
value={trackingId}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setAppData("trackedDomain", e.target.value);
|
setAppData("trackingId", e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</AppCard>
|
</AppCard>
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import type { InstallAppButtonProps } from "@calcom/app-store/types";
|
|
||||||
|
|
||||||
import useAddAppMutation from "../../_utils/useAddAppMutation";
|
|
||||||
|
|
||||||
export default function InstallAppButton(props: InstallAppButtonProps) {
|
|
||||||
const mutation = useAddAppMutation("plausible_analytics");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{props.render({
|
|
||||||
onClick() {
|
|
||||||
mutation.mutate("");
|
|
||||||
},
|
|
||||||
loading: mutation.isLoading,
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { default as InstallAppButton } from "./InstallAppButton";
|
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
{
|
{
|
||||||
"src": "https://plausible.io/js/script.js",
|
"src": "https://plausible.io/js/script.js",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"data-domain": "{TRACKED_DOMAIN}"
|
"data-domain": "{TRACKING_ID}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { eventTypeAppCardZod } from "../eventTypeAppCardZod";
|
|||||||
|
|
||||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||||
z.object({
|
z.object({
|
||||||
trackedDomain: z.string(),
|
trackingId: z.string(),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const getHtml = ({
|
|||||||
}) => {
|
}) => {
|
||||||
// IT IS A REQUIREMENT THAT ALL POSSIBLE CLASSES ARE HERE OTHERWISE TAILWIND WONT GENERATE THE CSS FOR CONDITIONAL CLASSES
|
// IT IS A REQUIREMENT THAT ALL POSSIBLE CLASSES ARE HERE OTHERWISE TAILWIND WONT GENERATE THE CSS FOR CONDITIONAL CLASSES
|
||||||
// To not let all these classes apply and visible, keep it hidden initially
|
// To not let all these classes apply and visible, keep it hidden initially
|
||||||
return `<button class="hidden fixed md:bottom-6 bottom-4 md:right-10 right-4 md:left-10 left-4 ${buttonClasses.join(
|
return `<button class="z-[10000000000] hidden fixed md:bottom-6 bottom-4 md:right-10 right-4 md:left-10 left-4 ${buttonClasses.join(
|
||||||
" "
|
" "
|
||||||
)} flex h-16 origin-center bg-red-50 transform cursor-pointer items-center
|
)} flex h-16 origin-center bg-red-50 transform cursor-pointer items-center
|
||||||
rounded-full py-4 px-6 text-base outline-none drop-shadow-md transition focus:outline-none fo
|
rounded-full py-4 px-6 text-base outline-none drop-shadow-md transition focus:outline-none fo
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export default function TeamList(props: Props) {
|
|||||||
icon={<FiUserPlus className="h-5 w-5 text-green-700" />}
|
icon={<FiUserPlus className="h-5 w-5 text-green-700" />}
|
||||||
variant="basic"
|
variant="basic"
|
||||||
title={t("invite_team_member")}
|
title={t("invite_team_member")}
|
||||||
description="Meetings are better with the right team members there. Invite them now."
|
description={t("meetings_are_better_with_the_right")}
|
||||||
actionButton={{
|
actionButton={{
|
||||||
href: "/settings/teams/" + team.id + "/members",
|
href: "/settings/teams/" + team.id + "/members",
|
||||||
child: t("invite"),
|
child: t("invite"),
|
||||||
@@ -77,7 +77,7 @@ export default function TeamList(props: Props) {
|
|||||||
icon={<FiUnlock className="h-5 w-5 text-blue-700" />}
|
icon={<FiUnlock className="h-5 w-5 text-blue-700" />}
|
||||||
variant="basic"
|
variant="basic"
|
||||||
title={t("create_a_managed_event")}
|
title={t("create_a_managed_event")}
|
||||||
description={t("meetings_are_better_with_the_right")}
|
description={t("create_a_one_one_template")}
|
||||||
actionButton={{
|
actionButton={{
|
||||||
href:
|
href:
|
||||||
"/event-types?dialog=new-eventtype&eventPage=team%2F" +
|
"/event-types?dialog=new-eventtype&eventPage=team%2F" +
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ const ScheduleDay = <TFieldValues extends FieldValues>({
|
|||||||
const watchDayRange = watch(name);
|
const watchDayRange = watch(name);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-1 flex w-full flex-col px-5 py-1 sm:flex-row sm:px-0">
|
<div className="mb-4 flex w-full flex-col last:mb-0 sm:flex-row sm:px-0">
|
||||||
{/* Label & switch container */}
|
{/* Label & switch container */}
|
||||||
<div className="flex h-11 items-center justify-between sm:w-32">
|
<div className="flex h-[36px] items-center justify-between sm:w-32">
|
||||||
<div>
|
<div>
|
||||||
<label className="flex flex-row items-center space-x-2 rtl:space-x-reverse">
|
<label className="flex flex-row items-center space-x-2 rtl:space-x-reverse">
|
||||||
<div>
|
<div>
|
||||||
@@ -67,9 +67,7 @@ const ScheduleDay = <TFieldValues extends FieldValues>({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="inline-block min-w-[88px] text-sm capitalize">{weekday}</span>
|
<span className="inline-block min-w-[88px] text-sm capitalize">{weekday}</span>
|
||||||
{watchDayRange && !!watchDayRange.length && (
|
{watchDayRange && !!watchDayRange.length && <div className="sm:hidden">{CopyButton}</div>}
|
||||||
<div className="mt-1 mb-1 sm:hidden">{CopyButton}</div>
|
|
||||||
)}
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -77,7 +75,7 @@ const ScheduleDay = <TFieldValues extends FieldValues>({
|
|||||||
{watchDayRange ? (
|
{watchDayRange ? (
|
||||||
<div className="flex sm:ml-2">
|
<div className="flex sm:ml-2">
|
||||||
<DayRanges control={control} name={name} />
|
<DayRanges control={control} name={name} />
|
||||||
{!!watchDayRange.length && <div className="mt-1 hidden sm:block">{CopyButton}</div>}
|
{!!watchDayRange.length && <div className="hidden sm:block">{CopyButton}</div>}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<SkeletonText className="mt-2.5 ml-1 h-6 w-48" />
|
<SkeletonText className="mt-2.5 ml-1 h-6 w-48" />
|
||||||
@@ -142,7 +140,7 @@ const Schedule = <
|
|||||||
const { i18n } = useLocale();
|
const { i18n } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="divide-y sm:divide-none">
|
<div className="divide-y p-4 sm:divide-none">
|
||||||
{/* First iterate for each day */}
|
{/* First iterate for each day */}
|
||||||
{weekdayNames(i18n.language, weekStart, "long").map((weekday, num) => {
|
{weekdayNames(i18n.language, weekStart, "long").map((weekday, num) => {
|
||||||
const weekdayIndex = (num + weekStart) % 7;
|
const weekdayIndex = (num + weekStart) % 7;
|
||||||
@@ -179,7 +177,7 @@ export const DayRanges = <TFieldValues extends FieldValues>({
|
|||||||
<div>
|
<div>
|
||||||
{fields.map((field, index: number) => (
|
{fields.map((field, index: number) => (
|
||||||
<Fragment key={field.id}>
|
<Fragment key={field.id}>
|
||||||
<div className="mb-2 flex first:mt-1">
|
<div className="mb-2 flex last:mb-0">
|
||||||
<Controller name={`${name}.${index}`} render={({ field }) => <TimeRangeField {...field} />} />
|
<Controller name={`${name}.${index}`} render={({ field }) => <TimeRangeField {...field} />} />
|
||||||
{index === 0 && (
|
{index === 0 && (
|
||||||
<Button
|
<Button
|
||||||
@@ -230,7 +228,7 @@ const TimeRangeField = ({ className, value, onChange }: { className?: string } &
|
|||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<LazySelect
|
<LazySelect
|
||||||
className="inline-block h-9 w-[100px]"
|
className="inline-block w-[100px]"
|
||||||
value={value.start}
|
value={value.start}
|
||||||
max={value.end}
|
max={value.end}
|
||||||
onChange={(option) => {
|
onChange={(option) => {
|
||||||
|
|||||||
@@ -780,10 +780,15 @@ function SideBar() {
|
|||||||
export function ShellMain(props: LayoutProps) {
|
export function ShellMain(props: LayoutProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { isLocaleReady } = useLocale();
|
const { isLocaleReady } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={classNames("mb-6 flex items-center sm:mt-0", props.smallHeading ? "lg:mb-7" : "lg:mb-8")}>
|
className={classNames(
|
||||||
|
"mb-6 flex items-center sm:mt-0",
|
||||||
|
props.smallHeading ? "lg:mb-7" : "lg:mb-8",
|
||||||
|
props.backPath === "/availability" && "ml-2.5 xl:ml-[7rem]"
|
||||||
|
)}>
|
||||||
{!!props.backPath && (
|
{!!props.backPath && (
|
||||||
<Button
|
<Button
|
||||||
variant="icon"
|
variant="icon"
|
||||||
@@ -800,18 +805,18 @@ export function ShellMain(props: LayoutProps) {
|
|||||||
{props.heading && (
|
{props.heading && (
|
||||||
<header className={classNames(props.large && "py-8", "flex w-full max-w-full items-center")}>
|
<header className={classNames(props.large && "py-8", "flex w-full max-w-full items-center")}>
|
||||||
{props.HeadingLeftIcon && <div className="ltr:mr-4">{props.HeadingLeftIcon}</div>}
|
{props.HeadingLeftIcon && <div className="ltr:mr-4">{props.HeadingLeftIcon}</div>}
|
||||||
<div className={classNames("w-full ltr:mr-4 rtl:ml-4 sm:block", props.headerClassName)}>
|
<div className={classNames("w-full ltr:mr-4 rtl:ml-4 md:block", props.headerClassName)}>
|
||||||
{props.heading && (
|
{props.heading && (
|
||||||
<h1
|
<h1
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"font-cal max-w-28 sm:max-w-72 md:max-w-80 mt-1 hidden truncate text-xl font-semibold tracking-wide text-black sm:block xl:max-w-full",
|
"font-cal max-w-28 sm:max-w-72 md:max-w-80 mt-1 hidden truncate text-xl font-semibold tracking-wide text-black md:block xl:max-w-full",
|
||||||
props.smallHeading ? "text-base" : "text-xl"
|
props.smallHeading ? "text-base" : "text-xl"
|
||||||
)}>
|
)}>
|
||||||
{!isLocaleReady ? <SkeletonText invisible /> : props.heading}
|
{!isLocaleReady ? <SkeletonText invisible /> : props.heading}
|
||||||
</h1>
|
</h1>
|
||||||
)}
|
)}
|
||||||
{props.subtitle && (
|
{props.subtitle && (
|
||||||
<p className="hidden text-sm text-gray-500 sm:block">
|
<p className="hidden text-sm text-gray-500 md:block">
|
||||||
{!isLocaleReady ? <SkeletonText invisible /> : props.subtitle}
|
{!isLocaleReady ? <SkeletonText invisible /> : props.subtitle}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -821,8 +826,8 @@ export function ShellMain(props: LayoutProps) {
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
props.backPath
|
props.backPath
|
||||||
? "relative"
|
? "relative"
|
||||||
: "fixed bottom-[88px] z-40 ltr:right-4 rtl:left-4 sm:z-auto md:ltr:right-0 md:rtl:left-0",
|
: "fixed bottom-[88px] z-40 ltr:right-4 rtl:left-4 md:z-auto md:ltr:right-0 md:rtl:left-0",
|
||||||
"flex-shrink-0 sm:relative sm:bottom-auto sm:right-auto"
|
"flex-shrink-0 md:relative md:bottom-auto md:right-auto"
|
||||||
)}>
|
)}>
|
||||||
{props.CTA}
|
{props.CTA}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -687,7 +687,7 @@ export const viewerTeamsRouter = router({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
type UserMap = Record<number, (typeof teams)[number]["members"][number]["user"]>;
|
type UserMap = Record<number, typeof teams[number]["members"][number]["user"]>;
|
||||||
// flattern users to be unique by id
|
// flattern users to be unique by id
|
||||||
const users = teams
|
const users = teams
|
||||||
.flatMap((t) => t.members)
|
.flatMap((t) => t.members)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const buttonClasses = cva(
|
|||||||
variant: {
|
variant: {
|
||||||
button: "",
|
button: "",
|
||||||
icon: "flex justify-center",
|
icon: "flex justify-center",
|
||||||
fab: "rounded-full justify-center sm:rounded-md radix-state-open:rotate-45 sm:radix-state-open:rotate-0 transition-transform radix-state-open:shadown-none radix-state-open:ring-0 !shadow-none",
|
fab: "rounded-full justify-center md:rounded-md radix-state-open:rotate-45 md:radix-state-open:rotate-0 transition-transform radix-state-open:shadown-none radix-state-open:ring-0 !shadow-none",
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
primary: "text-white dark:text-black",
|
primary: "text-white dark:text-black",
|
||||||
@@ -149,7 +149,7 @@ const buttonClasses = cva(
|
|||||||
{
|
{
|
||||||
variant: "fab",
|
variant: "fab",
|
||||||
size: "base",
|
size: "base",
|
||||||
className: "h-14 sm:h-9 sm:w-auto sm:px-4 sm:py-2.5",
|
className: "h-14 md:h-9 md:w-auto md:px-4 md:py-2.5",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
@@ -204,8 +204,8 @@ export const Button = forwardRef<HTMLAnchorElement | HTMLButtonElement, ButtonPr
|
|||||||
<>
|
<>
|
||||||
{variant === "fab" ? (
|
{variant === "fab" ? (
|
||||||
<>
|
<>
|
||||||
<StartIcon className="hidden h-4 w-4 stroke-[1.5px] ltr:mr-2 rtl:ml-2 sm:inline-flex" />
|
<StartIcon className="hidden h-4 w-4 stroke-[1.5px] ltr:mr-2 rtl:ml-2 md:inline-flex" />
|
||||||
<FiPlus className="inline h-6 w-6 sm:hidden" />
|
<FiPlus className="inline h-6 w-6 md:hidden" />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<StartIcon
|
<StartIcon
|
||||||
@@ -217,7 +217,7 @@ export const Button = forwardRef<HTMLAnchorElement | HTMLButtonElement, ButtonPr
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{variant === "fab" ? <span className="hidden sm:inline">{props.children}</span> : props.children}
|
{variant === "fab" ? <span className="hidden md:inline">{props.children}</span> : props.children}
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform">
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform">
|
||||||
<svg
|
<svg
|
||||||
@@ -238,8 +238,8 @@ export const Button = forwardRef<HTMLAnchorElement | HTMLButtonElement, ButtonPr
|
|||||||
<>
|
<>
|
||||||
{variant === "fab" ? (
|
{variant === "fab" ? (
|
||||||
<>
|
<>
|
||||||
<EndIcon className="-mr-1 hidden h-5 w-5 ltr:ml-2 rtl:-ml-1 rtl:mr-2 sm:inline" />
|
<EndIcon className="-mr-1 hidden h-5 w-5 ltr:ml-2 rtl:-ml-1 rtl:mr-2 md:inline" />
|
||||||
<FiPlus className="inline h-6 w-6 sm:hidden" />
|
<FiPlus className="inline h-6 w-6 md:hidden" />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<EndIcon
|
<EndIcon
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export const ControlComponent = <
|
|||||||
{...props}
|
{...props}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
"dark:bg-darkgray-100 dark:border-darkgray-300 border-gray-300 bg-white text-sm leading-4 placeholder:text-sm placeholder:font-normal focus-within:border-0 focus-within:ring-2 focus-within:ring-neutral-800 hover:border-neutral-400 dark:focus-within:ring-white"
|
"dark:bg-darkgray-100 dark:border-darkgray-300 !min-h-9 h-9 border-gray-300 bg-white text-sm leading-4 placeholder:text-sm placeholder:font-normal focus-within:border-0 focus-within:ring-2 focus-within:ring-neutral-800 hover:border-neutral-400 dark:focus-within:ring-white"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in new issue
Block a user