Feat/onboarding username (#3580)
* fix: remove duplicate button in textusernamefield * feat: adds username if not present in getting-started first onboarding step * fix: credentials * fix: typo * fix: user type * fix: type errors * Validates successRedirectUrl earlier with zod * Removes deprecated api endpoint * Reduces the data needed for the frontend on onboarding Co-authored-by: Agusti Fernandez Pardo <git@agusti.me> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
co-authored by
Agusti Fernandez Pardo
Peer Richelsen
Omar López
parent
54acf6d28f
commit
c1896fc833
@@ -245,7 +245,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
|
||||
)}
|
||||
|
||||
{(usernameIsAvailable || premiumUsername) && currentUsername !== inputUsernameValue && (
|
||||
<div className="mt-2 flex justify-end md:hidden">
|
||||
<div className="mt-2 flex justify-end sm:hidden">
|
||||
<ActionButtons index="mobile" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -149,7 +149,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
|
||||
{markAsError && <p className="mt-1 text-xs text-red-500">Username is already taken</p>}
|
||||
|
||||
{usernameIsAvailable && currentUsername !== inputUsernameValue && (
|
||||
<div className="mt-2 flex justify-end md:hidden">
|
||||
<div className="mt-2 flex justify-end sm:hidden">
|
||||
<ActionButtons index="mobile" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { EventType } from "@prisma/client";
|
||||
|
||||
import * as fetch from "@lib/core/http/fetch-wrapper";
|
||||
|
||||
type GetEventsResponse = { message: string; data: EventType[] };
|
||||
const getEventTypes = async () => {
|
||||
const response = await fetch.get<GetEventsResponse>("/api/event-type");
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export default getEventTypes;
|
||||
@@ -1,47 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { getSession } from "@lib/auth";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const session = await getSession({ req: req });
|
||||
|
||||
if (!session) {
|
||||
res.status(401).json({ message: "Not authenticated" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!session.user?.id) {
|
||||
console.error("Session is missing a user id");
|
||||
return res.status(500).json({ message: "Something went wrong" });
|
||||
}
|
||||
|
||||
if (req.method === "GET") {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: session.user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
eventTypes: {
|
||||
where: {
|
||||
team: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
length: true,
|
||||
schedulingType: true,
|
||||
slug: true,
|
||||
hidden: true,
|
||||
metadata: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).json({ message: "Events.", data: user?.eventTypes });
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,16 @@ import { useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
|
||||
import getApps from "@calcom/app-store/utils";
|
||||
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { DEFAULT_SCHEDULE } from "@calcom/lib/availability";
|
||||
import { DOCS_URL } from "@calcom/lib/constants";
|
||||
import { fetchUsername } from "@calcom/lib/fetchUsername";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import showToast from "@calcom/lib/notification";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import { isBrowserLocale24h } from "@calcom/lib/timeFormat";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
|
||||
import { Alert } from "@calcom/ui/Alert";
|
||||
import Button from "@calcom/ui/Button";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
@@ -27,8 +29,6 @@ import TimezoneSelect from "@calcom/ui/form/TimezoneSelect";
|
||||
import { Form } from "@calcom/ui/form/fields";
|
||||
|
||||
import { getSession } from "@lib/auth";
|
||||
import { DEFAULT_SCHEDULE } from "@lib/availability";
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
import { Schedule as ScheduleType } from "@lib/types/schedule";
|
||||
|
||||
@@ -36,8 +36,9 @@ import { ClientSuspense } from "@components/ClientSuspense";
|
||||
import Loader from "@components/Loader";
|
||||
import Schedule from "@components/availability/Schedule";
|
||||
import { CalendarListContainer } from "@components/integrations/CalendarListContainer";
|
||||
import { UsernameAvailability } from "@components/ui/UsernameAvailability";
|
||||
|
||||
import getEventTypes from "../lib/queries/event-types/get-event-types";
|
||||
import { TRPCClientErrorLike } from "@trpc/client";
|
||||
|
||||
type ScheduleFormValues = {
|
||||
schedule: ScheduleType;
|
||||
@@ -47,20 +48,36 @@ let mutationComplete: ((err: Error | null) => void) | null;
|
||||
|
||||
export default function Onboarding(props: inferSSRProps<typeof getServerSideProps>) {
|
||||
const { t } = useLocale();
|
||||
const { user } = props;
|
||||
const router = useRouter();
|
||||
const utils = trpc.useContext();
|
||||
const telemetry = useTelemetry();
|
||||
const [hasErrors, setHasErrors] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const { data: eventTypes } = trpc.useQuery(["viewer.eventTypes.list"]);
|
||||
const onSuccessMutation = async () => {
|
||||
showToast(t("your_user_profile_updated_successfully"), "success");
|
||||
setHasErrors(false); // dismiss any open errors
|
||||
await utils.invalidateQueries(["viewer.me"]);
|
||||
};
|
||||
|
||||
const onErrorMutation = (error: TRPCClientErrorLike<AppRouter>) => {
|
||||
setHasErrors(true);
|
||||
setErrorMessage(error.message);
|
||||
document?.getElementsByTagName("main")[0]?.scrollTo({ top: 0, behavior: "smooth" });
|
||||
};
|
||||
const mutation = trpc.useMutation("viewer.updateProfile", {
|
||||
onSuccess: async () => {
|
||||
setSubmitting(true);
|
||||
setEnteredName(nameRef.current?.value || "");
|
||||
setInputUsernameValue(usernameRef.current?.value || "");
|
||||
if (mutationComplete) {
|
||||
mutationComplete(null);
|
||||
mutationComplete = null;
|
||||
}
|
||||
setSubmitting(false);
|
||||
},
|
||||
onError: (err) => {
|
||||
onError: (err: TRPCClientErrorLike<AppRouter>) => {
|
||||
setError(new Error(err.message));
|
||||
if (mutationComplete) {
|
||||
mutationComplete(new Error(err.message));
|
||||
@@ -90,6 +107,9 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
|
||||
|
||||
const [isSubmitting, setSubmitting] = React.useState(false);
|
||||
const [enteredName, setEnteredName] = React.useState("");
|
||||
const [currentUsername, setCurrentUsername] = useState(user.username || undefined);
|
||||
const [inputUsernameValue, setInputUsernameValue] = useState(currentUsername);
|
||||
|
||||
const { status } = useSession();
|
||||
const loading = status === "loading";
|
||||
const [ready, setReady] = useState(false);
|
||||
@@ -98,7 +118,7 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
|
||||
|
||||
const updateUser = useCallback(
|
||||
async (data: Prisma.UserUpdateInput) => {
|
||||
const res = await fetch(`/api/user/${props.user.id}`, {
|
||||
const res = await fetch(`/api/user/${user.id}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({ data: { ...data } }),
|
||||
headers: {
|
||||
@@ -112,44 +132,29 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
|
||||
const responseData = await res.json();
|
||||
return responseData.data;
|
||||
},
|
||||
[props.user.id]
|
||||
[user.id]
|
||||
);
|
||||
|
||||
const createEventType = trpc.useMutation("viewer.eventTypes.create");
|
||||
|
||||
const createSchedule = trpc.useMutation("viewer.availability.schedule.create", {
|
||||
onError: (err) => {
|
||||
throw new Error(err.message);
|
||||
onError: (error: TRPCClientErrorLike<AppRouter>) => {
|
||||
throw new Error(error.message);
|
||||
},
|
||||
});
|
||||
|
||||
/** Name */
|
||||
const nameRef = useRef<HTMLInputElement>(null);
|
||||
/** Username */
|
||||
const usernameRef = useRef<HTMLInputElement>(null!);
|
||||
const bioRef = useRef<HTMLInputElement>(null);
|
||||
/** End Name */
|
||||
/** TimeZone */
|
||||
const [selectedTimeZone, setSelectedTimeZone] = useState(props.user.timeZone ?? dayjs.tz.guess());
|
||||
const [selectedTimeZone, setSelectedTimeZone] = useState(user.timeZone ?? dayjs.tz.guess());
|
||||
/** End TimeZone */
|
||||
|
||||
/** Onboarding Steps */
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const detectStep = () => {
|
||||
// Always set timezone if new user
|
||||
let step = 0;
|
||||
|
||||
const hasConfigureCalendar = props.integrations.some((integration) => integration.credential !== null);
|
||||
if (hasConfigureCalendar) {
|
||||
step = 2;
|
||||
}
|
||||
|
||||
const hasSchedules = props.schedules && props.schedules.length > 0;
|
||||
if (hasSchedules) {
|
||||
step = 3;
|
||||
}
|
||||
|
||||
setCurrentStep(step);
|
||||
};
|
||||
|
||||
const [currentStep, setCurrentStep] = useState(props.initialStep);
|
||||
const handleConfirmStep = async () => {
|
||||
try {
|
||||
setSubmitting(true);
|
||||
@@ -205,22 +210,15 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
|
||||
*/
|
||||
const completeOnboarding = async () => {
|
||||
setSubmitting(true);
|
||||
if (!props.eventTypes || props.eventTypes.length === 0) {
|
||||
const eventTypes = await getEventTypes();
|
||||
if (eventTypes.length === 0) {
|
||||
await Promise.all(
|
||||
DEFAULT_EVENT_TYPES.map(async (event) => {
|
||||
return createEventType.mutate(event);
|
||||
})
|
||||
);
|
||||
}
|
||||
if (eventTypes?.length === 0) {
|
||||
await Promise.all(
|
||||
DEFAULT_EVENT_TYPES.map(async (event) => {
|
||||
return createEventType.mutate(event);
|
||||
})
|
||||
);
|
||||
}
|
||||
// Write default timeformat to localStorage
|
||||
const browserTimeFormat = isBrowserLocale24h() ? 24 : 12;
|
||||
|
||||
await updateUser({
|
||||
completedOnboarding: true,
|
||||
timeFormat: browserTimeFormat,
|
||||
});
|
||||
|
||||
setSubmitting(false);
|
||||
@@ -301,7 +299,7 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
|
||||
token: values.token,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Type": "applishcation/json",
|
||||
},
|
||||
});
|
||||
if (response.status === 201) {
|
||||
@@ -314,6 +312,8 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
|
||||
});
|
||||
}
|
||||
})}>
|
||||
{hasErrors && <Alert severity="error" title={errorMessage} />}
|
||||
|
||||
<input
|
||||
onChange={async (e) => {
|
||||
formMethods.setValue("token", e.target.value);
|
||||
@@ -323,7 +323,7 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
|
||||
id="token"
|
||||
placeholder={t("access_token")}
|
||||
required
|
||||
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 text-sm focus:border-neutral-500 focus:outline-none focus:ring-neutral-500"
|
||||
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 focus:border-neutral-500 focus:outline-none focus:ring-neutral-500 sm:text-sm"
|
||||
/>
|
||||
<Button type="submit" className="mt-1 ml-4 h-10">
|
||||
{t("import")}
|
||||
@@ -341,6 +341,18 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
|
||||
</div>
|
||||
<form className="sm:mx-auto sm:w-full">
|
||||
<section className="space-y-8">
|
||||
{user.username !== "" && (
|
||||
<UsernameAvailability
|
||||
currentUsername={currentUsername}
|
||||
setCurrentUsername={setCurrentUsername}
|
||||
inputUsernameValue={inputUsernameValue}
|
||||
usernameRef={usernameRef}
|
||||
setInputUsernameValue={setInputUsernameValue}
|
||||
onSuccessMutation={onSuccessMutation}
|
||||
onErrorMutation={onErrorMutation}
|
||||
user={user}
|
||||
/>
|
||||
)}
|
||||
<fieldset>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
|
||||
{t("full_name")}
|
||||
@@ -352,9 +364,9 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
|
||||
id="name"
|
||||
autoComplete="given-name"
|
||||
placeholder={t("your_name")}
|
||||
defaultValue={props.user.name ?? enteredName}
|
||||
defaultValue={user.name ?? enteredName}
|
||||
required
|
||||
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 text-sm focus:border-neutral-500 focus:outline-none focus:ring-neutral-500"
|
||||
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 focus:border-neutral-500 focus:outline-none focus:ring-neutral-500 sm:text-sm"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
@@ -371,8 +383,8 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
|
||||
<TimezoneSelect
|
||||
id="timeZone"
|
||||
value={selectedTimeZone}
|
||||
onChange={({ value }) => setSelectedTimeZone(value)}
|
||||
className="mt-1 block w-full rounded-md border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
onChange={({ value }: { value: string }) => setSelectedTimeZone(value)}
|
||||
className="mt-1 block w-full rounded-md border-gray-300 focus:border-blue-500 focus:ring-blue-500 sm:text-sm"
|
||||
/>
|
||||
</fieldset>
|
||||
</section>
|
||||
@@ -398,6 +410,7 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
|
||||
|
||||
const userUpdateData = {
|
||||
name: nameRef.current?.value,
|
||||
username: usernameRef.current?.value,
|
||||
timeZone: selectedTimeZone,
|
||||
};
|
||||
|
||||
@@ -476,9 +489,9 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
|
||||
id="name"
|
||||
autoComplete="given-name"
|
||||
placeholder={t("your_name")}
|
||||
defaultValue={props.user.name || enteredName}
|
||||
defaultValue={user.name || enteredName}
|
||||
required
|
||||
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 text-sm focus:border-neutral-500 focus:outline-none focus:ring-neutral-500"
|
||||
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 focus:border-neutral-500 focus:outline-none focus:ring-neutral-500 sm:text-sm"
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
@@ -491,8 +504,8 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
|
||||
name="bio"
|
||||
id="bio"
|
||||
required
|
||||
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 text-sm focus:border-neutral-500 focus:outline-none focus:ring-neutral-500"
|
||||
defaultValue={props.user.bio || undefined}
|
||||
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 focus:border-neutral-500 focus:outline-none focus:ring-neutral-500 sm:text-sm"
|
||||
defaultValue={user.bio || undefined}
|
||||
/>
|
||||
<p className="mt-2 text-sm leading-tight text-gray-500 dark:text-white">
|
||||
{t("few_sentences_about_yourself")}
|
||||
@@ -522,7 +535,6 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
|
||||
/** End Onboarding Steps */
|
||||
|
||||
useEffect(() => {
|
||||
detectStep();
|
||||
setReady(true);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
@@ -584,6 +596,7 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
|
||||
className="justify-center"
|
||||
disabled={isSubmitting}
|
||||
onClick={debouncedHandleConfirmStep}
|
||||
EndIcon={Icon.ArrowRight}
|
||||
data-testid={`continue-button-${currentStep}`}>
|
||||
{steps[currentStep].confirmText}
|
||||
</Button>
|
||||
@@ -641,12 +654,41 @@ export async function getServerSideProps(context: NextPageContext) {
|
||||
timeZone: true,
|
||||
identityProvider: true,
|
||||
completedOnboarding: true,
|
||||
weekStart: true,
|
||||
hideBranding: true,
|
||||
theme: true,
|
||||
plan: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
metadata: true,
|
||||
timeFormat: true,
|
||||
allowDynamicBooking: true,
|
||||
selectedCalendars: {
|
||||
select: {
|
||||
externalId: true,
|
||||
integration: true,
|
||||
},
|
||||
},
|
||||
credentials: {
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
key: true,
|
||||
userId: true,
|
||||
appId: true,
|
||||
},
|
||||
},
|
||||
schedules: {
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
@@ -662,59 +704,18 @@ export async function getServerSideProps(context: NextPageContext) {
|
||||
};
|
||||
}
|
||||
|
||||
const credentials = await prisma.credential.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
key: true,
|
||||
userId: true,
|
||||
appId: true,
|
||||
},
|
||||
});
|
||||
|
||||
const integrations = getApps(credentials)
|
||||
const integrations = getApps(user.credentials)
|
||||
.filter((item) => item.type.endsWith("_calendar"))
|
||||
.map((item) => omit(item, "key"));
|
||||
|
||||
// get user's credentials + their connected integrations
|
||||
const calendarCredentials = getCalendarCredentials(credentials, user.id);
|
||||
// get all the connected integrations' calendars (from third party)
|
||||
const connectedCalendars = await getConnectedCalendars(calendarCredentials, user.selectedCalendars);
|
||||
|
||||
const eventTypes = await prisma.eventType.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
length: true,
|
||||
hidden: true,
|
||||
},
|
||||
});
|
||||
|
||||
const schedules = await prisma.schedule.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
const { schedules } = user;
|
||||
const hasConfigureCalendar = integrations.some((integration) => integration.credential !== null);
|
||||
const hasSchedules = schedules && schedules.length > 0;
|
||||
|
||||
return {
|
||||
props: {
|
||||
session,
|
||||
user,
|
||||
integrations,
|
||||
connectedCalendars,
|
||||
eventTypes,
|
||||
schedules,
|
||||
initialStep: hasSchedules ? (hasConfigureCalendar ? 2 : 3) : 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ model EventType {
|
||||
currency String @default("usd")
|
||||
slotInterval Int?
|
||||
metadata Json?
|
||||
/// @zod.custom(imports.successRedirectUrl)
|
||||
successRedirectUrl String?
|
||||
workflows WorkflowsOnEventTypes[]
|
||||
|
||||
|
||||
@@ -111,3 +111,23 @@ export const userMetadata = z
|
||||
intentUsername: z.string().optional(),
|
||||
})
|
||||
.nullable();
|
||||
|
||||
/**
|
||||
* Ensures that it is a valid HTTP URL
|
||||
* It automatically avoids
|
||||
* - XSS attempts through javascript:alert('hi')
|
||||
* - mailto: links
|
||||
*/
|
||||
export function assertValidUrl(url: string | null | undefined) {
|
||||
if (!url) return false;
|
||||
return url.startsWith("http://") && url.startsWith("https://");
|
||||
}
|
||||
|
||||
export const successRedirectUrl = z
|
||||
.string()
|
||||
.url()
|
||||
.refine(assertValidUrl, {
|
||||
path: ["successRedirectUrl"],
|
||||
message: "Invalid URL",
|
||||
})
|
||||
.nullish();
|
||||
|
||||
@@ -17,22 +17,6 @@ function isPeriodType(keyInput: string): keyInput is PeriodType {
|
||||
return Object.keys(PeriodType).includes(keyInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that it is a valid HTTP URL
|
||||
* It automatically avoids
|
||||
* - XSS attempts through javascript:alert('hi')
|
||||
* - mailto: links
|
||||
*/
|
||||
function assertValidUrl(url: string | null | undefined) {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
||||
throw new TRPCError({ code: "PARSE_ERROR", message: "Invalid URL" });
|
||||
}
|
||||
}
|
||||
|
||||
function handlePeriodType(periodType: string | undefined): PeriodType | undefined {
|
||||
if (typeof periodType !== "string") return undefined;
|
||||
const passedPeriodType = periodType.toUpperCase();
|
||||
@@ -102,9 +86,20 @@ const EventTypeUpdateInput = _EventTypeModel
|
||||
export const eventTypesRouter = createProtectedRouter()
|
||||
.query("list", {
|
||||
async resolve({ ctx }) {
|
||||
return await ctx.prisma.webhook.findMany({
|
||||
return await ctx.prisma.eventType.findMany({
|
||||
where: {
|
||||
userId: ctx.user.id,
|
||||
team: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
length: true,
|
||||
schedulingType: true,
|
||||
slug: true,
|
||||
hidden: true,
|
||||
metadata: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -269,7 +264,6 @@ export const eventTypesRouter = createProtectedRouter()
|
||||
hashedLink,
|
||||
...rest
|
||||
} = input;
|
||||
assertValidUrl(input.successRedirectUrl);
|
||||
const data: Prisma.EventTypeUpdateInput = rest;
|
||||
data.locations = locations ?? undefined;
|
||||
if (periodType) {
|
||||
|
||||
Reference in New Issue
Block a user