diff --git a/.gitignore b/.gitignore index b93b0f6248..a2c49f1abb 100644 --- a/.gitignore +++ b/.gitignore @@ -72,4 +72,10 @@ dist lint-results # Yarn -yarn-error.log \ No newline at end of file +yarn-error.log + +.turbo +.next +.husky +.vscode +.env \ No newline at end of file diff --git a/auth/README.md b/auth/README.md new file mode 100644 index 0000000000..5b10e7d32a --- /dev/null +++ b/auth/README.md @@ -0,0 +1,15 @@ +# Signup for self-hosted EE + +In order to open the signup, please replace the contents of the file in + +`calcom/apps/web/pages/auth/signup.tsx` + +with the contents of the new file provided here in the @calcom/api repo at the folder: +`/auth/signup.tsx` + +Once you do, if you run your cal.com self-hosted instance and go to: +`http://localhost:3000/auth/signup` + +You should see the signup page, and your users should be able to create new accounts. + +Please, keep in mind this will create git conflict resolutions if you try to git pull from main, where the signup.tsx file will still be different. We will find a better workaround for this soon in order to make it pain free. diff --git a/auth/signup.tsx b/auth/signup.tsx new file mode 100644 index 0000000000..8dc735ddab --- /dev/null +++ b/auth/signup.tsx @@ -0,0 +1,182 @@ +import { GetServerSidePropsContext } from "next"; +import { signIn } from "next-auth/react"; +import { useRouter } from "next/router"; +import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; + +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { Alert } from "@calcom/ui/Alert"; +import Button from "@calcom/ui/Button"; +import { EmailField, PasswordField, TextField } from "@calcom/ui/form/fields"; +import { HeadSeo } from "@calcom/web/components/seo/head-seo"; +import { asStringOrNull } from "@calcom/web/lib/asStringOrNull"; +import { WEBSITE_URL, WEBAPP_URL } from "@calcom/web/lib/config/constants"; +import prisma from "@calcom/web/lib/prisma"; +import { isSAMLLoginEnabled } from "@calcom/web/lib/saml"; +import { IS_GOOGLE_LOGIN_ENABLED } from "@calcom/web/server/lib/constants"; +import { ssrInit } from "@calcom/web/server/lib/ssr"; + +type FormValues = { + username: string; + email: string; + password: string; + passwordcheck: string; + apiError: string; +}; + +export default function Signup() { + const { t } = useLocale(); + const router = useRouter(); + const methods = useForm(); + const { + register, + formState: { errors, isSubmitting }, + } = methods; + + const handleErrors = async (resp: Response) => { + if (!resp.ok) { + const err = await resp.json(); + throw new Error(err.message); + } + }; + + const signUp: SubmitHandler = async (data) => { + await fetch("/api/auth/signup", { + body: JSON.stringify({ + ...data, + }), + headers: { + "Content-Type": "application/json", + }, + method: "POST", + }) + .then(handleErrors) + .then( + async () => + await signIn("Cal.com", { + callbackUrl: (`${WEBSITE_URL}/${router.query.callbackUrl}` || "") as string, + }) + ) + .catch((err) => { + methods.setError("apiError", { message: err.message }); + }); + }; + + return ( +
+ +
+

+ {t("create_your_account")} +

