Applied stash to newly merged
parent
801fd6d2f8
commit
10cf649189
|
@ -1,11 +1,8 @@
|
|||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"editor.formatOnSave": true,
|
||||
// Auto-fix issues with ESLint when you save code changes
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"eslint.run": "onSave",
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"spellright.language": ["en"],
|
||||
"spellright.documentTypes": ["markdown"]
|
||||
|
|
|
@ -299,9 +299,9 @@ export default function Shell(props: {
|
|||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className="block min-h-[80px] justify-between px-4 sm:flex sm:px-6 md:px-8">
|
||||
<div className="block justify-between px-4 sm:flex sm:px-6 md:px-8">
|
||||
{props.HeadingLeftIcon && <div className="ltr:mr-4">{props.HeadingLeftIcon}</div>}
|
||||
<div className="mb-8 w-full">
|
||||
<div className={classNames("w-full", props.subtitle ? "mb-8" : "mb-2")}>
|
||||
<h1 className="font-cal mb-1 text-xl font-bold tracking-wide text-gray-900">
|
||||
{props.heading}
|
||||
</h1>
|
||||
|
|
|
@ -139,7 +139,7 @@ const ScheduleBlock = ({ name, day, weekday }: ScheduleBlockProps) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<fieldset className="flex min-h-[86px] flex-col justify-between space-y-2 py-5 sm:flex-row sm:space-y-0">
|
||||
<fieldset className="flex flex-col justify-between space-y-2 py-5 sm:flex-row sm:space-y-0">
|
||||
<div className="w-1/3">
|
||||
<label className="flex items-center space-x-2 rtl:space-x-reverse">
|
||||
<input
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { CheckCircleIcon, InformationCircleIcon, XCircleIcon } from "@heroicons/react/solid";
|
||||
import { CheckCircleIcon, ExclamationIcon, InformationCircleIcon, XCircleIcon } from "@heroicons/react/solid";
|
||||
import classNames from "classnames";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
|
@ -7,7 +7,7 @@ export interface AlertProps {
|
|||
message?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
className?: string;
|
||||
severity: "success" | "warning" | "error";
|
||||
severity: "success" | "warning" | "error" | "info";
|
||||
}
|
||||
export function Alert(props: AlertProps) {
|
||||
const { severity } = props;
|
||||
|
@ -19,6 +19,7 @@ export function Alert(props: AlertProps) {
|
|||
props.className,
|
||||
severity === "error" && "border-red-900 bg-red-50 text-red-800",
|
||||
severity === "warning" && "border-yellow-700 bg-yellow-50 text-yellow-700",
|
||||
severity === "info" && "border-sky-700 bg-sky-50 text-sky-700",
|
||||
severity === "success" && "bg-gray-900 text-white"
|
||||
)}>
|
||||
<div className="flex">
|
||||
|
@ -27,7 +28,10 @@ export function Alert(props: AlertProps) {
|
|||
<XCircleIcon className={classNames("h-5 w-5 text-red-400")} aria-hidden="true" />
|
||||
)}
|
||||
{severity === "warning" && (
|
||||
<InformationCircleIcon className={classNames("h-5 w-5 text-yellow-400")} aria-hidden="true" />
|
||||
<ExclamationIcon className={classNames("h-5 w-5 text-yellow-400")} aria-hidden="true" />
|
||||
)}
|
||||
{severity === "info" && (
|
||||
<InformationCircleIcon className={classNames("h-5 w-5 text-sky-400")} aria-hidden="true" />
|
||||
)}
|
||||
{severity === "success" && (
|
||||
<CheckCircleIcon className={classNames("h-5 w-5 text-gray-400")} aria-hidden="true" />
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import { PencilIcon } from "@heroicons/react/solid";
|
||||
import { useState } from "react";
|
||||
|
||||
const EditableHeading = ({ title }: { title: string }) => {
|
||||
const [editIcon, setEditIcon] = useState(true);
|
||||
return (
|
||||
<div className="group relative cursor-pointer" onClick={() => setEditIcon(false)}>
|
||||
{editIcon ? (
|
||||
<>
|
||||
<h1
|
||||
style={{ fontSize: 22, letterSpacing: "-0.0009em" }}
|
||||
className="inline pl-0 text-gray-900 focus:text-black group-hover:text-gray-500">
|
||||
{title}
|
||||
</h1>
|
||||
<PencilIcon className="ml-1 -mt-1 inline h-4 w-4 text-gray-700 group-hover:text-gray-500" />
|
||||
</>
|
||||
) : (
|
||||
<div style={{ marginBottom: -11 }}>
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
style={{ top: -6, fontSize: 22 }}
|
||||
required
|
||||
className="relative h-10 w-full cursor-pointer border-none bg-transparent pl-0 text-gray-900 hover:text-gray-700 focus:text-black focus:outline-none focus:ring-0"
|
||||
defaultValue={title}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditableHeading;
|
|
@ -1,8 +1,12 @@
|
|||
import { PencilIcon } from "@heroicons/react/solid";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import TimezoneSelect, { ITimezoneOption } from "react-timezone-select";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
import { DEFAULT_SCHEDULE } from "@lib/availability";
|
||||
import { HttpError } from "@lib/core/http/error";
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
import showToast from "@lib/notification";
|
||||
import { inferQueryOutput, trpc } from "@lib/trpc";
|
||||
|
@ -12,6 +16,7 @@ import Shell from "@components/Shell";
|
|||
import Schedule from "@components/availability/Schedule";
|
||||
import { Form } from "@components/form/fields";
|
||||
import Button from "@components/ui/Button";
|
||||
import EditableHeading from "@components/ui/EditableHeading";
|
||||
import Switch from "@components/ui/Switch";
|
||||
|
||||
type FormValues = {
|
||||
|
@ -20,23 +25,21 @@ type FormValues = {
|
|||
|
||||
export function AvailabilityForm(props: inferQueryOutput<"viewer.availability">) {
|
||||
const { t } = useLocale();
|
||||
const [timeZone, setTimeZone] = useState(props.timeZone);
|
||||
const router = useRouter();
|
||||
|
||||
const createSchedule = async ({ schedule }: FormValues) => {
|
||||
const res = await fetch(`/api/schedule`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ schedule, timeZone: props.timeZone }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error((await res.json()).message);
|
||||
}
|
||||
const responseData = await res.json();
|
||||
showToast(t("availability_updated_successfully"), "success");
|
||||
return responseData.data;
|
||||
};
|
||||
const updateMutation = trpc.useMutation("viewer.schedule.update", {
|
||||
onSuccess: async () => {
|
||||
await router.push("/availability");
|
||||
showToast(t("availability_updated_successfully"), "success");
|
||||
},
|
||||
onError: (err) => {
|
||||
if (err instanceof HttpError) {
|
||||
const message = `${err.statusCode}: ${err.message}`;
|
||||
showToast(message, "error");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
defaultValues: {
|
||||
|
@ -49,7 +52,11 @@ export function AvailabilityForm(props: inferQueryOutput<"viewer.availability">)
|
|||
<Form
|
||||
form={form}
|
||||
handleSubmit={async (values) => {
|
||||
await createSchedule(values);
|
||||
updateMutation.mutate({
|
||||
scheduleId: parseInt(router.query.schedule as string, 10),
|
||||
schedule: values.schedule,
|
||||
timeZone: timeZone !== props.timeZone ? timeZone : undefined,
|
||||
});
|
||||
}}
|
||||
className="col-span-3 space-y-2 lg:col-span-2">
|
||||
<div className="divide-y rounded-sm border border-gray-200 bg-white px-4 py-5 sm:p-6">
|
||||
|
@ -63,7 +70,7 @@ export function AvailabilityForm(props: inferQueryOutput<"viewer.availability">)
|
|||
<Button>{t("save")}</Button>
|
||||
</div>
|
||||
</Form>
|
||||
<div className="min-w-40 col-span-3 ml-2 lg:col-span-1">
|
||||
<div className="min-w-40 col-span-3 ml-2 space-y-4 lg:col-span-1">
|
||||
<Switch
|
||||
defaultChecked={!!props.isDefault}
|
||||
onCheckedChange={(isChecked) => {
|
||||
|
@ -71,6 +78,19 @@ export function AvailabilityForm(props: inferQueryOutput<"viewer.availability">)
|
|||
}}
|
||||
label={t("set_to_default")}
|
||||
/>
|
||||
<div>
|
||||
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
|
||||
{t("timezone")}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<TimezoneSelect
|
||||
id="timeZone"
|
||||
value={timeZone}
|
||||
onChange={(tz: ITimezoneOption) => setTimeZone(tz.value)}
|
||||
className="focus:border-brand mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 rounded-sm border border-gray-200 px-4 py-5 sm:p-6 ">
|
||||
<h3 className="text-base font-medium leading-6 text-gray-900">
|
||||
{t("something_doesnt_look_right")}
|
||||
|
@ -90,19 +110,25 @@ export function AvailabilityForm(props: inferQueryOutput<"viewer.availability">)
|
|||
}
|
||||
|
||||
export default function Availability() {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
const query = trpc.useQuery([
|
||||
"viewer.availability",
|
||||
{
|
||||
scheduleId: parseInt(router.query.availability as string),
|
||||
scheduleId: parseInt(router.query.schedule as string),
|
||||
},
|
||||
]);
|
||||
return (
|
||||
<div>
|
||||
<Shell heading={t("availability")} subtitle={t("configure_availability")}>
|
||||
<QueryCell query={query} success={({ data }) => <AvailabilityForm {...data} />} />
|
||||
</Shell>
|
||||
<QueryCell
|
||||
query={query}
|
||||
success={({ data }) => {
|
||||
return (
|
||||
<Shell heading={<EditableHeading title={data.schedule.name} />}>
|
||||
<AvailabilityForm {...data} />
|
||||
</Shell>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,13 +1,60 @@
|
|||
import { Schedule, Availability } from "@prisma/client";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
import { nameOfDay } from "@lib/core/i18n/weekday";
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
import { inferQueryOutput, trpc } from "@lib/trpc";
|
||||
|
||||
import Shell from "@components/Shell";
|
||||
import Button from "@components/ui/Button";
|
||||
|
||||
function scheduleAsString(schedule: Schedule, locale: string) {
|
||||
const weekSpan = (availability: Availability) => {
|
||||
const days = availability.days.slice(1).reduce(
|
||||
(days, day) => {
|
||||
if (days[days.length - 1].length === 1 && days[days.length - 1][0] === day - 1) {
|
||||
// append if the range is not complete (but the next day needs adding)
|
||||
days[days.length - 1].push(day);
|
||||
} else if (days[days.length - 1][days[days.length - 1].length - 1] === day - 1) {
|
||||
// range complete, overwrite if the last day directly preceeds the current day
|
||||
days[days.length - 1] = [days[days.length - 1][0], day];
|
||||
} else {
|
||||
// new range
|
||||
days.push([day]);
|
||||
}
|
||||
return days;
|
||||
},
|
||||
[[availability.days[0]]] as number[][]
|
||||
);
|
||||
return days
|
||||
.map((dayRange) => dayRange.map((day) => nameOfDay(locale, day, "short")).join(" - "))
|
||||
.join(", ");
|
||||
};
|
||||
|
||||
const timeSpan = (availability: Availability) => {
|
||||
return (
|
||||
new Intl.DateTimeFormat(locale, { hour: "numeric", minute: "numeric" }).format(availability.startTime) +
|
||||
" - " +
|
||||
new Intl.DateTimeFormat(locale, { hour: "numeric", minute: "numeric" }).format(availability.endTime)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{schedule.availability.map((availability: Availability) => (
|
||||
<>
|
||||
{weekSpan(availability)}, {timeSpan(availability)}
|
||||
<br />
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function AvailabilityList({ schedules }: inferQueryOutput<"viewer.availability.list">) {
|
||||
const { t } = useLocale();
|
||||
const { t, i18n } = useLocale();
|
||||
return (
|
||||
<div className="-mx-4 mb-16 overflow-hidden rounded-sm border border-gray-200 bg-white sm:mx-0">
|
||||
<ul className="divide-y divide-neutral-200" data-testid="schedules">
|
||||
|
@ -25,6 +72,9 @@ export function AvailabilityList({ schedules }: inferQueryOutput<"viewer.availab
|
|||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-neutral-500">
|
||||
{scheduleAsString(schedule, i18n.language)}
|
||||
</p>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -36,12 +86,19 @@ export function AvailabilityList({ schedules }: inferQueryOutput<"viewer.availab
|
|||
);
|
||||
}
|
||||
|
||||
export default function Availability() {
|
||||
export default function AvailabilityPage() {
|
||||
const { t } = useLocale();
|
||||
const query = trpc.useQuery(["viewer.availability.list"]);
|
||||
return (
|
||||
<div>
|
||||
<Shell heading={t("availability")} subtitle={t("configure_availability")}>
|
||||
<Shell
|
||||
heading={t("availability")}
|
||||
subtitle={t("configure_availability")}
|
||||
CTA={
|
||||
<Link href="/availability/9">
|
||||
<Button>New schedule</Button>
|
||||
</Link>
|
||||
}>
|
||||
<QueryCell query={query} success={({ data }) => <AvailabilityList {...data} />} />
|
||||
</Shell>
|
||||
</div>
|
||||
|
|
|
@ -19,6 +19,7 @@ import dayjs from "dayjs";
|
|||
import timezone from "dayjs/plugin/timezone";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { GetServerSidePropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
@ -28,6 +29,7 @@ import { JSONObject } from "superjson/dist/types";
|
|||
|
||||
import { StripeData } from "@ee/lib/stripe/server";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
import { asStringOrThrow, asStringOrUndefined } from "@lib/asStringOrNull";
|
||||
import { getSession } from "@lib/auth";
|
||||
import { HttpError } from "@lib/core/http/error";
|
||||
|
@ -45,9 +47,9 @@ import Shell from "@components/Shell";
|
|||
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
|
||||
import { Form } from "@components/form/fields";
|
||||
import CustomInputTypeForm from "@components/pages/eventtypes/CustomInputTypeForm";
|
||||
import { Alert } from "@components/ui/Alert";
|
||||
import Button from "@components/ui/Button";
|
||||
import InfoBadge from "@components/ui/InfoBadge";
|
||||
import { Scheduler } from "@components/ui/Scheduler";
|
||||
import Switch from "@components/ui/Switch";
|
||||
import CheckboxField from "@components/ui/form/CheckboxField";
|
||||
import CheckedSelect from "@components/ui/form/CheckedSelect";
|
||||
|
@ -91,6 +93,37 @@ const addDefaultLocationOptions = (
|
|||
});
|
||||
};
|
||||
|
||||
const AvailabilitySelect = () => {
|
||||
const query = trpc.useQuery(["viewer.availability.list"]);
|
||||
return (
|
||||
<QueryCell
|
||||
query={query}
|
||||
success={({ data }) => {
|
||||
const defaultSchedule = data.schedules.find((schedule) => schedule.isDefault) as {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
return (
|
||||
<Select
|
||||
name="schedule"
|
||||
defaultValue={{
|
||||
value: defaultSchedule.id,
|
||||
label: defaultSchedule.name,
|
||||
}}
|
||||
options={data.schedules.map((schedule) => ({
|
||||
value: schedule.id,
|
||||
label: schedule.name,
|
||||
}))}
|
||||
isSearchable={false}
|
||||
classNamePrefix="react-select"
|
||||
className="react-select-container focus:border-primary-500 focus:ring-primary-500 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
||||
const { t } = useLocale();
|
||||
const PERIOD_TYPES = [
|
||||
|
@ -1179,29 +1212,19 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
|||
</div>
|
||||
<div className="w-full">
|
||||
<Controller
|
||||
name="availability"
|
||||
name="schedule"
|
||||
control={formMethods.control}
|
||||
render={() => (
|
||||
<Scheduler
|
||||
setAvailability={(val) => {
|
||||
formMethods.setValue("availability", {
|
||||
openingHours: val.openingHours,
|
||||
dateOverrides: val.dateOverrides,
|
||||
});
|
||||
}}
|
||||
setTimeZone={(timeZone) => {
|
||||
formMethods.setValue("timeZone", timeZone);
|
||||
setSelectedTimeZone(timeZone);
|
||||
}}
|
||||
timeZone={selectedTimeZone}
|
||||
availability={availability.map((schedule) => ({
|
||||
...schedule,
|
||||
startTime: new Date(schedule.startTime),
|
||||
endTime: new Date(schedule.endTime),
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
render={() => <AvailabilitySelect />}
|
||||
/>
|
||||
<Link href="/availability">
|
||||
<a>
|
||||
<Alert
|
||||
className="mt-1 text-xs"
|
||||
severity="info"
|
||||
message="You can manage your schedules on the Availability page."
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -658,5 +658,6 @@
|
|||
"error_404": "Error 404",
|
||||
"set_to_default": "Set to Default",
|
||||
"requires_ownership_of_a_token": "Requires ownership of a token belonging to the following address:",
|
||||
"example_name": "John Doe"
|
||||
"example_name": "John Doe",
|
||||
"click_to_add_a_new_schedule": "Add a new schedule"
|
||||
}
|
|
@ -520,5 +520,8 @@
|
|||
"calendar": "Agenda",
|
||||
"not_installed": "Niet geïnstalleerd",
|
||||
"error_password_mismatch": "Wachtwoorden komen niet overeen.",
|
||||
"error_required_field": "Dit veld is verplicht."
|
||||
"error_required_field": "Dit veld is verplicht.",
|
||||
"default": "standaard keuze",
|
||||
"set_to_default": "Zet als standaard keuze",
|
||||
"click_here_to_add_a_new_schedule": "Klik hier om een nieuwe planning aan te maken"
|
||||
}
|
|
@ -5,6 +5,7 @@ import { z } from "zod";
|
|||
|
||||
import { checkPremiumUsername } from "@ee/lib/core/checkPremiumUsername";
|
||||
|
||||
import { getAvailabilityFromSchedule } from "@lib/availability";
|
||||
import { checkRegularUsername } from "@lib/core/checkRegularUsername";
|
||||
import { getCalendarCredentials, getConnectedCalendars } from "@lib/integrations/calendar/CalendarManager";
|
||||
import { ALL_INTEGRATIONS } from "@lib/integrations/getIntegrations";
|
||||
|
@ -636,6 +637,45 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
};
|
||||
},
|
||||
})
|
||||
.mutation("schedule.update", {
|
||||
input: z.object({
|
||||
scheduleId: z.number(),
|
||||
timeZone: z.string().optional(),
|
||||
schedule: z.array(
|
||||
z.array(
|
||||
z.object({
|
||||
start: z.date(),
|
||||
end: z.date(),
|
||||
})
|
||||
)
|
||||
),
|
||||
}),
|
||||
async resolve({ input, ctx }) {
|
||||
const { user, prisma } = ctx;
|
||||
const availability = getAvailabilityFromSchedule(input.schedule);
|
||||
await prisma.schedule.update({
|
||||
where: {
|
||||
id: input.scheduleId,
|
||||
},
|
||||
data: {
|
||||
availability: {
|
||||
deleteMany: {
|
||||
scheduleId: {
|
||||
equals: input.scheduleId,
|
||||
},
|
||||
},
|
||||
createMany: {
|
||||
data: availability.map((schedule) => ({
|
||||
days: schedule.days,
|
||||
startTime: schedule.startTime,
|
||||
endTime: schedule.endTime,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
})
|
||||
.mutation("updateProfile", {
|
||||
input: z.object({
|
||||
username: z.string().optional(),
|
||||
|
|
|
@ -38,7 +38,9 @@ export function getAvailabilityFromSchedule(schedule: Schedule): Availability[]
|
|||
let idx;
|
||||
if (
|
||||
(idx = availability.findIndex(
|
||||
(schedule) => schedule.startTime === time.start && schedule.endTime === time.end
|
||||
(schedule) =>
|
||||
schedule.startTime.toString() === time.start.toString() &&
|
||||
schedule.endTime.toString() === time.end.toString()
|
||||
)) !== -1
|
||||
) {
|
||||
availability[idx].days.push(day);
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
import * as z from "zod"
|
||||
import * as imports from "../zod-utils"
|
||||
import { CompleteUser, UserModel, CompleteEventType, EventTypeModel } from "./index"
|
||||
import { CompleteUser, UserModel, CompleteEventType, EventTypeModel, CompleteSchedule, ScheduleModel } from "./index"
|
||||
|
||||
export const _AvailabilityModel = z.object({
|
||||
id: z.number().int(),
|
||||
label: z.string().nullish(),
|
||||
userId: z.number().int().nullish(),
|
||||
eventTypeId: z.number().int().nullish(),
|
||||
days: z.number().int().array(),
|
||||
startTime: z.date(),
|
||||
endTime: z.date(),
|
||||
date: z.date().nullish(),
|
||||
scheduleId: z.number().int().nullish(),
|
||||
})
|
||||
|
||||
export interface CompleteAvailability extends z.infer<typeof _AvailabilityModel> {
|
||||
user?: CompleteUser | null
|
||||
eventType?: CompleteEventType | null
|
||||
Schedule?: CompleteSchedule | null
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,4 +27,5 @@ export interface CompleteAvailability extends z.infer<typeof _AvailabilityModel>
|
|||
export const AvailabilityModel: z.ZodSchema<CompleteAvailability> = z.lazy(() => _AvailabilityModel.extend({
|
||||
user: UserModel.nullish(),
|
||||
eventType: EventTypeModel.nullish(),
|
||||
Schedule: ScheduleModel.nullish(),
|
||||
}))
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import { _EventTypeModel } from "./eventtype";
|
||||
|
||||
const createEventTypeBaseInput = _EventTypeModel
|
||||
.pick({
|
||||
title: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
length: true,
|
||||
teamId: true,
|
||||
schedulingType: true,
|
||||
})
|
||||
.refine((data) => (data.teamId ? data.teamId && data.schedulingType : true), {
|
||||
path: ["schedulingType"],
|
||||
message: "You must select a scheduling type for team events",
|
||||
});
|
||||
|
||||
export const createEventTypeInput = createEventTypeBaseInput;
|
|
@ -1,24 +1,18 @@
|
|||
import * as z from "zod"
|
||||
import * as imports from "../zod-utils"
|
||||
import { CompleteUser, UserModel, CompleteEventType, EventTypeModel } from "./index"
|
||||
|
||||
// Helper schema for JSON fields
|
||||
type Literal = boolean | number | string
|
||||
type Json = Literal | { [key: string]: Json } | Json[]
|
||||
const literalSchema = z.union([z.string(), z.number(), z.boolean()])
|
||||
const jsonSchema: z.ZodSchema<Json> = z.lazy(() => z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]))
|
||||
import { CompleteUser, UserModel, CompleteEventType, EventTypeModel, CompleteAvailability, AvailabilityModel } from "./index"
|
||||
|
||||
export const _ScheduleModel = z.object({
|
||||
id: z.number().int(),
|
||||
userId: z.number().int().nullish(),
|
||||
userId: z.number().int(),
|
||||
eventTypeId: z.number().int().nullish(),
|
||||
title: z.string().nullish(),
|
||||
freeBusyTimes: jsonSchema,
|
||||
name: z.string(),
|
||||
})
|
||||
|
||||
export interface CompleteSchedule extends z.infer<typeof _ScheduleModel> {
|
||||
user?: CompleteUser | null
|
||||
user: CompleteUser
|
||||
eventType?: CompleteEventType | null
|
||||
availability: CompleteAvailability[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,6 +21,7 @@ export interface CompleteSchedule extends z.infer<typeof _ScheduleModel> {
|
|||
* NOTE: Lazy required in case of potential circular dependencies within schema
|
||||
*/
|
||||
export const ScheduleModel: z.ZodSchema<CompleteSchedule> = z.lazy(() => _ScheduleModel.extend({
|
||||
user: UserModel.nullish(),
|
||||
user: UserModel,
|
||||
eventType: EventTypeModel.nullish(),
|
||||
availability: AvailabilityModel.array(),
|
||||
}))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as z from "zod"
|
||||
import * as imports from "../zod-utils"
|
||||
import { IdentityProvider, UserPlan } from "@prisma/client"
|
||||
import { CompleteEventType, EventTypeModel, CompleteCredential, CredentialModel, CompleteMembership, MembershipModel, CompleteBooking, BookingModel, CompleteAvailability, AvailabilityModel, CompleteSelectedCalendar, SelectedCalendarModel, CompleteSchedule, ScheduleModel, CompleteWebhook, WebhookModel, CompleteDestinationCalendar, DestinationCalendarModel } from "./index"
|
||||
import { CompleteEventType, EventTypeModel, CompleteCredential, CredentialModel, CompleteMembership, MembershipModel, CompleteBooking, BookingModel, CompleteSchedule, ScheduleModel, CompleteSelectedCalendar, SelectedCalendarModel, CompleteAvailability, AvailabilityModel, CompleteWebhook, WebhookModel, CompleteDestinationCalendar, DestinationCalendarModel } from "./index"
|
||||
|
||||
// Helper schema for JSON fields
|
||||
type Literal = boolean | number | string
|
||||
|
@ -45,9 +45,9 @@ export interface CompleteUser extends z.infer<typeof _UserModel> {
|
|||
credentials: CompleteCredential[]
|
||||
teams: CompleteMembership[]
|
||||
bookings: CompleteBooking[]
|
||||
availability: CompleteAvailability[]
|
||||
schedule?: CompleteSchedule | null
|
||||
selectedCalendars: CompleteSelectedCalendar[]
|
||||
Schedule: CompleteSchedule[]
|
||||
availability: CompleteAvailability[]
|
||||
webhooks: CompleteWebhook[]
|
||||
destinationCalendar?: CompleteDestinationCalendar | null
|
||||
}
|
||||
|
@ -62,9 +62,9 @@ export const UserModel: z.ZodSchema<CompleteUser> = z.lazy(() => _UserModel.exte
|
|||
credentials: CredentialModel.array(),
|
||||
teams: MembershipModel.array(),
|
||||
bookings: BookingModel.array(),
|
||||
availability: AvailabilityModel.array(),
|
||||
schedule: ScheduleModel.nullish(),
|
||||
selectedCalendars: SelectedCalendarModel.array(),
|
||||
Schedule: ScheduleModel.array(),
|
||||
availability: AvailabilityModel.array(),
|
||||
webhooks: WebhookModel.array(),
|
||||
destinationCalendar: DestinationCalendarModel.nullish(),
|
||||
}))
|
||||
|
|
66
yarn.lock
66
yarn.lock
|
@ -2080,27 +2080,40 @@
|
|||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-dialog@^0.1.0":
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-0.1.5.tgz#4310659607f5ad0b8796623d5f7490dc47d3d295"
|
||||
integrity sha512-WftvXcQSszUphCTLQkkpBIkrYYU0IYqgIvACLQady4BN4YHDgdNlrwdg2ti9QrXgq1PZ+0S/6BIaA1dmSuRQ2g==
|
||||
"@radix-ui/react-dialog@0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-0.1.4.tgz#c2ac74abfe8404dbf7582a439c4e8c39ff569f3a"
|
||||
integrity sha512-EENTf+WVtRv4zhYf6Lddd0he/HBUMp19T4J1YyYdN1uw/yOlRVx73etxa1GvQYsiVx1aE5DJ8C3lNLSWlZ0fag==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "0.1.0"
|
||||
"@radix-ui/react-compose-refs" "0.1.0"
|
||||
"@radix-ui/react-context" "0.1.1"
|
||||
"@radix-ui/react-dismissable-layer" "0.1.3"
|
||||
"@radix-ui/react-dismissable-layer" "0.1.2"
|
||||
"@radix-ui/react-focus-guards" "0.1.0"
|
||||
"@radix-ui/react-focus-scope" "0.1.3"
|
||||
"@radix-ui/react-id" "0.1.4"
|
||||
"@radix-ui/react-portal" "0.1.3"
|
||||
"@radix-ui/react-focus-scope" "0.1.2"
|
||||
"@radix-ui/react-id" "0.1.3"
|
||||
"@radix-ui/react-portal" "0.1.2"
|
||||
"@radix-ui/react-presence" "0.1.1"
|
||||
"@radix-ui/react-primitive" "0.1.3"
|
||||
"@radix-ui/react-primitive" "0.1.2"
|
||||
"@radix-ui/react-slot" "0.1.2"
|
||||
"@radix-ui/react-use-controllable-state" "0.1.0"
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "^2.4.0"
|
||||
|
||||
"@radix-ui/react-dismissable-layer@0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.1.2.tgz#10192ca6f28f1add825445afdfc23798cfd9342e"
|
||||
integrity sha512-qQ8lK2PW8P3qEjJw3cKauwPNOZ2eaIffp9/WDOh0BjKg0YOf3RdLB3BuFwfULs5avo5rC4u85D0NQuw0IsPplQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "0.1.0"
|
||||
"@radix-ui/react-context" "0.1.1"
|
||||
"@radix-ui/react-primitive" "0.1.2"
|
||||
"@radix-ui/react-use-body-pointer-events" "0.1.0"
|
||||
"@radix-ui/react-use-callback-ref" "0.1.0"
|
||||
"@radix-ui/react-use-escape-keydown" "0.1.0"
|
||||
|
||||
"@radix-ui/react-dismissable-layer@0.1.3":
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.1.3.tgz#d427c7520c3799d2b957e40e7d67045d96120356"
|
||||
|
@ -2135,6 +2148,16 @@
|
|||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-focus-scope@0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-0.1.2.tgz#a25da04a5e3ccccc34707837153a5dcb957e86fb"
|
||||
integrity sha512-oYtrTi5in6YWf2H6PEzpHu9upFZXJ1GDmWAZ3TE78d2YBstCykKNTRX/pAmNonxI8Men607eKNbVBHPROjprhA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "0.1.0"
|
||||
"@radix-ui/react-primitive" "0.1.2"
|
||||
"@radix-ui/react-use-callback-ref" "0.1.0"
|
||||
|
||||
"@radix-ui/react-focus-scope@0.1.3":
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-0.1.3.tgz#b1cc825b6190001d731417ed90d192d13b41bce1"
|
||||
|
@ -2145,6 +2168,14 @@
|
|||
"@radix-ui/react-primitive" "0.1.3"
|
||||
"@radix-ui/react-use-callback-ref" "0.1.0"
|
||||
|
||||
"@radix-ui/react-id@0.1.3":
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-0.1.3.tgz#007c41749628ec6c2c801ad0b8f0a454bb181ae3"
|
||||
integrity sha512-kA7erDg8S/bad9O6RTC+laW11gm2pvmEqlZA+rvzI+WEkQXStIgnVp+wA420be4q20UiLQw9cmIP01AIhDVdZQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-layout-effect" "0.1.0"
|
||||
|
||||
"@radix-ui/react-id@0.1.4", "@radix-ui/react-id@^0.1.0":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-0.1.4.tgz#4cd6126e6ac8a43ebe6d52948a068b797cc9ad71"
|
||||
|
@ -2203,6 +2234,15 @@
|
|||
"@radix-ui/react-use-size" "0.1.0"
|
||||
"@radix-ui/rect" "0.1.1"
|
||||
|
||||
"@radix-ui/react-portal@0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.1.2.tgz#a47059fe04ead0749d879818a7cbe9e7c751c4d5"
|
||||
integrity sha512-rLSe5aeJ7yWD6CuUyg+U9wCoMLleRyxQS67eqALzLW7zk0glB5q5x2ihAEjocZH2Tng9v5QkYaLyh2+sO3TMRA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "0.1.2"
|
||||
"@radix-ui/react-use-layout-effect" "0.1.0"
|
||||
|
||||
"@radix-ui/react-portal@0.1.3":
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.1.3.tgz#56826e789b3d4e37983f6d23666e3f1b1b9ee358"
|
||||
|
@ -2221,6 +2261,14 @@
|
|||
"@radix-ui/react-compose-refs" "0.1.0"
|
||||
"@radix-ui/react-use-layout-effect" "0.1.0"
|
||||
|
||||
"@radix-ui/react-primitive@0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.1.2.tgz#ca20fb15fc83124eead186333f917145e5e53378"
|
||||
integrity sha512-mVgeBkuNRZRCzHuDm2DWjZEIs3ntp4m3GtKWPXUn+SgmJXIIpVLt7KhvEmNkgXURq/DJgxG9GmJJMXkACioH/A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-slot" "0.1.2"
|
||||
|
||||
"@radix-ui/react-primitive@0.1.3":
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.1.3.tgz#585c35ef2ec06bab0ea9e0fc5c916e556661b881"
|
||||
|
|
Loading…
Reference in New Issue