+
+
+
+ +
+ {errors.apiError && } +
+ + {process.env.NEXT_PUBLIC_WEBSITE_URL}/ + + } + labelProps={{ className: "block text-sm font-medium text-gray-700" }} + className="block w-full min-w-0 flex-grow rounded-none rounded-r-sm border-gray-300 lowercase focus:border-black focus:ring-black sm:text-sm" + {...register("username")} + required + /> + + + + value === methods.watch("password") || (t("error_password_mismatch") as string), + })} + className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-black focus:outline-none focus:ring-black sm:text-sm" + /> +
+
+ + +
+ +
+
+
+
+ ); +} + +export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { + const ssr = await ssrInit(ctx); + const token = asStringOrNull(ctx.query.token); + if (token) { + const verificationToken = await prisma.verificationToken.findUnique({ + where: { + token, + }, + }); + if (verificationToken) { + const existingUser = await prisma.user.findFirst({ + where: { + AND: [ + { + email: verificationToken?.identifier, + }, + { + emailVerified: { + not: null, + }, + }, + ], + }, + }); + + if (existingUser) { + return { + redirect: { + permanent: false, + destination: "/auth/login?callbackUrl=" + `${WEBAPP_URL}/${ctx.query.callbackUrl}`, + }, + }; + } + } + } + + return { + props: { + isGoogleLoginEnabled: IS_GOOGLE_LOGIN_ENABLED, + isSAMLLoginEnabled, + trpcState: ssr.dehydrate(), + }, + }; +}; diff --git a/package.json b/package.json index 5677dc043f..14c34d18af 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build": "next build", "lint": "next lint", "lint-fix": "next lint --fix && prettier --write .", - "test": "jest --detectOpenHandles", + "test": "jest --detectOpenHandles --passWithNoTests", "type-check": "tsc --pretty --noEmit", "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist" }, diff --git a/pages/api/booking-references/[id].ts b/pages/api/booking-references/[id].ts index d86d0154dc..d1c28b8d04 100644 --- a/pages/api/booking-references/[id].ts +++ b/pages/api/booking-references/[id].ts @@ -64,11 +64,11 @@ export async function bookingReferenceById( * 404: * description: BookingReference was not found */ - console.log(safeQuery.data.id); + // console.log(safeQuery.data.id); const bookingReference = await prisma.bookingReference.findFirst().catch((error: Error) => { console.log("hoerr:", error); }); - console.log(bookingReference); + // console.log(bookingReference); if (!bookingReference) res.status(404).json({ message: "Booking reference not found" }); else res.status(200).json({ booking_reference: bookingReference }); @@ -92,6 +92,23 @@ export async function bookingReferenceById( * patch: * operationId: editBookingReferenceById * summary: Edit an existing booking reference + * requestBody: + * description: Edit an existing booking reference related to one of your bookings + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * days: + * type: array + * example: email@example.com + * startTime: + * type: string + * example: 1970-01-01T17:00:00.000Z + * endTime: + * type: string + * example: 1970-01-01T17:00:00.000Z * parameters: * - in: path * name: id @@ -112,7 +129,8 @@ export async function bookingReferenceById( const safeBody = schemaBookingEditBodyParams.safeParse(body); if (!safeBody.success) { - res.status(401).json({ message: "Invalid request body", error: safeBody.error }); + console.log(safeBody.error); + res.status(400).json({ message: "Invalid request body", error: safeBody.error }); return; // throw new Error("Invalid request body"); } diff --git a/pages/api/bookings/[id].ts b/pages/api/bookings/[id].ts index ff2f40671c..b094df73e9 100644 --- a/pages/api/bookings/[id].ts +++ b/pages/api/bookings/[id].ts @@ -30,6 +30,7 @@ export async function bookingById( * /bookings/{id}: * get: * summary: Find a booking + * operationId: getBookingById * parameters: * - in: path * name: id @@ -64,6 +65,24 @@ export async function bookingById( * /bookings/{id}: * patch: * summary: Edit an existing booking + * operationId: editBookingById + * requestBody: + * description: Edit an existing booking related to one of your event-types + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * title: + * type: string + * example: 15min + * startTime: + * type: string + * example: 1970-01-01T17:00:00.000Z + * endTime: + * type: string + * example: 1970-01-01T17:00:00.000Z * parameters: * - in: path * name: id @@ -84,14 +103,17 @@ export async function bookingById( case "PATCH": const safeBody = schemaBookingEditBodyParams.safeParse(body); if (!safeBody.success) { - throw new Error("Invalid request body"); + console.log(safeBody.error); + res.status(400).json({ message: "Bad request", error: safeBody.error }); + return; + // throw new Error("Invalid request body"); } await prisma.booking .update({ where: { id: safeQuery.data.id }, data: safeBody.data, }) - .then((data) => schemaBookingReadPublic.parse(data)) + // .then((data) => schemaBookingReadPublic.parse(data)) .then((booking) => res.status(200).json({ booking })) .catch((error: Error) => res.status(404).json({ @@ -105,6 +127,7 @@ export async function bookingById( * /bookings/{id}: * delete: * summary: Remove an existing booking + * operationId: removeBookingById * parameters: * - in: path * name: id diff --git a/pages/api/bookings/index.ts b/pages/api/bookings/index.ts index bd1595ff6c..1ee689b195 100644 --- a/pages/api/bookings/index.ts +++ b/pages/api/bookings/index.ts @@ -16,6 +16,7 @@ async function createOrlistAllBookings( * /bookings: * get: * summary: Find all bookings + * operationId: listBookings * tags: * - bookings * responses: @@ -41,6 +42,24 @@ async function createOrlistAllBookings( * /bookings: * post: * summary: Creates a new booking + * operationId: addBooking + * requestBody: + * description: Edit an existing booking related to one of your event-types + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * title: + * type: string + * example: 15min + * startTime: + * type: string + * example: 1970-01-01T17:00:00.000Z + * endTime: + * type: string + * example: 1970-01-01T17:00:00.000Z * tags: * - bookings * responses: @@ -52,18 +71,25 @@ async function createOrlistAllBookings( * description: Authorization information is missing or invalid. */ const safe = schemaBookingCreateBodyParams.safeParse(body); - if (!safe.success) throw new Error("Invalid request body"); + if (!safe.success) { + console.log(safe.error); + res.status(400).json({ message: "Bad request. Booking body is invalid." }); + return; + // throw new Error("Invalid request body"); + } const data = await prisma.booking.create({ data: { ...safe.data, userId } }); const booking = schemaBookingReadPublic.parse(data); if (booking) res.status(201).json({ booking, message: "Booking created successfully" }); else - (error: Error) => + (error: Error) => { + console.log(error); res.status(400).json({ message: "Could not create new booking", error, }); + }; } else res.status(405).json({ message: `Method ${method} not allowed` }); } diff --git a/tsconfig.json b/tsconfig.json index 4943d154a6..5622a85041 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,5 +15,5 @@ "**/*.ts", "**/*.tsx" ], - "exclude": ["node_modules", "templates"] + "exclude": ["node_modules", "templates", "auth"] }