diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 3edecfe1b6..0000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,37 +0,0 @@ -# This is a comment. -# Each line is a file pattern followed by one or more owners. - -# These owners will be the default owners for everything in -# the repo. Unless a later match takes precedence, -# @global-owner1 and @global-owner2 will be requested for -# review when someone opens a pull request. -# ⬇️ Removed so we can manually trigger checks -# * @calcom/reviewers - -.github/ @calcom/core -.husky/ @calcom/core -.vcode/ @calcom/core -.snaplet/ @calcom/core -deploy/ @calcom/core -scripts/ @calcom/core - -apps/docs @calcom/docs -apps/swagger @calcom/docs - -packages/app-store-cli @hariombalhara @calcom/core -packages/dayjs @zomars @calcom/core -packages/emails @zomars @calcom/core -packages/embeds @hariombalhara @calcom/core -packages/eslint-plugin @zomars @calcom/core -packages/features @zomars @calcom/core -packages/features/kbar @alishaz-polymath @calcom/core -packages/features/ee/impersonation @sean-brydon @calcom/core -packages/features/ee/payments @alannnc @calcom/core -packages/features/ee/workflows @CarinaWolli @calcom/core -packages/features/tips @PeerRich @calcom/core -# Trivial changes should be mergeable by core team as well -# Major UI changes must be reviewed by UI team -# Critical changes even if major can still be merged by core team if needed. -packages/ui @calcom/ui @calcom/core -packages/config @calcom/core -packages/app-store/routing-forms @hariombalhara @calcom/core diff --git a/.gitignore b/.gitignore index 28ce05562d..a89fca99ab 100644 --- a/.gitignore +++ b/.gitignore @@ -84,7 +84,6 @@ apps/storybook/build-storybook.log # Submodules .gitmodules -apps/api apps/website apps/console apps/auth diff --git a/.prettierignore b/.prettierignore index 7944833941..013d82b3a2 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,6 +7,7 @@ public *.lock *.log +*.test.ts .gitignore .npmignore diff --git a/apps/api/.env.example b/apps/api/.env.example new file mode 100644 index 0000000000..c5354bfd7d --- /dev/null +++ b/apps/api/.env.example @@ -0,0 +1,6 @@ +API_KEY_PREFIX=cal_ +DATABASE_URL="postgresql://postgres:@localhost:5450/calendso" +NEXT_PUBLIC_WEBAPP_URL=http://localhost:3000 + +# Get it in console.cal.com +CALCOM_LICENSE_KEY="" diff --git a/apps/api/.eslintrc.js b/apps/api/.eslintrc.js new file mode 100644 index 0000000000..0e71ec1eae --- /dev/null +++ b/apps/api/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require("@calcom/config/eslint-preset"); diff --git a/apps/api/.gitignore b/apps/api/.gitignore new file mode 100644 index 0000000000..a2c49f1abb --- /dev/null +++ b/apps/api/.gitignore @@ -0,0 +1,81 @@ +# .env file +.env + +# dependencies +node_modules +.pnp +.pnp.js + +# testing +coverage +/test-results/ +playwright/videos +playwright/screenshots +playwright/artifacts +playwright/results +playwright/reports/* + +# next.js +.next/ +out/ +build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local +.env.* +!.env.example + +# vercel +.vercel + +# Webstorm +.idea + +### VisualStudioCode template +.vscode/ +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Typescript +tsconfig.tsbuildinfo + +# turbo +.turbo + +# Prisma-Zod +packages/prisma/zod/*.ts + +# Builds +dist + +# Linting +lint-results + +# Yarn +yarn-error.log + +.turbo +.next +.husky +.vscode +.env \ No newline at end of file diff --git a/apps/docs/README.md b/apps/api/.gitkeep similarity index 100% rename from apps/docs/README.md rename to apps/api/.gitkeep diff --git a/apps/api/.prettierignore b/apps/api/.prettierignore new file mode 100644 index 0000000000..745ffcee46 --- /dev/null +++ b/apps/api/.prettierignore @@ -0,0 +1,5 @@ +.next/ +coverage/ +node_modules/ +tests/ +templates/ \ No newline at end of file diff --git a/apps/api/README.md b/apps/api/README.md new file mode 100644 index 0000000000..bd9217ba09 --- /dev/null +++ b/apps/api/README.md @@ -0,0 +1,225 @@ + +
+ + Logo + + + Read the API docs +
+ +# Cal.com Public API + +Welcome to the Public API ("/apps/api") of the Cal.com. + +This is the public REST api for cal.com. +It exposes CRUD Endpoints of all our most important resources. +And it makes it easy for anyone to integrate with Cal.com at the application programming level. + +## Stack + +- NextJS +- TypeScript +- Prisma + +## Development + +### Setup + +1. Clone the main repo (NOT THIS ONE) + + ```sh + git clone --recurse-submodules -j8 https://github.com/calcom/cal.com.git + ``` + +1. Go to the project folder + + ```sh + cd cal.com + ``` + +1. Copy `apps/api/.env.example` to `apps/api/.env` + + ```sh + cp apps/api/.env.example apps/api/.env + cp .env.example .env + ``` + +1. Install packages with yarn + + ```sh + yarn + ``` + +1. Start developing + + ```sh + yarn workspace @calcom/api dev + ``` + +1. Open [http://localhost:3002](http://localhost:3002) with your browser to see the result. + +## API Authentication (API Keys) + +The API requires a valid apiKey query param to be passed: +You can generate them at + +For example: + +```sh +GET https://api.cal.com/v1/users?apiKey={INSERT_YOUR_CAL.COM_API_KEY_HERE} +``` + +API Keys optionally may have expiry dates, if they are expired they won't work. If you create an apiKey without a userId relation, it won't work either for now as it relies on it to establish the current authenticated user. + +In the future we might add support for header Bearer Auth if we need to or if our customers require it. + +## Middlewares + +We don't use the new NextJS 12 Beta Middlewares, mainly because they run on the edge, and are not able to call prisma from api endpoints. We use instead a very nifty library called next-api-middleware that let's us use a similar approach building our own middlewares and applying them as we see fit. + +- withMiddleware() requires some default middlewares (verifyApiKey, etc...) + +## Next.config.js + +### Redirects + +Since this is an API only project, we don't want to have to type /api/ in all the routes, and so redirect all traffic to api, so a call to `api.cal.com/v1` will resolve to `api.cal.com/api/v1` + +Likewise, v1 is added as param query called version to final /api call so we don't duplicate endpoints in the future for versioning if needed. + +### Transpiling locally shared monorepo modules + +We're calling several packages from monorepo, this need to be transpiled before building since are not available as regular npm packages. That's what withTM does. + +```js + "@calcom/app-store", + "@calcom/prisma", + "@calcom/lib", + "@calcom/features", +``` + +## API Endpoint Validation + +We validate that only the supported methods are accepted at each endpoint, so in + +- **/endpoint**: you can only [GET] (all) and [POST] (create new) +- **/endpoint/id**: you can read create and edit [GET, PATCH, DELETE] + +### Zod Validations + +The API uses `zod` library like our main web repo. It validates that either GET query parameters or POST body content's are valid and up to our spec. It gives errors when parsing result's with schemas and failing validation. + +We use it in several ways, but mainly, we first import the auto-generated schema from @calcom/prisma for each model, which lives in `lib/validations/` + +We have some shared validations which several resources require, like baseApiParams which parses apiKey in all requests, or querIdAsString or TransformParseInt which deal with the id's coming from req.query. + +- **[*]BaseBodyParams** that omits any values from the model that are too sensitive or we don't want to pick when creating a new resource like id, userId, etc.. (those are gotten from context or elswhere) + +- **[*]Public** that also omits any values that we don't want to expose when returning the model as a response, which we parse against before returning all resources. + +- **[*]BodyParams** which merges both `[*]BaseBodyParams.merge([*]RequiredParams);` + +### Next Validations + +[Next-Validations Docs](https://next-validations.productsway.com/) +[Next-Validations Repo](https://github.com/jellydn/next-validations) +We also use this useful helper library that let us wrap our endpoints in a validate HOC that checks the req against our validation schema built out with zod for either query and / or body's requests. + +## Testing with Jest + node-mocks-http + +We aim to provide a fully tested API for our peace of mind, this is accomplished by using jest + node-mocks-http + +## Endpoints matrix + +| resource | get [id] | get all | create | edit | delete | +| --------------------- | -------- | ------- | ------ | ---- | ------ | +| attendees | ✅ | ✅ | ✅ | ✅ | ✅ | +| availabilities | ✅ | ✅ | ✅ | ✅ | ✅ | +| booking-references | ✅ | ✅ | ✅ | ✅ | ✅ | +| event-references | ✅ | ✅ | ✅ | ✅ | ✅ | +| destination-calendars | ✅ | ✅ | ✅ | ✅ | ✅ | +| custom-inputs | ✅ | ✅ | ✅ | ✅ | ✅ | +| event-types | ✅ | ✅ | ✅ | ✅ | ✅ | +| memberships | ✅ | ✅ | ✅ | ✅ | ✅ | +| payments | ✅ | ✅ | ❌ | ❌ | ❌ | +| schedules | ✅ | ✅ | ✅ | ✅ | ✅ | +| selected-calendars | ✅ | ✅ | ✅ | ✅ | ✅ | +| teams | ✅ | ✅ | ✅ | ✅ | ✅ | +| users | ✅ | 👤[1] | ✅ | ✅ | ✅ | + +## Models from database that are not exposed + +mostly because they're deemed too sensitive can be revisited if needed. Also they are expected to be used via cal's webapp. + +- [ ] Api Keys +- [ ] Credentials +- [ ] Webhooks +- [ ] ResetPasswordRequest +- [ ] VerificationToken +- [ ] ReminderMail + +## Documentation (OpenAPI) + +You will see that each endpoint has a comment at the top with the annotation `@swagger` with the documentation of the endpoint, **please update it if you change the code!** This is what auto-generates the OpenAPI spec by collecting the YAML in each endpoint and parsing it in /docs alongside the json-schema (auto-generated from prisma package, not added to code but manually for now, need to fix later) + +### @calcom/apps/swagger + +The documentation of the API lives inside the code, and it's auto-generated, the only endpoints that return without a valid apiKey are the homepage, with a JSON message redirecting you to the docs. and the /docs endpoint, which returns the OpenAPI 3.0 JSON Spec. Which SwaggerUi then consumes and generates the docs on. + +## Deployment + +`scripts/vercel-deploy.sh` +The API is deployed to vercel.com, it uses a similar deployment script to website or webapp, and requires transpilation of several shared packages that are part of our turborepo ["app-store", "prisma", "lib", "ee"] +in order to build and deploy properly. + +## Envirorment variables + +### Required + +DATABASE_URL=DATABASE_URL="postgresql://postgres:@localhost:5450/calendso" + +## Optional + +API*KEY_PREFIX=cal*# This can be changed per envirorment so cal*test* for staging for example. + +> If you're self-hosting under our commercial license, you can use any prefix you want for api keys. either leave the default cal\_ (not providing any envirorment variable) or modify it + +**Ensure that while testing swagger, API project should be run in production mode** +We make sure of this by not using next in dev, but next build && next start, if you want hot module reloading and such when developing, please use yarn run next directly on apps/api. + +See . Here in dev mode OPTIONS method is hardcoded to return only GET and OPTIONS as allowed method. Running in Production mode would cause this file to be not used. This is hot-reloading logic only. +To remove this limitation, we need to ensure that on local endpoints are requested by swagger at /api/v1 and not /v1 + +## Hosted api through cal.com + +> _❗ WARNING: This is still experimental and not fully implemented yet❗_ + +Go to console.cal.com +Add a deployment or go to an existing one. +Activate API or Admin addon +Provide your `DATABASE_URL` +Now you can call api.cal.com?key=CALCOM_LICENSE_KEY, which will connect to your own databaseUrl. + +## How to deploy + +We recommend deploying API in vercel. + +There's some settings that you'll need to setup. + +Under Vercel > Your API Project > Settings + +In General > Build & Development Settings +BUILD COMMAND: `yarn turbo run build --scope=@calcom/api --include-dependencies --no-deps` +OUTPUT DIRECTORY: `apps/api/.next` + +In Git > Ignored Build Step + +Add this command: `./scripts/vercel-deploy.sh` + +See `scripts/vercel-deploy.sh` for more info on how the deployment is done. + +> _❗ IMORTANT: If you're forking the API repo you will need to update the URLs in both the main repo [`.gitmodules`](https://github.com/calcom/cal.com/blob/main/.gitmodules#L7) and this repo [`./scripts/vercel-deploy.sh`](https://github.com/calcom/api/blob/main/scripts/vercel-deploy.sh#L3) ❗_ + +## Environment variables + +Lastly API requires an env var for `DATABASE_URL` and `CALCOM_LICENSE_KEY` diff --git a/apps/api/lib/constants.ts b/apps/api/lib/constants.ts new file mode 100644 index 0000000000..fb04db4a1c --- /dev/null +++ b/apps/api/lib/constants.ts @@ -0,0 +1 @@ +export const PRISMA_CLIENT_CACHING_TIME = 1000 * 60 * 60 * 24; // one day in ms diff --git a/apps/api/lib/helpers/addRequestid.ts b/apps/api/lib/helpers/addRequestid.ts new file mode 100644 index 0000000000..92d2d6919a --- /dev/null +++ b/apps/api/lib/helpers/addRequestid.ts @@ -0,0 +1,24 @@ +import { nanoid } from "nanoid"; +import type { NextMiddleware } from "next-api-middleware"; + +export const addRequestId: NextMiddleware = async (_req, res, next) => { + // Apply header with unique ID to every request + res.setHeader("Calcom-Response-ID", nanoid()); + // Add all headers here instead of next.config.js as it is throwing error( Cannot set headers after they are sent to the client) for OPTIONS method + // It is known to happen only in Dev Mode. + res.setHeader("Access-Control-Allow-Credentials", "true"); + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS, PATCH, DELETE, POST, PUT"); + res.setHeader( + "Access-Control-Allow-Headers", + "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, Content-Type, api_key, Authorization" + ); + + // Ensure all OPTIONS request are automatically successful. Headers are already set above. + if (_req.method === "OPTIONS") { + res.status(200).end(); + return; + } + // Let remaining middleware and API route execute + await next(); +}; diff --git a/apps/api/lib/helpers/captureErrors.ts b/apps/api/lib/helpers/captureErrors.ts new file mode 100644 index 0000000000..0d850b052b --- /dev/null +++ b/apps/api/lib/helpers/captureErrors.ts @@ -0,0 +1,14 @@ +import * as Sentry from "@sentry/nextjs"; +import type { NextMiddleware } from "next-api-middleware"; + +export const captureErrors: NextMiddleware = async (_req, res, next) => { + try { + // Catch any errors that are thrown in remaining + // middleware and the API route handler + await next(); + } catch (error) { + Sentry.captureException(error); + console.log(error); + res.status(400).json({ message: "Something went wrong", error }); + } +}; diff --git a/apps/api/lib/helpers/customPrisma.ts b/apps/api/lib/helpers/customPrisma.ts new file mode 100644 index 0000000000..a30cb6bcb6 --- /dev/null +++ b/apps/api/lib/helpers/customPrisma.ts @@ -0,0 +1,43 @@ +import { PrismaClient } from "@prisma/client"; +import type { NextMiddleware } from "next-api-middleware"; + +import { CONSOLE_URL } from "@calcom/lib/constants"; + +const LOCAL_CONSOLE_URL = process.env.NEXT_PUBLIC_CONSOLE_URL || CONSOLE_URL; + +// This replaces the prisma client for the custom one if the key is valid +export const customPrismaClient: NextMiddleware = async (req, res, next) => { + const { + query: { key }, + } = req; + // If no custom api Id is provided, attach to request the regular cal.com prisma client. + if (!key) { + req.prisma = new PrismaClient(); + await next(); + return; + } + + // If we have a key, we check if the deployment matching the key, has a databaseUrl value set. + const databaseUrl = await fetch(`${LOCAL_CONSOLE_URL}/api/deployments/database?key=${key}`) + .then((res) => res.json()) + .then((res) => res.databaseUrl); + + if (!databaseUrl) { + res.status(400).json({ error: "no databaseUrl set up at your instance yet" }); + return; + } + req.prisma = new PrismaClient({ datasources: { db: { url: databaseUrl } } }); + /* @note: + In order to skip verifyApiKey for customPrisma requests, + we pass isAdmin true, and userId 0, if we detect them later, + we skip verifyApiKey logic and pass onto next middleware instead. + */ + req.isAdmin = true; + req.isCustomPrisma = true; + // We don't need the key from here and on. Prevents unrecognized key errors. + delete req.query.key; + await next(); + await req.prisma.$disconnect(); + // @ts-expect-error testing + delete req.prisma; +}; diff --git a/apps/api/lib/helpers/extendRequest.ts b/apps/api/lib/helpers/extendRequest.ts new file mode 100644 index 0000000000..d6fff1cbd0 --- /dev/null +++ b/apps/api/lib/helpers/extendRequest.ts @@ -0,0 +1,9 @@ +import type { NextMiddleware } from "next-api-middleware"; + +export const extendRequest: NextMiddleware = async (req, res, next) => { + req.pagination = { + take: 100, + skip: 0, + }; + await next(); +}; diff --git a/apps/api/lib/helpers/httpMethods.ts b/apps/api/lib/helpers/httpMethods.ts new file mode 100644 index 0000000000..8003a71984 --- /dev/null +++ b/apps/api/lib/helpers/httpMethods.ts @@ -0,0 +1,32 @@ +import type { NextMiddleware } from "next-api-middleware"; + +export const httpMethod = (allowedHttpMethod: "GET" | "POST" | "PATCH" | "DELETE"): NextMiddleware => { + return async function (req, res, next) { + if (req.method === allowedHttpMethod || req.method == "OPTIONS") { + await next(); + } else { + res.status(405).json({ message: `Only ${allowedHttpMethod} Method allowed` }); + res.end(); + } + }; +}; +// Made this so we can support several HTTP Methods in one route and use it there. +// Could be further extracted into a third function or refactored into one. +// that checks if it's just a string or an array and apply the correct logic to both cases. +export const httpMethods = (allowedHttpMethod: string[]): NextMiddleware => { + return async function (req, res, next) { + if (allowedHttpMethod.some((method) => method === req.method || req.method == "OPTIONS")) { + await next(); + } else { + res.status(405).json({ message: `Only ${allowedHttpMethod} Method allowed` }); + res.end(); + } + }; +}; + +export const HTTP_POST = httpMethod("POST"); +export const HTTP_GET = httpMethod("GET"); +export const HTTP_PATCH = httpMethod("PATCH"); +export const HTTP_DELETE = httpMethod("DELETE"); +export const HTTP_GET_DELETE_PATCH = httpMethods(["GET", "DELETE", "PATCH"]); +export const HTTP_GET_OR_POST = httpMethods(["GET", "POST"]); diff --git a/apps/api/lib/helpers/safeParseJSON.ts b/apps/api/lib/helpers/safeParseJSON.ts new file mode 100644 index 0000000000..3681e42273 --- /dev/null +++ b/apps/api/lib/helpers/safeParseJSON.ts @@ -0,0 +1,14 @@ +export default function parseJSONSafely(str: string) { + try { + return JSON.parse(str); + } catch (e) { + console.error((e as Error).message); + if ((e as Error).message.includes("Unexpected token")) { + return { + success: false, + message: `Invalid JSON in the body: ${(e as Error).message}`, + }; + } + return {}; + } +} diff --git a/apps/api/lib/helpers/verifyApiKey.ts b/apps/api/lib/helpers/verifyApiKey.ts new file mode 100644 index 0000000000..45df30e5ef --- /dev/null +++ b/apps/api/lib/helpers/verifyApiKey.ts @@ -0,0 +1,47 @@ +import type { NextMiddleware } from "next-api-middleware"; + +import { hashAPIKey } from "@calcom/features/ee/api-keys/lib/apiKeys"; +import checkLicense from "@calcom/features/ee/common/server/checkLicense"; + +import { isAdminGuard } from "~/lib/utils/isAdmin"; + +// Used to check if the apiKey is not expired, could be extracted if reused. but not for now. +export const dateNotInPast = function (date: Date) { + const now = new Date(); + if (now.setHours(0, 0, 0, 0) > date.setHours(0, 0, 0, 0)) { + return true; + } +}; + +// This verifies the apiKey and sets the user if it is valid. +export const verifyApiKey: NextMiddleware = async (req, res, next) => { + const { prisma, isCustomPrisma, isAdmin } = req; + const hasValidLicense = await checkLicense(prisma); + if (!hasValidLicense) + return res.status(401).json({ error: "Invalid or missing CALCOM_LICENSE_KEY environment variable" }); + // If the user is an admin and using a license key (from customPrisma), skip the apiKey check. + if (isCustomPrisma && isAdmin) { + await next(); + return; + } + // Check if the apiKey query param is provided. + if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" }); + // remove the prefix from the user provided api_key. If no env set default to "cal_" + const strippedApiKey = `${req.query.apiKey}`.replace(process.env.API_KEY_PREFIX || "cal_", ""); + // Hash the key again before matching against the database records. + const hashedKey = hashAPIKey(strippedApiKey); + // Check if the hashed api key exists in database. + const apiKey = await prisma.apiKey.findUnique({ where: { hashedKey } }); + // If cannot find any api key. Throw a 401 Unauthorized. + if (!apiKey) return res.status(401).json({ error: "Your apiKey is not valid" }); + if (apiKey.expiresAt && dateNotInPast(apiKey.expiresAt)) { + return res.status(401).json({ error: "This apiKey is expired" }); + } + if (!apiKey.userId) return res.status(404).json({ error: "No user found for this apiKey" }); + // save the user id in the request for later use + req.userId = apiKey.userId; + // save the isAdmin boolean here for later use + req.isAdmin = await isAdminGuard(req); + req.isCustomPrisma = false; + await next(); +}; diff --git a/apps/api/lib/helpers/withMiddleware.ts b/apps/api/lib/helpers/withMiddleware.ts new file mode 100644 index 0000000000..6f7d7927c0 --- /dev/null +++ b/apps/api/lib/helpers/withMiddleware.ts @@ -0,0 +1,37 @@ +import { label } from "next-api-middleware"; + +import { addRequestId } from "./addRequestid"; +import { captureErrors } from "./captureErrors"; +import { customPrismaClient } from "./customPrisma"; +import { extendRequest } from "./extendRequest"; +import { + HTTP_POST, + HTTP_DELETE, + HTTP_PATCH, + HTTP_GET, + HTTP_GET_OR_POST, + HTTP_GET_DELETE_PATCH, +} from "./httpMethods"; +import { verifyApiKey } from "./verifyApiKey"; +import { withPagination } from "./withPagination"; + +const withMiddleware = label( + { + HTTP_GET_OR_POST, + HTTP_GET_DELETE_PATCH, + HTTP_GET, + HTTP_PATCH, + HTTP_POST, + HTTP_DELETE, + addRequestId, + verifyApiKey, + customPrismaClient, + extendRequest, + pagination: withPagination, + sentry: captureErrors, + }, + // The order here, determines the order of execution, put customPrismaClient before verifyApiKey always. + ["extendRequest", "sentry", "customPrismaClient", "verifyApiKey", "addRequestId"] // <-- Provide a list of middleware to call automatically +); + +export { withMiddleware }; diff --git a/apps/api/lib/helpers/withPagination.ts b/apps/api/lib/helpers/withPagination.ts new file mode 100644 index 0000000000..f29c08829b --- /dev/null +++ b/apps/api/lib/helpers/withPagination.ts @@ -0,0 +1,17 @@ +import type { NextMiddleware } from "next-api-middleware"; +import z from "zod"; + +const withPage = z.object({ + page: z.coerce.number().min(1).optional().default(1), + take: z.coerce.number().min(1).optional().default(10), +}); + +export const withPagination: NextMiddleware = async (req, _, next) => { + const { page, take } = withPage.parse(req.query); + const skip = (page - 1) * take; + req.pagination = { + take, + skip, + }; + await next(); +}; diff --git a/apps/api/lib/types.ts b/apps/api/lib/types.ts new file mode 100644 index 0000000000..3022c8c0c8 --- /dev/null +++ b/apps/api/lib/types.ts @@ -0,0 +1,187 @@ +import type { EventLocationType } from "@calcom/app-store/locations"; +import type { + Attendee, + Availability, + Booking, + BookingReference, + Credential, + DestinationCalendar, + EventType, + EventTypeCustomInput, + Membership, + Payment, + ReminderMail, + Schedule, + SelectedCalendar, + Team, + User, + Webhook, +} from "@calcom/prisma/client"; + +// Base response, used for all responses +export type BaseResponse = { + message?: string; + error?: Error; +}; + +// User +export type UserResponse = BaseResponse & { + user?: Partial; +}; + +export type UsersResponse = BaseResponse & { + users?: Partial[]; +}; + +// Team +export type TeamResponse = BaseResponse & { + team?: Partial; + owner?: Partial; +}; +export type TeamsResponse = BaseResponse & { + teams?: Partial[]; +}; + +// SelectedCalendar +export type SelectedCalendarResponse = BaseResponse & { + selected_calendar?: Partial; +}; +export type SelectedCalendarsResponse = BaseResponse & { + selected_calendars?: Partial[]; +}; + +// Attendee +export type AttendeeResponse = BaseResponse & { + attendee?: Partial; +}; +// Grouping attendees in booking arrays for now, +// later might remove endpoint and move to booking endpoint altogether. +export type AttendeesResponse = BaseResponse & { + attendees?: Partial[]; +}; + +// Availability +export type AvailabilityResponse = BaseResponse & { + availability?: Partial; +}; +export type AvailabilitiesResponse = BaseResponse & { + availabilities?: Partial[]; +}; + +// BookingReference +export type BookingReferenceResponse = BaseResponse & { + booking_reference?: Partial; +}; +export type BookingReferencesResponse = BaseResponse & { + booking_references?: Partial[]; +}; + +// Booking +export type BookingResponse = BaseResponse & { + booking?: Partial; +}; +export type BookingsResponse = BaseResponse & { + bookings?: Partial[]; +}; + +// Credential +export type CredentialResponse = BaseResponse & { + credential?: Partial; +}; +export type CredentialsResponse = BaseResponse & { + credentials?: Partial[]; +}; + +// DestinationCalendar +export type DestinationCalendarResponse = BaseResponse & { + destination_calendar?: Partial; +}; +export type DestinationCalendarsResponse = BaseResponse & { + destination_calendars?: Partial[]; +}; + +// Membership +export type MembershipResponse = BaseResponse & { + membership?: Partial; +}; +export type MembershipsResponse = BaseResponse & { + memberships?: Partial[]; +}; + +// EventTypeCustomInput +export type EventTypeCustomInputResponse = BaseResponse & { + event_type_custom_input?: Partial; +}; +export type EventTypeCustomInputsResponse = BaseResponse & { + event_type_custom_inputs?: Partial[]; +}; +// From rrule https://jakubroztocil.github.io/rrule freq +export enum Frequency { + "YEARLY", + "MONTHLY", + "WEEKLY", + "DAILY", + "HOURLY", + "MINUTELY", + "SECONDLY", +} +interface EventTypeExtended extends Omit { + recurringEvent: { + dtstart?: Date | undefined; + interval?: number | undefined; + count?: number | undefined; + freq?: Frequency | undefined; + until?: Date | undefined; + tzid?: string | undefined; + } | null; + locations: + | { + link?: string | undefined; + address?: string | undefined; + hostPhoneNumber?: string | undefined; + type: EventLocationType; + }[] + | null + // eslint-disable-next-line @typescript-eslint/no-explicit-any + | any; +} + +// EventType +export type EventTypeResponse = BaseResponse & { + event_type?: Partial; +}; +export type EventTypesResponse = BaseResponse & { + event_types?: Partial[]; +}; + +// Payment +export type PaymentResponse = BaseResponse & { + payment?: Partial; +}; +export type PaymentsResponse = BaseResponse & { + payments?: Partial[]; +}; + +// Schedule +export type ScheduleResponse = BaseResponse & { + schedule?: Partial; +}; +export type SchedulesResponse = BaseResponse & { + schedules?: Partial[]; +}; + +// Webhook +export type WebhookResponse = BaseResponse & { + webhook?: Partial | null; +}; +export type WebhooksResponse = BaseResponse & { + webhooks?: Partial[]; +}; + +// ReminderMail +export type ReminderMailResponse = BaseResponse & { + reminder_mail?: Partial; +}; +export type ReminderMailsResponse = BaseResponse & { + reminder_mails?: Partial[]; +}; diff --git a/apps/api/lib/utils/isAdmin.ts b/apps/api/lib/utils/isAdmin.ts new file mode 100644 index 0000000000..9b9e6f5342 --- /dev/null +++ b/apps/api/lib/utils/isAdmin.ts @@ -0,0 +1,9 @@ +import type { NextApiRequest } from "next"; + +import { UserPermissionRole } from "@calcom/prisma/enums"; + +export const isAdminGuard = async (req: NextApiRequest) => { + const { userId, prisma } = req; + const user = await prisma.user.findUnique({ where: { id: userId } }); + return user?.role === UserPermissionRole.ADMIN; +}; diff --git a/apps/api/lib/utils/stringifyISODate.ts b/apps/api/lib/utils/stringifyISODate.ts new file mode 100644 index 0000000000..cf21cd804b --- /dev/null +++ b/apps/api/lib/utils/stringifyISODate.ts @@ -0,0 +1,4 @@ +export const stringifyISODate = (date: Date | undefined): string => { + return `${date?.toISOString()}`; +}; +// TODO: create a function that takes an object and returns a stringified version of dates of it. diff --git a/apps/api/lib/validations/api-key.ts b/apps/api/lib/validations/api-key.ts new file mode 100644 index 0000000000..c0afb8eb16 --- /dev/null +++ b/apps/api/lib/validations/api-key.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; + +import { _ApiKeyModel as ApiKey } from "@calcom/prisma/zod"; + +export const apiKeyCreateBodySchema = ApiKey.pick({ + note: true, + expiresAt: true, + userId: true, +}) + .partial({ userId: true }) + .merge(z.object({ neverExpires: z.boolean().optional() })) + .strict(); + +export const apiKeyEditBodySchema = ApiKey.pick({ + note: true, +}) + .partial() + .strict(); + +export const apiKeyPublicSchema = ApiKey.pick({ + id: true, + userId: true, + note: true, + createdAt: true, + expiresAt: true, + lastUsedAt: true, + /** We might never want to expose these. Leaving this a as reminder. */ + // hashedKey: true, +}); diff --git a/apps/api/lib/validations/attendee.ts b/apps/api/lib/validations/attendee.ts new file mode 100644 index 0000000000..b1be1c2024 --- /dev/null +++ b/apps/api/lib/validations/attendee.ts @@ -0,0 +1,39 @@ +import { z } from "zod"; + +import { _AttendeeModel as Attendee } from "@calcom/prisma/zod"; + +import { timeZone } from "~/lib/validations/shared/timeZone"; + +export const schemaAttendeeBaseBodyParams = Attendee.pick({ + bookingId: true, + email: true, + name: true, + timeZone: true, +}); + +const schemaAttendeeCreateParams = z + .object({ + bookingId: z.number().int(), + email: z.string().email(), + name: z.string(), + timeZone: timeZone, + }) + .strict(); + +const schemaAttendeeEditParams = z + .object({ + name: z.string().optional(), + email: z.string().email().optional(), + timeZone: timeZone.optional(), + }) + .strict(); +export const schemaAttendeeEditBodyParams = schemaAttendeeBaseBodyParams.merge(schemaAttendeeEditParams); +export const schemaAttendeeCreateBodyParams = schemaAttendeeBaseBodyParams.merge(schemaAttendeeCreateParams); + +export const schemaAttendeeReadPublic = Attendee.pick({ + id: true, + bookingId: true, + name: true, + email: true, + timeZone: true, +}); diff --git a/apps/api/lib/validations/availability.ts b/apps/api/lib/validations/availability.ts new file mode 100644 index 0000000000..9d6fd20d1f --- /dev/null +++ b/apps/api/lib/validations/availability.ts @@ -0,0 +1,54 @@ +import { z } from "zod"; + +import { _AvailabilityModel as Availability, _ScheduleModel as Schedule } from "@calcom/prisma/zod"; +import { denullishShape } from "@calcom/prisma/zod-utils"; + +export const schemaAvailabilityBaseBodyParams = /** We make all these properties required */ denullishShape( + Availability.pick({ + /** We need to pass the schedule where this availability belongs to */ + scheduleId: true, + }) +); + +export const schemaAvailabilityReadPublic = Availability.pick({ + id: true, + startTime: true, + endTime: true, + date: true, + scheduleId: true, + days: true, + // eventTypeId: true /** @deprecated */, + // userId: true /** @deprecated */, +}).merge(z.object({ success: z.boolean().optional(), Schedule: Schedule.partial() }).partial()); + +const schemaAvailabilityCreateParams = z + .object({ + startTime: z.date().or(z.string()), + endTime: z.date().or(z.string()), + days: z.array(z.number()).optional(), + }) + .strict(); + +const schemaAvailabilityEditParams = z + .object({ + startTime: z.date().or(z.string()).optional(), + endTime: z.date().or(z.string()).optional(), + days: z.array(z.number()).optional(), + }) + .strict(); + +export const schemaAvailabilityEditBodyParams = schemaAvailabilityEditParams; + +export const schemaAvailabilityCreateBodyParams = schemaAvailabilityBaseBodyParams.merge( + schemaAvailabilityCreateParams +); + +export const schemaAvailabilityReadBodyParams = z + .object({ + userId: z.union([z.number(), z.array(z.number())]), + }) + .partial(); + +export const schemaSingleAvailabilityReadBodyParams = z.object({ + userId: z.number(), +}); diff --git a/apps/api/lib/validations/booking-reference.ts b/apps/api/lib/validations/booking-reference.ts new file mode 100644 index 0000000000..662285b690 --- /dev/null +++ b/apps/api/lib/validations/booking-reference.ts @@ -0,0 +1,28 @@ +import { _BookingReferenceModel as BookingReference } from "@calcom/prisma/zod"; +import { denullishShape } from "@calcom/prisma/zod-utils"; + +export const schemaBookingReferenceBaseBodyParams = BookingReference.pick({ + type: true, + bookingId: true, + uid: true, + meetingId: true, + meetingPassword: true, + meetingUrl: true, + deleted: true, +}).partial(); + +export const schemaBookingReferenceReadPublic = BookingReference.pick({ + id: true, + type: true, + bookingId: true, + uid: true, + meetingId: true, + meetingPassword: true, + meetingUrl: true, + deleted: true, +}); + +export const schemaBookingCreateBodyParams = BookingReference.omit({ id: true, bookingId: true }) + .merge(denullishShape(BookingReference.pick({ bookingId: true }))) + .strict(); +export const schemaBookingEditBodyParams = schemaBookingCreateBodyParams.partial(); diff --git a/apps/api/lib/validations/booking.ts b/apps/api/lib/validations/booking.ts new file mode 100644 index 0000000000..53da5396aa --- /dev/null +++ b/apps/api/lib/validations/booking.ts @@ -0,0 +1,67 @@ +import { z } from "zod"; + +import { _BookingModel as Booking, _AttendeeModel, _UserModel } from "@calcom/prisma/zod"; +import { extendedBookingCreateBody, iso8601 } from "@calcom/prisma/zod-utils"; + +import { schemaQueryUserId } from "./shared/queryUserId"; + +const schemaBookingBaseBodyParams = Booking.pick({ + uid: true, + userId: true, + eventTypeId: true, + title: true, + description: true, + startTime: true, + endTime: true, + status: true, +}).partial(); + +export const schemaBookingCreateBodyParams = extendedBookingCreateBody.merge(schemaQueryUserId.partial()); + +const schemaBookingEditParams = z + .object({ + title: z.string().optional(), + startTime: iso8601.optional(), + endTime: iso8601.optional(), + // Not supporting responses in edit as that might require re-triggering emails + // responses + }) + .strict(); + +export const schemaBookingEditBodyParams = schemaBookingBaseBodyParams.merge(schemaBookingEditParams); + +export const schemaBookingReadPublic = Booking.extend({ + attendees: z + .array( + _AttendeeModel.pick({ + email: true, + name: true, + timeZone: true, + locale: true, + }) + ) + .optional(), + user: _UserModel + .pick({ + email: true, + name: true, + timeZone: true, + locale: true, + }) + .optional(), +}).pick({ + id: true, + userId: true, + description: true, + eventTypeId: true, + uid: true, + title: true, + startTime: true, + endTime: true, + timeZone: true, + attendees: true, + user: true, + metadata: true, + status: true, + responses: true, +}); diff --git a/apps/api/lib/validations/destination-calendar.ts b/apps/api/lib/validations/destination-calendar.ts new file mode 100644 index 0000000000..7f90cd0002 --- /dev/null +++ b/apps/api/lib/validations/destination-calendar.ts @@ -0,0 +1,48 @@ +import { z } from "zod"; + +import { _DestinationCalendarModel as DestinationCalendar } from "@calcom/prisma/zod"; + +export const schemaDestinationCalendarBaseBodyParams = DestinationCalendar.pick({ + integration: true, + externalId: true, + eventTypeId: true, + bookingId: true, + userId: true, +}).partial(); + +const schemaDestinationCalendarCreateParams = z + .object({ + integration: z.string(), + externalId: z.string(), + eventTypeId: z.number(), + bookingId: z.number(), + userId: z.number(), + }) + .strict(); + +export const schemaDestinationCalendarCreateBodyParams = schemaDestinationCalendarBaseBodyParams.merge( + schemaDestinationCalendarCreateParams +); + +const schemaDestinationCalendarEditParams = z + .object({ + integration: z.string().optional(), + externalId: z.string().optional(), + eventTypeId: z.number().optional(), + bookingId: z.number().optional(), + userId: z.number().optional(), + }) + .strict(); + +export const schemaDestinationCalendarEditBodyParams = schemaDestinationCalendarBaseBodyParams.merge( + schemaDestinationCalendarEditParams +); + +export const schemaDestinationCalendarReadPublic = DestinationCalendar.pick({ + id: true, + integration: true, + externalId: true, + eventTypeId: true, + bookingId: true, + userId: true, +}); diff --git a/apps/api/lib/validations/event-type-custom-input.ts b/apps/api/lib/validations/event-type-custom-input.ts new file mode 100644 index 0000000000..8a9fb3dbbe --- /dev/null +++ b/apps/api/lib/validations/event-type-custom-input.ts @@ -0,0 +1,13 @@ +import { _EventTypeCustomInputModel as EventTypeCustomInput } from "@calcom/prisma/zod"; + +export const schemaEventTypeCustomInputBaseBodyParams = EventTypeCustomInput.omit({ + id: true, +}); + +export const schemaEventTypeCustomInputPublic = EventTypeCustomInput.omit({}); + +export const schemaEventTypeCustomInputBodyParams = schemaEventTypeCustomInputBaseBodyParams.strict(); + +export const schemaEventTypeCustomInputEditBodyParams = schemaEventTypeCustomInputBaseBodyParams + .partial() + .strict(); diff --git a/apps/api/lib/validations/event-type.ts b/apps/api/lib/validations/event-type.ts new file mode 100644 index 0000000000..2b7768a656 --- /dev/null +++ b/apps/api/lib/validations/event-type.ts @@ -0,0 +1,139 @@ +import { z } from "zod"; + +import { _EventTypeModel as EventType, _HostModel } from "@calcom/prisma/zod"; +import { customInputSchema, eventTypeBookingFields } from "@calcom/prisma/zod-utils"; + +import { Frequency } from "~/lib/types"; + +import { jsonSchema } from "./shared/jsonSchema"; +import { schemaQueryUserId } from "./shared/queryUserId"; +import { timeZone } from "./shared/timeZone"; + +const recurringEventInputSchema = z.object({ + dtstart: z.string().optional(), + interval: z.number().int().optional(), + count: z.number().int().optional(), + freq: z.nativeEnum(Frequency).optional(), + until: z.string().optional(), + tzid: timeZone.optional(), +}); + +const hostSchema = _HostModel.pick({ + isFixed: true, + userId: true, +}); + +export const schemaEventTypeBaseBodyParams = EventType.pick({ + title: true, + description: true, + slug: true, + length: true, + hidden: true, + position: true, + eventName: true, + timeZone: true, + periodType: true, + periodStartDate: true, + schedulingType: true, + periodEndDate: true, + periodDays: true, + periodCountCalendarDays: true, + requiresConfirmation: true, + disableGuests: true, + hideCalendarNotes: true, + minimumBookingNotice: true, + beforeEventBuffer: true, + afterEventBuffer: true, + teamId: true, + price: true, + currency: true, + slotInterval: true, + successRedirectUrl: true, + locations: true, +}) + .merge(z.object({ hosts: z.array(hostSchema).optional().default([]) })) + .partial() + .strict(); + +const schemaEventTypeCreateParams = z + .object({ + title: z.string(), + slug: z.string(), + description: z.string().optional().nullable(), + length: z.number().int(), + metadata: z.any().optional(), + recurringEvent: recurringEventInputSchema.optional(), + seatsPerTimeSlot: z.number().optional(), + seatsShowAttendees: z.boolean().optional(), + bookingFields: eventTypeBookingFields.optional(), + }) + .strict(); + +export const schemaEventTypeCreateBodyParams = schemaEventTypeBaseBodyParams + .merge(schemaEventTypeCreateParams) + .merge(schemaQueryUserId.partial()); + +const schemaEventTypeEditParams = z + .object({ + title: z.string().optional(), + slug: z.string().optional(), + length: z.number().int().optional(), + seatsPerTimeSlot: z.number().optional(), + seatsShowAttendees: z.boolean().optional(), + bookingFields: eventTypeBookingFields.optional(), + }) + .strict(); + +export const schemaEventTypeEditBodyParams = schemaEventTypeBaseBodyParams.merge(schemaEventTypeEditParams); +export const schemaEventTypeReadPublic = EventType.pick({ + id: true, + title: true, + slug: true, + length: true, + hidden: true, + position: true, + userId: true, + teamId: true, + eventName: true, + timeZone: true, + periodType: true, + periodStartDate: true, + periodEndDate: true, + periodDays: true, + periodCountCalendarDays: true, + requiresConfirmation: true, + recurringEvent: true, + disableGuests: true, + hideCalendarNotes: true, + minimumBookingNotice: true, + beforeEventBuffer: true, + afterEventBuffer: true, + schedulingType: true, + price: true, + currency: true, + slotInterval: true, + successRedirectUrl: true, + description: true, + locations: true, + metadata: true, + seatsPerTimeSlot: true, + seatsShowAttendees: true, + bookingFields: true, +}).merge( + z.object({ + locations: z + .array( + z.object({ + link: z.string().optional(), + address: z.string().optional(), + hostPhoneNumber: z.string().optional(), + type: z.any().optional(), + }) + ) + .nullable(), + metadata: jsonSchema.nullable(), + customInputs: customInputSchema.array().optional(), + link: z.string().optional(), + bookingFields: eventTypeBookingFields.optional().nullable(), + }) +); diff --git a/apps/api/lib/validations/membership.ts b/apps/api/lib/validations/membership.ts new file mode 100644 index 0000000000..24740eaa52 --- /dev/null +++ b/apps/api/lib/validations/membership.ts @@ -0,0 +1,65 @@ +import { z } from "zod"; + +import { MembershipRole } from "@calcom/prisma/enums"; +import { _MembershipModel as Membership, _TeamModel } from "@calcom/prisma/zod"; +import { stringOrNumber } from "@calcom/prisma/zod-utils"; + +import { schemaQueryIdAsString } from "~/lib/validations/shared/queryIdString"; +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +export const schemaMembershipBaseBodyParams = Membership.omit({}); + +const schemaMembershipRequiredParams = z.object({ + teamId: z.number(), +}); + +export const membershipCreateBodySchema = Membership.partial({ + accepted: true, + role: true, + disableImpersonation: true, +}).transform((v) => ({ + accepted: false, + role: MembershipRole.MEMBER, + disableImpersonation: false, + ...v, +})); + +export const membershipEditBodySchema = Membership.omit({ + /** To avoid complication, let's avoid updating these, instead you can delete and create a new invite */ + teamId: true, + userId: true, +}) + .partial({ + accepted: true, + role: true, + disableImpersonation: true, + }) + .strict(); + +export const schemaMembershipBodyParams = schemaMembershipBaseBodyParams.merge( + schemaMembershipRequiredParams +); + +export const schemaMembershipPublic = Membership.merge(z.object({ team: _TeamModel }).partial()); + +/** We extract userId and teamId from compound ID string */ +export const membershipIdSchema = schemaQueryIdAsString + // So we can query additional team data in memberships + .merge(z.object({ teamId: z.union([stringOrNumber, z.array(stringOrNumber)]) }).partial()) + .transform((v, ctx) => { + const [userIdStr, teamIdStr] = v.id.split("_"); + const userIdInt = schemaQueryIdParseInt.safeParse({ id: userIdStr }); + const teamIdInt = schemaQueryIdParseInt.safeParse({ id: teamIdStr }); + if (!userIdInt.success) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "userId is not a number" }); + return z.NEVER; + } + if (!teamIdInt.success) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "teamId is not a number " }); + return z.NEVER; + } + return { + userId: userIdInt.data.id, + teamId: teamIdInt.data.id, + }; + }); diff --git a/apps/api/lib/validations/payment.ts b/apps/api/lib/validations/payment.ts new file mode 100644 index 0000000000..32c5d8d641 --- /dev/null +++ b/apps/api/lib/validations/payment.ts @@ -0,0 +1,5 @@ +import { _PaymentModel as Payment } from "@calcom/prisma/zod"; + +// FIXME: Payment seems a delicate endpoint, do we need to remove anything here? +export const schemaPaymentBodyParams = Payment.omit({ id: true }); +export const schemaPaymentPublic = Payment.omit({ externalId: true }); diff --git a/apps/api/lib/validations/reminder-mail.ts b/apps/api/lib/validations/reminder-mail.ts new file mode 100644 index 0000000000..635dc84dac --- /dev/null +++ b/apps/api/lib/validations/reminder-mail.ts @@ -0,0 +1,17 @@ +import { z } from "zod"; + +import { _ReminderMailModel as ReminderMail } from "@calcom/prisma/zod"; + +export const schemaReminderMailBaseBodyParams = ReminderMail.omit({ id: true }).partial(); + +export const schemaReminderMailPublic = ReminderMail.omit({}); + +const schemaReminderMailRequiredParams = z.object({ + referenceId: z.number().int(), + reminderType: z.enum(["PENDING_BOOKING_CONFIRMATION"]), + elapsedMinutes: z.number().int(), +}); + +export const schemaReminderMailBodyParams = schemaReminderMailBaseBodyParams.merge( + schemaReminderMailRequiredParams +); diff --git a/apps/api/lib/validations/schedule.ts b/apps/api/lib/validations/schedule.ts new file mode 100644 index 0000000000..51a3fbe1af --- /dev/null +++ b/apps/api/lib/validations/schedule.ts @@ -0,0 +1,34 @@ +import { z } from "zod"; + +import dayjs from "@calcom/dayjs"; +import { _ScheduleModel as Schedule, _AvailabilityModel as Availability } from "@calcom/prisma/zod"; + +import { timeZone } from "./shared/timeZone"; + +const schemaScheduleBaseBodyParams = Schedule.omit({ id: true, timeZone: true }).partial(); + +export const schemaSingleScheduleBodyParams = schemaScheduleBaseBodyParams.merge( + z.object({ userId: z.number().optional(), timeZone: timeZone.optional() }) +); + +export const schemaCreateScheduleBodyParams = schemaScheduleBaseBodyParams.merge( + z.object({ userId: z.number().optional(), name: z.string(), timeZone }) +); + +export const schemaSchedulePublic = z + .object({ id: z.number() }) + .merge(Schedule) + .merge( + z.object({ + availability: z + .array(Availability.pick({ id: true, eventTypeId: true, days: true, startTime: true, endTime: true })) + .transform((v) => + v.map((item) => ({ + ...item, + startTime: dayjs.utc(item.startTime).format("HH:mm:ss"), + endTime: dayjs.utc(item.endTime).format("HH:mm:ss"), + })) + ) + .optional(), + }) + ); diff --git a/apps/api/lib/validations/selected-calendar.ts b/apps/api/lib/validations/selected-calendar.ts new file mode 100644 index 0000000000..e1b2138e51 --- /dev/null +++ b/apps/api/lib/validations/selected-calendar.ts @@ -0,0 +1,48 @@ +import z from "zod"; + +import { _SelectedCalendarModel as SelectedCalendar } from "@calcom/prisma/zod"; + +import { schemaQueryIdAsString } from "./shared/queryIdString"; +import { schemaQueryIdParseInt } from "./shared/queryIdTransformParseInt"; + +export const schemaSelectedCalendarBaseBodyParams = SelectedCalendar; + +export const schemaSelectedCalendarPublic = SelectedCalendar.omit({}); + +export const schemaSelectedCalendarBodyParams = schemaSelectedCalendarBaseBodyParams.partial({ + userId: true, +}); + +export const schemaSelectedCalendarUpdateBodyParams = schemaSelectedCalendarBaseBodyParams.partial(); + +export const selectedCalendarIdSchema = schemaQueryIdAsString.transform((v, ctx) => { + /** We can assume the first part is the userId since it's an integer */ + const [userIdStr, ...rest] = v.id.split("_"); + /** We can assume that the remainder is both the integration type and external id combined */ + const integration_externalId = rest.join("_"); + /** + * Since we only handle calendars here we can split by `_calendar_` and re add it later on. + * This handle special cases like `google_calendar_c_blabla@group.calendar.google.com` and + * `hubspot_other_calendar`. + **/ + const [_integration, externalId] = integration_externalId.split("_calendar_"); + const userIdInt = schemaQueryIdParseInt.safeParse({ id: userIdStr }); + if (!userIdInt.success) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "userId is not a number" }); + return z.NEVER; + } + if (!_integration) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Missing integration" }); + return z.NEVER; + } + if (!externalId) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Missing externalId" }); + return z.NEVER; + } + return { + userId: userIdInt.data.id, + /** We re-add the split `_calendar` string */ + integration: `${_integration}_calendar`, + externalId, + }; +}); diff --git a/apps/api/lib/validations/shared/baseApiParams.ts b/apps/api/lib/validations/shared/baseApiParams.ts new file mode 100644 index 0000000000..d0482d0d27 --- /dev/null +++ b/apps/api/lib/validations/shared/baseApiParams.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; + +// Extracted out as utility function so can be reused +// at different endpoints that require this validation. +export const baseApiParams = z.object({ + // since we added apiKey as query param this is required by next-validations helper + // for query params to work properly and not fail. + apiKey: z.string().optional(), + // version required for supporting /v1/ redirect to query in api as *?version=1 + version: z.string().optional(), +}); diff --git a/apps/api/lib/validations/shared/jsonSchema.ts b/apps/api/lib/validations/shared/jsonSchema.ts new file mode 100644 index 0000000000..bfc7ae1f2b --- /dev/null +++ b/apps/api/lib/validations/shared/jsonSchema.ts @@ -0,0 +1,9 @@ +import { z } from "zod"; + +// 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()]); +export const jsonSchema: z.ZodSchema = z.lazy(() => + z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]) +); diff --git a/apps/api/lib/validations/shared/queryIdString.ts b/apps/api/lib/validations/shared/queryIdString.ts new file mode 100644 index 0000000000..97bdfa4b15 --- /dev/null +++ b/apps/api/lib/validations/shared/queryIdString.ts @@ -0,0 +1,19 @@ +import { withValidation } from "next-validations"; +import { z } from "zod"; + +import { baseApiParams } from "./baseApiParams"; + +// Extracted out as utility function so can be reused +// at different endpoints that require this validation. +/** Used for UUID style id queries */ +export const schemaQueryIdAsString = baseApiParams + .extend({ + id: z.string(), + }) + .strict(); + +export const withValidQueryIdString = withValidation({ + schema: schemaQueryIdAsString, + type: "Zod", + mode: "query", +}); diff --git a/apps/api/lib/validations/shared/queryIdTransformParseInt.ts b/apps/api/lib/validations/shared/queryIdTransformParseInt.ts new file mode 100644 index 0000000000..b9ec495f47 --- /dev/null +++ b/apps/api/lib/validations/shared/queryIdTransformParseInt.ts @@ -0,0 +1,16 @@ +import { withValidation } from "next-validations"; +import { z } from "zod"; + +import { baseApiParams } from "./baseApiParams"; + +// Extracted out as utility function so can be reused +// at different endpoints that require this validation. +export const schemaQueryIdParseInt = baseApiParams.extend({ + id: z.coerce.number(), +}); + +export const withValidQueryIdTransformParseInt = withValidation({ + schema: schemaQueryIdParseInt, + type: "Zod", + mode: "query", +}); diff --git a/apps/api/lib/validations/shared/queryTeamId.ts b/apps/api/lib/validations/shared/queryTeamId.ts new file mode 100644 index 0000000000..f2a80361eb --- /dev/null +++ b/apps/api/lib/validations/shared/queryTeamId.ts @@ -0,0 +1,21 @@ +import { withValidation } from "next-validations"; +import { z } from "zod"; + +import { baseApiParams } from "./baseApiParams"; + +// Extracted out as utility function so can be reused +// at different endpoints that require this validation. +export const schemaQueryTeamId = baseApiParams + .extend({ + teamId: z + .string() + .regex(/^\d+$/) + .transform((id) => parseInt(id)), + }) + .strict(); + +export const withValidQueryTeamId = withValidation({ + schema: schemaQueryTeamId, + type: "Zod", + mode: "query", +}); diff --git a/apps/api/lib/validations/shared/queryUserId.ts b/apps/api/lib/validations/shared/queryUserId.ts new file mode 100644 index 0000000000..f187d0f134 --- /dev/null +++ b/apps/api/lib/validations/shared/queryUserId.ts @@ -0,0 +1,26 @@ +import { withValidation } from "next-validations"; +import { z } from "zod"; + +import { stringOrNumber } from "@calcom/prisma/zod-utils"; + +import { baseApiParams } from "./baseApiParams"; + +// Extracted out as utility function so can be reused +// at different endpoints that require this validation. +export const schemaQueryUserId = baseApiParams.extend({ + userId: stringOrNumber, +}); + +export const schemaQuerySingleOrMultipleUserIds = z.object({ + userId: z.union([stringOrNumber, z.array(stringOrNumber)]), +}); + +export const schemaQuerySingleOrMultipleTeamIds = z.object({ + teamId: z.union([stringOrNumber, z.array(stringOrNumber)]), +}); + +export const withValidQueryUserId = withValidation({ + schema: schemaQueryUserId, + type: "Zod", + mode: "query", +}); diff --git a/apps/api/lib/validations/shared/timeZone.ts b/apps/api/lib/validations/shared/timeZone.ts new file mode 100644 index 0000000000..e94b0afa09 --- /dev/null +++ b/apps/api/lib/validations/shared/timeZone.ts @@ -0,0 +1,7 @@ +import tzdata from "tzdata"; +import * as z from "zod"; + +// @note: This is a custom validation that checks if the timezone is valid and exists in the tzdb library +export const timeZone = z.string().refine((tz: string) => Object.keys(tzdata.zones).includes(tz), { + message: `Expected one of the following: ${Object.keys(tzdata.zones).join(", ")}`, +}); diff --git a/apps/api/lib/validations/team.ts b/apps/api/lib/validations/team.ts new file mode 100644 index 0000000000..e5f8e3cbfa --- /dev/null +++ b/apps/api/lib/validations/team.ts @@ -0,0 +1,18 @@ +import { z } from "zod"; + +import { _TeamModel as Team } from "@calcom/prisma/zod"; + +export const schemaTeamBaseBodyParams = Team.omit({ id: true, createdAt: true }).partial({ + hideBranding: true, + metadata: true, +}); + +const schemaTeamRequiredParams = z.object({}); + +export const schemaTeamBodyParams = schemaTeamBaseBodyParams.merge(schemaTeamRequiredParams).strict(); + +export const schemaTeamUpdateBodyParams = schemaTeamBodyParams.partial(); + +export const schemaTeamReadPublic = Team.omit({}); + +export const schemaTeamsReadPublic = z.array(schemaTeamReadPublic); diff --git a/apps/api/lib/validations/user.ts b/apps/api/lib/validations/user.ts new file mode 100644 index 0000000000..25810f27c6 --- /dev/null +++ b/apps/api/lib/validations/user.ts @@ -0,0 +1,170 @@ +import { z } from "zod"; + +import { checkUsername } from "@calcom/lib/server/checkUsername"; +import { _UserModel as User } from "@calcom/prisma/zod"; +import { iso8601 } from "@calcom/prisma/zod-utils"; + +import { timeZone } from "~/lib/validations/shared/timeZone"; + +// @note: These are the ONLY values allowed as weekStart. So user don't introduce bad data. +enum weekdays { + MONDAY = "Monday", + TUESDAY = "Tuesday", + WEDNESDAY = "Wednesday", + THURSDAY = "Thursday", + FRIDAY = "Friday", + SATURDAY = "Saturday", + SUNDAY = "Sunday", +} + +// @note: extracted from apps/web/next-i18next.config.js, update if new locales. +enum locales { + EN = "en", + FR = "fr", + IT = "it", + RU = "ru", + ES = "es", + DE = "de", + PT = "pt", + RO = "ro", + NL = "nl", + PT_BR = "pt-BR", + ES_419 = "es-419", + KO = "ko", + JA = "ja", + PL = "pl", + AR = "ar", + IW = "iw", + ZH_CN = "zh-CN", + ZH_TW = "zh-TW", + CS = "cs", + SR = "sr", + SV = "sv", + VI = "vi", +} +enum theme { + DARK = "dark", + LIGHT = "light", +} + +enum timeFormat { + TWELVE = 12, + TWENTY_FOUR = 24, +} + +const usernameSchema = z + .string() + .transform((v) => v.toLowerCase()) + // .refine(() => {}) + .superRefine(async (val, ctx) => { + if (val) { + const result = await checkUsername(val); + if (!result.available) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "already_in_use_error" }); + if (result.premium) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "premium_username" }); + } + }); + +// @note: These are the values that are editable via PATCH method on the user Model +export const schemaUserBaseBodyParams = User.pick({ + name: true, + email: true, + username: true, + bio: true, + timeZone: true, + weekStart: true, + theme: true, + defaultScheduleId: true, + locale: true, + timeFormat: true, + brandColor: true, + darkBrandColor: true, + allowDynamicBooking: true, + away: true, + role: true, + // @note: disallowing avatar changes via API for now. We can add it later if needed. User should upload image via UI. + // avatar: true, +}).partial(); +// @note: partial() is used to allow for the user to edit only the fields they want to edit making all optional, +// if want to make any required do it in the schemaRequiredParams + +// Here we can both require or not (adding optional or nullish) and also rewrite validations for any value +// for example making weekStart only accept weekdays as input +const schemaUserEditParams = z.object({ + email: z.string().email(), + username: usernameSchema, + weekStart: z.nativeEnum(weekdays).optional(), + brandColor: z.string().min(4).max(9).regex(/^#/).optional(), + darkBrandColor: z.string().min(4).max(9).regex(/^#/).optional(), + timeZone: timeZone.optional(), + theme: z.nativeEnum(theme).optional().nullable(), + timeFormat: z.nativeEnum(timeFormat).optional(), + defaultScheduleId: z + .number() + .refine((id: number) => id > 0) + .optional() + .nullable(), + locale: z.nativeEnum(locales).optional().nullable(), +}); + +// @note: These are the values that are editable via PATCH method on the user Model, +// merging both BaseBodyParams with RequiredParams, and omiting whatever we want at the end. + +const schemaUserCreateParams = z.object({ + email: z.string().email(), + username: usernameSchema, + weekStart: z.nativeEnum(weekdays).optional(), + brandColor: z.string().min(4).max(9).regex(/^#/).optional(), + darkBrandColor: z.string().min(4).max(9).regex(/^#/).optional(), + timeZone: timeZone.optional(), + theme: z.nativeEnum(theme).optional().nullable(), + timeFormat: z.nativeEnum(timeFormat).optional(), + defaultScheduleId: z + .number() + .refine((id: number) => id > 0) + .optional() + .nullable(), + locale: z.nativeEnum(locales).optional(), + createdDate: iso8601.optional(), +}); + +// @note: These are the values that are editable via PATCH method on the user Model, +// merging both BaseBodyParams with RequiredParams, and omiting whatever we want at the end. +export const schemaUserEditBodyParams = schemaUserBaseBodyParams + .merge(schemaUserEditParams) + .omit({}) + .partial() + .strict(); + +export const schemaUserCreateBodyParams = schemaUserBaseBodyParams + .merge(schemaUserCreateParams) + .omit({}) + .strict(); + +// @note: These are the values that are always returned when reading a user +export const schemaUserReadPublic = User.pick({ + id: true, + username: true, + name: true, + email: true, + emailVerified: true, + bio: true, + avatar: true, + timeZone: true, + weekStart: true, + endTime: true, + bufferTime: true, + theme: true, + defaultScheduleId: true, + locale: true, + timeFormat: true, + brandColor: true, + darkBrandColor: true, + allowDynamicBooking: true, + away: true, + createdDate: true, + verified: true, + invitedTo: true, + role: true, +}); + +export const schemaUsersReadPublic = z.array(schemaUserReadPublic); diff --git a/apps/api/lib/validations/webhook.ts b/apps/api/lib/validations/webhook.ts new file mode 100644 index 0000000000..91d8560195 --- /dev/null +++ b/apps/api/lib/validations/webhook.ts @@ -0,0 +1,55 @@ +import { z } from "zod"; + +import { WEBHOOK_TRIGGER_EVENTS } from "@calcom/features/webhooks/lib/constants"; +import { _WebhookModel as Webhook } from "@calcom/prisma/zod"; + +const schemaWebhookBaseBodyParams = Webhook.pick({ + userId: true, + eventTypeId: true, + eventTriggers: true, + active: true, + subscriberUrl: true, + payloadTemplate: true, +}); + +export const schemaWebhookCreateParams = z + .object({ + // subscriberUrl: z.string().url(), + // eventTriggers: z.enum(WEBHOOK_TRIGGER_EVENTS).array(), + // active: z.boolean(), + payloadTemplate: z.string().optional().nullable(), + eventTypeId: z.number().optional(), + userId: z.number().optional(), + // API shouldn't mess with Apps webhooks yet (ie. Zapier) + // appId: z.string().optional().nullable(), + }) + .strict(); + +export const schemaWebhookCreateBodyParams = schemaWebhookBaseBodyParams.merge(schemaWebhookCreateParams); + +export const schemaWebhookEditBodyParams = schemaWebhookBaseBodyParams + .merge( + z.object({ + eventTriggers: z.enum(WEBHOOK_TRIGGER_EVENTS).array().optional(), + }) + ) + .partial() + .strict(); + +export const schemaWebhookReadPublic = Webhook.pick({ + id: true, + userId: true, + eventTypeId: true, + payloadTemplate: true, + eventTriggers: true, + // FIXME: We have some invalid urls saved in the DB + // subscriberUrl: true, + /** @todo: find out how to properly add back and validate those. */ + // eventType: true, + // app: true, + appId: true, +}).merge( + z.object({ + subscriberUrl: z.string(), + }) +); diff --git a/apps/docs/next-env.d.ts b/apps/api/next-env.d.ts similarity index 100% rename from apps/docs/next-env.d.ts rename to apps/api/next-env.d.ts diff --git a/apps/api/next-i18next.config.js b/apps/api/next-i18next.config.js new file mode 100644 index 0000000000..402b72363c --- /dev/null +++ b/apps/api/next-i18next.config.js @@ -0,0 +1,10 @@ +const path = require("path"); +const i18nConfig = require("@calcom/config/next-i18next.config"); + +/** @type {import("next-i18next").UserConfig} */ +const config = { + ...i18nConfig, + localePath: path.resolve("../web/public/static/locales"), +}; + +module.exports = config; diff --git a/apps/api/next.config.js b/apps/api/next.config.js new file mode 100644 index 0000000000..ba2dcbf89b --- /dev/null +++ b/apps/api/next.config.js @@ -0,0 +1,69 @@ +const { withAxiom } = require("next-axiom"); + +module.exports = withAxiom({ + transpilePackages: [ + "@calcom/app-store", + "@calcom/core", + "@calcom/dayjs", + "@calcom/emails", + "@calcom/features", + "@calcom/lib", + "@calcom/prisma", + "@calcom/trpc", + ], + async headers() { + return [ + { + source: "/docs", + headers: [ + { + key: "Access-Control-Allow-Credentials", + value: "true", + }, + { + key: "Access-Control-Allow-Origin", + value: "*", + }, + { + key: "Access-Control-Allow-Methods", + value: "GET, OPTIONS, PATCH, DELETE, POST, PUT", + }, + { + key: "Access-Control-Allow-Headers", + value: + "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, Content-Type, api_key, Authorization", + }, + ], + }, + ]; + }, + async rewrites() { + return { + afterFiles: [ + // This redirects requests recieved at / the root to the /api/ folder. + { + source: "/v:version/:rest*", + destination: "/api/v:version/:rest*", + }, + // This redirects requests to api/v*/ to /api/ passing version as a query parameter. + { + source: "/api/v:version/:rest*", + destination: "/api/:rest*?version=:version", + }, + // Keeps backwards compatibility with old webhook URLs + { + source: "/api/hooks/:rest*", + destination: "/api/webhooks/:rest*", + }, + ], + fallback: [ + // These rewrites are checked after both pages/public files + // and dynamic routes are checked + { + source: "/:path*", + destination: `/api/:path*`, + }, + ], + }; + }, +}); diff --git a/apps/api/next.d.ts b/apps/api/next.d.ts new file mode 100644 index 0000000000..7d5f82441a --- /dev/null +++ b/apps/api/next.d.ts @@ -0,0 +1,21 @@ +import type { Session } from "next-auth"; +import type { NextApiRequest as BaseNextApiRequest } from "next/types"; + +import type { PrismaClient } from "@calcom/prisma/client"; + +export type * from "next/types"; + +export declare module "next" { + interface NextApiRequest extends BaseNextApiRequest { + session?: Session | null; + + userId: number; + method: string; + prisma: PrismaClient; + // session: { user: { id: number } }; + // query: Partial<{ [key: string]: string | string[] }>; + isAdmin: boolean; + isCustomPrisma: boolean; + pagination: { take: number; skip: number }; + } +} diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 0000000000..af678fe46b --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,47 @@ +{ + "name": "@calcom/api", + "version": "1.0.0", + "description": "Public API for Cal.com", + "main": "index.ts", + "repository": "git@github.com:calcom/api.git", + "author": "Cal.com Inc.", + "private": true, + "scripts": { + "build": "next build", + "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next", + "dev": "PORT=3002 next dev", + "lint": "eslint . --ignore-path .gitignore", + "lint:report": "eslint . --format json --output-file ../../lint-results/api.json", + "lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix", + "start": "PORT=3002 next start", + "test": "echo 'No tests yet' && exit 0", + "type-check": "tsc --pretty --noEmit" + }, + "devDependencies": { + "@calcom/tsconfig": "*", + "@calcom/types": "*", + "node-mocks-http": "^1.11.0" + }, + "dependencies": { + "@calcom/app-store": "*", + "@calcom/core": "*", + "@calcom/dayjs": "*", + "@calcom/emails": "*", + "@calcom/features": "*", + "@calcom/lib": "*", + "@calcom/prisma": "*", + "@calcom/trpc": "*", + "@sentry/nextjs": "^7.20.0", + "bcryptjs": "^2.4.3", + "memory-cache": "^0.2.0", + "next": "~13.2.1", + "next-api-middleware": "^1.0.1", + "next-axiom": "^0.16.0", + "next-swagger-doc": "^0.3.6", + "next-validations": "^0.2.0", + "typescript": "^4.9.4", + "tzdata": "^1.0.30", + "uuid": "^8.3.2", + "zod": "^3.20.2" + } +} diff --git a/apps/api/pages/api/api-keys/[id]/_auth-middleware.ts b/apps/api/pages/api/api-keys/[id]/_auth-middleware.ts new file mode 100644 index 0000000000..fb1c917485 --- /dev/null +++ b/apps/api/pages/api/api-keys/[id]/_auth-middleware.ts @@ -0,0 +1,17 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; + +import { schemaQueryIdAsString } from "~/lib/validations/shared/queryIdString"; + +export async function authMiddleware(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const { id } = schemaQueryIdAsString.parse(req.query); + // Admin can check any api key + if (isAdmin) return; + // Check if user can access the api key + const apiKey = await prisma.apiKey.findFirst({ + where: { id, userId }, + }); + if (!apiKey) throw new HttpError({ statusCode: 404, message: "API key not found" }); +} diff --git a/apps/api/pages/api/api-keys/[id]/_delete.ts b/apps/api/pages/api/api-keys/[id]/_delete.ts new file mode 100644 index 0000000000..099c6ba7b1 --- /dev/null +++ b/apps/api/pages/api/api-keys/[id]/_delete.ts @@ -0,0 +1,14 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryIdAsString } from "~/lib/validations/shared/queryIdString"; + +async function deleteHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdAsString.parse(query); + await prisma.apiKey.delete({ where: { id } }); + return { message: `ApiKey with id: ${id} deleted` }; +} + +export default defaultResponder(deleteHandler); diff --git a/apps/api/pages/api/api-keys/[id]/_get.ts b/apps/api/pages/api/api-keys/[id]/_get.ts new file mode 100644 index 0000000000..99b1188507 --- /dev/null +++ b/apps/api/pages/api/api-keys/[id]/_get.ts @@ -0,0 +1,15 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { apiKeyPublicSchema } from "~/lib/validations/api-key"; +import { schemaQueryIdAsString } from "~/lib/validations/shared/queryIdString"; + +async function getHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdAsString.parse(query); + const api_key = await prisma.apiKey.findUniqueOrThrow({ where: { id } }); + return { api_key: apiKeyPublicSchema.parse(api_key) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/api-keys/[id]/_patch.ts b/apps/api/pages/api/api-keys/[id]/_patch.ts new file mode 100644 index 0000000000..673a081f54 --- /dev/null +++ b/apps/api/pages/api/api-keys/[id]/_patch.ts @@ -0,0 +1,16 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { apiKeyEditBodySchema, apiKeyPublicSchema } from "~/lib/validations/api-key"; +import { schemaQueryIdAsString } from "~/lib/validations/shared/queryIdString"; + +async function patchHandler(req: NextApiRequest) { + const { prisma, body } = req; + const { id } = schemaQueryIdAsString.parse(req.query); + const data = apiKeyEditBodySchema.parse(body); + const api_key = await prisma.apiKey.update({ where: { id }, data }); + return { api_key: apiKeyPublicSchema.parse(api_key) }; +} + +export default defaultResponder(patchHandler); diff --git a/apps/api/pages/api/api-keys/[id]/index.ts b/apps/api/pages/api/api-keys/[id]/index.ts new file mode 100644 index 0000000000..2e0c358153 --- /dev/null +++ b/apps/api/pages/api/api-keys/[id]/index.ts @@ -0,0 +1,18 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { defaultHandler, defaultResponder } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +import { authMiddleware } from "./_auth-middleware"; + +export default withMiddleware()( + defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req); + return defaultHandler({ + GET: import("./_get"), + PATCH: import("./_patch"), + DELETE: import("./_delete"), + })(req, res); + }) +); diff --git a/apps/api/pages/api/api-keys/_get.ts b/apps/api/pages/api/api-keys/_get.ts new file mode 100644 index 0000000000..8c23c6fe9e --- /dev/null +++ b/apps/api/pages/api/api-keys/_get.ts @@ -0,0 +1,40 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; +import type { Ensure } from "@calcom/types/utils"; + +import { apiKeyPublicSchema } from "~/lib/validations/api-key"; +import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId"; + +type CustomNextApiRequest = NextApiRequest & { + args?: Prisma.ApiKeyFindManyArgs; +}; + +/** Admins can query other users' API keys */ +function handleAdminRequests(req: CustomNextApiRequest) { + // To match type safety with runtime + if (!hasReqArgs(req)) throw Error("Missing req.args"); + const { userId, isAdmin } = req; + if (isAdmin && req.query.userId) { + const query = schemaQuerySingleOrMultipleUserIds.parse(req.query); + const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId]; + req.args.where = { userId: { in: userIds } }; + if (Array.isArray(query.userId)) req.args.orderBy = { userId: "asc" }; + } +} + +function hasReqArgs(req: CustomNextApiRequest): req is Ensure { + return "args" in req; +} + +async function getHandler(req: CustomNextApiRequest) { + const { userId, isAdmin, prisma } = req; + req.args = isAdmin ? {} : { where: { userId } }; + // Proof of concept: allowing mutation in exchange of composability + handleAdminRequests(req); + const data = await prisma.apiKey.findMany(req.args); + return { api_keys: data.map((v) => apiKeyPublicSchema.parse(v)) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/api-keys/_post.ts b/apps/api/pages/api/api-keys/_post.ts new file mode 100644 index 0000000000..8329ac0df5 --- /dev/null +++ b/apps/api/pages/api/api-keys/_post.ts @@ -0,0 +1,44 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; +import { v4 } from "uuid"; + +import { generateUniqueAPIKey } from "@calcom/features/ee/api-keys/lib/apiKeys"; +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { apiKeyCreateBodySchema, apiKeyPublicSchema } from "~/lib/validations/api-key"; + +async function postHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const { neverExpires, userId: bodyUserId, ...input } = apiKeyCreateBodySchema.parse(req.body); + const [hashedKey, apiKey] = generateUniqueAPIKey(); + const args: Prisma.ApiKeyCreateArgs = { + data: { + id: v4(), + userId, + ...input, + // And here we pass a null to expiresAt if never expires is true. otherwise just pass expiresAt from input + expiresAt: neverExpires ? null : input.expiresAt, + hashedKey, + }, + }; + + if (!isAdmin && bodyUserId) throw new HttpError({ statusCode: 403, message: `ADMIN required for userId` }); + + if (isAdmin && bodyUserId) { + const where: Prisma.UserWhereInput = { id: bodyUserId }; + await prisma.user.findFirstOrThrow({ where }); + args.data.userId = bodyUserId; + } + + const result = await prisma.apiKey.create(args); + return { + api_key: { + ...apiKeyPublicSchema.parse(result), + key: `${process.env.API_KEY_PREFIX ?? "cal_"}${apiKey}`, + }, + message: "API key created successfully. Save the `key` value as it won't be displayed again.", + }; +} + +export default defaultResponder(postHandler); diff --git a/apps/api/pages/api/api-keys/index.ts b/apps/api/pages/api/api-keys/index.ts new file mode 100644 index 0000000000..0737e056fb --- /dev/null +++ b/apps/api/pages/api/api-keys/index.ts @@ -0,0 +1,10 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware("HTTP_GET_OR_POST")( + defaultHandler({ + GET: import("./_get"), + POST: import("./_post"), + }) +); diff --git a/apps/api/pages/api/attendees/[id]/_auth-middleware.ts b/apps/api/pages/api/attendees/[id]/_auth-middleware.ts new file mode 100644 index 0000000000..4397583057 --- /dev/null +++ b/apps/api/pages/api/attendees/[id]/_auth-middleware.ts @@ -0,0 +1,20 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; + +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +async function authMiddleware(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const query = schemaQueryIdParseInt.parse(req.query); + // @note: Here we make sure to only return attendee's of the user's own bookings if the user is not an admin. + if (isAdmin) return; + // Find all user bookings, including attendees + const attendee = await prisma.attendee.findFirst({ + where: { id: query.id, booking: { userId } }, + }); + // Flatten and merge all the attendees in one array + if (!attendee) throw new HttpError({ statusCode: 403, message: "Forbidden" }); +} + +export default authMiddleware; diff --git a/apps/api/pages/api/attendees/[id]/_delete.ts b/apps/api/pages/api/attendees/[id]/_delete.ts new file mode 100644 index 0000000000..4d0475b864 --- /dev/null +++ b/apps/api/pages/api/attendees/[id]/_delete.ts @@ -0,0 +1,43 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /attendees/{id}: + * delete: + * operationId: removeAttendeeById + * summary: Remove an existing attendee + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the attendee to delete + * tags: + * - attendees + * responses: + * 201: + * description: OK, attendee removed successfully + * 400: + * description: Bad request. Attendee id is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function deleteHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + await prisma.attendee.delete({ where: { id } }); + return { message: `Attendee with id: ${id} deleted successfully` }; +} + +export default defaultResponder(deleteHandler); diff --git a/apps/api/pages/api/attendees/[id]/_get.ts b/apps/api/pages/api/attendees/[id]/_get.ts new file mode 100644 index 0000000000..bf1680de29 --- /dev/null +++ b/apps/api/pages/api/attendees/[id]/_get.ts @@ -0,0 +1,44 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaAttendeeReadPublic } from "~/lib/validations/attendee"; +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /attendees/{id}: + * get: + * operationId: getAttendeeById + * summary: Find an attendee + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the attendee to get + * tags: + * - attendees + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: Attendee was not found + */ +export async function getHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const attendee = await prisma.attendee.findUnique({ where: { id } }); + return { attendee: schemaAttendeeReadPublic.parse(attendee) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/attendees/[id]/_patch.ts b/apps/api/pages/api/attendees/[id]/_patch.ts new file mode 100644 index 0000000000..67269c7197 --- /dev/null +++ b/apps/api/pages/api/attendees/[id]/_patch.ts @@ -0,0 +1,76 @@ +import type { NextApiRequest } from "next"; +import type { z } from "zod"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaAttendeeEditBodyParams, schemaAttendeeReadPublic } from "~/lib/validations/attendee"; +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /attendees/{id}: + * patch: + * operationId: editAttendeeById + * summary: Edit an existing attendee + * requestBody: + * description: Edit an existing attendee related to one of your bookings + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * email: + * type: string + * format: email + * name: + * type: string + * timeZone: + * type: string + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the attendee to get + * tags: + * - attendees + * responses: + * 201: + * description: OK, attendee edited successfully + * 400: + * description: Bad request. Attendee body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ + +export async function patchHandler(req: NextApiRequest) { + const { prisma, query, body } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const data = schemaAttendeeEditBodyParams.parse(body); + await checkPermissions(req, data); + const attendee = await prisma.attendee.update({ where: { id }, data }); + return { attendee: schemaAttendeeReadPublic.parse(attendee) }; +} + +async function checkPermissions(req: NextApiRequest, body: z.infer) { + const { isAdmin, prisma } = req; + if (isAdmin) return; + const { userId } = req; + const { bookingId } = body; + if (bookingId) { + // Ensure that the booking the attendee is being added to belongs to the user + const booking = await prisma.booking.findFirst({ where: { id: bookingId, userId } }); + if (!booking) throw new HttpError({ statusCode: 403, message: "You don't have access to the booking" }); + } +} + +export default defaultResponder(patchHandler); diff --git a/apps/api/pages/api/attendees/[id]/index.ts b/apps/api/pages/api/attendees/[id]/index.ts new file mode 100644 index 0000000000..727ad02843 --- /dev/null +++ b/apps/api/pages/api/attendees/[id]/index.ts @@ -0,0 +1,18 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { defaultHandler, defaultResponder } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +import authMiddleware from "./_auth-middleware"; + +export default withMiddleware()( + defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req); + return defaultHandler({ + GET: import("./_get"), + PATCH: import("./_patch"), + DELETE: import("./_delete"), + })(req, res); + }) +); diff --git a/apps/api/pages/api/attendees/_get.ts b/apps/api/pages/api/attendees/_get.ts new file mode 100644 index 0000000000..d6662d897c --- /dev/null +++ b/apps/api/pages/api/attendees/_get.ts @@ -0,0 +1,41 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaAttendeeReadPublic } from "~/lib/validations/attendee"; + +/** + * @swagger + * /attendees: + * get: + * operationId: listAttendees + * summary: Find all attendees + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - attendees + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No attendees were found + */ +async function handler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const args: Prisma.AttendeeFindManyArgs = isAdmin ? {} : { where: { booking: { userId } } }; + const data = await prisma.attendee.findMany(args); + const attendees = data.map((attendee) => schemaAttendeeReadPublic.parse(attendee)); + if (!attendees) throw new HttpError({ statusCode: 404, message: "No attendees were found" }); + return { attendees }; +} + +export default defaultResponder(handler); diff --git a/apps/api/pages/api/attendees/_post.ts b/apps/api/pages/api/attendees/_post.ts new file mode 100644 index 0000000000..8570376c55 --- /dev/null +++ b/apps/api/pages/api/attendees/_post.ts @@ -0,0 +1,81 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaAttendeeCreateBodyParams, schemaAttendeeReadPublic } from "~/lib/validations/attendee"; + +/** + * @swagger + * /attendees: + * post: + * operationId: addAttendee + * summary: Creates a new attendee + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * requestBody: + * description: Create a new attendee related to one of your bookings + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - bookingId + * - name + * - email + * - timeZone + * properties: + * bookingId: + * type: number + * email: + * type: string + * format: email + * name: + * type: string + * timeZone: + * type: string + * tags: + * - attendees + * responses: + * 201: + * description: OK, attendee created + * 400: + * description: Bad request. Attendee body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +async function postHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const body = schemaAttendeeCreateBodyParams.parse(req.body); + + if (!isAdmin) { + const userBooking = await prisma.booking.findFirst({ + where: { userId, id: body.bookingId }, + select: { id: true }, + }); + // Here we make sure to only return attendee's of the user's own bookings. + if (!userBooking) throw new HttpError({ statusCode: 403, message: "Forbidden" }); + } + + const data = await prisma.attendee.create({ + data: { + email: body.email, + name: body.name, + timeZone: body.timeZone, + booking: { connect: { id: body.bookingId } }, + }, + }); + + return { + attendee: schemaAttendeeReadPublic.parse(data), + message: "Attendee created successfully", + }; +} + +export default defaultResponder(postHandler); diff --git a/apps/api/pages/api/attendees/index.ts b/apps/api/pages/api/attendees/index.ts new file mode 100644 index 0000000000..2a15abfa5b --- /dev/null +++ b/apps/api/pages/api/attendees/index.ts @@ -0,0 +1,10 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("./_get"), + POST: import("./_post"), + }) +); diff --git a/apps/api/pages/api/availabilities/[id]/_auth-middleware.ts b/apps/api/pages/api/availabilities/[id]/_auth-middleware.ts new file mode 100644 index 0000000000..63ad3a3c32 --- /dev/null +++ b/apps/api/pages/api/availabilities/[id]/_auth-middleware.ts @@ -0,0 +1,19 @@ +import type { NextApiRequest } from "next"; + +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +async function authMiddleware(req: NextApiRequest) { + const { userId, prisma, isAdmin, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + /** Admins can skip the ownership verification */ + if (isAdmin) return; + /** + * There's a caveat here. If the availability exists but the user doesn't own it, + * the user will see a 404 error which may or not be the desired behavior. + */ + await prisma.availability.findFirstOrThrow({ + where: { id, Schedule: { userId } }, + }); +} + +export default authMiddleware; diff --git a/apps/api/pages/api/availabilities/[id]/_delete.ts b/apps/api/pages/api/availabilities/[id]/_delete.ts new file mode 100644 index 0000000000..d480ad4a93 --- /dev/null +++ b/apps/api/pages/api/availabilities/[id]/_delete.ts @@ -0,0 +1,45 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /availabilities/{id}: + * delete: + * operationId: removeAvailabilityById + * summary: Remove an existing availability + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the availability to delete + * - in: query + * name: apiKey + * required: true + * schema: + * type: integer + * description: Your API key + * tags: + * - availabilities + * externalDocs: + * url: https://docs.cal.com/availability + * responses: + * 201: + * description: OK, availability removed successfully + * 400: + * description: Bad request. Availability id is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function deleteHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + await prisma.availability.delete({ where: { id } }); + return { message: `Availability with id: ${id} deleted successfully` }; +} + +export default defaultResponder(deleteHandler); diff --git a/apps/api/pages/api/availabilities/[id]/_get.ts b/apps/api/pages/api/availabilities/[id]/_get.ts new file mode 100644 index 0000000000..a481ac1f89 --- /dev/null +++ b/apps/api/pages/api/availabilities/[id]/_get.ts @@ -0,0 +1,49 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaAvailabilityReadPublic } from "~/lib/validations/availability"; +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /availabilities/{id}: + * get: + * operationId: getAvailabilityById + * summary: Find an availability + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the availability to get + * - in: query + * name: apiKey + * required: true + * schema: + * type: integer + * description: Your API key + * tags: + * - availabilities + * externalDocs: + * url: https://docs.cal.com/availability + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid + * 404: + * description: Availability not found + */ +export async function getHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const availability = await prisma.availability.findUnique({ + where: { id }, + include: { Schedule: { select: { userId: true } } }, + }); + return { availability: schemaAvailabilityReadPublic.parse(availability) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/availabilities/[id]/_patch.ts b/apps/api/pages/api/availabilities/[id]/_patch.ts new file mode 100644 index 0000000000..2b97d01461 --- /dev/null +++ b/apps/api/pages/api/availabilities/[id]/_patch.ts @@ -0,0 +1,86 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { + schemaAvailabilityEditBodyParams, + schemaAvailabilityReadPublic, +} from "~/lib/validations/availability"; +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /availabilities/{id}: + * patch: + * operationId: editAvailabilityById + * summary: Edit an existing availability + * parameters: + * - in: query + * name: apiKey + * required: true + * description: Your API key + * schema: + * type: integer + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID of the availability to edit + * requestBody: + * description: Edit an existing availability related to one of your bookings + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * days: + * type: array + * description: Array of integers depicting weekdays + * items: + * type: integer + * enum: [0, 1, 2, 3, 4, 5] + * scheduleId: + * type: integer + * description: ID of schedule this availability is associated with + * startTime: + * type: string + * description: Start time of the availability + * endTime: + * type: string + * description: End time of the availability + * examples: + * availability: + * summary: An example of availability + * value: + * scheduleId: 123 + * days: [1,2,3,5] + * startTime: 1970-01-01T17:00:00.000Z + * endTime: 1970-01-01T17:00:00.000Z + * + * tags: + * - availabilities + * externalDocs: + * url: https://docs.cal.com/availability + * responses: + * 201: + * description: OK, availability edited successfully + * 400: + * description: Bad request. Availability body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function patchHandler(req: NextApiRequest) { + const { prisma, query, body } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const data = schemaAvailabilityEditBodyParams.parse(body); + const availability = await prisma.availability.update({ + where: { id }, + data, + include: { Schedule: { select: { userId: true } } }, + }); + return { availability: schemaAvailabilityReadPublic.parse(availability) }; +} + +export default defaultResponder(patchHandler); diff --git a/apps/api/pages/api/availabilities/[id]/index.ts b/apps/api/pages/api/availabilities/[id]/index.ts new file mode 100644 index 0000000000..727ad02843 --- /dev/null +++ b/apps/api/pages/api/availabilities/[id]/index.ts @@ -0,0 +1,18 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { defaultHandler, defaultResponder } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +import authMiddleware from "./_auth-middleware"; + +export default withMiddleware()( + defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req); + return defaultHandler({ + GET: import("./_get"), + PATCH: import("./_patch"), + DELETE: import("./_delete"), + })(req, res); + }) +); diff --git a/apps/api/pages/api/availabilities/_post.ts b/apps/api/pages/api/availabilities/_post.ts new file mode 100644 index 0000000000..19c76f26cb --- /dev/null +++ b/apps/api/pages/api/availabilities/_post.ts @@ -0,0 +1,99 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { + schemaAvailabilityCreateBodyParams, + schemaAvailabilityReadPublic, +} from "~/lib/validations/availability"; + +/** + * @swagger + * /availabilities: + * post: + * operationId: addAvailability + * summary: Creates a new availability + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * requestBody: + * description: Edit an existing availability related to one of your bookings + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - scheduleId + * - startTime + * - endTime + * properties: + * days: + * type: array + * description: Array of integers depicting weekdays + * items: + * type: integer + * enum: [0, 1, 2, 3, 4, 5] + * scheduleId: + * type: integer + * description: ID of schedule this availability is associated with + * startTime: + * type: string + * description: Start time of the availability + * endTime: + * type: string + * description: End time of the availability + * examples: + * availability: + * summary: An example of availability + * value: + * scheduleId: 123 + * days: [1,2,3,5] + * startTime: 1970-01-01T17:00:00.000Z + * endTime: 1970-01-01T17:00:00.000Z + * + * + * tags: + * - availabilities + * externalDocs: + * url: https://docs.cal.com/availability + * responses: + * 201: + * description: OK, availability created + * 400: + * description: Bad request. Availability body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +async function postHandler(req: NextApiRequest) { + const { prisma } = req; + const data = schemaAvailabilityCreateBodyParams.parse(req.body); + await checkPermissions(req); + const availability = await prisma.availability.create({ + data, + include: { Schedule: { select: { userId: true } } }, + }); + req.statusCode = 201; + return { + availability: schemaAvailabilityReadPublic.parse(availability), + message: "Availability created successfully", + }; +} + +async function checkPermissions(req: NextApiRequest) { + const { userId, prisma, isAdmin } = req; + if (isAdmin) return; + const data = schemaAvailabilityCreateBodyParams.parse(req.body); + const schedule = await prisma.schedule.findFirst({ + where: { userId, id: data.scheduleId }, + }); + if (!schedule) + throw new HttpError({ statusCode: 401, message: "You can't add availabilities to this schedule" }); +} + +export default defaultResponder(postHandler); diff --git a/apps/api/pages/api/availabilities/index.ts b/apps/api/pages/api/availabilities/index.ts new file mode 100644 index 0000000000..3ccafb7e55 --- /dev/null +++ b/apps/api/pages/api/availabilities/index.ts @@ -0,0 +1,9 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + POST: import("./_post"), + }) +); diff --git a/apps/api/pages/api/availability/_get.ts b/apps/api/pages/api/availability/_get.ts new file mode 100644 index 0000000000..42371e0be7 --- /dev/null +++ b/apps/api/pages/api/availability/_get.ts @@ -0,0 +1,158 @@ +import type { NextApiRequest } from "next"; +import { z } from "zod"; + +import { getUserAvailability } from "@calcom/core/getUserAvailability"; +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; +import { availabilityUserSelect } from "@calcom/prisma"; +import { stringOrNumber } from "@calcom/prisma/zod-utils"; + +/** + * @swagger + * /availability: + * get: + * summary: Find user or team availability + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * example: "1234abcd5678efgh" + * description: Your API key + * - in: query + * name: userId + * schema: + * type: integer + * example: 101 + * description: ID of the user to fetch the availability for + * - in: query + * name: teamId + * schema: + * type: integer + * example: 123 + * description: ID of the team to fetch the availability for + * - in: query + * name: username + * schema: + * type: string + * example: "alice" + * description: username of the user to fetch the availability for + * - in: query + * name: dateFrom + * schema: + * type: string + * format: date + * example: "2023-05-14 00:00:00" + * description: Start Date of the availability query + * - in: query + * name: dateTo + * schema: + * type: string + * format: date + * example: "2023-05-20 00:00:00" + * description: End Date of the availability query + * - in: query + * name: eventTypeId + * schema: + * type: integer + * example: 123 + * description: Event Type ID of the event type to fetch the availability for + * operationId: availability + * tags: + * - availability + * responses: + * 200: + * description: OK + * content: + * application/json: + * schema: + * type: object + * example: + * busy: + * - start: "2023-05-14T10:00:00.000Z" + * end: "2023-05-14T11:00:00.000Z" + * title: "Team meeting between Alice and Bob" + * - start: "2023-05-15T14:00:00.000Z" + * end: "2023-05-15T15:00:00.000Z" + * title: "Project review between Carol and Dave" + * - start: "2023-05-16T09:00:00.000Z" + * end: "2023-05-16T10:00:00.000Z" + * - start: "2023-05-17T13:00:00.000Z" + * end: "2023-05-17T14:00:00.000Z" + * timeZone: "America/New_York" + * workingHours: + * - days: [1, 2, 3, 4, 5] + * startTime: 540 + * endTime: 1020 + * userId: 101 + * dateOverrides: + * - date: "2023-05-15" + * startTime: 600 + * endTime: 960 + * userId: 101 + * currentSeats: 4 + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: User not found | Team not found | Team has no members + */ + +const availabilitySchema = z + .object({ + userId: stringOrNumber.optional(), + teamId: stringOrNumber.optional(), + username: z.string().optional(), + dateFrom: z.string(), + dateTo: z.string(), + eventTypeId: stringOrNumber.optional(), + }) + .refine( + (data) => !!data.username || !!data.userId || !!data.teamId, + "Either username or userId or teamId should be filled in." + ); + +async function handler(req: NextApiRequest) { + const { prisma, isAdmin } = req; + const { username, userId, eventTypeId, dateTo, dateFrom, teamId } = availabilitySchema.parse(req.query); + if (!teamId) + return getUserAvailability({ + username, + dateFrom, + dateTo, + eventTypeId, + userId, + }); + const team = await prisma.team.findUnique({ + where: { id: teamId }, + select: { members: true }, + }); + if (!team) throw new HttpError({ statusCode: 404, message: "teamId not found" }); + if (!team.members) throw new HttpError({ statusCode: 404, message: "team has no members" }); + const allMemberIds = team.members.map((membership) => membership.userId); + const members = await prisma.user.findMany({ + where: { id: { in: allMemberIds } }, + select: availabilityUserSelect, + }); + if (!isAdmin) throw new HttpError({ statusCode: 403, message: "Forbidden" }); + const availabilities = members.map(async (user) => { + return { + userId: user.id, + availability: await getUserAvailability({ + userId: user.id, + dateFrom, + dateTo, + eventTypeId, + }), + }; + }); + const settled = await Promise.all(availabilities); + if (!settled) + throw new HttpError({ + statusCode: 401, + message: "We had an issue retrieving all your members availabilities", + }); + return settled; +} + +export default defaultResponder(handler); diff --git a/apps/api/pages/api/availability/index.ts b/apps/api/pages/api/availability/index.ts new file mode 100644 index 0000000000..c53e4b8ef3 --- /dev/null +++ b/apps/api/pages/api/availability/index.ts @@ -0,0 +1,9 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("./_get"), + }) +); diff --git a/apps/api/pages/api/booking-references/[id]/_auth-middleware.ts b/apps/api/pages/api/booking-references/[id]/_auth-middleware.ts new file mode 100644 index 0000000000..fb4d179e61 --- /dev/null +++ b/apps/api/pages/api/booking-references/[id]/_auth-middleware.ts @@ -0,0 +1,19 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; + +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +async function authMiddleware(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const { id } = schemaQueryIdParseInt.parse(req.query); + // Here we make sure to only return references of the user's own bookings if the user is not an admin. + if (isAdmin) return; + // Find all references where the user has bookings + const bookingReference = await prisma.bookingReference.findFirst({ + where: { id, booking: { userId } }, + }); + if (!bookingReference) throw new HttpError({ statusCode: 403, message: "Forbidden" }); +} + +export default authMiddleware; diff --git a/apps/api/pages/api/booking-references/[id]/_delete.ts b/apps/api/pages/api/booking-references/[id]/_delete.ts new file mode 100644 index 0000000000..23a83ce311 --- /dev/null +++ b/apps/api/pages/api/booking-references/[id]/_delete.ts @@ -0,0 +1,43 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /booking-references/{id}: + * delete: + * operationId: removeBookingReferenceById + * summary: Remove an existing booking reference + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the booking reference to delete + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - booking-references + * responses: + * 201: + * description: OK, bookingReference removed successfully + * 400: + * description: Bad request. BookingReference id is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function deleteHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + await prisma.bookingReference.delete({ where: { id } }); + return { message: `BookingReference with id: ${id} deleted` }; +} + +export default defaultResponder(deleteHandler); diff --git a/apps/api/pages/api/booking-references/[id]/_get.ts b/apps/api/pages/api/booking-references/[id]/_get.ts new file mode 100644 index 0000000000..6baf71a550 --- /dev/null +++ b/apps/api/pages/api/booking-references/[id]/_get.ts @@ -0,0 +1,44 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaBookingReferenceReadPublic } from "~/lib/validations/booking-reference"; +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /booking-references/{id}: + * get: + * operationId: getBookingReferenceById + * summary: Find a booking reference + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the booking reference to get + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - booking-references + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: BookingReference was not found + */ +export async function getHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const booking_reference = await prisma.bookingReference.findUniqueOrThrow({ where: { id } }); + return { booking_reference: schemaBookingReferenceReadPublic.parse(booking_reference) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/booking-references/[id]/_patch.ts b/apps/api/pages/api/booking-references/[id]/_patch.ts new file mode 100644 index 0000000000..37e17e2e00 --- /dev/null +++ b/apps/api/pages/api/booking-references/[id]/_patch.ts @@ -0,0 +1,78 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { + schemaBookingEditBodyParams, + schemaBookingReferenceReadPublic, +} from "~/lib/validations/booking-reference"; +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /booking-references/{id}: + * 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: + * type: + * type: string + * meetingId: + * type: string + * meetingPassword: + * type: string + * externalCalendarId: + * type: string + * deleted: + * type: boolean + * credentialId: + * type: integer + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the booking reference to edit + * tags: + * - booking-references + * responses: + * 201: + * description: OK, BookingReference edited successfully + * 400: + * description: Bad request. BookingReference body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function patchHandler(req: NextApiRequest) { + const { prisma, query, body, isAdmin, userId } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const data = schemaBookingEditBodyParams.parse(body); + /* If user tries to update bookingId, we run extra checks */ + if (data.bookingId) { + const args: Prisma.BookingFindFirstOrThrowArgs = isAdmin + ? /* If admin, we only check that the booking exists */ + { where: { id: data.bookingId } } + : /* For non-admins we make sure the booking belongs to the user */ + { where: { id: data.bookingId, userId } }; + await prisma.booking.findFirstOrThrow(args); + } + const booking_reference = await prisma.bookingReference.update({ where: { id }, data }); + return { booking_reference: schemaBookingReferenceReadPublic.parse(booking_reference) }; +} + +export default defaultResponder(patchHandler); diff --git a/apps/api/pages/api/booking-references/[id]/index.ts b/apps/api/pages/api/booking-references/[id]/index.ts new file mode 100644 index 0000000000..727ad02843 --- /dev/null +++ b/apps/api/pages/api/booking-references/[id]/index.ts @@ -0,0 +1,18 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { defaultHandler, defaultResponder } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +import authMiddleware from "./_auth-middleware"; + +export default withMiddleware()( + defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req); + return defaultHandler({ + GET: import("./_get"), + PATCH: import("./_patch"), + DELETE: import("./_delete"), + })(req, res); + }) +); diff --git a/apps/api/pages/api/booking-references/_get.ts b/apps/api/pages/api/booking-references/_get.ts new file mode 100644 index 0000000000..c3b81a4622 --- /dev/null +++ b/apps/api/pages/api/booking-references/_get.ts @@ -0,0 +1,38 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaBookingReferenceReadPublic } from "~/lib/validations/booking-reference"; + +/** + * @swagger + * /booking-references: + * get: + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * operationId: listBookingReferences + * summary: Find all booking references + * tags: + * - booking-references + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No booking references were found + */ +async function getHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const args: Prisma.BookingReferenceFindManyArgs = isAdmin ? {} : { where: { booking: { userId } } }; + const data = await prisma.bookingReference.findMany(args); + return { booking_references: data.map((br) => schemaBookingReferenceReadPublic.parse(br)) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/booking-references/_post.ts b/apps/api/pages/api/booking-references/_post.ts new file mode 100644 index 0000000000..b3b8b713cf --- /dev/null +++ b/apps/api/pages/api/booking-references/_post.ts @@ -0,0 +1,86 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { + schemaBookingCreateBodyParams, + schemaBookingReferenceReadPublic, +} from "~/lib/validations/booking-reference"; + +/** + * @swagger + * /booking-references: + * post: + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * operationId: addBookingReference + * summary: Creates a new booking reference + * requestBody: + * description: Create a new booking reference related to one of your bookings + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - type + * - uid + * properties: + * type: + * type: string + * uid: + * type: string + * meetingId: + * type: string + * meetingPassword: + * type: string + * meetingUrl: + * type: string + * bookingId: + * type: boolean + * externalCalendarId: + * type: string + * deleted: + * type: boolean + * credentialId: + * type: integer + * tags: + * - booking-references + * responses: + * 201: + * description: OK, booking reference created + * 400: + * description: Bad request. BookingReference body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +async function postHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const body = schemaBookingCreateBodyParams.parse(req.body); + const args: Prisma.BookingFindFirstOrThrowArgs = isAdmin + ? /* If admin, we only check that the booking exists */ + { where: { id: body.bookingId } } + : /* For non-admins we make sure the booking belongs to the user */ + { where: { id: body.bookingId, userId } }; + await prisma.booking.findFirstOrThrow(args); + + const data = await prisma.bookingReference.create({ + data: { + ...body, + bookingId: body.bookingId, + }, + }); + + return { + booking_reference: schemaBookingReferenceReadPublic.parse(data), + message: "Booking reference created successfully", + }; +} + +export default defaultResponder(postHandler); diff --git a/apps/api/pages/api/booking-references/index.ts b/apps/api/pages/api/booking-references/index.ts new file mode 100644 index 0000000000..2a15abfa5b --- /dev/null +++ b/apps/api/pages/api/booking-references/index.ts @@ -0,0 +1,10 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("./_get"), + POST: import("./_post"), + }) +); diff --git a/apps/api/pages/api/bookings/[id]/_auth-middleware.ts b/apps/api/pages/api/bookings/[id]/_auth-middleware.ts new file mode 100644 index 0000000000..c785ccc3be --- /dev/null +++ b/apps/api/pages/api/bookings/[id]/_auth-middleware.ts @@ -0,0 +1,24 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; + +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +async function authMiddleware(req: NextApiRequest) { + const { userId, prisma, isAdmin, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const userWithBookings = await prisma.user.findUnique({ + where: { id: userId }, + include: { bookings: true }, + }); + + if (!userWithBookings) throw new HttpError({ statusCode: 404, message: "User not found" }); + + const userBookingIds = userWithBookings.bookings.map((booking) => booking.id); + + if (!isAdmin && !userBookingIds.includes(id)) { + throw new HttpError({ statusCode: 401, message: "You are not authorized" }); + } +} + +export default authMiddleware; diff --git a/apps/api/pages/api/bookings/[id]/_delete.ts b/apps/api/pages/api/bookings/[id]/_delete.ts new file mode 100644 index 0000000000..a1cdbc9c3e --- /dev/null +++ b/apps/api/pages/api/bookings/[id]/_delete.ts @@ -0,0 +1,82 @@ +import type { NextApiRequest } from "next"; + +import handleCancelBooking from "@calcom/features/bookings/lib/handleCancelBooking"; +import { defaultResponder } from "@calcom/lib/server"; +import { schemaBookingCancelParams } from "@calcom/prisma/zod-utils"; + +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /bookings/{id}/cancel: + * delete: + * summary: Booking cancellation + * operationId: cancelBookingById + * + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the booking to cancel + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * - in: query + * name: allRemainingBookings + * required: false + * schema: + * type: boolean + * description: Delete all remaining bookings + * - in: query + * name: reason + * required: false + * schema: + * type: string + * description: The reason for cancellation of the booking + * tags: + * - bookings + * responses: + * 200: + * description: OK, booking cancelled successfully + * 400: + * description: | + * Bad request + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
MessageCause
Booking not foundThe provided id didn't correspond to any existing booking.
Cannot cancel past eventsThe provided id matched an existing booking with a past startDate.
User not foundThe userId did not matched an existing user.
+ * 404: + * description: User not found + */ +async function handler(req: NextApiRequest) { + const { id, allRemainingBookings, cancellationReason } = schemaQueryIdParseInt + .merge(schemaBookingCancelParams.pick({ allRemainingBookings: true, cancellationReason: true })) + .parse({ + ...req.query, + allRemainingBookings: req.query.allRemainingBookings === "true", + }); + // Normalizing for universal handler + req.body = { id, allRemainingBookings, cancellationReason }; + return await handleCancelBooking(req); +} + +export default defaultResponder(handler); diff --git a/apps/api/pages/api/bookings/[id]/_get.ts b/apps/api/pages/api/bookings/[id]/_get.ts new file mode 100644 index 0000000000..5064ba2d78 --- /dev/null +++ b/apps/api/pages/api/bookings/[id]/_get.ts @@ -0,0 +1,73 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaBookingReadPublic } from "~/lib/validations/booking"; +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /bookings/{id}: + * get: + * summary: Find a booking + * operationId: getBookingById + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the booking to get + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - bookings + * responses: + * 200: + * description: OK + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/ArrayOfBookings" + * examples: + * bookings: + * value: [ + * { + * "id": 1, + * "description": "Meeting with John", + * "eventTypeId": 2, + * "uid": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8", + * "title": "Business Meeting", + * "startTime": "2023-04-20T10:00:00.000Z", + * "endTime": "2023-04-20T11:00:00.000Z", + * "timeZone": "Europe/London", + * "attendees": [ + * { + * "email": "example@cal.com", + * "name": "John Doe", + * "timeZone": "Europe/London", + * "locale": "en" + * } + * ] + * } + * ] + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: Booking was not found + */ +export async function getHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const booking = await prisma.booking.findUnique({ + where: { id }, + include: { attendees: true, user: true }, + }); + return { booking: schemaBookingReadPublic.parse(booking) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/bookings/[id]/_patch.ts b/apps/api/pages/api/bookings/[id]/_patch.ts new file mode 100644 index 0000000000..74999ab13c --- /dev/null +++ b/apps/api/pages/api/bookings/[id]/_patch.ts @@ -0,0 +1,110 @@ +import type { NextApiRequest } from "next"; +import type { z } from "zod"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaBookingEditBodyParams, schemaBookingReadPublic } from "~/lib/validations/booking"; +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /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 + * description: 'Booking event title' + * start: + * type: string + * format: date-time + * description: 'Start time of the Event' + * end: + * type: string + * format: date-time + * description: 'End time of the Event' + * status: + * type: string + * description: 'Acceptable values one of ["ACCEPTED", "PENDING", "CANCELLED", "REJECTED"]' + * examples: + * editBooking: + * value: + * { + * "title": "Debugging between Syed Ali Shahbaz and Hello Hello", + * "start": "2023-05-24T13:00:00.000Z", + * "end": "2023-05-24T13:30:00.000Z", + * "status": "CANCELLED" + * } + * + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the booking to edit + * tags: + * - bookings + * responses: + * 200: + * description: OK, booking edited successfully + * content: + * application/json: + * examples: + * bookings: + * value: + * { + * "booking": { + * "id": 11223344, + * "userId": 182, + * "description": null, + * "eventTypeId": 2323232, + * "uid": "stoSJtnh83PEL4rZmqdHe2", + * "title": "Debugging between Syed Ali Shahbaz and Hello Hello", + * "startTime": "2023-05-24T13:00:00.000Z", + * "endTime": "2023-05-24T13:30:00.000Z", + * "metadata": {}, + * "status": "CANCELLED" + * } + * } + * 400: + * description: Bad request. Booking body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function patchHandler(req: NextApiRequest) { + const { prisma, query, body } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const data = schemaBookingEditBodyParams.parse(body); + await checkPermissions(req, data); + const booking = await prisma.booking.update({ where: { id }, data }); + return { booking: schemaBookingReadPublic.parse(booking) }; +} + +async function checkPermissions(req: NextApiRequest, body: z.infer) { + const { isAdmin } = req; + if (body.userId && !isAdmin) { + // Organizer has to be a cal user and we can't allow a booking to be transfered to some other cal user's name + throw new HttpError({ + statusCode: 403, + message: "Only admin can change the organizer of a booking", + }); + } +} + +export default defaultResponder(patchHandler); diff --git a/apps/api/pages/api/bookings/[id]/cancel.ts b/apps/api/pages/api/bookings/[id]/cancel.ts new file mode 100644 index 0000000000..8dd20bef17 --- /dev/null +++ b/apps/api/pages/api/bookings/[id]/cancel.ts @@ -0,0 +1,14 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +import authMiddleware from "./_auth-middleware"; + +export default withMiddleware()(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req); + return defaultHandler({ + DELETE: import("./_delete"), + })(req, res); +}); diff --git a/apps/api/pages/api/bookings/[id]/index.ts b/apps/api/pages/api/bookings/[id]/index.ts new file mode 100644 index 0000000000..727ad02843 --- /dev/null +++ b/apps/api/pages/api/bookings/[id]/index.ts @@ -0,0 +1,18 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { defaultHandler, defaultResponder } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +import authMiddleware from "./_auth-middleware"; + +export default withMiddleware()( + defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req); + return defaultHandler({ + GET: import("./_get"), + PATCH: import("./_patch"), + DELETE: import("./_delete"), + })(req, res); + }) +); diff --git a/apps/api/pages/api/bookings/_get.ts b/apps/api/pages/api/bookings/_get.ts new file mode 100644 index 0000000000..006b39472e --- /dev/null +++ b/apps/api/pages/api/bookings/_get.ts @@ -0,0 +1,130 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaBookingReadPublic } from "~/lib/validations/booking"; +import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId"; + +/** + * @swagger + * /bookings: + * get: + * summary: Find all bookings + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * example: 123456789abcdefgh + * - in: query + * name: userId + * required: false + * schema: + * oneOf: + * - type: integer + * example: 1 + * - type: array + * items: + * type: integer + * example: [2, 3, 4] + * operationId: listBookings + * tags: + * - bookings + * responses: + * 200: + * description: OK + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/ArrayOfBookings" + * examples: + * bookings: + * value: [ + * { + * "id": 1, + * "description": "Meeting with John", + * "eventTypeId": 2, + * "uid": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8", + * "title": "Business Meeting", + * "startTime": "2023-04-20T10:00:00.000Z", + * "endTime": "2023-04-20T11:00:00.000Z", + * "timeZone": "Europe/London", + * "attendees": [ + * { + * "email": "example@cal.com", + * "name": "John Doe", + * "timeZone": "Europe/London", + * "locale": "en" + * } + * ] + * } + * ] + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No bookings were found + */ + +async function handler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const args: Prisma.BookingFindManyArgs = {}; + args.include = { + attendees: true, + user: true, + }; + + /** Only admins can query other users */ + if (isAdmin && req.query.userId) { + const query = schemaQuerySingleOrMultipleUserIds.parse(req.query); + const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId]; + const users = await prisma.user.findMany({ + where: { id: { in: userIds } }, + select: { email: true }, + }); + const userEmails = users.map((u) => u.email); + args.where = { + OR: [ + { userId: { in: userIds } }, + { + attendees: { + some: { + email: { in: userEmails }, + }, + }, + }, + ], + }; + } else if (!isAdmin) { + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { + email: true, + }, + }); + if (!user) { + throw new HttpError({ message: "User not found", statusCode: 500 }); + } + args.where = { + OR: [ + { + userId, + }, + { + attendees: { + some: { + email: user.email, + }, + }, + }, + ], + }; + } + const data = await prisma.booking.findMany(args); + return { bookings: data.map((booking) => schemaBookingReadPublic.parse(booking)) }; +} + +export default defaultResponder(handler); diff --git a/apps/api/pages/api/bookings/_post.ts b/apps/api/pages/api/bookings/_post.ts new file mode 100644 index 0000000000..d6baaa4891 --- /dev/null +++ b/apps/api/pages/api/bookings/_post.ts @@ -0,0 +1,204 @@ +import type { NextApiRequest } from "next"; + +import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking"; +import { defaultResponder } from "@calcom/lib/server"; + +/** + * @swagger + * /bookings: + * post: + * summary: Creates a new booking + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * operationId: addBooking + * requestBody: + * description: Create a new booking related to one of your event-types + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - eventTypeId + * - start + * - end + * - name + * - email + * - timeZone + * - language + * - metadata + * - customInputs + * - location + * properties: + * eventTypeId: + * type: integer + * description: 'ID of the event type to book' + * start: + * type: string + * format: date-time + * description: 'Start time of the Event' + * end: + * type: string + * format: date-time + * description: 'End time of the Event' + * name: + * type: string + * description: 'Name of the Attendee' + * email: + * type: string + * format: email + * description: 'Email ID of the Attendee' + * timeZone: + * type: string + * description: 'TimeZone of the Attendee' + * language: + * type: string + * description: 'Language of the Attendee' + * metadata: + * type: object + * properties: {} + * description: 'Any metadata associated with the booking' + * customInputs: + * type: array + * items: {} + * location: + * type: string + * description: 'Meeting location' + * title: + * type: string + * description: 'Booking event title' + * recurringEventId: + * type: integer + * description: 'Recurring event ID if the event is recurring' + * description: + * type: string + * description: 'Event description' + * status: + * type: string + * description: 'Acceptable values one of ["ACCEPTED", "PENDING", "CANCELLED", "REJECTED"]' + * seatsPerTimeSlot: + * type: integer + * description: 'The number of seats for each time slot' + * seatsShowAttendees: + * type: boolean + * description: 'Share Attendee information in seats' + * smsReminderNumber: + * type: number + * description: 'SMS reminder number' + * examples: + * New Booking example: + * value: + * { + * "eventTypeId": 2323232, + * "start": "2023-05-24T13:00:00.000Z", + * "end": "2023-05-24T13:30:00.000Z", + * "name": "Hello Hello", + * "email": "hello@gmail.com", + * "timeZone": "Europe/London", + * "language": "en", + * "metadata": {}, + * "customInputs": [], + * "location": "Calcom HQ", + * "title": "Debugging between Syed Ali Shahbaz and Hello Hello", + * "description": null, + * "status": "PENDING", + * "smsReminderNumber": null + * } + * + * tags: + * - bookings + * responses: + * 200: + * description: Booking(s) created successfully. + * content: + * application/json: + * examples: + * bookings: + * value: + * { + * "id": 11223344, + * "uid": "5yUjmAYTDF6MXo98re8SkX", + * "userId": 123, + * "eventTypeId": 2323232, + * "title": "Debugging between Syed Ali Shahbaz and Hello Hello", + * "description": null, + * "customInputs": {}, + * "responses": null, + * "startTime": "2023-05-24T13:00:00.000Z", + * "endTime": "2023-05-24T13:30:00.000Z", + * "location": "Calcom HQ", + * "createdAt": "2023-04-19T10:17:58.580Z", + * "updatedAt": null, + * "status": "PENDING", + * "paid": false, + * "destinationCalendarId": 2180, + * "cancellationReason": null, + * "rejectionReason": null, + * "dynamicEventSlugRef": null, + * "dynamicGroupSlugRef": null, + * "rescheduled": null, + * "fromReschedule": null, + * "recurringEventId": null, + * "smsReminderNumber": null, + * "scheduledJobs": [], + * "metadata": {}, + * "isRecorded": false, + * "user": { + * "email": "test@cal.com", + * "name": "Syed Ali Shahbaz", + * "timeZone": "Asia/Calcutta" + * }, + * "attendees": [ + * { + * "id": 12345, + * "email": "hello@gmail.com", + * "name": "Hello Hello", + * "timeZone": "Europe/London", + * "locale": "en", + * "bookingId": 11223344 + * } + * ], + * "payment": [], + * "references": [] + * } + * 400: + * description: | + * Bad request + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
MessageCause
Booking body is invalidMissing property on booking entity.
Invalid eventTypeIdThe provided eventTypeId does not exist.
Missing recurringCountThe eventType is recurring, and no recurringCount was passed.
Invalid recurringCountThe provided recurringCount is greater than the eventType recurring config
+ * 401: + * description: Authorization information is missing or invalid. + */ +async function handler(req: NextApiRequest) { + const { userId, isAdmin } = req; + if (isAdmin) req.userId = req.body.userId || userId; + const booking = await handleNewBooking(req); + return booking; +} + +export default defaultResponder(handler); diff --git a/apps/api/pages/api/bookings/index.ts b/apps/api/pages/api/bookings/index.ts new file mode 100644 index 0000000000..2a15abfa5b --- /dev/null +++ b/apps/api/pages/api/bookings/index.ts @@ -0,0 +1,10 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("./_get"), + POST: import("./_post"), + }) +); diff --git a/apps/api/pages/api/custom-inputs/[id]/_auth-middleware.ts b/apps/api/pages/api/custom-inputs/[id]/_auth-middleware.ts new file mode 100644 index 0000000000..3bae379831 --- /dev/null +++ b/apps/api/pages/api/custom-inputs/[id]/_auth-middleware.ts @@ -0,0 +1,19 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; + +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +async function authMiddleware(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const { id } = schemaQueryIdParseInt.parse(req.query); + // Admins can just skip this check + if (isAdmin) return; + // Check if the current user can access the event type of this input + const eventTypeCustomInput = await prisma.eventTypeCustomInput.findFirst({ + where: { id, eventType: { userId } }, + }); + if (!eventTypeCustomInput) throw new HttpError({ statusCode: 403, message: "Forbidden" }); +} + +export default authMiddleware; diff --git a/apps/api/pages/api/custom-inputs/[id]/_delete.ts b/apps/api/pages/api/custom-inputs/[id]/_delete.ts new file mode 100644 index 0000000000..747e1954ff --- /dev/null +++ b/apps/api/pages/api/custom-inputs/[id]/_delete.ts @@ -0,0 +1,42 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /custom-inputs/{id}: + * delete: + * summary: Remove an existing eventTypeCustomInput + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the eventTypeCustomInput to delete + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - custom-inputs + * responses: + * 201: + * description: OK, eventTypeCustomInput removed successfully + * 400: + * description: Bad request. EventType id is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function deleteHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + await prisma.eventTypeCustomInput.delete({ where: { id } }); + return { message: `CustomInputEventType with id: ${id} deleted successfully` }; +} + +export default defaultResponder(deleteHandler); diff --git a/apps/api/pages/api/custom-inputs/[id]/_get.ts b/apps/api/pages/api/custom-inputs/[id]/_get.ts new file mode 100644 index 0000000000..96cb8133dc --- /dev/null +++ b/apps/api/pages/api/custom-inputs/[id]/_get.ts @@ -0,0 +1,43 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaEventTypeCustomInputPublic } from "~/lib/validations/event-type-custom-input"; +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /custom-inputs/{id}: + * get: + * summary: Find a eventTypeCustomInput + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the eventTypeCustomInput to get + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - custom-inputs + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: EventType was not found + */ +export async function getHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const data = await prisma.eventTypeCustomInput.findUniqueOrThrow({ where: { id } }); + return { event_type_custom_input: schemaEventTypeCustomInputPublic.parse(data) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/custom-inputs/[id]/_patch.ts b/apps/api/pages/api/custom-inputs/[id]/_patch.ts new file mode 100644 index 0000000000..9ecdd822de --- /dev/null +++ b/apps/api/pages/api/custom-inputs/[id]/_patch.ts @@ -0,0 +1,86 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { + schemaEventTypeCustomInputEditBodyParams, + schemaEventTypeCustomInputPublic, +} from "~/lib/validations/event-type-custom-input"; +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /custom-inputs/{id}: + * patch: + * summary: Edit an existing eventTypeCustomInput + * requestBody: + * description: Edit an existing eventTypeCustomInput for an event type + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * eventTypeId: + * type: integer + * description: 'ID of the event type to which the custom input is being added' + * label: + * type: string + * description: 'Label of the custom input' + * type: + * type: string + * description: 'Type of the custom input. The value is ENUM; one of [TEXT, TEXTLONG, NUMBER, BOOL, RADIO, PHONE]' + * options: + * type: object + * properties: + * label: + * type: string + * type: + * type: string + * description: 'Options for the custom input' + * required: + * type: boolean + * description: 'If the custom input is required before booking' + * placeholder: + * type: string + * description: 'Placeholder text for the custom input' + * + * examples: + * custom-inputs: + * summary: Example of patching an existing Custom Input + * value: + * required: true + * + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the eventTypeCustomInput to edit + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * + * tags: + * - custom-inputs + * responses: + * 201: + * description: OK, eventTypeCustomInput edited successfully + * 400: + * description: Bad request. EventType body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function patchHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const data = schemaEventTypeCustomInputEditBodyParams.parse(req.body); + const result = await prisma.eventTypeCustomInput.update({ where: { id }, data }); + return { event_type_custom_input: schemaEventTypeCustomInputPublic.parse(result) }; +} + +export default defaultResponder(patchHandler); diff --git a/apps/api/pages/api/custom-inputs/[id]/index.ts b/apps/api/pages/api/custom-inputs/[id]/index.ts new file mode 100644 index 0000000000..727ad02843 --- /dev/null +++ b/apps/api/pages/api/custom-inputs/[id]/index.ts @@ -0,0 +1,18 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { defaultHandler, defaultResponder } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +import authMiddleware from "./_auth-middleware"; + +export default withMiddleware()( + defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req); + return defaultHandler({ + GET: import("./_get"), + PATCH: import("./_patch"), + DELETE: import("./_delete"), + })(req, res); + }) +); diff --git a/apps/api/pages/api/custom-inputs/_get.ts b/apps/api/pages/api/custom-inputs/_get.ts new file mode 100644 index 0000000000..db63434ddf --- /dev/null +++ b/apps/api/pages/api/custom-inputs/_get.ts @@ -0,0 +1,37 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaEventTypeCustomInputPublic } from "~/lib/validations/event-type-custom-input"; + +/** + * @swagger + * /custom-inputs: + * get: + * summary: Find all eventTypeCustomInputs + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - custom-inputs + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No eventTypeCustomInputs were found + */ +async function getHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const args: Prisma.EventTypeCustomInputFindManyArgs = isAdmin ? {} : { where: { eventType: { userId } } }; + const data = await prisma.eventTypeCustomInput.findMany(args); + return { event_type_custom_inputs: data.map((v) => schemaEventTypeCustomInputPublic.parse(v)) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/custom-inputs/_post.ts b/apps/api/pages/api/custom-inputs/_post.ts new file mode 100644 index 0000000000..331fdd732f --- /dev/null +++ b/apps/api/pages/api/custom-inputs/_post.ts @@ -0,0 +1,103 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { + schemaEventTypeCustomInputBodyParams, + schemaEventTypeCustomInputPublic, +} from "~/lib/validations/event-type-custom-input"; + +/** + * @swagger + * /custom-inputs: + * post: + * summary: Creates a new eventTypeCustomInput + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * requestBody: + * description: Create a new custom input for an event type + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - eventTypeId + * - label + * - type + * - required + * - placeholder + * properties: + * eventTypeId: + * type: integer + * description: 'ID of the event type to which the custom input is being added' + * label: + * type: string + * description: 'Label of the custom input' + * type: + * type: string + * description: 'Type of the custom input. The value is ENUM; one of [TEXT, TEXTLONG, NUMBER, BOOL, RADIO, PHONE]' + * options: + * type: object + * properties: + * label: + * type: string + * type: + * type: string + * description: 'Options for the custom input' + * required: + * type: boolean + * description: 'If the custom input is required before booking' + * placeholder: + * type: string + * description: 'Placeholder text for the custom input' + * + * examples: + * custom-inputs: + * summary: An example of custom-inputs + * value: + * eventTypeID: 1 + * label: "Phone Number" + * type: "PHONE" + * required: true + * placeholder: "100 101 1234" + * + * tags: + * - custom-inputs + * responses: + * 201: + * description: OK, eventTypeCustomInput created + * 400: + * description: Bad request. EventTypeCustomInput body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +async function postHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const { eventTypeId, ...body } = schemaEventTypeCustomInputBodyParams.parse(req.body); + + if (!isAdmin) { + /* We check that the user has access to the event type he's trying to add a custom input to. */ + const eventType = await prisma.eventType.findFirst({ + where: { id: eventTypeId, userId }, + }); + if (!eventType) throw new HttpError({ statusCode: 403, message: "Forbidden" }); + } + + const data = await prisma.eventTypeCustomInput.create({ + data: { ...body, eventType: { connect: { id: eventTypeId } } }, + }); + + return { + event_type_custom_input: schemaEventTypeCustomInputPublic.parse(data), + message: "EventTypeCustomInput created successfully", + }; +} + +export default defaultResponder(postHandler); diff --git a/apps/api/pages/api/custom-inputs/index.ts b/apps/api/pages/api/custom-inputs/index.ts new file mode 100644 index 0000000000..2a15abfa5b --- /dev/null +++ b/apps/api/pages/api/custom-inputs/index.ts @@ -0,0 +1,10 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("./_get"), + POST: import("./_post"), + }) +); diff --git a/apps/api/pages/api/destination-calendars/[id].ts b/apps/api/pages/api/destination-calendars/[id].ts new file mode 100644 index 0000000000..1f521c40d7 --- /dev/null +++ b/apps/api/pages/api/destination-calendars/[id].ts @@ -0,0 +1,240 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; +import type { DestinationCalendarResponse } from "~/lib/types"; +import { + schemaDestinationCalendarEditBodyParams, + schemaDestinationCalendarReadPublic, +} from "~/lib/validations/destination-calendar"; +import { + schemaQueryIdParseInt, + withValidQueryIdTransformParseInt, +} from "~/lib/validations/shared/queryIdTransformParseInt"; + +export async function destionationCalendarById( + { method, query, body, userId, prisma }: NextApiRequest, + res: NextApiResponse +) { + const safeQuery = schemaQueryIdParseInt.safeParse(query); + const safeBody = schemaDestinationCalendarEditBodyParams.safeParse(body); + if (!safeQuery.success) { + res.status(400).json({ message: "Your query was invalid" }); + return; + } + const data = await prisma.destinationCalendar.findMany({ where: { userId } }); + const userDestinationCalendars = data.map((destinationCalendar) => destinationCalendar.id); + // FIXME: Should we also check ownership of bokingId and eventTypeId to avoid users cross-pollinating other users calendars. + // On a related note, moving from sequential integer IDs to UUIDs would be a good idea. and maybe help avoid having this problem. + if (userDestinationCalendars.includes(safeQuery.data.id)) res.status(401).json({ message: "Unauthorized" }); + else { + switch (method) { + /** + * @swagger + * /destination-calendars/{id}: + * get: + * summary: Find a destination calendar + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the destination calendar to get + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - destination-calendars + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: DestinationCalendar was not found + * patch: + * summary: Edit an existing destination calendar + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the destination calendar to edit + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * requestBody: + * description: Create a new booking related to one of your event-types + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * integration: + * type: string + * description: 'The integration' + * externalId: + * type: string + * description: 'The external ID of the integration' + * eventTypeId: + * type: integer + * description: 'The ID of the eventType it is associated with' + * bookingId: + * type: integer + * description: 'The booking ID it is associated with' + * tags: + * - destination-calendars + * responses: + * 201: + * description: OK, destinationCalendar edited successfuly + * 400: + * description: Bad request. DestinationCalendar body is invalid. + * 401: + * description: Authorization information is missing or invalid. + * delete: + * summary: Remove an existing destination calendar + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the destination calendar to delete + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - destination-calendars + * responses: + * 201: + * description: OK, destinationCalendar removed successfuly + * 400: + * description: Bad request. DestinationCalendar id is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ + case "GET": + await prisma.destinationCalendar + .findUnique({ where: { id: safeQuery.data.id } }) + .then((data) => schemaDestinationCalendarReadPublic.parse(data)) + .then((destination_calendar) => res.status(200).json({ destination_calendar })) + .catch((error: Error) => + res.status(404).json({ + message: `DestinationCalendar with id: ${safeQuery.data.id} not found`, + error, + }) + ); + break; + /** + * @swagger + * /destination-calendars/{id}: + * patch: + * summary: Edit an existing destination calendar + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the destination calendar to edit + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - destination-calendars + * responses: + * 201: + * description: OK, destinationCalendar edited successfuly + * 400: + * description: Bad request. DestinationCalendar body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ + case "PATCH": + if (!safeBody.success) { + { + res.status(400).json({ message: "Invalid request body" }); + return; + } + } + await prisma.destinationCalendar + .update({ where: { id: safeQuery.data.id }, data: safeBody.data }) + .then((data) => schemaDestinationCalendarReadPublic.parse(data)) + .then((destination_calendar) => res.status(200).json({ destination_calendar })) + .catch((error: Error) => + res.status(404).json({ + message: `DestinationCalendar with id: ${safeQuery.data.id} not found`, + error, + }) + ); + break; + /** + * @swagger + * /destination-calendars/{id}: + * delete: + * summary: Remove an existing destination calendar + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the destination calendar to delete + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - destination-calendars + * responses: + * 201: + * description: OK, destinationCalendar removed successfuly + * 400: + * description: Bad request. DestinationCalendar id is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ + case "DELETE": + await prisma.destinationCalendar + .delete({ + where: { id: safeQuery.data.id }, + }) + .then(() => + res.status(200).json({ + message: `DestinationCalendar with id: ${safeQuery.data.id} deleted`, + }) + ) + .catch((error: Error) => + res.status(404).json({ + message: `DestinationCalendar with id: ${safeQuery.data.id} not found`, + error, + }) + ); + break; + + default: + res.status(405).json({ message: "Method not allowed" }); + break; + } + } +} + +export default withMiddleware("HTTP_GET_DELETE_PATCH")( + withValidQueryIdTransformParseInt(destionationCalendarById) +); diff --git a/apps/api/pages/api/destination-calendars/index.ts b/apps/api/pages/api/destination-calendars/index.ts new file mode 100644 index 0000000000..c1330d49fa --- /dev/null +++ b/apps/api/pages/api/destination-calendars/index.ts @@ -0,0 +1,114 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; +import type { DestinationCalendarResponse, DestinationCalendarsResponse } from "~/lib/types"; +import { + schemaDestinationCalendarCreateBodyParams, + schemaDestinationCalendarReadPublic, +} from "~/lib/validations/destination-calendar"; + +async function createOrlistAllDestinationCalendars( + { method, body, userId, prisma }: NextApiRequest, + res: NextApiResponse +) { + if (method === "GET") { + /** + * @swagger + * /destination-calendars: + * get: + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * summary: Find all destination calendars + * tags: + * - destination-calendars + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No destination calendars were found + */ + const data = await prisma.destinationCalendar.findMany({ where: { userId } }); + const destination_calendars = data.map((destinationCalendar) => + schemaDestinationCalendarReadPublic.parse(destinationCalendar) + ); + if (data) res.status(200).json({ destination_calendars }); + else + (error: Error) => + res.status(404).json({ + message: "No DestinationCalendars were found", + error, + }); + } else if (method === "POST") { + /** + * @swagger + * /destination-calendars: + * post: + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * summary: Creates a new destination calendar + * requestBody: + * description: Create a new destination calendar for your events + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - integration + * - externalId + * properties: + * integration: + * type: string + * description: 'The integration' + * externalId: + * type: string + * description: 'The external ID of the integration' + * eventTypeId: + * type: integer + * description: 'The ID of the eventType it is associated with' + * bookingId: + * type: integer + * description: 'The booking ID it is associated with' + * tags: + * - destination-calendars + * responses: + * 201: + * description: OK, destination calendar created + * 400: + * description: Bad request. DestinationCalendar body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ + const safe = schemaDestinationCalendarCreateBodyParams.safeParse(body); + if (!safe.success) { + res.status(400).json({ message: "Invalid request body" }); + return; + } + + const data = await prisma.destinationCalendar.create({ data: { ...safe.data, userId } }); + const destination_calendar = schemaDestinationCalendarReadPublic.parse(data); + + if (destination_calendar) + res.status(201).json({ destination_calendar, message: "DestinationCalendar created successfully" }); + else + (error: Error) => + res.status(400).json({ + message: "Could not create new destinationCalendar", + error, + }); + } else res.status(405).json({ message: `Method ${method} not allowed` }); +} + +export default withMiddleware("HTTP_GET_OR_POST")(createOrlistAllDestinationCalendars); diff --git a/apps/api/pages/api/docs.ts b/apps/api/pages/api/docs.ts new file mode 100644 index 0000000000..1773d87457 --- /dev/null +++ b/apps/api/pages/api/docs.ts @@ -0,0 +1,129 @@ +import { withSwagger } from "next-swagger-doc"; + +import pjson from "~/package.json"; + +const swaggerHandler = withSwagger({ + definition: { + openapi: "3.0.3", + servers: [ + { url: "http://localhost:3002/v1" }, + { url: "https://api.cal.dev/v1" }, + { url: "https://api.cal.com/v1" }, + ], + externalDocs: { + url: "https://docs.cal.com", + description: "Find more info at our main docs: https://docs.cal.com/", + }, + info: { + title: `${pjson.name}: ${pjson.description}`, + version: pjson.version, + }, + components: { + securitySchemes: { ApiKeyAuth: { type: "apiKey", in: "query", name: "apiKey" } }, + schemas: { + ArrayOfBookings: { + type: "array", + items: { + $ref: "#/components/schemas/Booking", + }, + }, + Booking: { + properties: { + id: { + type: "number", + }, + description: { + type: "string", + }, + eventTypeId: { + type: "number", + }, + uid: { + type: "string", + format: "uuid", + }, + title: { + type: "string", + }, + startTime: { + type: "string", + format: "date-time", + }, + endTime: { + type: "string", + format: "date-time", + }, + timeZone: { + type: "string", + example: "Europe/London", + }, + attendees: { + type: "array", + items: { + properties: { + email: { + type: "string", + example: "example@cal.com", + }, + name: { + type: "string", + }, + timeZone: { + type: "string", + example: "Europe/London", + }, + locale: { + type: "string", + example: "en", + }, + }, + }, + }, + user: { + properties: { + email: { + type: "string", + example: "example@cal.com", + }, + name: { + type: "string", + }, + timeZone: { + type: "string", + example: "Europe/London", + }, + locale: { + type: "string", + example: "en", + }, + }, + }, + }, + }, + }, + }, + security: [{ ApiKeyAuth: [] }], + tags: [ + { name: "users" }, + { name: "event-types" }, + { name: "bookings" }, + { name: "attendees" }, + { name: "payments" }, + { name: "schedules" }, + { name: "teams" }, + { name: "memberships" }, + { + name: "availabilities", + description: "Allows modifying unique availabilities tied to a schedule.", + }, + { name: "custom-inputs" }, + { name: "event-references" }, + { name: "booking-references" }, + { name: "destination-calendars" }, + { name: "selected-calendars" }, + ], + }, + apiFolder: "pages/api", +}); + +export default swaggerHandler(); diff --git a/apps/api/pages/api/event-types/[id]/_auth-middleware.ts b/apps/api/pages/api/event-types/[id]/_auth-middleware.ts new file mode 100644 index 0000000000..8907d7cd3b --- /dev/null +++ b/apps/api/pages/api/event-types/[id]/_auth-middleware.ts @@ -0,0 +1,17 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; + +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +async function authMiddleware(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const { id } = schemaQueryIdParseInt.parse(req.query); + if (isAdmin) return; + const eventType = await prisma.eventType.findFirst({ + where: { id, users: { some: { id: userId } } }, + }); + if (!eventType) throw new HttpError({ statusCode: 403, message: "Forbidden" }); +} + +export default authMiddleware; diff --git a/apps/api/pages/api/event-types/[id]/_delete.ts b/apps/api/pages/api/event-types/[id]/_delete.ts new file mode 100644 index 0000000000..2e598afc54 --- /dev/null +++ b/apps/api/pages/api/event-types/[id]/_delete.ts @@ -0,0 +1,56 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /event-types/{id}: + * delete: + * operationId: removeEventTypeById + * summary: Remove an existing eventType + * parameters: + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the eventType to delete + * tags: + * - event-types + * externalDocs: + * url: https://docs.cal.com/core-features/event-types + * responses: + * 201: + * description: OK, eventType removed successfully + * 400: + * description: Bad request. EventType id is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function deleteHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + await checkPermissions(req); + await prisma.eventType.delete({ where: { id } }); + return { message: `Event Type with id: ${id} deleted successfully` }; +} + +async function checkPermissions(req: NextApiRequest) { + const { userId, prisma, isAdmin } = req; + const { id } = schemaQueryIdParseInt.parse(req.query); + if (isAdmin) return; + /** Only event type owners can delete it */ + const eventType = await prisma.eventType.findFirst({ where: { id, userId } }); + if (!eventType) throw new HttpError({ statusCode: 403, message: "Forbidden" }); +} + +export default defaultResponder(deleteHandler); diff --git a/apps/api/pages/api/event-types/[id]/_get.ts b/apps/api/pages/api/event-types/[id]/_get.ts new file mode 100644 index 0000000000..74d9d14456 --- /dev/null +++ b/apps/api/pages/api/event-types/[id]/_get.ts @@ -0,0 +1,60 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaEventTypeReadPublic } from "~/lib/validations/event-type"; +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +import getCalLink from "../_utils/getCalLink"; + +/** + * @swagger + * /event-types/{id}: + * get: + * operationId: getEventTypeById + * summary: Find a eventType + * parameters: + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * - in: path + * name: id + * example: 4 + * schema: + * type: integer + * required: true + * description: ID of the eventType to get + * tags: + * - event-types + * externalDocs: + * url: https://docs.cal.com/core-features/event-types + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: EventType was not found + */ +export async function getHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const event_type = await prisma.eventType.findUnique({ + where: { id }, + include: { + customInputs: true, + team: { select: { slug: true } }, + users: true, + owner: { select: { username: true, id: true } }, + }, + }); + + const link = event_type ? getCalLink(event_type) : null; + + return { event_type: schemaEventTypeReadPublic.parse({ ...event_type, link }) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/event-types/[id]/_patch.ts b/apps/api/pages/api/event-types/[id]/_patch.ts new file mode 100644 index 0000000000..2468d1621b --- /dev/null +++ b/apps/api/pages/api/event-types/[id]/_patch.ts @@ -0,0 +1,233 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; +import type { z } from "zod"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; +import { SchedulingType } from "@calcom/prisma/enums"; + +import type { schemaEventTypeBaseBodyParams } from "~/lib/validations/event-type"; +import { schemaEventTypeEditBodyParams, schemaEventTypeReadPublic } from "~/lib/validations/event-type"; +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; +import ensureOnlyMembersAsHosts from "~/pages/api/event-types/_utils/ensureOnlyMembersAsHosts"; + +import checkTeamEventEditPermission from "../_utils/checkTeamEventEditPermission"; + +/** + * @swagger + * /event-types/{id}: + * patch: + * operationId: editEventTypeById + * summary: Edit an existing eventType + * parameters: + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the eventType to edit + * requestBody: + * description: Create a new event-type related to your user or team + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * length: + * type: integer + * description: Duration of the event type in minutes + * metadata: + * type: object + * description: Metadata relating to event type. Pass {} if empty + * title: + * type: string + * description: Title of the event type + * slug: + * type: string + * description: Unique slug for the event type + * hosts: + * type: array + * items: + * type: object + * properties: + * userId: + * type: number + * isFixed: + * type: boolean + * description: Host MUST be available for any slot to be bookable. + * hidden: + * type: boolean + * description: If the event type should be hidden from your public booking page + * position: + * type: integer + * description: The position of the event type on the public booking page + * teamId: + * type: integer + * description: Team ID if the event type should belong to a team + * periodType: + * type: string + * enum: [UNLIMITED, ROLLING, RANGE] + * description: To decide how far into the future an invitee can book an event with you + * periodStartDate: + * type: string + * format: date-time + * description: Start date of bookable period (Required if periodType is 'range') + * periodEndDate: + * type: string + * format: date-time + * description: End date of bookable period (Required if periodType is 'range') + * periodDays: + * type: integer + * description: Number of bookable days (Required if periodType is rolling) + * periodCountCalendarDays: + * type: boolean + * description: If calendar days should be counted for period days + * requiresConfirmation: + * type: boolean + * description: If the event type should require your confirmation before completing the booking + * recurringEvent: + * type: object + * description: If the event should recur every week/month/year with the selected frequency + * properties: + * interval: + * type: integer + * count: + * type: integer + * freq: + * type: integer + * disableGuests: + * type: boolean + * description: If the event type should disable adding guests to the booking + * hideCalendarNotes: + * type: boolean + * description: If the calendar notes should be hidden from the booking + * minimumBookingNotice: + * type: integer + * description: Minimum time in minutes before the event is bookable + * beforeEventBuffer: + * type: integer + * description: Number of minutes of buffer time before a Cal Event + * afterEventBuffer: + * type: integer + * description: Number of minutes of buffer time after a Cal Event + * schedulingType: + * type: string + * description: The type of scheduling if a Team event. Required for team events only + * enum: [ROUND_ROBIN, COLLECTIVE] + * price: + * type: integer + * description: Price of the event type booking + * currency: + * type: string + * description: Currency acronym. Eg- usd, eur, gbp, etc. + * slotInterval: + * type: integer + * description: The intervals of available bookable slots in minutes + * successRedirectUrl: + * type: string + * format: url + * description: A valid URL where the booker will redirect to, once the booking is completed successfully + * description: + * type: string + * description: Description of the event type + * seatsPerTimeSlot: + * type: integer + * description: 'The number of seats for each time slot' + * seatsShowAttendees: + * type: boolean + * description: 'Share Attendee information in seats' + * locations: + * type: array + * description: A list of all available locations for the event type + * items: + * type: array + * items: + * oneOf: + * - type: object + * properties: + * type: + * type: string + * enum: ['integrations:daily'] + * - type: object + * properties: + * type: + * type: string + * enum: ['attendeeInPerson'] + * - type: object + * properties: + * type: + * type: string + * enum: ['inPerson'] + * address: + * type: string + * displayLocationPublicly: + * type: boolean + * - type: object + * properties: + * type: + * type: string + * enum: ['link'] + * link: + * type: string + * displayLocationPublicly: + * type: boolean + * example: + * event-type: + * summary: An example of event type PATCH request + * value: + * length: 60 + * requiresConfirmation: true + * tags: + * - event-types + * externalDocs: + * url: https://docs.cal.com/core-features/event-types + * responses: + * 201: + * description: OK, eventType edited successfully + * 400: + * description: Bad request. EventType body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function patchHandler(req: NextApiRequest) { + const { prisma, query, body } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const { hosts = [], ...parsedBody } = schemaEventTypeEditBodyParams.parse(body); + + const data: Prisma.EventTypeUpdateArgs["data"] = { + ...parsedBody, + }; + + if (hosts) { + await ensureOnlyMembersAsHosts(req, parsedBody); + data.hosts = { + deleteMany: {}, + create: hosts.map((host) => ({ + ...host, + isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed, + })), + }; + } + await checkPermissions(req, parsedBody); + const eventType = await prisma.eventType.update({ where: { id }, data }); + return { event_type: schemaEventTypeReadPublic.parse(eventType) }; +} + +async function checkPermissions(req: NextApiRequest, body: z.infer) { + const { userId, prisma, isAdmin } = req; + const { id } = schemaQueryIdParseInt.parse(req.query); + if (isAdmin) return; + /** Only event type owners can modify it */ + const eventType = await prisma.eventType.findFirst({ where: { id, userId } }); + if (!eventType) throw new HttpError({ statusCode: 403, message: "Forbidden" }); + await checkTeamEventEditPermission(req, body); +} + +export default defaultResponder(patchHandler); diff --git a/apps/api/pages/api/event-types/[id]/index.ts b/apps/api/pages/api/event-types/[id]/index.ts new file mode 100644 index 0000000000..727ad02843 --- /dev/null +++ b/apps/api/pages/api/event-types/[id]/index.ts @@ -0,0 +1,18 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { defaultHandler, defaultResponder } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +import authMiddleware from "./_auth-middleware"; + +export default withMiddleware()( + defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req); + return defaultHandler({ + GET: import("./_get"), + PATCH: import("./_patch"), + DELETE: import("./_delete"), + })(req, res); + }) +); diff --git a/apps/api/pages/api/event-types/_get.ts b/apps/api/pages/api/event-types/_get.ts new file mode 100644 index 0000000000..acdfdc4dee --- /dev/null +++ b/apps/api/pages/api/event-types/_get.ts @@ -0,0 +1,67 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaEventTypeReadPublic } from "~/lib/validations/event-type"; +import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId"; + +import getCalLink from "./_utils/getCalLink"; + +/** + * @swagger + * /event-types: + * get: + * summary: Find all event types + * operationId: listEventTypes + * parameters: + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * tags: + * - event-types + * externalDocs: + * url: https://docs.cal.com/core-features/event-types + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No event types were found + */ +async function getHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + + const args: Prisma.EventTypeFindManyArgs = { + where: { userId }, + }; + /** Only admins can query other users */ + if (!isAdmin && req.query.userId) throw new HttpError({ statusCode: 401, message: "ADMIN required" }); + if (isAdmin && req.query.userId) { + const query = schemaQuerySingleOrMultipleUserIds.parse(req.query); + const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId]; + args.where = { userId: { in: userIds } }; + } + const data = await prisma.eventType.findMany({ + ...args, + include: { + customInputs: true, + team: { select: { slug: true } }, + users: true, + owner: { select: { username: true, id: true } }, + }, + }); + return { + event_types: data.map((eventType) => { + const link = getCalLink(eventType); + return schemaEventTypeReadPublic.parse({ ...eventType, link }); + }), + }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/event-types/_post.ts b/apps/api/pages/api/event-types/_post.ts new file mode 100644 index 0000000000..488a15e6b9 --- /dev/null +++ b/apps/api/pages/api/event-types/_post.ts @@ -0,0 +1,265 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaEventTypeCreateBodyParams, schemaEventTypeReadPublic } from "~/lib/validations/event-type"; + +import checkTeamEventEditPermission from "./_utils/checkTeamEventEditPermission"; +import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts"; + +/** + * @swagger + * /event-types: + * post: + * summary: Creates a new event type + * operationId: addEventType + * parameters: + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * requestBody: + * description: Create a new event-type related to your user or team + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - title + * - slug + * - length + * - metadata + * properties: + * length: + * type: integer + * description: Duration of the event type in minutes + * metadata: + * type: object + * description: Metadata relating to event type. Pass {} if empty + * title: + * type: string + * description: Title of the event type + * slug: + * type: string + * description: Unique slug for the event type + * example: my-event + * hosts: + * type: array + * items: + * type: object + * properties: + * userId: + * type: number + * isFixed: + * type: boolean + * description: Host MUST be available for any slot to be bookable. + * hidden: + * type: boolean + * description: If the event type should be hidden from your public booking page + * position: + * type: integer + * description: The position of the event type on the public booking page + * teamId: + * type: integer + * description: Team ID if the event type should belong to a team + * periodType: + * type: string + * enum: [UNLIMITED, ROLLING, RANGE] + * description: To decide how far into the future an invitee can book an event with you + * periodStartDate: + * type: string + * format: date-time + * description: Start date of bookable period (Required if periodType is 'range') + * periodEndDate: + * type: string + * format: date-time + * description: End date of bookable period (Required if periodType is 'range') + * periodDays: + * type: integer + * description: Number of bookable days (Required if periodType is rolling) + * periodCountCalendarDays: + * type: boolean + * description: If calendar days should be counted for period days + * requiresConfirmation: + * type: boolean + * description: If the event type should require your confirmation before completing the booking + * recurringEvent: + * type: object + * description: If the event should recur every week/month/year with the selected frequency + * properties: + * interval: + * type: integer + * count: + * type: integer + * freq: + * type: integer + * disableGuests: + * type: boolean + * description: If the event type should disable adding guests to the booking + * hideCalendarNotes: + * type: boolean + * description: If the calendar notes should be hidden from the booking + * minimumBookingNotice: + * type: integer + * description: Minimum time in minutes before the event is bookable + * beforeEventBuffer: + * type: integer + * description: Number of minutes of buffer time before a Cal Event + * afterEventBuffer: + * type: integer + * description: Number of minutes of buffer time after a Cal Event + * schedulingType: + * type: string + * description: The type of scheduling if a Team event. Required for team events only + * enum: [ROUND_ROBIN, COLLECTIVE] + * price: + * type: integer + * description: Price of the event type booking + * currency: + * type: string + * description: Currency acronym. Eg- usd, eur, gbp, etc. + * slotInterval: + * type: integer + * description: The intervals of available bookable slots in minutes + * successRedirectUrl: + * type: string + * format: url + * description: A valid URL where the booker will redirect to, once the booking is completed successfully + * description: + * type: string + * description: Description of the event type + * locations: + * type: array + * description: A list of all available locations for the event type + * items: + * type: array + * items: + * oneOf: + * - type: object + * properties: + * type: + * type: string + * enum: ['integrations:daily'] + * - type: object + * properties: + * type: + * type: string + * enum: ['attendeeInPerson'] + * - type: object + * properties: + * type: + * type: string + * enum: ['inPerson'] + * address: + * type: string + * displayLocationPublicly: + * type: boolean + * - type: object + * properties: + * type: + * type: string + * enum: ['link'] + * link: + * type: string + * displayLocationPublicly: + * type: boolean + * example: + * event-type: + * summary: An example of event type POST request + * value: + * title: Hello World + * slug: hello-world + * length: 30 + * hidden: false + * position: 0 + * eventName: null + * timeZone: null + * periodType: UNLIMITED + * periodStartDate: 2023-02-15T08:46:16.000Z + * periodEndDate: 2023-0-15T08:46:16.000Z + * periodDays: null + * periodCountCalendarDays: false + * requiresConfirmation: false + * recurringEvent: null + * disableGuests: false + * hideCalendarNotes: false + * minimumBookingNotice: 120 + * beforeEventBuffer: 0 + * afterEventBuffer: 0 + * price: 0 + * currency: usd + * slotInterval: null + * successRedirectUrl: null + * description: A test event type + * metadata: { + * apps: { + * stripe: { + * price: 0, + * enabled: false, + * currency: usd + * } + * } + * } + * tags: + * - event-types + * externalDocs: + * url: https://docs.cal.com/core-features/event-types + * responses: + * 201: + * description: OK, event type created + * 400: + * description: Bad request. EventType body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +async function postHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma, body } = req; + + const { hosts = [], ...parsedBody } = schemaEventTypeCreateBodyParams.parse(body || {}); + + let data: Prisma.EventTypeCreateArgs["data"] = { + ...parsedBody, + userId, + users: { connect: { id: userId } }, + }; + + await checkPermissions(req); + + if (isAdmin && parsedBody.userId) { + data = { ...parsedBody, users: { connect: { id: parsedBody.userId } } }; + } + + await checkTeamEventEditPermission(req, parsedBody); + await ensureOnlyMembersAsHosts(req, parsedBody); + + if (hosts) { + data.hosts = { createMany: { data: hosts } }; + } + + const eventType = await prisma.eventType.create({ data }); + + return { + event_type: schemaEventTypeReadPublic.parse(eventType), + message: "Event type created successfully", + }; +} + +async function checkPermissions(req: NextApiRequest) { + const { isAdmin } = req; + const body = schemaEventTypeCreateBodyParams.parse(req.body); + /* Non-admin users can only create event types for themselves */ + if (!isAdmin && body.userId) + throw new HttpError({ + statusCode: 401, + message: "ADMIN required for `userId`", + }); + /* Admin users are required to pass in a userId */ + if (isAdmin && !body.userId) throw new HttpError({ statusCode: 400, message: "`userId` required" }); +} + +export default defaultResponder(postHandler); diff --git a/apps/api/pages/api/event-types/_utils/checkTeamEventEditPermission.ts b/apps/api/pages/api/event-types/_utils/checkTeamEventEditPermission.ts new file mode 100644 index 0000000000..abcfc055f2 --- /dev/null +++ b/apps/api/pages/api/event-types/_utils/checkTeamEventEditPermission.ts @@ -0,0 +1,29 @@ +import type { NextApiRequest } from "next"; +import type { z } from "zod"; + +import { HttpError } from "@calcom/lib/http-error"; + +import type { schemaEventTypeBaseBodyParams } from "~/lib/validations/event-type"; + +export default async function checkTeamEventEditPermission( + req: NextApiRequest, + body: Pick, "teamId"> +) { + const { prisma, userId } = req; + if (body.teamId) { + const membership = await prisma.membership.findFirst({ + where: { + userId, + teamId: body.teamId, + accepted: true, + }, + }); + + if (!membership?.role || !["ADMIN", "OWNER"].includes(membership.role)) { + throw new HttpError({ + statusCode: 401, + message: "No permission to operate on event-type for this team", + }); + } + } +} diff --git a/apps/api/pages/api/event-types/_utils/ensureOnlyMembersAsHosts.ts b/apps/api/pages/api/event-types/_utils/ensureOnlyMembersAsHosts.ts new file mode 100644 index 0000000000..301c5307a1 --- /dev/null +++ b/apps/api/pages/api/event-types/_utils/ensureOnlyMembersAsHosts.ts @@ -0,0 +1,21 @@ +import type { NextApiRequest } from "next"; +import type { z } from "zod"; + +import type { schemaEventTypeCreateBodyParams } from "~/lib/validations/event-type"; + +export default async function ensureOnlyMembersAsHosts( + req: NextApiRequest, + body: Pick, "hosts" | "teamId"> +) { + if (body.teamId && body.hosts && body.hosts.length > 0) { + const teamMemberCount = await req.prisma.membership.count({ + where: { + teamId: body.teamId, + userId: { in: body.hosts.map((host) => host.userId) }, + }, + }); + if (teamMemberCount !== body.hosts.length) { + throw new Error("You can only add members of the team to a team event type."); + } + } +} diff --git a/apps/api/pages/api/event-types/_utils/getCalLink.ts b/apps/api/pages/api/event-types/_utils/getCalLink.ts new file mode 100644 index 0000000000..5c46575585 --- /dev/null +++ b/apps/api/pages/api/event-types/_utils/getCalLink.ts @@ -0,0 +1,16 @@ +import { CAL_URL } from "@calcom/lib/constants"; + +export default function getCalLink(eventType: { + team?: { slug: string | null } | null; + owner: { username: string | null } | null; + users?: { username: string | null }[]; + slug: string; +}) { + return `${CAL_URL}/${ + eventType?.team + ? `team/${eventType?.team?.slug}` + : eventType?.owner + ? eventType.owner.username + : eventType?.users?.[0]?.username + }/${eventType?.slug}`; +} diff --git a/apps/api/pages/api/event-types/index.ts b/apps/api/pages/api/event-types/index.ts new file mode 100644 index 0000000000..2a15abfa5b --- /dev/null +++ b/apps/api/pages/api/event-types/index.ts @@ -0,0 +1,10 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("./_get"), + POST: import("./_post"), + }) +); diff --git a/apps/api/pages/api/index.ts b/apps/api/pages/api/index.ts new file mode 100644 index 0000000000..85f1d6be3b --- /dev/null +++ b/apps/api/pages/api/index.ts @@ -0,0 +1,5 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +export default async function CalcomApi(_: NextApiRequest, res: NextApiResponse) { + res.status(201).json({ message: "Welcome to Cal.com API - docs are at https://developer.cal.com/api" }); +} diff --git a/apps/api/pages/api/me/_get.ts b/apps/api/pages/api/me/_get.ts new file mode 100644 index 0000000000..d0a3759730 --- /dev/null +++ b/apps/api/pages/api/me/_get.ts @@ -0,0 +1,12 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaUserReadPublic } from "~/lib/validations/user"; + +async function handler({ userId, prisma }: NextApiRequest) { + const data = await prisma.user.findUniqueOrThrow({ where: { id: userId } }); + return { user: schemaUserReadPublic.parse(data) }; +} + +export default defaultResponder(handler); diff --git a/apps/api/pages/api/me/index.ts b/apps/api/pages/api/me/index.ts new file mode 100644 index 0000000000..c53e4b8ef3 --- /dev/null +++ b/apps/api/pages/api/me/index.ts @@ -0,0 +1,9 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("./_get"), + }) +); diff --git a/apps/api/pages/api/memberships/[id]/_auth-middleware.ts b/apps/api/pages/api/memberships/[id]/_auth-middleware.ts new file mode 100644 index 0000000000..97ac4ff16d --- /dev/null +++ b/apps/api/pages/api/memberships/[id]/_auth-middleware.ts @@ -0,0 +1,17 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; + +import { membershipIdSchema } from "~/lib/validations/membership"; + +async function authMiddleware(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const { teamId } = membershipIdSchema.parse(req.query); + // Admins can just skip this check + if (isAdmin) return; + // Only team members can modify a membership + const membership = await prisma.membership.findFirst({ where: { userId, teamId } }); + if (!membership) throw new HttpError({ statusCode: 403, message: "Forbidden" }); +} + +export default authMiddleware; diff --git a/apps/api/pages/api/memberships/[id]/_delete.ts b/apps/api/pages/api/memberships/[id]/_delete.ts new file mode 100644 index 0000000000..06acefb3bb --- /dev/null +++ b/apps/api/pages/api/memberships/[id]/_delete.ts @@ -0,0 +1,102 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { membershipIdSchema } from "~/lib/validations/membership"; + +/** + * @swagger + * /memberships/{userId}_{teamId}: + * delete: + * summary: Remove an existing membership + * parameters: + * - in: path + * name: userId + * schema: + * type: integer + * required: true + * description: Numeric userId of the membership to get + * - in: path + * name: teamId + * schema: + * type: integer + * required: true + * description: Numeric teamId of the membership to get + * tags: + * - memberships + * responses: + * 201: + * description: OK, membership removed successfuly + * 400: + * description: Bad request. Membership id is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function deleteHandler(req: NextApiRequest) { + const { prisma, query } = req; + const userId_teamId = membershipIdSchema.parse(query); + await checkPermissions(req); + await prisma.membership.delete({ where: { userId_teamId } }); + return { message: `Membership with id: ${query.id} deleted successfully` }; +} + +async function checkPermissions(req: NextApiRequest) { + const { prisma, isAdmin, userId, query } = req; + const userId_teamId = membershipIdSchema.parse(query); + // Admin User can do anything including deletion of Admin Team Member in any team + if (isAdmin) { + return; + } + + // Owner can delete Admin and Member + // Admin Team Member can delete Member + // Member can't delete anyone + const PRIVILEGE_ORDER = ["OWNER", "ADMIN", "MEMBER"]; + + const memberShipToBeDeleted = await prisma.membership.findUnique({ + where: { userId_teamId }, + }); + + if (!memberShipToBeDeleted) { + throw new HttpError({ statusCode: 404, message: "Membership not found" }); + } + + // If a user is deleting their own membership, then they can do it + if (userId === memberShipToBeDeleted.userId) { + return; + } + + const currentUserMembership = await prisma.membership.findUnique({ + where: { + userId_teamId: { + userId, + teamId: memberShipToBeDeleted.teamId, + }, + }, + }); + + if (!currentUserMembership) { + // Current User isn't a member of the team + throw new HttpError({ statusCode: 403, message: "You are not a member of the team" }); + } + + if ( + PRIVILEGE_ORDER.indexOf(memberShipToBeDeleted.role) === -1 || + PRIVILEGE_ORDER.indexOf(currentUserMembership.role) === -1 + ) { + throw new HttpError({ statusCode: 400, message: "Invalid role" }); + } + + // If Role that is being deleted comes before the current User's Role, or it's the same ROLE, throw error + if ( + PRIVILEGE_ORDER.indexOf(memberShipToBeDeleted.role) <= PRIVILEGE_ORDER.indexOf(currentUserMembership.role) + ) { + throw new HttpError({ + statusCode: 403, + message: "You don't have the appropriate role to delete this membership", + }); + } +} + +export default defaultResponder(deleteHandler); diff --git a/apps/api/pages/api/memberships/[id]/_get.ts b/apps/api/pages/api/memberships/[id]/_get.ts new file mode 100644 index 0000000000..cf44094bfa --- /dev/null +++ b/apps/api/pages/api/memberships/[id]/_get.ts @@ -0,0 +1,46 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { membershipIdSchema, schemaMembershipPublic } from "~/lib/validations/membership"; + +/** + * @swagger + * /memberships/{userId}_{teamId}: + * get: + * summary: Find a membership by userID and teamID + * parameters: + * - in: path + * name: userId + * schema: + * type: integer + * required: true + * description: Numeric userId of the membership to get + * - in: path + * name: teamId + * schema: + * type: integer + * required: true + * description: Numeric teamId of the membership to get + * tags: + * - memberships + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: Membership was not found + */ +export async function getHandler(req: NextApiRequest) { + const { prisma, query } = req; + const userId_teamId = membershipIdSchema.parse(query); + const args: Prisma.MembershipFindUniqueOrThrowArgs = { where: { userId_teamId } }; + // Just in case the user want to get more info about the team itself + if (req.query.include === "team") args.include = { team: true }; + const data = await prisma.membership.findUniqueOrThrow(args); + return { membership: schemaMembershipPublic.parse(data) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/memberships/[id]/_patch.ts b/apps/api/pages/api/memberships/[id]/_patch.ts new file mode 100644 index 0000000000..98115eff39 --- /dev/null +++ b/apps/api/pages/api/memberships/[id]/_patch.ts @@ -0,0 +1,75 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { + membershipEditBodySchema, + membershipIdSchema, + schemaMembershipPublic, +} from "~/lib/validations/membership"; + +/** + * @swagger + * /memberships/{userId}_{teamId}: + * patch: + * summary: Edit an existing membership + * parameters: + * - in: path + * name: userId + * schema: + * type: integer + * required: true + * description: Numeric userId of the membership to get + * - in: path + * name: teamId + * schema: + * type: integer + * required: true + * description: Numeric teamId of the membership to get + * tags: + * - memberships + * responses: + * 201: + * description: OK, membership edited successfully + * 400: + * description: Bad request. Membership body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function patchHandler(req: NextApiRequest) { + const { prisma, query } = req; + const userId_teamId = membershipIdSchema.parse(query); + const data = membershipEditBodySchema.parse(req.body); + const args: Prisma.MembershipUpdateArgs = { where: { userId_teamId }, data }; + + await checkPermissions(req); + + const result = await prisma.membership.update(args); + return { membership: schemaMembershipPublic.parse(result) }; +} + +async function checkPermissions(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const { userId: queryUserId, teamId } = membershipIdSchema.parse(req.query); + const data = membershipEditBodySchema.parse(req.body); + // Admins can just skip this check + if (isAdmin) return; + // Only the invited user can accept the invite + if ("accepted" in data && queryUserId !== userId) + throw new HttpError({ + statusCode: 403, + message: "Only the invited user can accept the invite", + }); + // Only team OWNERS and ADMINS can modify `role` + if ("role" in data) { + const membership = await prisma.membership.findFirst({ + where: { userId, teamId, role: { in: ["ADMIN", "OWNER"] } }, + }); + if (!membership || (membership.role !== "OWNER" && req.body.role === "OWNER")) + throw new HttpError({ statusCode: 403, message: "Forbidden" }); + } +} + +export default defaultResponder(patchHandler); diff --git a/apps/api/pages/api/memberships/[id]/index.ts b/apps/api/pages/api/memberships/[id]/index.ts new file mode 100644 index 0000000000..727ad02843 --- /dev/null +++ b/apps/api/pages/api/memberships/[id]/index.ts @@ -0,0 +1,18 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { defaultHandler, defaultResponder } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +import authMiddleware from "./_auth-middleware"; + +export default withMiddleware()( + defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req); + return defaultHandler({ + GET: import("./_get"), + PATCH: import("./_patch"), + DELETE: import("./_delete"), + })(req, res); + }) +); diff --git a/apps/api/pages/api/memberships/_get.ts b/apps/api/pages/api/memberships/_get.ts new file mode 100644 index 0000000000..da6e936df3 --- /dev/null +++ b/apps/api/pages/api/memberships/_get.ts @@ -0,0 +1,76 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaMembershipPublic } from "~/lib/validations/membership"; +import { + schemaQuerySingleOrMultipleTeamIds, + schemaQuerySingleOrMultipleUserIds, +} from "~/lib/validations/shared/queryUserId"; + +/** + * @swagger + * /memberships: + * get: + * summary: Find all memberships + * tags: + * - memberships + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No memberships were found + */ +async function getHandler(req: NextApiRequest) { + const { prisma } = req; + const args: Prisma.MembershipFindManyArgs = { + where: { + /** Admins can query multiple users */ + userId: { in: getUserIds(req) }, + /** Admins can query multiple teams as well */ + teamId: { in: getTeamIds(req) }, + }, + }; + // Just in case the user want to get more info about the team itself + if (req.query.include === "team") args.include = { team: true }; + + const data = await prisma.membership.findMany(args); + return { memberships: data.map((v) => schemaMembershipPublic.parse(v)) }; +} + +/** + * Returns requested users IDs only if admin, otherwise return only current user ID + */ +function getUserIds(req: NextApiRequest) { + const { userId, isAdmin } = req; + /** Only admins can query other users */ + if (!isAdmin && req.query.userId) throw new HttpError({ statusCode: 403, message: "ADMIN required" }); + if (isAdmin && req.query.userId) { + const query = schemaQuerySingleOrMultipleUserIds.parse(req.query); + const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId]; + return userIds; + } + // Return all memberships for ADMIN, limit to current user to non-admins + return isAdmin ? undefined : [userId]; +} + +/** + * Returns requested teams IDs only if admin + */ +function getTeamIds(req: NextApiRequest) { + const { isAdmin } = req; + /** Only admins can query other teams */ + if (!isAdmin && req.query.teamId) throw new HttpError({ statusCode: 403, message: "ADMIN required" }); + if (isAdmin && req.query.teamId) { + const query = schemaQuerySingleOrMultipleTeamIds.parse(req.query); + const teamIds = Array.isArray(query.teamId) ? query.teamId : [query.teamId]; + return teamIds; + } + return undefined; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/memberships/_post.ts b/apps/api/pages/api/memberships/_post.ts new file mode 100644 index 0000000000..11cba74938 --- /dev/null +++ b/apps/api/pages/api/memberships/_post.ts @@ -0,0 +1,53 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { membershipCreateBodySchema, schemaMembershipPublic } from "~/lib/validations/membership"; + +/** + * @swagger + * /memberships: + * post: + * summary: Creates a new membership + * tags: + * - memberships + * responses: + * 201: + * description: OK, membership created + * 400: + * description: Bad request. Membership body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +async function postHandler(req: NextApiRequest) { + const { prisma } = req; + const data = membershipCreateBodySchema.parse(req.body); + const args: Prisma.MembershipCreateArgs = { data }; + + await checkPermissions(req); + + const result = await prisma.membership.create(args); + + return { + membership: schemaMembershipPublic.parse(result), + message: "Membership created successfully", + }; +} + +async function checkPermissions(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + if (isAdmin) return; + const body = membershipCreateBodySchema.parse(req.body); + // To prevent auto-accepted invites, limit it to ADMIN users + if (!isAdmin && "accepted" in body) + throw new HttpError({ statusCode: 403, message: "ADMIN needed for `accepted`" }); + // Only team OWNERS and ADMINS can add other members + const membership = await prisma.membership.findFirst({ + where: { userId, teamId: body.teamId, role: { in: ["ADMIN", "OWNER"] } }, + }); + if (!membership) throw new HttpError({ statusCode: 403, message: "You can't add members to this team" }); +} + +export default defaultResponder(postHandler); diff --git a/apps/api/pages/api/memberships/index.ts b/apps/api/pages/api/memberships/index.ts new file mode 100644 index 0000000000..2a15abfa5b --- /dev/null +++ b/apps/api/pages/api/memberships/index.ts @@ -0,0 +1,10 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("./_get"), + POST: import("./_post"), + }) +); diff --git a/apps/api/pages/api/payments/[id].ts b/apps/api/pages/api/payments/[id].ts new file mode 100644 index 0000000000..f8a54ff7f4 --- /dev/null +++ b/apps/api/pages/api/payments/[id].ts @@ -0,0 +1,67 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; +import type { PaymentResponse } from "~/lib/types"; +import { schemaPaymentPublic } from "~/lib/validations/payment"; +import { + schemaQueryIdParseInt, + withValidQueryIdTransformParseInt, +} from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /payments/{id}: + * get: + * summary: Find a payment + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the payment to get + * tags: + * - payments + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: Payment was not found + */ +export async function paymentById( + { method, query, userId, prisma }: NextApiRequest, + res: NextApiResponse +) { + const safeQuery = schemaQueryIdParseInt.safeParse(query); + if (safeQuery.success && method === "GET") { + const userWithBookings = await prisma.user.findUnique({ + where: { id: userId }, + include: { bookings: true }, + }); + await prisma.payment + .findUnique({ where: { id: safeQuery.data.id } }) + .then((data) => schemaPaymentPublic.parse(data)) + .then((payment) => { + if (!userWithBookings?.bookings.map((b) => b.id).includes(payment.bookingId)) { + res.status(401).json({ message: "Unauthorized" }); + } else { + res.status(200).json({ payment }); + } + }) + .catch((error: Error) => + res.status(404).json({ + message: `Payment with id: ${safeQuery.data.id} not found`, + error, + }) + ); + } +} +export default withMiddleware("HTTP_GET")(withValidQueryIdTransformParseInt(paymentById)); diff --git a/apps/api/pages/api/payments/index.ts b/apps/api/pages/api/payments/index.ts new file mode 100644 index 0000000000..c6f8c79fec --- /dev/null +++ b/apps/api/pages/api/payments/index.ts @@ -0,0 +1,42 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; +import type { PaymentsResponse } from "~/lib/types"; +import { schemaPaymentPublic } from "~/lib/validations/payment"; + +/** + * @swagger + * /payments: + * get: + * summary: Find all payments + * tags: + * - payments + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No payments were found + */ +async function allPayments({ userId, prisma }: NextApiRequest, res: NextApiResponse) { + const userWithBookings = await prisma.user.findUnique({ + where: { id: userId }, + include: { bookings: true }, + }); + if (!userWithBookings) throw new Error("No user found"); + const bookings = userWithBookings.bookings; + const bookingIds = bookings.map((booking) => booking.id); + const data = await prisma.payment.findMany({ where: { bookingId: { in: bookingIds } } }); + const payments = data.map((payment) => schemaPaymentPublic.parse(payment)); + + if (payments) res.status(200).json({ payments }); + else + (error: Error) => + res.status(404).json({ + message: "No Payments were found", + error, + }); +} +// NO POST FOR PAYMENTS FOR NOW +export default withMiddleware("HTTP_GET")(allPayments); diff --git a/apps/api/pages/api/schedules/[id]/_auth-middleware.ts b/apps/api/pages/api/schedules/[id]/_auth-middleware.ts new file mode 100644 index 0000000000..42184440d3 --- /dev/null +++ b/apps/api/pages/api/schedules/[id]/_auth-middleware.ts @@ -0,0 +1,19 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; + +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +async function authMiddleware(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const { id } = schemaQueryIdParseInt.parse(req.query); + // Admins can just skip this check + if (isAdmin) return; + // Check if the current user can access the schedule + const schedule = await prisma.schedule.findFirst({ + where: { id, userId }, + }); + if (!schedule) throw new HttpError({ statusCode: 403, message: "Forbidden" }); +} + +export default authMiddleware; diff --git a/apps/api/pages/api/schedules/[id]/_delete.ts b/apps/api/pages/api/schedules/[id]/_delete.ts new file mode 100644 index 0000000000..e48c76c469 --- /dev/null +++ b/apps/api/pages/api/schedules/[id]/_delete.ts @@ -0,0 +1,47 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /schedules/{id}: + * delete: + * operationId: removeScheduleById + * summary: Remove an existing schedule + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the schedule to delete + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * tags: + * - schedules + * responses: + * 201: + * description: OK, schedule removed successfully + * 400: + * description: Bad request. Schedule id is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function deleteHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + + /* If we're deleting any default user schedule, we unset it */ + await prisma.user.updateMany({ where: { defaultScheduleId: id }, data: { defaultScheduleId: undefined } }); + + await prisma.schedule.delete({ where: { id } }); + return { message: `Schedule with id: ${id} deleted successfully` }; +} + +export default defaultResponder(deleteHandler); diff --git a/apps/api/pages/api/schedules/[id]/_get.ts b/apps/api/pages/api/schedules/[id]/_get.ts new file mode 100644 index 0000000000..fdadce2cba --- /dev/null +++ b/apps/api/pages/api/schedules/[id]/_get.ts @@ -0,0 +1,81 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaSchedulePublic } from "~/lib/validations/schedule"; +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /schedules/{id}: + * get: + * operationId: getScheduleById + * summary: Find a schedule + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the schedule to get + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * tags: + * - schedules + * responses: + * 200: + * description: OK + * content: + * application/json: + * examples: + * schedule: + * value: + * { + * "schedule": { + * "id": 12345, + * "userId": 182, + * "name": "Sample Schedule", + * "timeZone": "Asia/Calcutta", + * "availability": [ + * { + * "id": 111, + * "eventTypeId": null, + * "days": [0, 1, 2, 3, 4, 6], + * "startTime": "00:00:00", + * "endTime": "23:45:00" + * }, + * { + * "id": 112, + * "eventTypeId": null, + * "days": [5], + * "startTime": "00:00:00", + * "endTime": "12:00:00" + * }, + * { + * "id": 113, + * "eventTypeId": null, + * "days": [5], + * "startTime": "15:00:00", + * "endTime": "23:45:00" + * } + * ] + * } + * } + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: Schedule was not found + */ + +export async function getHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const data = await prisma.schedule.findUniqueOrThrow({ where: { id }, include: { availability: true } }); + return { schedule: schemaSchedulePublic.parse(data) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/schedules/[id]/_patch.ts b/apps/api/pages/api/schedules/[id]/_patch.ts new file mode 100644 index 0000000000..1c8f52d688 --- /dev/null +++ b/apps/api/pages/api/schedules/[id]/_patch.ts @@ -0,0 +1,101 @@ +import type { NextApiRequest } from "next"; +import type { z } from "zod"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaSchedulePublic, schemaSingleScheduleBodyParams } from "~/lib/validations/schedule"; +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +/** + * @swagger + * /schedules/{id}: + * patch: + * operationId: editScheduleById + * summary: Edit an existing schedule + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID of the schedule to edit + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * requestBody: + * description: Edit an existing schedule + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * description: Name of the schedule + * timeZone: + * type: string + * description: The timezone for this schedule + * examples: + * schedule: + * value: + * { + * "name": "Updated Schedule", + * "timeZone": "Asia/Calcutta" + * } + * tags: + * - schedules + * responses: + * 200: + * description: OK, schedule edited successfully + * content: + * application/json: + * examples: + * schedule: + * value: + * { + * "schedule": { + * "id": 12345, + * "userId": 1, + * "name": "Total Testing Part 2", + * "timeZone": "Asia/Calcutta", + * "availability": [ + * { + * "id": 4567, + * "eventTypeId": null, + * "days": [1, 2, 3, 4, 5], + * "startTime": "09:00:00", + * "endTime": "17:00:00" + * } + * ] + * } + * } + * 400: + * description: Bad request. Schedule body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ + +export async function patchHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdParseInt.parse(query); + const data = schemaSingleScheduleBodyParams.parse(req.body); + await checkPermissions(req, data); + const result = await prisma.schedule.update({ where: { id }, data, include: { availability: true } }); + return { schedule: schemaSchedulePublic.parse(result) }; +} + +async function checkPermissions(req: NextApiRequest, body: z.infer) { + const { isAdmin } = req; + if (isAdmin) return; + if (body.userId) { + throw new HttpError({ statusCode: 403, message: "Non admin cannot change the owner of a schedule" }); + } + //_auth-middleware takes care of verifying the ownership of schedule. +} + +export default defaultResponder(patchHandler); diff --git a/apps/api/pages/api/schedules/[id]/index.ts b/apps/api/pages/api/schedules/[id]/index.ts new file mode 100644 index 0000000000..727ad02843 --- /dev/null +++ b/apps/api/pages/api/schedules/[id]/index.ts @@ -0,0 +1,18 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { defaultHandler, defaultResponder } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +import authMiddleware from "./_auth-middleware"; + +export default withMiddleware()( + defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req); + return defaultHandler({ + GET: import("./_get"), + PATCH: import("./_patch"), + DELETE: import("./_delete"), + })(req, res); + }) +); diff --git a/apps/api/pages/api/schedules/_get.ts b/apps/api/pages/api/schedules/_get.ts new file mode 100644 index 0000000000..1b06a116e9 --- /dev/null +++ b/apps/api/pages/api/schedules/_get.ts @@ -0,0 +1,99 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; +import { z } from "zod"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaSchedulePublic } from "~/lib/validations/schedule"; +import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId"; + +export const schemaUserIds = z + .union([z.string(), z.array(z.string())]) + .transform((val) => (Array.isArray(val) ? val.map((v) => parseInt(v, 10)) : [parseInt(val, 10)])); + +/** + * @swagger + * /schedules: + * get: + * operationId: listSchedules + * summary: Find all schedules + * parameters: + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * tags: + * - schedules + * responses: + * 200: + * description: OK + * content: + * application/json: + * examples: + * schedules: + * value: + * { + * "schedules": [ + * { + * "id": 1234, + * "userId": 5678, + * "name": "Sample Schedule 1", + * "timeZone": "America/Chicago", + * "availability": [ + * { + * "id": 987, + * "eventTypeId": null, + * "days": [1, 2, 3, 4, 5], + * "startTime": "09:00:00", + * "endTime": "23:00:00" + * } + * ] + * }, + * { + * "id": 2345, + * "userId": 6789, + * "name": "Sample Schedule 2", + * "timeZone": "Europe/Amsterdam", + * "availability": [ + * { + * "id": 876, + * "eventTypeId": null, + * "days": [1, 2, 3, 4, 5], + * "startTime": "09:00:00", + * "endTime": "17:00:00" + * } + * ] + * } + * ] + * } + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No schedules were found + */ + +async function handler(req: NextApiRequest) { + const { prisma, userId, isAdmin } = req; + const args: Prisma.ScheduleFindManyArgs = isAdmin ? {} : { where: { userId } }; + args.include = { availability: true }; + + if (!isAdmin && req.query.userId) + throw new HttpError({ + statusCode: 401, + message: "Unauthorized: Only admins can query other users", + }); + + if (isAdmin && req.query.userId) { + const query = schemaQuerySingleOrMultipleUserIds.parse(req.query); + const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId]; + args.where = { userId: { in: userIds } }; + if (Array.isArray(query.userId)) args.orderBy = { userId: "asc" }; + } + const data = await prisma.schedule.findMany(args); + return { schedules: data.map((s) => schemaSchedulePublic.parse(s)) }; +} + +export default defaultResponder(handler); diff --git a/apps/api/pages/api/schedules/_post.ts b/apps/api/pages/api/schedules/_post.ts new file mode 100644 index 0000000000..a50f2cd5b6 --- /dev/null +++ b/apps/api/pages/api/schedules/_post.ts @@ -0,0 +1,113 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability"; +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaCreateScheduleBodyParams, schemaSchedulePublic } from "~/lib/validations/schedule"; + +/** + * @swagger + * /schedules: + * post: + * operationId: addSchedule + * summary: Creates a new schedule + * parameters: + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * requestBody: + * description: Create a new schedule + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - name + * - timeZone + * properties: + * name: + * type: string + * description: Name of the schedule + * timeZone: + * type: string + * description: The timeZone for this schedule + * examples: + * schedule: + * value: + * { + * "name": "Sample Schedule", + * "timeZone": "Asia/Calcutta" + * } + * tags: + * - schedules + * responses: + * 200: + * description: OK, schedule created + * content: + * application/json: + * examples: + * schedule: + * value: + * { + * "schedule": { + * "id": 79471, + * "userId": 182, + * "name": "Total Testing", + * "timeZone": "Asia/Calcutta", + * "availability": [ + * { + * "id": 337917, + * "eventTypeId": null, + * "days": [1, 2, 3, 4, 5], + * "startTime": "09:00:00", + * "endTime": "17:00:00" + * } + * ] + * }, + * "message": "Schedule created successfully" + * } + * 400: + * description: Bad request. Schedule body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ + +async function postHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const body = schemaCreateScheduleBodyParams.parse(req.body); + let args: Prisma.ScheduleCreateArgs = { data: { ...body, userId } }; + + /* If ADMIN we create the schedule for selected user */ + if (isAdmin && body.userId) args = { data: { ...body, userId: body.userId } }; + + if (!isAdmin && body.userId) + throw new HttpError({ statusCode: 403, message: "ADMIN required for `userId`" }); + + // We create default availabilities for the schedule + args.data.availability = { + createMany: { + data: getAvailabilityFromSchedule(DEFAULT_SCHEDULE).map((schedule) => ({ + days: schedule.days, + startTime: schedule.startTime, + endTime: schedule.endTime, + })), + }, + }; + // We include the recently created availability + args.include = { availability: true }; + + const data = await prisma.schedule.create(args); + + return { + schedule: schemaSchedulePublic.parse(data), + message: "Schedule created successfully", + }; +} + +export default defaultResponder(postHandler); diff --git a/apps/api/pages/api/schedules/index.ts b/apps/api/pages/api/schedules/index.ts new file mode 100644 index 0000000000..2a15abfa5b --- /dev/null +++ b/apps/api/pages/api/schedules/index.ts @@ -0,0 +1,10 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("./_get"), + POST: import("./_post"), + }) +); diff --git a/apps/api/pages/api/selected-calendars/[id]/_auth-middleware.ts b/apps/api/pages/api/selected-calendars/[id]/_auth-middleware.ts new file mode 100644 index 0000000000..620ba818fb --- /dev/null +++ b/apps/api/pages/api/selected-calendars/[id]/_auth-middleware.ts @@ -0,0 +1,16 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; + +import { selectedCalendarIdSchema } from "~/lib/validations/selected-calendar"; + +async function authMiddleware(req: NextApiRequest) { + const { userId, isAdmin } = req; + const { userId: queryUserId } = selectedCalendarIdSchema.parse(req.query); + // Admins can just skip this check + if (isAdmin) return; + // Check if the current user requesting is the same as the one being requested + if (userId !== queryUserId) throw new HttpError({ statusCode: 403, message: "Forbidden" }); +} + +export default authMiddleware; diff --git a/apps/api/pages/api/selected-calendars/[id]/_delete.ts b/apps/api/pages/api/selected-calendars/[id]/_delete.ts new file mode 100644 index 0000000000..e04b67f711 --- /dev/null +++ b/apps/api/pages/api/selected-calendars/[id]/_delete.ts @@ -0,0 +1,55 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { selectedCalendarIdSchema } from "~/lib/validations/selected-calendar"; + +/** + * @swagger + * /selected-calendars/{userId}_{integration}_{externalId}: + * delete: + * operationId: removeSelectedCalendarById + * summary: Remove a selected calendar + * parameters: + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * - in: path + * name: userId + * schema: + * type: integer + * required: true + * description: userId of the selected calendar to get + * - in: path + * name: externalId + * schema: + * type: integer + * required: true + * description: externalId of the selected-calendar to get + * - in: path + * name: integration + * schema: + * type: string + * required: true + * description: integration of the selected calendar to get + * tags: + * - selected-calendars + * responses: + * 201: + * description: OK, selected-calendar removed successfully + * 400: + * description: Bad request. SelectedCalendar id is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function deleteHandler(req: NextApiRequest) { + const { prisma, query } = req; + const userId_integration_externalId = selectedCalendarIdSchema.parse(query); + await prisma.selectedCalendar.delete({ where: { userId_integration_externalId } }); + return { message: `Selected Calendar with id: ${query.id} deleted successfully` }; +} + +export default defaultResponder(deleteHandler); diff --git a/apps/api/pages/api/selected-calendars/[id]/_get.ts b/apps/api/pages/api/selected-calendars/[id]/_get.ts new file mode 100644 index 0000000000..a5549f1a21 --- /dev/null +++ b/apps/api/pages/api/selected-calendars/[id]/_get.ts @@ -0,0 +1,57 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaSelectedCalendarPublic, selectedCalendarIdSchema } from "~/lib/validations/selected-calendar"; + +/** + * @swagger + * /selected-calendars/{userId}_{integration}_{externalId}: + * get: + * operationId: getSelectedCalendarById + * summary: Find a selected calendar by providing the compoundId(userId_integration_externalId) separated by `_` + * parameters: + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * - in: path + * name: userId + * schema: + * type: integer + * required: true + * description: userId of the selected calendar to get + * - in: path + * name: externalId + * schema: + * type: string + * required: true + * description: externalId of the selected calendar to get + * - in: path + * name: integration + * schema: + * type: string + * required: true + * description: integration of the selected calendar to get + * tags: + * - selected-calendars + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: SelectedCalendar was not found + */ +export async function getHandler(req: NextApiRequest) { + const { prisma, query } = req; + const userId_integration_externalId = selectedCalendarIdSchema.parse(query); + const data = await prisma.selectedCalendar.findUniqueOrThrow({ + where: { userId_integration_externalId }, + }); + return { selected_calendar: schemaSelectedCalendarPublic.parse(data) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/selected-calendars/[id]/_patch.ts b/apps/api/pages/api/selected-calendars/[id]/_patch.ts new file mode 100644 index 0000000000..c2b526303d --- /dev/null +++ b/apps/api/pages/api/selected-calendars/[id]/_patch.ts @@ -0,0 +1,72 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { + schemaSelectedCalendarPublic, + schemaSelectedCalendarUpdateBodyParams, + selectedCalendarIdSchema, +} from "~/lib/validations/selected-calendar"; + +/** + * @swagger + * /selected-calendars/{userId}_{integration}_{externalId}: + * patch: + * operationId: editSelectedCalendarById + * summary: Edit a selected calendar + * parameters: + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * - in: path + * name: userId + * schema: + * type: integer + * required: true + * description: userId of the selected calendar to get + * - in: path + * name: externalId + * schema: + * type: string + * required: true + * description: externalId of the selected calendar to get + * - in: path + * name: integration + * schema: + * type: string + * required: true + * description: integration of the selected calendar to get + * tags: + * - selected-calendars + * responses: + * 201: + * description: OK, selected-calendar edited successfully + * 400: + * description: Bad request. SelectedCalendar body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function patchHandler(req: NextApiRequest) { + const { prisma, query, isAdmin } = req; + const userId_integration_externalId = selectedCalendarIdSchema.parse(query); + const { userId: bodyUserId, ...data } = schemaSelectedCalendarUpdateBodyParams.parse(req.body); + const args: Prisma.SelectedCalendarUpdateArgs = { where: { userId_integration_externalId }, data }; + + if (!isAdmin && bodyUserId) throw new HttpError({ statusCode: 403, message: `ADMIN required for userId` }); + + if (isAdmin && bodyUserId) { + const where: Prisma.UserWhereInput = { id: bodyUserId }; + await prisma.user.findFirstOrThrow({ where }); + args.data.userId = bodyUserId; + } + + const result = await prisma.selectedCalendar.update(args); + return { selected_calendar: schemaSelectedCalendarPublic.parse(result) }; +} + +export default defaultResponder(patchHandler); diff --git a/apps/api/pages/api/selected-calendars/[id]/index.ts b/apps/api/pages/api/selected-calendars/[id]/index.ts new file mode 100644 index 0000000000..727ad02843 --- /dev/null +++ b/apps/api/pages/api/selected-calendars/[id]/index.ts @@ -0,0 +1,18 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { defaultHandler, defaultResponder } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +import authMiddleware from "./_auth-middleware"; + +export default withMiddleware()( + defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req); + return defaultHandler({ + GET: import("./_get"), + PATCH: import("./_patch"), + DELETE: import("./_delete"), + })(req, res); + }) +); diff --git a/apps/api/pages/api/selected-calendars/_get.ts b/apps/api/pages/api/selected-calendars/_get.ts new file mode 100644 index 0000000000..c5e2182adf --- /dev/null +++ b/apps/api/pages/api/selected-calendars/_get.ts @@ -0,0 +1,51 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaSelectedCalendarPublic } from "~/lib/validations/selected-calendar"; +import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId"; + +/** + * @swagger + * /selected-calendars: + * get: + * operationId: listSelectedCalendars + * summary: Find all selected calendars + * parameters: + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * tags: + * - selected-calendars + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No selected calendars were found + */ +async function getHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + /* Admin gets all selected calendar by default, otherwise only the user's ones */ + const args: Prisma.SelectedCalendarFindManyArgs = isAdmin ? {} : { where: { userId } }; + + /** Only admins can query other users */ + if (!isAdmin && req.query.userId) throw new HttpError({ statusCode: 403, message: "ADMIN required" }); + if (isAdmin && req.query.userId) { + const query = schemaQuerySingleOrMultipleUserIds.parse(req.query); + const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId]; + args.where = { userId: { in: userIds } }; + if (Array.isArray(query.userId)) args.orderBy = { userId: "asc" }; + } + + const data = await prisma.selectedCalendar.findMany(args); + return { selected_calendars: data.map((v) => schemaSelectedCalendarPublic.parse(v)) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/selected-calendars/_post.ts b/apps/api/pages/api/selected-calendars/_post.ts new file mode 100644 index 0000000000..23b26a76e9 --- /dev/null +++ b/apps/api/pages/api/selected-calendars/_post.ts @@ -0,0 +1,72 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { + schemaSelectedCalendarBodyParams, + schemaSelectedCalendarPublic, +} from "~/lib/validations/selected-calendar"; + +/** + * @swagger + * /selected-calendars: + * post: + * summary: Creates a new selected calendar + * parameters: + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * requestBody: + * description: Create a new selected calendar + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - integration + * - externalId + * properties: + * integration: + * type: string + * description: The integration name + * externalId: + * type: string + * description: The external ID of the integration + * tags: + * - selected-calendars + * responses: + * 201: + * description: OK, selected calendar created + * 400: + * description: Bad request. SelectedCalendar body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +async function postHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const { userId: bodyUserId, ...body } = schemaSelectedCalendarBodyParams.parse(req.body); + const args: Prisma.SelectedCalendarCreateArgs = { data: { ...body, userId } }; + + if (!isAdmin && bodyUserId) throw new HttpError({ statusCode: 403, message: `ADMIN required for userId` }); + + if (isAdmin && bodyUserId) { + const where: Prisma.UserWhereInput = { id: bodyUserId }; + await prisma.user.findFirstOrThrow({ where }); + args.data.userId = bodyUserId; + } + + const data = await prisma.selectedCalendar.create(args); + + return { + selected_calendar: schemaSelectedCalendarPublic.parse(data), + message: "Selected Calendar created successfully", + }; +} + +export default defaultResponder(postHandler); diff --git a/apps/api/pages/api/selected-calendars/index.ts b/apps/api/pages/api/selected-calendars/index.ts new file mode 100644 index 0000000000..2a15abfa5b --- /dev/null +++ b/apps/api/pages/api/selected-calendars/index.ts @@ -0,0 +1,10 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("./_get"), + POST: import("./_post"), + }) +); diff --git a/apps/api/pages/api/teams/[teamId]/_auth-middleware.ts b/apps/api/pages/api/teams/[teamId]/_auth-middleware.ts new file mode 100644 index 0000000000..bde11bb75c --- /dev/null +++ b/apps/api/pages/api/teams/[teamId]/_auth-middleware.ts @@ -0,0 +1,34 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { MembershipRole } from "@calcom/prisma/enums"; + +import { schemaQueryTeamId } from "~/lib/validations/shared/queryTeamId"; + +async function authMiddleware(req: NextApiRequest) { + const { userId, prisma, isAdmin } = req; + const { teamId } = schemaQueryTeamId.parse(req.query); + /** Admins can skip the ownership verification */ + if (isAdmin) return; + /** Non-members will see a 404 error which may or not be the desired behavior. */ + await prisma.team.findFirstOrThrow({ + where: { id: teamId, members: { some: { userId } } }, + }); +} + +export async function checkPermissions( + req: NextApiRequest, + role: Prisma.MembershipWhereInput["role"] = MembershipRole.OWNER +) { + const { userId, prisma, isAdmin } = req; + const { teamId } = schemaQueryTeamId.parse(req.query); + const args: Prisma.TeamFindFirstArgs = { where: { id: teamId } }; + /** If not ADMIN then we check if the actual user belongs to team and matches the required role */ + if (!isAdmin) args.where = { ...args.where, members: { some: { userId, role } } }; + const team = await prisma.team.findFirst(args); + if (!team) throw new HttpError({ statusCode: 401, message: `Unauthorized: ${role.toString()} required` }); + return team; +} + +export default authMiddleware; diff --git a/apps/api/pages/api/teams/[teamId]/_delete.ts b/apps/api/pages/api/teams/[teamId]/_delete.ts new file mode 100644 index 0000000000..13c5c35370 --- /dev/null +++ b/apps/api/pages/api/teams/[teamId]/_delete.ts @@ -0,0 +1,46 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryTeamId } from "~/lib/validations/shared/queryTeamId"; + +import { checkPermissions } from "./_auth-middleware"; + +/** + * @swagger + * /teams/{teamId}: + * delete: + * operationId: removeTeamById + * summary: Remove an existing team + * parameters: + * - in: path + * name: teamId + * schema: + * type: integer + * required: true + * description: ID of the team to delete + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - teams + * responses: + * 201: + * description: OK, team removed successfully + * 400: + * description: Bad request. Team id is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function deleteHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { teamId } = schemaQueryTeamId.parse(query); + await checkPermissions(req); + await prisma.team.delete({ where: { id: teamId } }); + return { message: `Team with id: ${teamId} deleted successfully` }; +} + +export default defaultResponder(deleteHandler); diff --git a/apps/api/pages/api/teams/[teamId]/_get.ts b/apps/api/pages/api/teams/[teamId]/_get.ts new file mode 100644 index 0000000000..cff0e987c9 --- /dev/null +++ b/apps/api/pages/api/teams/[teamId]/_get.ts @@ -0,0 +1,48 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryTeamId } from "~/lib/validations/shared/queryTeamId"; +import { schemaTeamReadPublic } from "~/lib/validations/team"; + +/** + * @swagger + * /teams/{teamId}: + * get: + * operationId: getTeamById + * summary: Find a team + * parameters: + * - in: path + * name: teamId + * schema: + * type: integer + * required: true + * description: ID of the team to get + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - teams + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: Team was not found + */ +export async function getHandler(req: NextApiRequest) { + const { prisma, isAdmin, userId } = req; + const { teamId } = schemaQueryTeamId.parse(req.query); + const where: Prisma.TeamWhereInput = { id: teamId }; + // Non-admins can only query the teams they're part of + if (!isAdmin) where.members = { some: { userId } }; + const data = await prisma.team.findFirstOrThrow({ where }); + return { team: schemaTeamReadPublic.parse(data) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/teams/[teamId]/_patch.ts b/apps/api/pages/api/teams/[teamId]/_patch.ts new file mode 100644 index 0000000000..93d1a3a46a --- /dev/null +++ b/apps/api/pages/api/teams/[teamId]/_patch.ts @@ -0,0 +1,107 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { purchaseTeamSubscription } from "@calcom/features/ee/teams/lib/payments"; +import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants"; +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { TRPCError } from "@trpc/server"; + +import { schemaQueryTeamId } from "~/lib/validations/shared/queryTeamId"; +import { schemaTeamReadPublic, schemaTeamUpdateBodyParams } from "~/lib/validations/team"; + +/** + * @swagger + * /teams/{teamId}: + * patch: + * operationId: editTeamById + * summary: Edit an existing team + * parameters: + * - in: path + * name: teamId + * schema: + * type: integer + * required: true + * description: ID of the team to edit + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * requestBody: + * description: Create a new custom input for an event type + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * description: Name of the team + * slug: + * type: string + * description: A unique slug that works as path for the team public page + * tags: + * - teams + * responses: + * 201: + * description: OK, team edited successfully + * 400: + * description: Bad request. Team body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function patchHandler(req: NextApiRequest) { + const { prisma, body, userId } = req; + const data = schemaTeamUpdateBodyParams.parse(body); + const { teamId } = schemaQueryTeamId.parse(req.query); + /** Only OWNERS and ADMINS can edit teams */ + const _team = await prisma.team.findFirst({ + include: { members: true }, + where: { id: teamId, members: { some: { userId, role: { in: ["OWNER", "ADMIN"] } } } }, + }); + if (!_team) throw new HttpError({ statusCode: 401, message: "Unauthorized: OWNER or ADMIN required" }); + let paymentUrl; + if (_team.slug === null && data.slug) { + data.metadata = { + ...(_team.metadata as Prisma.JsonObject), + requestedSlug: data.slug, + }; + delete data.slug; + if (IS_TEAM_BILLING_ENABLED) { + const checkoutSession = await purchaseTeamSubscription({ + teamId: _team.id, + seats: _team.members.length, + userId, + }); + if (!checkoutSession.url) + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed retrieving a checkout session URL.", + }); + paymentUrl = checkoutSession.url; + } + } + + // TODO: Perhaps there is a better fix for this? + const cloneData: typeof data & { + metadata: NonNullable | undefined; + } = { + ...data, + metadata: data.metadata === null ? {} : data.metadata || undefined, + }; + const team = await prisma.team.update({ where: { id: teamId }, data: cloneData }); + const result = { + team: schemaTeamReadPublic.parse(team), + paymentUrl, + }; + if (!paymentUrl) { + delete result.paymentUrl; + } + return result; +} + +export default defaultResponder(patchHandler); diff --git a/apps/api/pages/api/teams/[teamId]/availability/index.ts b/apps/api/pages/api/teams/[teamId]/availability/index.ts new file mode 100644 index 0000000000..95a9f13f9c --- /dev/null +++ b/apps/api/pages/api/teams/[teamId]/availability/index.ts @@ -0,0 +1,9 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("~/pages/api/availability/_get"), + }) +); diff --git a/apps/api/pages/api/teams/[teamId]/event-types/_get.ts b/apps/api/pages/api/teams/[teamId]/event-types/_get.ts new file mode 100644 index 0000000000..38d1a6d425 --- /dev/null +++ b/apps/api/pages/api/teams/[teamId]/event-types/_get.ts @@ -0,0 +1,65 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; +import { z } from "zod"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaEventTypeReadPublic } from "~/lib/validations/event-type"; + +const querySchema = z.object({ + teamId: z.coerce.number(), +}); + +/** + * @swagger + * /teams/{teamId}/event-types: + * get: + * summary: Find all event types that belong to teamId + * operationId: listEventTypesByTeamId + * parameters: + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API Key + * - in: path + * name: teamId + * schema: + * type: number + * required: true + * tags: + * - event-types + * externalDocs: + * url: https://docs.cal.com/core-features/event-types + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No event types were found + */ +async function getHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + + const { teamId } = querySchema.parse(req.query); + + const args: Prisma.EventTypeFindManyArgs = { + where: { + team: isAdmin + ? { + id: teamId, + } + : { + id: teamId, + members: { some: { userId } }, + }, + }, + }; + + const data = await prisma.eventType.findMany(args); + return { event_types: data.map((attendee) => schemaEventTypeReadPublic.parse(attendee)) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/teams/[teamId]/event-types/index.ts b/apps/api/pages/api/teams/[teamId]/event-types/index.ts new file mode 100644 index 0000000000..c53e4b8ef3 --- /dev/null +++ b/apps/api/pages/api/teams/[teamId]/event-types/index.ts @@ -0,0 +1,9 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("./_get"), + }) +); diff --git a/apps/api/pages/api/teams/[teamId]/index.ts b/apps/api/pages/api/teams/[teamId]/index.ts new file mode 100644 index 0000000000..727ad02843 --- /dev/null +++ b/apps/api/pages/api/teams/[teamId]/index.ts @@ -0,0 +1,18 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { defaultHandler, defaultResponder } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +import authMiddleware from "./_auth-middleware"; + +export default withMiddleware()( + defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req); + return defaultHandler({ + GET: import("./_get"), + PATCH: import("./_patch"), + DELETE: import("./_delete"), + })(req, res); + }) +); diff --git a/apps/api/pages/api/teams/[teamId]/publish.ts b/apps/api/pages/api/teams/[teamId]/publish.ts new file mode 100644 index 0000000000..4cf45e12fd --- /dev/null +++ b/apps/api/pages/api/teams/[teamId]/publish.ts @@ -0,0 +1,44 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultHandler, defaultResponder } from "@calcom/lib/server"; +import { MembershipRole } from "@calcom/prisma/enums"; +import { createContext } from "@calcom/trpc/server/createContext"; +import { publishHandler } from "@calcom/trpc/server/routers/viewer/teams/publish.handler"; + +import { TRPCError } from "@trpc/server"; +import { getHTTPStatusCodeFromError } from "@trpc/server/http"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; +import { schemaQueryTeamId } from "~/lib/validations/shared/queryTeamId"; + +import authMiddleware, { checkPermissions } from "./_auth-middleware"; + +const patchHandler = async (req: NextApiRequest, res: NextApiResponse) => { + await checkPermissions(req, { in: [MembershipRole.OWNER, MembershipRole.ADMIN] }); + /** @see https://trpc.io/docs/server-side-calls */ + const ctx = await createContext({ req, res }); + const user = ctx.user; + if (!user) { + throw new Error("Internal Error."); + } + try { + const { teamId } = schemaQueryTeamId.parse(req.query); + return await publishHandler({ input: { teamId }, ctx: { ...ctx, user } }); + } catch (cause) { + if (cause instanceof TRPCError) { + const statusCode = getHTTPStatusCodeFromError(cause); + throw new HttpError({ statusCode, message: cause.message }); + } + throw cause; + } +}; + +export default withMiddleware()( + defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req); + return defaultHandler({ + PATCH: Promise.resolve({ default: defaultResponder(patchHandler) }), + })(req, res); + }) +); diff --git a/apps/api/pages/api/teams/_get.ts b/apps/api/pages/api/teams/_get.ts new file mode 100644 index 0000000000..49af07ac8e --- /dev/null +++ b/apps/api/pages/api/teams/_get.ts @@ -0,0 +1,40 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaTeamsReadPublic } from "~/lib/validations/team"; + +/** + * @swagger + * /teams: + * get: + * operationId: listTeams + * summary: Find all teams + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - teams + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No teams were found + */ +async function getHandler(req: NextApiRequest) { + const { userId, prisma, isAdmin } = req; + const where: Prisma.TeamWhereInput = {}; + // If user is not ADMIN, return only his data. + if (!isAdmin) where.members = { some: { userId } }; + const data = await prisma.team.findMany({ where }); + return { teams: schemaTeamsReadPublic.parse(data) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/teams/_post.ts b/apps/api/pages/api/teams/_post.ts new file mode 100644 index 0000000000..56e0820535 --- /dev/null +++ b/apps/api/pages/api/teams/_post.ts @@ -0,0 +1,98 @@ +import type { NextApiRequest } from "next"; + +import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants"; +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; +import { MembershipRole } from "@calcom/prisma/enums"; + +import { schemaMembershipPublic } from "~/lib/validations/membership"; +import { schemaTeamBodyParams, schemaTeamReadPublic } from "~/lib/validations/team"; + +/** + * @swagger + * /teams: + * post: + * operationId: addTeam + * summary: Creates a new team + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * requestBody: + * description: Create a new custom input for an event type + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - name + * - slug + * properties: + * name: + * type: string + * description: Name of the team + * slug: + * type: string + * description: A unique slug that works as path for the team public page + * tags: + * - teams + * responses: + * 201: + * description: OK, team created + * 400: + * description: Bad request. Team body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +async function postHandler(req: NextApiRequest) { + const { prisma, body, userId } = req; + const data = schemaTeamBodyParams.parse(body); + + if (data.slug) { + const alreadyExist = await prisma.team.findFirst({ + where: { + slug: data.slug, + }, + }); + if (alreadyExist) throw new HttpError({ statusCode: 409, message: "Team slug already exists" }); + if (IS_TEAM_BILLING_ENABLED) { + // Setting slug in metadata, so it can be published later + data.metadata = { + requestedSlug: data.slug, + }; + delete data.slug; + } + } + + // TODO: Perhaps there is a better fix for this? + const cloneData: typeof data & { + metadata: NonNullable | undefined; + } = { + ...data, + metadata: data.metadata === null ? {} : data.metadata || undefined, + }; + const team = await prisma.team.create({ + data: { + ...cloneData, + createdAt: new Date(), + members: { + // We're also creating the relation membership of team ownership in this call. + create: { userId, role: MembershipRole.OWNER, accepted: true }, + }, + }, + include: { members: true }, + }); + req.statusCode = 201; + // We are also returning the new ownership relation as owner besides team. + return { + team: schemaTeamReadPublic.parse(team), + owner: schemaMembershipPublic.parse(team.members[0]), + message: "Team created successfully, we also made you the owner of this team", + }; +} + +export default defaultResponder(postHandler); diff --git a/apps/api/pages/api/teams/index.ts b/apps/api/pages/api/teams/index.ts new file mode 100644 index 0000000000..2a15abfa5b --- /dev/null +++ b/apps/api/pages/api/teams/index.ts @@ -0,0 +1,10 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("./_get"), + POST: import("./_post"), + }) +); diff --git a/apps/api/pages/api/users/[userId]/_delete.ts b/apps/api/pages/api/users/[userId]/_delete.ts new file mode 100644 index 0000000000..44e8f0df0f --- /dev/null +++ b/apps/api/pages/api/users/[userId]/_delete.ts @@ -0,0 +1,51 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryUserId } from "~/lib/validations/shared/queryUserId"; + +/** + * @swagger + * /users/{userId}: + * delete: + * summary: Remove an existing user + * operationId: removeUserById + * parameters: + * - in: path + * name: userId + * example: 1 + * schema: + * type: integer + * required: true + * description: ID of the user to delete + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API key + * tags: + * - users + * responses: + * 201: + * description: OK, user removed successfuly + * 400: + * description: Bad request. User id is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function deleteHandler(req: NextApiRequest) { + const { prisma, isAdmin } = req; + const query = schemaQueryUserId.parse(req.query); + // Here we only check for ownership of the user if the user is not admin, otherwise we let ADMIN's edit any user + if (!isAdmin && query.userId !== req.userId) throw new HttpError({ statusCode: 403, message: "Forbidden" }); + + const user = await prisma.user.findUnique({ where: { id: query.userId } }); + if (!user) throw new HttpError({ statusCode: 404, message: "User not found" }); + + await prisma.user.delete({ where: { id: user.id } }); + return { message: `User with id: ${user.id} deleted successfully` }; +} + +export default defaultResponder(deleteHandler); diff --git a/apps/api/pages/api/users/[userId]/_get.ts b/apps/api/pages/api/users/[userId]/_get.ts new file mode 100644 index 0000000000..5be3926ae0 --- /dev/null +++ b/apps/api/pages/api/users/[userId]/_get.ts @@ -0,0 +1,50 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryUserId } from "~/lib/validations/shared/queryUserId"; +import { schemaUserReadPublic } from "~/lib/validations/user"; + +/** + * @swagger + * /users/{userId}: + * get: + * summary: Find a user, returns your user if regular user. + * operationId: getUserById + * parameters: + * - in: path + * name: userId + * example: 4 + * schema: + * type: integer + * required: true + * description: ID of the user to get + * - in: query + * name: apiKey + * schema: + * type: string + * required: true + * description: Your API key + * tags: + * - users + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: User was not found + */ +export async function getHandler(req: NextApiRequest) { + const { prisma, isAdmin } = req; + + const query = schemaQueryUserId.parse(req.query); + // Here we only check for ownership of the user if the user is not admin, otherwise we let ADMIN's edit any user + if (!isAdmin && query.userId !== req.userId) throw new HttpError({ statusCode: 403, message: "Forbidden" }); + const data = await prisma.user.findUnique({ where: { id: query.userId } }); + const user = schemaUserReadPublic.parse(data); + return { user }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/users/[userId]/_patch.ts b/apps/api/pages/api/users/[userId]/_patch.ts new file mode 100644 index 0000000000..59d8b76f94 --- /dev/null +++ b/apps/api/pages/api/users/[userId]/_patch.ts @@ -0,0 +1,121 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryUserId } from "~/lib/validations/shared/queryUserId"; +import { schemaUserEditBodyParams, schemaUserReadPublic } from "~/lib/validations/user"; + +/** + * @swagger + * /users/{userId}: + * patch: + * summary: Edit an existing user + * operationId: editUserById + * parameters: + * - in: path + * name: userId + * example: 4 + * schema: + * type: integer + * required: true + * description: ID of the user to edit + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * requestBody: + * description: Edit an existing attendee related to one of your bookings + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * email: + * type: string + * format: email + * description: Email that belongs to the user being edited + * username: + * type: string + * description: Username for the user being edited + * brandColor: + * description: The user's brand color + * type: string + * darkBrandColor: + * description: The user's brand color for dark mode + * type: string + * weekStart: + * description: Start of the week. Acceptable values are one of [SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY] + * type: string + * timeZone: + * description: The user's time zone + * type: string + * theme: + * description: Default theme for the user. Acceptable values are one of [DARK, LIGHT] + * type: string + * timeFormat: + * description: The user's time format. Acceptable values are one of [TWELVE, TWENTY_FOUR] + * type: string + * locale: + * description: The user's locale. Acceptable values are one of [EN, FR, IT, RU, ES, DE, PT, RO, NL, PT_BR, ES_419, KO, JA, PL, AR, IW, ZH_CH, ZH_TW, CS, SR, SV, VI] + * type: string + * examples: + * user: + * summary: An example of USER + * value: + * email: email@example.com + * username: johndoe + * weekStart: MONDAY + * brandColor: #555555 + * darkBrandColor: #111111 + * timeZone: EUROPE/PARIS + * theme: LIGHT + * timeFormat: TWELVE + * locale: FR + * tags: + * - users + * responses: + * 200: + * description: OK, user edited successfuly + * 400: + * description: Bad request. User body is invalid. + * 401: + * description: Authorization information is missing or invalid. + * 403: + * description: Insufficient permissions to access resource. + */ +export async function patchHandler(req: NextApiRequest) { + const { prisma, isAdmin } = req; + const query = schemaQueryUserId.parse(req.query); + // Here we only check for ownership of the user if the user is not admin, otherwise we let ADMIN's edit any user + if (!isAdmin && query.userId !== req.userId) throw new HttpError({ statusCode: 403, message: "Forbidden" }); + + const body = await schemaUserEditBodyParams.parseAsync(req.body); + // disable role changes unless admin. + if (!isAdmin && body.role) { + body.role = undefined; + } + + const userSchedules = await prisma.schedule.findMany({ + where: { userId: query.userId }, + }); + const userSchedulesIds = userSchedules.map((schedule) => schedule.id); + // @note: here we make sure user can only make as default his own scheudles + if (body.defaultScheduleId && !userSchedulesIds.includes(Number(body.defaultScheduleId))) { + throw new HttpError({ + statusCode: 400, + message: "Bad request: Invalid default schedule id", + }); + } + const data = await prisma.user.update({ + where: { id: query.userId }, + data: body, + }); + const user = schemaUserReadPublic.parse(data); + return { user }; +} + +export default defaultResponder(patchHandler); diff --git a/apps/api/pages/api/users/[userId]/availability/index.ts b/apps/api/pages/api/users/[userId]/availability/index.ts new file mode 100644 index 0000000000..95a9f13f9c --- /dev/null +++ b/apps/api/pages/api/users/[userId]/availability/index.ts @@ -0,0 +1,9 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("~/pages/api/availability/_get"), + }) +); diff --git a/apps/api/pages/api/users/[userId]/index.ts b/apps/api/pages/api/users/[userId]/index.ts new file mode 100644 index 0000000000..ef0f429b1e --- /dev/null +++ b/apps/api/pages/api/users/[userId]/index.ts @@ -0,0 +1,11 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("./_get"), + PATCH: import("./_patch"), + DELETE: import("./_delete"), + }) +); diff --git a/apps/api/pages/api/users/_get.ts b/apps/api/pages/api/users/_get.ts new file mode 100644 index 0000000000..59e09bdeb5 --- /dev/null +++ b/apps/api/pages/api/users/_get.ts @@ -0,0 +1,50 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; +import { schemaUsersReadPublic } from "~/lib/validations/user"; + +/** + * @swagger + * /users: + * get: + * operationId: listUsers + * summary: Find all users. + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - users + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No users were found + */ +export async function getHandler(req: NextApiRequest) { + const { + userId, + prisma, + isAdmin, + pagination: { take, skip }, + } = req; + const where: Prisma.UserWhereInput = {}; + // If user is not ADMIN, return only his data. + if (!isAdmin) where.id = userId; + const [total, data] = await prisma.$transaction([ + prisma.user.count(), + prisma.user.findMany({ where, take, skip }), + ]); + const users = schemaUsersReadPublic.parse(data); + return { users, total }; +} + +export default withMiddleware("pagination")(defaultResponder(getHandler)); diff --git a/apps/api/pages/api/users/_post.ts b/apps/api/pages/api/users/_post.ts new file mode 100644 index 0000000000..7c945399d0 --- /dev/null +++ b/apps/api/pages/api/users/_post.ts @@ -0,0 +1,93 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaUserCreateBodyParams } from "~/lib/validations/user"; + +/** + * @swagger + * /users: + * post: + * operationId: addUser + * summary: Creates a new user + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * requestBody: + * description: Create a new user + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - email + * - username + * properties: + * email: + * type: string + * format: email + * description: Email that belongs to the user being edited + * username: + * type: string + * description: Username for the user being created + * brandColor: + * description: The new user's brand color + * type: string + * darkBrandColor: + * description: The new user's brand color for dark mode + * type: string + * weekStart: + * description: Start of the week. Acceptable values are one of [SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY] + * type: string + * timeZone: + * description: The new user's time zone. Eg- 'EUROPE/PARIS' + * type: string + * theme: + * description: Default theme for the new user. Acceptable values are one of [DARK, LIGHT] + * type: string + * timeFormat: + * description: The new user's time format. Acceptable values are one of [TWELVE, TWENTY_FOUR] + * type: string + * locale: + * description: The new user's locale. Acceptable values are one of [EN, FR, IT, RU, ES, DE, PT, RO, NL, PT_BR, ES_419, KO, JA, PL, AR, IW, ZH_CH, ZH_TW, CS, SR, SV, VI] + * type: string + * examples: + * user: + * summary: An example of USER + * value: + * email: 'email@example.com' + * username: 'johndoe' + * weekStart: 'MONDAY' + * brandColor: '#555555' + * darkBrandColor: '#111111' + * timeZone: 'EUROPE/PARIS' + * theme: 'LIGHT' + * timeFormat: 'TWELVE' + * locale: 'FR' + * tags: + * - users + * responses: + * 201: + * description: OK, user created + * 400: + * description: Bad request. user body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +async function postHandler(req: NextApiRequest) { + const { prisma, isAdmin } = req; + // If user is not ADMIN, return unauthorized. + if (!isAdmin) throw new HttpError({ statusCode: 401, message: "You are not authorized" }); + const data = await schemaUserCreateBodyParams.parseAsync(req.body); + const user = await prisma.user.create({ data }); + req.statusCode = 201; + return { user }; +} + +export default defaultResponder(postHandler); diff --git a/apps/api/pages/api/users/index.ts b/apps/api/pages/api/users/index.ts new file mode 100644 index 0000000000..2a15abfa5b --- /dev/null +++ b/apps/api/pages/api/users/index.ts @@ -0,0 +1,10 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("./_get"), + POST: import("./_post"), + }) +); diff --git a/apps/api/pages/api/webhooks/[id]/_auth-middleware.ts b/apps/api/pages/api/webhooks/[id]/_auth-middleware.ts new file mode 100644 index 0000000000..6f2eb04db5 --- /dev/null +++ b/apps/api/pages/api/webhooks/[id]/_auth-middleware.ts @@ -0,0 +1,19 @@ +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; + +import { schemaQueryIdAsString } from "~/lib/validations/shared/queryIdString"; + +async function authMiddleware(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const { id } = schemaQueryIdAsString.parse(req.query); + // Admins can just skip this check + if (isAdmin) return; + // Check if the current user can access the webhook + const webhook = await prisma.webhook.findFirst({ + where: { id, appId: null, OR: [{ userId }, { eventType: { team: { members: { some: { userId } } } } }] }, + }); + if (!webhook) throw new HttpError({ statusCode: 403, message: "Forbidden" }); +} + +export default authMiddleware; diff --git a/apps/api/pages/api/webhooks/[id]/_delete.ts b/apps/api/pages/api/webhooks/[id]/_delete.ts new file mode 100644 index 0000000000..4750741338 --- /dev/null +++ b/apps/api/pages/api/webhooks/[id]/_delete.ts @@ -0,0 +1,45 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryIdAsString } from "~/lib/validations/shared/queryIdString"; + +/** + * @swagger + * /webhooks/{id}: + * delete: + * summary: Remove an existing hook + * operationId: removeWebhookById + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: Numeric ID of the hooks to delete + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - webhooks + * externalDocs: + * url: https://docs.cal.com/core-features/webhooks + * responses: + * 201: + * description: OK, hook removed successfully + * 400: + * description: Bad request. hook id is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function deleteHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdAsString.parse(query); + await prisma.webhook.delete({ where: { id } }); + return { message: `Schedule with id: ${id} deleted successfully` }; +} + +export default defaultResponder(deleteHandler); diff --git a/apps/api/pages/api/webhooks/[id]/_get.ts b/apps/api/pages/api/webhooks/[id]/_get.ts new file mode 100644 index 0000000000..3bde62987a --- /dev/null +++ b/apps/api/pages/api/webhooks/[id]/_get.ts @@ -0,0 +1,46 @@ +import type { NextApiRequest } from "next"; + +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryIdAsString } from "~/lib/validations/shared/queryIdString"; +import { schemaWebhookReadPublic } from "~/lib/validations/webhook"; + +/** + * @swagger + * /webhooks/{id}: + * get: + * summary: Find a webhook + * operationId: getWebhookById + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: Numeric ID of the webhook to get + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - webhooks + * externalDocs: + * url: https://docs.cal.com/core-features/webhooks + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: Webhook was not found + */ +export async function getHandler(req: NextApiRequest) { + const { prisma, query } = req; + const { id } = schemaQueryIdAsString.parse(query); + const data = await prisma.webhook.findUniqueOrThrow({ where: { id } }); + return { webhook: schemaWebhookReadPublic.parse(data) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/webhooks/[id]/_patch.ts b/apps/api/pages/api/webhooks/[id]/_patch.ts new file mode 100644 index 0000000000..35c2810f39 --- /dev/null +++ b/apps/api/pages/api/webhooks/[id]/_patch.ts @@ -0,0 +1,91 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQueryIdAsString } from "~/lib/validations/shared/queryIdString"; +import { schemaWebhookEditBodyParams, schemaWebhookReadPublic } from "~/lib/validations/webhook"; + +/** + * @swagger + * /webhooks/{id}: + * patch: + * summary: Edit an existing webhook + * operationId: editWebhookById + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: Numeric ID of the webhook to edit + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * requestBody: + * description: Edit an existing webhook + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * subscriberUrl: + * type: string + * format: uri + * description: The URL to subscribe to this webhook + * eventTriggers: + * type: string + * enum: [BOOKING_CREATED, BOOKING_RESCHEDULED, BOOKING_CANCELLED, MEETING_ENDED] + * description: The events which should trigger this webhook call + * active: + * type: boolean + * description: Whether the webhook is active and should trigger on associated trigger events + * payloadTemplate: + * type: string + * description: The template of the webhook's payload + * eventTypeId: + * type: number + * description: The event type ID if this webhook should be associated with only that event type + * tags: + * - webhooks + * externalDocs: + * url: https://docs.cal.com/core-features/webhooks + * responses: + * 201: + * description: OK, webhook edited successfully + * 400: + * description: Bad request. Webhook body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +export async function patchHandler(req: NextApiRequest) { + const { prisma, query, userId, isAdmin } = req; + const { id } = schemaQueryIdAsString.parse(query); + const { eventTypeId, userId: bodyUserId, ...data } = schemaWebhookEditBodyParams.parse(req.body); + const args: Prisma.WebhookUpdateArgs = { where: { id }, data }; + + if (eventTypeId) { + const where: Prisma.EventTypeWhereInput = { id: eventTypeId }; + if (!isAdmin) where.userId = userId; + await prisma.eventType.findFirstOrThrow({ where }); + args.data.eventTypeId = eventTypeId; + } + + if (!isAdmin && bodyUserId) throw new HttpError({ statusCode: 403, message: `ADMIN required for userId` }); + + if (isAdmin && bodyUserId) { + const where: Prisma.UserWhereInput = { id: bodyUserId }; + await prisma.user.findFirstOrThrow({ where }); + args.data.userId = bodyUserId; + } + + const result = await prisma.webhook.update(args); + return { webhook: schemaWebhookReadPublic.parse(result) }; +} + +export default defaultResponder(patchHandler); diff --git a/apps/api/pages/api/webhooks/[id]/index.ts b/apps/api/pages/api/webhooks/[id]/index.ts new file mode 100644 index 0000000000..727ad02843 --- /dev/null +++ b/apps/api/pages/api/webhooks/[id]/index.ts @@ -0,0 +1,18 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { defaultHandler, defaultResponder } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +import authMiddleware from "./_auth-middleware"; + +export default withMiddleware()( + defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => { + await authMiddleware(req); + return defaultHandler({ + GET: import("./_get"), + PATCH: import("./_patch"), + DELETE: import("./_delete"), + })(req, res); + }) +); diff --git a/apps/api/pages/api/webhooks/_get.ts b/apps/api/pages/api/webhooks/_get.ts new file mode 100644 index 0000000000..8708c303e8 --- /dev/null +++ b/apps/api/pages/api/webhooks/_get.ts @@ -0,0 +1,54 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId"; +import { schemaWebhookReadPublic } from "~/lib/validations/webhook"; + +/** + * @swagger + * /webhooks: + * get: + * summary: Find all webhooks + * operationId: listWebhooks + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * tags: + * - webhooks + * externalDocs: + * url: https://docs.cal.com/core-features/webhooks + * responses: + * 200: + * description: OK + * 401: + * description: Authorization information is missing or invalid. + * 404: + * description: No webhooks were found + */ +async function getHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const args: Prisma.WebhookFindManyArgs = isAdmin + ? {} + : { where: { OR: [{ eventType: { userId } }, { userId }] } }; + + /** Only admins can query other users */ + if (!isAdmin && req.query.userId) throw new HttpError({ statusCode: 403, message: "ADMIN required" }); + if (isAdmin && req.query.userId) { + const query = schemaQuerySingleOrMultipleUserIds.parse(req.query); + const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId]; + args.where = { OR: [{ eventType: { userId: { in: userIds } } }, { userId: { in: userIds } }] }; + if (Array.isArray(query.userId)) args.orderBy = { userId: "asc", eventType: { userId: "asc" } }; + } + + const data = await prisma.webhook.findMany(args); + return { webhooks: data.map((v) => schemaWebhookReadPublic.parse(v)) }; +} + +export default defaultResponder(getHandler); diff --git a/apps/api/pages/api/webhooks/_post.ts b/apps/api/pages/api/webhooks/_post.ts new file mode 100644 index 0000000000..2a99c903e8 --- /dev/null +++ b/apps/api/pages/api/webhooks/_post.ts @@ -0,0 +1,95 @@ +import type { Prisma } from "@prisma/client"; +import type { NextApiRequest } from "next"; +import { v4 as uuidv4 } from "uuid"; + +import { HttpError } from "@calcom/lib/http-error"; +import { defaultResponder } from "@calcom/lib/server"; + +import { schemaWebhookCreateBodyParams, schemaWebhookReadPublic } from "~/lib/validations/webhook"; + +/** + * @swagger + * /webhooks: + * post: + * summary: Creates a new webhook + * operationId: addWebhook + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key + * requestBody: + * description: Create a new webhook + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - subscriberUrl + * - eventTriggers + * - active + * properties: + * subscriberUrl: + * type: string + * format: uri + * description: The URL to subscribe to this webhook + * eventTriggers: + * type: string + * enum: [BOOKING_CREATED, BOOKING_RESCHEDULED, BOOKING_CANCELLED, MEETING_ENDED] + * description: The events which should trigger this webhook call + * active: + * type: boolean + * description: Whether the webhook is active and should trigger on associated trigger events + * payloadTemplate: + * type: string + * description: The template of the webhook's payload + * eventTypeId: + * type: number + * description: The event type ID if this webhook should be associated with only that event type + * tags: + * - webhooks + * externalDocs: + * url: https://docs.cal.com/core-features/webhooks + * responses: + * 201: + * description: OK, webhook created + * 400: + * description: Bad request. webhook body is invalid. + * 401: + * description: Authorization information is missing or invalid. + */ +async function postHandler(req: NextApiRequest) { + const { userId, isAdmin, prisma } = req; + const { eventTypeId, userId: bodyUserId, ...body } = schemaWebhookCreateBodyParams.parse(req.body); + const args: Prisma.WebhookCreateArgs = { data: { id: uuidv4(), ...body } }; + + // If no event type, we assume is for the current user. If admin we run more checks below... + if (!eventTypeId) args.data.userId = userId; + + if (eventTypeId) { + const where: Prisma.EventTypeWhereInput = { id: eventTypeId }; + if (!isAdmin) where.userId = userId; + await prisma.eventType.findFirstOrThrow({ where }); + args.data.eventTypeId = eventTypeId; + } + + if (!isAdmin && bodyUserId) throw new HttpError({ statusCode: 403, message: `ADMIN required for userId` }); + + if (isAdmin && bodyUserId) { + const where: Prisma.UserWhereInput = { id: bodyUserId }; + await prisma.user.findFirstOrThrow({ where }); + args.data.userId = bodyUserId; + } + + const data = await prisma.webhook.create(args); + + return { + webhook: schemaWebhookReadPublic.parse(data), + message: "Webhook created successfully", + }; +} + +export default defaultResponder(postHandler); diff --git a/apps/api/pages/api/webhooks/index.ts b/apps/api/pages/api/webhooks/index.ts new file mode 100644 index 0000000000..2a15abfa5b --- /dev/null +++ b/apps/api/pages/api/webhooks/index.ts @@ -0,0 +1,10 @@ +import { defaultHandler } from "@calcom/lib/server"; + +import { withMiddleware } from "~/lib/helpers/withMiddleware"; + +export default withMiddleware()( + defaultHandler({ + GET: import("./_get"), + POST: import("./_post"), + }) +); diff --git a/apps/api/scripts/vercel-deploy.sh b/apps/api/scripts/vercel-deploy.sh new file mode 100755 index 0000000000..689616931c --- /dev/null +++ b/apps/api/scripts/vercel-deploy.sh @@ -0,0 +1,66 @@ +# github submodule repo addresses without https:// prefix +BRANCH_TO_CLONE="" +SUBMODULE_GITHUB=github.com/calcom/api +SUBMODULE_PATH=apps/api +COMMIT=$VERCEL_GIT_COMMIT_SHA + +if [ "$VERCEL_GIT_COMMIT_SHA" == "" ]; then + echo "Error: VERCEL_GIT_COMMIT_SHA is empty" + exit 0 +fi + +# github access token is necessary +# add it to Environment Variables on Vercel +if [ "$GITHUB_ACCESS_TOKEN" == "" ]; then + echo "Error: GITHUB_ACCESS_TOKEN is empty" + exit 0 +fi + +# We add an exception to test on staging +if [ "$VERCEL_GIT_COMMIT_REF" == "production" ]; then + BRANCH_TO_CLONE="-b $VERCEL_GIT_COMMIT_REF" +fi +if [ "$VERCEL_GIT_COMMIT_REF" == "staging" ]; then + BRANCH_TO_CLONE="-b $VERCEL_GIT_COMMIT_REF" +fi + +# stop execution on error - don't let it build if something goes wrong +set -e + +git config --global init.defaultBranch main +git config --global advice.detachedHead false + +# set up an empty temporary work directory +rm -rf ..?* .[!.]* * || true + +# checkout the current commit +git clone $BRANCH_TO_CLONE https://$GITHUB_ACCESS_TOKEN@github.com/calcom/cal.com.git . + +echo "Cloned" + +# Ensure the submodule directory exists +mkdir -p $SUBMODULE_PATH + +# set up an empty temporary work directory +rm -rf tmp || true # remove the tmp folder if exists +mkdir tmp # create the tmp folder +cd tmp # go into the tmp folder + +# checkout the current submodule commit +git init # initialise empty repo +git remote add $SUBMODULE_PATH https://$GITHUB_ACCESS_TOKEN@$SUBMODULE_GITHUB # add origin of the submodule +git fetch --depth=1 $SUBMODULE_PATH $COMMIT # fetch only the required version +git checkout $COMMIT # checkout on the right commit + +# move the submodule from tmp to the submodule path +cd .. # go folder up +rm -rf tmp/.git # remove .git +mv tmp/* $SUBMODULE_PATH/ # move the submodule to the submodule path + +# clean up +rm -rf tmp # remove the tmp folder + +git diff --quiet HEAD^ HEAD ':!/apps/docs/*' ':!/apps/website/*' ':!/apps/web/*' ':!/apps/swagger/*' ':!/apps/console/*' + +echo "✅ - Build can proceed" +exit 1 diff --git a/apps/api/test/README.md b/apps/api/test/README.md new file mode 100644 index 0000000000..938d54f0be --- /dev/null +++ b/apps/api/test/README.md @@ -0,0 +1,12 @@ +# Unit and Integration Tests + +Make sure you have copied .env.test.example to .env.test + +You can run all jest tests as + +`yarn test` + +You can run tests matching specific description by following command +`yarn test -t _post` + +Tip: Use `--watchAll` flag to run tests on every change diff --git a/apps/api/test/docker-compose.yml b/apps/api/test/docker-compose.yml new file mode 100644 index 0000000000..769adab097 --- /dev/null +++ b/apps/api/test/docker-compose.yml @@ -0,0 +1,15 @@ +# Set the version of docker compose to use +version: '3.9' + +# The containers that compose the project +services: + db: + image: postgres:13 + restart: always + container_name: integration-tests-prisma + ports: + - '5433:5432' + environment: + POSTGRES_USER: prisma + POSTGRES_PASSWORD: prisma + POSTGRES_DB: tests \ No newline at end of file diff --git a/apps/api/test/jest-resolver.js b/apps/api/test/jest-resolver.js new file mode 100644 index 0000000000..d5fb532a29 --- /dev/null +++ b/apps/api/test/jest-resolver.js @@ -0,0 +1,15 @@ +module.exports = (path, options) => { + // Call the defaultResolver, so we leverage its cache, error handling, etc. + return options.defaultResolver(path, { + ...options, + // Use packageFilter to process parsed `package.json` before the resolution (see https://www.npmjs.com/package/resolve#resolveid-opts-cb) + packageFilter: (pkg) => { + // See https://github.com/microsoft/accessibility-insights-web/blob/40416a4ae6b91baf43102f58e069eff787de4de2/src/tests/common/resolver.js + if (pkg.name === "uuid" || pkg.name === "nanoid") { + delete pkg["exports"]; + delete pkg["module"]; + } + return pkg; + }, + }); +}; diff --git a/apps/api/test/jest-setup.js b/apps/api/test/jest-setup.js new file mode 100644 index 0000000000..fa5c44868d --- /dev/null +++ b/apps/api/test/jest-setup.js @@ -0,0 +1,6 @@ +// This is a workaround for https://github.com/jsdom/jsdom/issues/2524#issuecomment-902027138 + +// See https://github.com/microsoft/accessibility-insights-web/blob/40416a4ae6b91baf43102f58e069eff787de4de2/src/tests/unit/jest-setup.ts +const { TextEncoder, TextDecoder } = require("util"); +global.TextEncoder = TextEncoder; +global.TextDecoder = TextDecoder; diff --git a/apps/api/test/lib/bookings/_post.test.ts b/apps/api/test/lib/bookings/_post.test.ts new file mode 100644 index 0000000000..5e4b17f23d --- /dev/null +++ b/apps/api/test/lib/bookings/_post.test.ts @@ -0,0 +1,265 @@ +import type { Request, Response } from "express"; +import type { NextApiRequest, NextApiResponse } from "next"; +import { createMocks } from "node-mocks-http"; +import { describe, expect, test, vi } from "vitest"; + +import dayjs from "@calcom/dayjs"; +import sendPayload from "@calcom/features/webhooks/lib/sendPayload"; +import { buildBooking, buildEventType, buildWebhook } from "@calcom/lib/test/builder"; +import prisma from "@calcom/prisma"; + +import prismaMock from "../../../../../tests/libs/__mocks__/prisma"; +import handler from "../../../pages/api/bookings/_post"; + +type CustomNextApiRequest = NextApiRequest & Request; +type CustomNextApiResponse = NextApiResponse & Response; +vi.mock("@calcom/features/webhooks/lib/sendPayload"); +vi.mock("@calcom/lib/server/i18n", () => { + return { + getTranslation: (key: string) => key, + }; +}); + +describe("POST /api/bookings", () => { + describe("Errors", () => { + test("Missing required data", async () => { + const { req, res } = createMocks({ + method: "POST", + body: {}, + }); + + await handler(req, res); + + expect(res._getStatusCode()).toBe(400); + expect(JSON.parse(res._getData())).toEqual( + expect.objectContaining({ + message: + "invalid_type in 'eventTypeId': Required; invalid_type in 'title': Required; invalid_type in 'startTime': Required; invalid_type in 'startTime': Required; invalid_type in 'endTime': Required; invalid_type in 'endTime': Required", + }) + ); + }); + + test("Invalid eventTypeId", async () => { + const { req, res } = createMocks({ + method: "POST", + body: { + title: "test", + eventTypeId: 2, + startTime: dayjs().toDate(), + endTime: dayjs().add(1, "day").toDate(), + }, + prisma, + }); + + prismaMock.eventType.findUnique.mockResolvedValue(null); + + await handler(req, res); + + expect(res._getStatusCode()).toBe(400); + expect(JSON.parse(res._getData())).toEqual( + expect.objectContaining({ + message: + "'invalid_type' in 'email': Required; 'invalid_type' in 'end': Required; 'invalid_type' in 'location': Required; 'invalid_type' in 'name': Required; 'invalid_type' in 'start': Required; 'invalid_type' in 'timeZone': Required; 'invalid_type' in 'language': Required; 'invalid_type' in 'customInputs': Required; 'invalid_type' in 'metadata': Required", + }) + ); + }); + + test("Missing recurringCount", async () => { + const { req, res } = createMocks({ + method: "POST", + body: { + title: "test", + eventTypeId: 2, + startTime: dayjs().toDate(), + endTime: dayjs().add(1, "day").toDate(), + }, + prisma, + }); + + prismaMock.eventType.findUnique.mockResolvedValue( + buildEventType({ recurringEvent: { freq: 2, count: 12, interval: 1 } }) + ); + + await handler(req, res); + + expect(res._getStatusCode()).toBe(400); + expect(JSON.parse(res._getData())).toEqual( + expect.objectContaining({ + message: + "'invalid_type' in 'email': Required; 'invalid_type' in 'end': Required; 'invalid_type' in 'location': Required; 'invalid_type' in 'name': Required; 'invalid_type' in 'start': Required; 'invalid_type' in 'timeZone': Required; 'invalid_type' in 'language': Required; 'invalid_type' in 'customInputs': Required; 'invalid_type' in 'metadata': Required", + }) + ); + }); + + test("Invalid recurringCount", async () => { + const { req, res } = createMocks({ + method: "POST", + body: { + title: "test", + eventTypeId: 2, + startTime: dayjs().toDate(), + endTime: dayjs().add(1, "day").toDate(), + recurringCount: 15, + }, + prisma, + }); + + prismaMock.eventType.findUnique.mockResolvedValue( + buildEventType({ recurringEvent: { freq: 2, count: 12, interval: 1 } }) + ); + + await handler(req, res); + + expect(res._getStatusCode()).toBe(400); + expect(JSON.parse(res._getData())).toEqual( + expect.objectContaining({ + message: + "'invalid_type' in 'email': Required; 'invalid_type' in 'end': Required; 'invalid_type' in 'location': Required; 'invalid_type' in 'name': Required; 'invalid_type' in 'start': Required; 'invalid_type' in 'timeZone': Required; 'invalid_type' in 'language': Required; 'invalid_type' in 'customInputs': Required; 'invalid_type' in 'metadata': Required", + }) + ); + }); + + test("No available users", async () => { + const { req, res } = createMocks({ + method: "POST", + body: { + name: "test", + start: dayjs().format(), + end: dayjs().add(1, "day").format(), + eventTypeId: 2, + email: "test@example.com", + location: "Cal.com Video", + timeZone: "America/Montevideo", + language: "en", + customInputs: [], + metadata: {}, + userId: 4, + }, + prisma, + }); + + prismaMock.eventType.findUniqueOrThrow.mockResolvedValue(buildEventType()); + + await handler(req, res); + console.log({ statusCode: res._getStatusCode(), data: JSON.parse(res._getData()) }); + + expect(res._getStatusCode()).toBe(500); + expect(JSON.parse(res._getData())).toEqual( + expect.objectContaining({ + message: "No available users found.", + }) + ); + }); + }); + + describe("Success", () => { + describe("Regular event-type", () => { + test("Creates one single booking", async () => { + const { req, res } = createMocks({ + method: "POST", + body: { + name: "test", + start: dayjs().format(), + end: dayjs().add(1, "day").format(), + eventTypeId: 2, + email: "test@example.com", + location: "Cal.com Video", + timeZone: "America/Montevideo", + language: "en", + customInputs: [], + metadata: {}, + userId: 4, + }, + prisma, + }); + + prismaMock.eventType.findUniqueOrThrow.mockResolvedValue(buildEventType()); + prismaMock.booking.findMany.mockResolvedValue([]); + + await handler(req, res); + console.log({ statusCode: res._getStatusCode(), data: JSON.parse(res._getData()) }); + + expect(prismaMock.booking.create).toHaveBeenCalledTimes(1); + }); + }); + + describe("Recurring event-type", () => { + test("Creates multiple bookings", async () => { + const { req, res } = createMocks({ + method: "POST", + body: { + title: "test", + eventTypeId: 2, + startTime: dayjs().toDate(), + endTime: dayjs().add(1, "day").toDate(), + recurringCount: 12, + }, + prisma, + }); + + prismaMock.eventType.findUnique.mockResolvedValue( + buildEventType({ recurringEvent: { freq: 2, count: 12, interval: 1 } }) + ); + + Array.from(Array(12).keys()).map(async () => { + prismaMock.booking.create.mockResolvedValue(buildBooking()); + }); + + prismaMock.webhook.findMany.mockResolvedValue([]); + + await handler(req, res); + const data = JSON.parse(res._getData()); + + expect(prismaMock.booking.create).toHaveBeenCalledTimes(12); + expect(res._getStatusCode()).toBe(201); + expect(data.message).toEqual("Bookings created successfully."); + expect(data.bookings.length).toEqual(12); + }); + }); + test("Notifies multiple bookings", async () => { + const { req, res } = createMocks({ + method: "POST", + body: { + title: "test", + eventTypeId: 2, + startTime: dayjs().toDate(), + endTime: dayjs().add(1, "day").toDate(), + recurringCount: 12, + }, + prisma, + }); + + prismaMock.eventType.findUnique.mockResolvedValue( + buildEventType({ recurringEvent: { freq: 2, count: 12, interval: 1 } }) + ); + + const createdAt = new Date(); + Array.from(Array(12).keys()).map(async () => { + prismaMock.booking.create.mockResolvedValue(buildBooking({ createdAt })); + }); + + const mockedWebhooks = [ + buildWebhook({ + subscriberUrl: "http://mockedURL1.com", + createdAt, + eventTypeId: 1, + secret: "secret1", + }), + buildWebhook({ + subscriberUrl: "http://mockedURL2.com", + createdAt, + eventTypeId: 2, + secret: "secret2", + }), + ]; + prismaMock.webhook.findMany.mockResolvedValue(mockedWebhooks); + + await handler(req, res); + const data = JSON.parse(res._getData()); + + expect(sendPayload).toHaveBeenCalledTimes(24); + expect(data.message).toEqual("Bookings created successfully."); + expect(data.bookings.length).toEqual(12); + }); + }); +}); diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json new file mode 100644 index 0000000000..c6b3666313 --- /dev/null +++ b/apps/api/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "@calcom/tsconfig/nextjs.json", + "compilerOptions": { + "strict": true, + "jsx": "preserve", + "baseUrl": ".", + "paths": { + "~/*": ["*"], + "@prisma/client/*": ["@calcom/prisma/client/*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + "../../packages/types/*.d.ts", + "../../packages/types/next-auth.d.ts" + ], + "exclude": ["node_modules", "templates", "auth"] +} diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore deleted file mode 100644 index fbea7fdedb..0000000000 --- a/apps/docs/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules -.next -.DS_Store -yarn-error.log -dist -examples -packages \ No newline at end of file diff --git a/apps/docs/.nvmrc b/apps/docs/.nvmrc deleted file mode 100644 index 3f430af82b..0000000000 --- a/apps/docs/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v18 diff --git a/apps/docs/.prettierignore b/apps/docs/.prettierignore deleted file mode 100644 index f74c78183c..0000000000 --- a/apps/docs/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -.next -node_modules diff --git a/apps/docs/LICENSE b/apps/docs/LICENSE deleted file mode 100644 index 6734428423..0000000000 --- a/apps/docs/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Shu Ding - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/apps/docs/components/Anchor.tsx b/apps/docs/components/Anchor.tsx deleted file mode 100644 index b7322b6bf8..0000000000 --- a/apps/docs/components/Anchor.tsx +++ /dev/null @@ -1,21 +0,0 @@ -function getAnchor(text) { - return text - .toLowerCase() - .replace(/[^a-z0-9 ]/g, "") - .replace(/[ ]/g, "-") - .replace(/ /g, "%20"); -} - -export default function Anchor({ as, children }) { - const anchor = getAnchor(children); - const link = `#${anchor}`; - const Component = as || "div"; - return ( - - - § - - {children} - - ); -} diff --git a/apps/docs/lib/useWindowSize.ts b/apps/docs/lib/useWindowSize.ts deleted file mode 100644 index 1987e6f363..0000000000 --- a/apps/docs/lib/useWindowSize.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { useState, useEffect } from "react"; - -// Define general type for useWindowSize hook, which includes width and height -export interface Size { - width: number | undefined; - height: number | undefined; -} -// Hook from: https://usehooks.com/useWindowSize/ -export function useWindowSize(): Size { - // Initialize state with undefined width/height so server and client renders match - // Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/ - const [windowSize, setWindowSize] = useState({ - width: undefined, - height: undefined, - }); - useEffect(() => { - // Handler to call on window resize - function handleResize() { - // Set window width/height to state - setWindowSize({ - width: window.innerWidth, - height: window.innerHeight, - }); - } - // Add event listener - window.addEventListener("resize", handleResize); - // Call handler right away so state gets updated with initial window size - handleResize(); - // Remove event listener on cleanup - return () => window.removeEventListener("resize", handleResize); - }, []); // Empty array ensures that effect is only run on mount - return windowSize; -} diff --git a/apps/docs/next.config.js b/apps/docs/next.config.js deleted file mode 100644 index dce01e92c1..0000000000 --- a/apps/docs/next.config.js +++ /dev/null @@ -1,17 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -const withNextra = require("nextra")({ - theme: "nextra-theme-docs", - themeConfig: "./theme.config.js", - unstable_staticImage: true, -}); -module.exports = withNextra({ - async rewrites() { - return [ - // This redirects requests recieved at /api to /public-api to workaround nextjs default use of /api. - { - source: "/api", - destination: "/public-api", - }, - ]; - }, -}); diff --git a/apps/docs/package.json b/apps/docs/package.json deleted file mode 100644 index 236c09a272..0000000000 --- a/apps/docs/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@calcom/docs", - "version": "1.0.0", - "private": true, - "description": "", - "main": "index.js", - "scripts": { - "build": "next build", - "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next", - "dev": "PORT=4000 next", - "lint": "eslint . --ignore-path .gitignore", - "lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix", - "lint:report": "eslint . --format json --output-file ../../lint-results/docs.json", - "start": "PORT=4000 next start", - "type-check": "tsc --pretty --noEmit", - "type-check:ci": "tsc-absolute --pretty --noEmit" - }, - "author": "Cal.com, Inc.", - "license": "MIT", - "dependencies": { - "iframe-resizer-react": "^1.1.0", - "next": "^13.2.1", - "nextra": "^1.1.0", - "nextra-theme-docs": "^1.2.2", - "react": "^18.2.0", - "react-dom": "^18.2.0" - }, - "devDependencies": { - "@calcom/config": "*" - } -} diff --git a/apps/docs/pages/_app.tsx b/apps/docs/pages/_app.tsx deleted file mode 100644 index e8aad3b926..0000000000 --- a/apps/docs/pages/_app.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import type { AppProps } from "next/app"; -import "nextra-theme-docs/style.css"; - -import "./style.css"; - -export default function Nextra({ Component, pageProps }: AppProps) { - return ; -} diff --git a/apps/docs/pages/availability.mdx b/apps/docs/pages/availability.mdx deleted file mode 100644 index 413101f259..0000000000 --- a/apps/docs/pages/availability.mdx +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Availability ---- - -# Availability - -## Setting your availability - -1. Go to [Your Availability](https://app.cal.com/availability) page within your cal.com account. -2. On the availability page you can manage your availability to suit your working day accordingly. -You can also troubleshoot your availability if your calendar does not look like it is blocking out the correct times. - - -## Troubleshooting availability - -This will tell you when your calendar shows you as busy, so you can understand why Cal is blocking certain times. - -## How to troubleshoot availability - -1. Go to [Your Availability](https://app.cal.com/availability). -2. To the right of your screen a box saying 'Something doesn't look right?' will appear. Press the button 'Launch Troubleshooter'. -3. After pressing this, your availability will appear according to your calendar and inform you of the times booked up for that day! - -By doing this you will now understand why certain times are available and others are blocked. - -## In team settings availability set to only Mondays but the calendar shows availability on other days as well -It’s showing as available on days other than Monday because only you have set your availability to only Mondays, whereas the rest of the team haven't made that setting. diff --git a/apps/docs/pages/billing.mdx b/apps/docs/pages/billing.mdx deleted file mode 100644 index 36ede5c157..0000000000 --- a/apps/docs/pages/billing.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Billing ---- - -# Billing -## How the trial works -You are given FREE access for 14 days of our PRO subscription, you can use this to test and try out our product and see if it works for you. No credit card is required to sign up and you decide if you want to upgrade to a PRO subscription afterwards. - -## How to cancel the trial -If you are looking to cancel the trial and want to downgrade to the free option you can contact our support team where we will be happy to help you make that change! - -## How to upgrade -If you are looking at upgrading from our FREE subscription to our PRO subscription, head over to cal.com/upgrade where you can easily upgrade hassle free. - -## How to downgrade -1. Go to your [Billing Settings](https://app.cal.com/settings/billing). -2. From here you can press the button called `Go to the billing Portal`. That will take you to the page where you can upgrade/downgrade your plan. - -## How to delete your account -You can delete your account from within the [Settings](https://app.cal.com/settings/profile) option. Just scroll all the way down and click on `Delete Account`. - -## Purchasing a premium username -We've reserved a ton of premium usernames, such as short handles or first names to prevent name squatters. To find out if your username is premium, head over to [cal.com/signup](https://cal.com/signup) and choose your desired username. From here you will be taken straight to checkout and after proceeding your Cal.com account will be created with your desired username. - -## Manage your subscription -1. Go to your [Billing Settings](https://app.cal.com/settings/billing). -2. From here you can press the button called `Go to the billing Portal`. -3. This will take you to an external site provided by Stripe as they deal with all our payments. - -Some users may not be able to access Billing as their billing email is different to their account email. If this is the case, you can change the email associated with your account in [Profile Settings](https://app.cal.com/settings/profile). - -## Subscription for each team member -If your team requires multiple event types then each team member has to be subscribed to our paid plan. If that is something that isn’t necessary for your team, you can proceed with your FREE plan. - -## Discount for non-profits and students -We offer 50% for non-profit organizations and students. Just raise a ticket with our support team and submit the necessary proof of status. - diff --git a/apps/docs/pages/bookings.mdx b/apps/docs/pages/bookings.mdx deleted file mode 100644 index 0519aeec30..0000000000 --- a/apps/docs/pages/bookings.mdx +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Bookings ---- - -# Bookings - -## What can you do on the bookings page? - -On the bookings page you are able to see upcoming and past events booked through your event type links. This page also lets you cancel or reschedule upcoming events users have scheduled. - -## How to view bookings - -1. Go to [your upcoming and past bookings](https://app.cal.com/bookings/upcoming). -2. On this page you can see upcoming and past events booked through your event type links. - -## Reschedule bookings - -1. Go to [your rescheduled bookings](https://app.cal.com/bookings/upcoming). -2. Hovering over the event you want to reschedule, click the button saying 'Reschedule' -3. Clicking 'Reschedule' will bring you to your Cal booking page. Select a new Time and Date. -4. After selecting this you can go ahead and click that 'Reschedule' Button! -5. After, you and your attendee will receive a new confirmation email of your new date and time. - -## Cancel bookings - -1. Go to [your cancelled](https://app.cal.com/bookings/cancelled). -2. Hovering over the event you want to cancel, click the button saying 'Cancel' -3. Once done, you will receive a confirmation message saying 'Really cancel your booking?'. After confirming you can go ahead and click the red 'Cancel' button at the bottom of your screen. -4. After, you and your attendee will receive a new confirmation email of your event being cancelled. diff --git a/apps/docs/pages/contributing.mdx b/apps/docs/pages/contributing.mdx deleted file mode 100644 index 0714ddd466..0000000000 --- a/apps/docs/pages/contributing.mdx +++ /dev/null @@ -1,83 +0,0 @@ ---- -title: Contributing ---- - -# Contributing - -Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. - -- Before jumping into a PR be sure to search [existing PRs](https://github.com/calcom/cal.com/pulls) or [issues](https://github.com/calcom/cal.com/issues) for an open or closed item that relates to your submission. - -## Developing - -The development branch is `main`. This is the branch that all pull -requests should be made against. The changes on the `main` -branch are tagged into a release monthly. - -To develop locally: - -1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your - own GitHub account and then - [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device. -2. Create a new branch: - - ```sh - git checkout -b MY_BRANCH_NAME - ``` - -3. Install yarn: - - ```sh - npm install -g yarn - ``` - -4. Install the dependencies with: - - ```sh - yarn - ``` - -5. Start developing and watch for code changes: - - ```sh - yarn dev - ``` - -## Building - -You can build the project with: - -```bash -yarn build -``` - -Please be sure that you can make a full production build before pushing code. - -## Testing - -More info on how to add new tests coming soon. - -### Running tests - -This will run and test all flows in multiple Chromium windows to verify that no critical flow breaks: - -```sh -yarn test-e2e -``` - -## Linting - -To check the formatting of your code: - -```sh -yarn lint -``` - -If you get errors, be sure to fix them before comitting. - -## Making a Pull Request - -- Be sure to [check the "Allow edits from maintainers" option](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) while creating you PR. -- If your PR refers to or fixes an issue, be sure to add `refs #XXX` or `fixes #XXX` to the PR description. Replacing `XXX` with the respective issue number. Se more about [Linking a pull request to an issue - ](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue). -- Be sure to fill the PR Template accordingly. diff --git a/apps/docs/pages/developer/adding-css.mdx b/apps/docs/pages/developer/adding-css.mdx deleted file mode 100644 index 219d1187dd..0000000000 --- a/apps/docs/pages/developer/adding-css.mdx +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: Adding CSS ---- - -import Callout from "nextra-theme-docs/callout"; - -# Adding CSS - - - Adding or modifying CSS counts as changing the code, so as per [our license](https://github.com/calcom/cal.com/blob/main/LICENSE) you must either open-source your modified version or purchase an enterprise license. - - -Cal.com uses [TailwindCSS](https://tailwindcss.com) as a replacement for traditional CSS styling within the application, but some people prefer to add CSS styles themselves. - -CSS files should be stored in the `styles` directory within the codebase. - -Within the `styles` directory, you will find a single CSS file, `global.css`. We suggest not to add to this file, and instead create new CSS files and import them into the application. This helps reduce conflicts when pulling in changes that we've made to either of the existing CSS files. - -## Adding new stylesheets -Firstly, create the CSS file inside the `styles` directory. - -Then, open the `pages/_app.tsx` file, and you will see the following two lines: -```javascript -import "../styles/globals.css"; -``` - -Duplicate one of these import statements and change the path to link to your new CSS stylesheet, like so: -```javascript -import "../styles/your-new-stylesheet.css"; -``` - - - These styles will apply to all pages and components in your application. - - -Due to the global nature of stylesheets, and to avoid conflicts, you may **only import them inside `pages/_app.tsx`**. diff --git a/apps/docs/pages/developer/app-store.mdx b/apps/docs/pages/developer/app-store.mdx deleted file mode 100644 index c0acd23996..0000000000 --- a/apps/docs/pages/developer/app-store.mdx +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: Contributing to App Store ---- - -# Contributing to the App Store - -Since Cal.com is open source we encourage developers to create new apps for others to use. This guide is to help you get started. - -## Structure - -All apps can be found under `packages/app-store`. In this folder is `_example` which shows the general structure of an app. - -```sh -├──_example -| -| ├──api -| | ├──example.ts -| | ├──index.ts -| -| ├──components -| | ├──InstallAppButton.tsx -| | ├──index.ts -| -| ├──lib -| | ├──adaptor.ts -| | ├──index.ts -| -| ├──static -| | ├──icon.svg -| -| ├──index.ts -| ├──package.json -| ├──.env.example -| ├──DESCRIPTION.md -``` - -## Getting Started - -In the `package.json` name your package appropriately and list the dependencies needed for the package. - -Next in the `.env.example` specify the environmental variables (ex. auth token, API secrets) that your app will need. In a comment add a link to instructions on how to obtain the credentials. Create a `.env` with your the filled in environmental variables. - -In `index.js` fill out the meta data that will be rendered on the app page. Under `packages/app-store/index.ts`, import your app and add it under `appStore`. Your app should now appear in the app store. - -Under the `/api` folder, this is where any API calls that are associated with your app will be handled. Since cal.com uses Next.js we use dynamic API routes. In this example if we want to hit `/api/example.ts` the route would be `{BASE_URL}/api/integrations/_example/example`. Export your endpoints in an `index.ts` file under `/api` folder and import them in your main `index.ts` file. - -Under the `/components` folder, this is where the install button for your app should live. Follow the template under `_example` to add your on click action (ex. Redirecting to a log in page or opening a modal). - -The `/lib` folder is where the functions of your app live. For example, when creating a booking with a MS Teams link the function to make the call to grab the link lives in the `/lib` folder. Export your endpoints in an `index.ts` file under `/lib` folder and import them in your main `index.ts` file. - -On the app store page you can customize your apps description by adding a markdown file called `DESCRIPTION.md`. If you do not add one then the description from you `package.json` will be used instead. - -The `/static` folder is where you can store your app icon and any images that your `DESCRIPTION.md` may use. - -## Adding Your App to the App Store -To render your app on the app store page, go to `packages/app-store/index.ts`. Import your app into the file and add it to the `appStore` object. - -Under `packages/app-store/components.tsx`, in the `InstallAppButtonMap` object dynamically import your install button. Your install button should live under `{your_app}/components`. - -If you need any help feel free to join us on [Slack](https://cal.com/slack) diff --git a/apps/docs/pages/developer/code-styling.mdx b/apps/docs/pages/developer/code-styling.mdx deleted file mode 100644 index b18a4cf26b..0000000000 --- a/apps/docs/pages/developer/code-styling.mdx +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Code styling ---- - -# Code Styling -Keeping our code styles consistent is key to making the repository easy to read and work with. - -We use a number of style guides written by other amazing companies, simply because they are widely used and because we like working with them. - -We don't expect you to study every single rule of each style guide, but these are great reference points as to how your code should be styled. We may reject a pull request if your code style significantly differs from these style guides however. - -## ESLint & Prettier -Cal.com uses the ESLint and Prettier formatting tools, and the repository comes with defined rules for each tool. We recommend setting up both tools and using these to help automatically style your code to our guidelines. - -## JavaScript/TypeScript -We use the [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) for all JavaScript and TypeScript code. - -## HTML & CSS -We use the [Google HTML/CSS Style Guide](https://google.github.io/styleguide/htmlcssguide.html) for any HTML and CSS markup. However, exceptions to the HTML guide apply where JSX differentiates from standard HTML. diff --git a/apps/docs/pages/developer/meta.json b/apps/docs/pages/developer/meta.json deleted file mode 100644 index be45eb35eb..0000000000 --- a/apps/docs/pages/developer/meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "migrations": "Migrations", - "pre-fill": "Pre-fill fields", - "code-styling": "Code styling", - "project-structure": "Project structure", - "pull-requests": "Pull requests", - "adding-css": "Adding CSS", - "app-store": "Contributing to App Store" -} diff --git a/apps/docs/pages/developer/migrations.mdx b/apps/docs/pages/developer/migrations.mdx deleted file mode 100644 index 28d5acefa3..0000000000 --- a/apps/docs/pages/developer/migrations.mdx +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: Migrations ---- - -# Database Migrations -As described in the [upgrade guide](https://docs.cal.com/self-hosting/upgrade), you should use the `yarn workspace @calcom/prisma db-migrate` or `yarn workspace @calcom/prisma db-deploy` command to update the database. - -We use database migrations in order to handle changes to the database schema in a more secure and stable way. This is actually very common. The thing is that when just changing the schema in `schema.prisma` without creating migrations, the update to the newer database schema can damage or delete all data in production mode, since the system sometimes doesn't know how to transform the data from A to B. Using migrations, each step is reproducable, transparent and can be undone in a simple way. - -## Creating migrations -If you are modifying the codebase and make a change to the `schema.prisma` file, you must create a migration. - -To create a migration for your previously changed `schema.prisma`, simply run the following: -```sh -yarn workspace @calcom/prisma db-migrate -``` - -Now, you must create a short name for your migration to describe what changed (for example, "user_add_email_verified"). Then just add and commit it with the corresponding code that uses your new database schema. - -:::caution - -Always keep an eye on what migrations Prisma is generating. Prisma often happily will drop entire columns of data because it can't figure out what to do. - -::: - -## Error: The database schema is not empty -Prisma uses a database called `_prisma_migrations` to keep track of which migrations have been applied and which haven't. If your local migrations database doesn't match up with what's in the actual database, then Prisma will throw the following error: -```text -Error: P3005 - -The database schema for `localhost:5432` is not empty. Read more about how to baseline an existing production database: https://pris.ly/d/migrate-baseline -``` - -In order to fix this, we need to tell Prisma which migrations have already been applied. - -This can be done by running the following command, replacing `migration_name` with each migration that you have already applied: -```sh -yarn prisma migrate resolve --applied migration_name -``` - -You will need to run the command for each migration that you want to mark as applied. diff --git a/apps/docs/pages/developer/pre-fill.mdx b/apps/docs/pages/developer/pre-fill.mdx deleted file mode 100644 index e4c6075811..0000000000 --- a/apps/docs/pages/developer/pre-fill.mdx +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Pre-fill fields ---- - -# Pre-fill fields - -You can pre-fill a number of fields on the booking form by using their corresponding URL parameters. This can include the user’s name, email, or guests to be added to the booking. - -Usually when they go to the link, all that is needed will be the time of the booking, and the form will be pre-filled with the information given. - -Pre-filling booking fields can save a lot of time and speed up processes with filling out these forms, and assist with a smooth integration with your existing website or app. - -You can pre-fill a user’s name and email address like so: -```text -cal.com/rick/quick-chat/?email=attendee@example.com&name=John -``` -The data is persisted through the 3 booking pages; and is used to prefill the booking form. - -Guests can also be added to the link, there is also no limit to the amount of guests you wish to add. - -These should be added to your link like this: -```text -guest=guest1@example.com&guest=guest2@example.com -``` diff --git a/apps/docs/pages/developer/project-structure.mdx b/apps/docs/pages/developer/project-structure.mdx deleted file mode 100644 index 72449b6e37..0000000000 --- a/apps/docs/pages/developer/project-structure.mdx +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Project structure ---- - -# Project Structure - -This page gives an overview of how the codebase is structured so you can easily dive into the Cal.com code. - -Cal.com is written in Next.js, so you will find that we follow Next.js best practices for structure and layouts. - -## Basic folder structure - -The project comprises of the following folder structure: - -```text -.github/ - GitHub configuration files - ISSUE_TEMPLATE/ - workflows/ -.husky/ - Git hooks -.vscode/ - VS Code editor configuration -components/ - Application components - booking/ - dialog/ - team/ - ui/ -lib/ - Reusable code - emails/ - events/ - forgot-password/messaging/ - integrations/CalDav/ - teams/ -pages/ - Most of the project lives here - [user]/ - Booking pages - api/ - The backend API - auth/ - Next-Auth.js and other authentication routes - availability/ - bookings/ - cancel/ - event-types/ - integrations/ - reschedule/ - settings/ - team/ -prisma/ - The database schema and migrations - migrations/ -public/ - Images and static files - integrations/ -styles/ -test/lib/ -``` diff --git a/apps/docs/pages/developer/pull-requests.mdx b/apps/docs/pages/developer/pull-requests.mdx deleted file mode 100644 index 56d698d0f3..0000000000 --- a/apps/docs/pages/developer/pull-requests.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Pull requests ---- - -# Pull Requests - -## Requirements - -We have a number of requirements for PRs to ensure they are as easy to review as possible and to ensure that they are up to standard with the code. - -### Title & Content - -Start by providing a short and concise title. Don’t put something generic (e.g. bug fixes), and instead mention more specifically what your PR achieves, for instance “Fixes dropdown not expanding on settings page”. - -For the PR description, you should go into much greater detail about what your PR adds or fixes. Firstly, the description should include a link to any relevant issues or discussions surrounding the feature or bug that your PR addresses. - -#### Feature PRs - -Give a functional overview of how your feature works, including how the user can use the feature. Then, share any technical details in an overview of how the PR works (e.g. “Once the user enters their password, the password is hashed using BCrypt and stored in the Users database field”). - -#### Bug Fix PRs - -Give an overview of how your PR fixes the bug both as a high-level overview and a technical explanation of what caused the issue and how your PR resolves this. - -Feel free to add a short video or screenshots of what your PR achieves. Loom is a great way of sharing short videos. - -### Code Quality & Styling - -All submitted code must match our [code styling](/docs/code-styling) standards. We will reject pull requests that differ significantly from our standardised code styles. - -All code is automatically checked by Codacy and our linting process, and will notify you if there are any issues with the code that you submit. We require that code passes these quality checks before merging. - -## PR review process - -At least two members of the Calendso team should review and approve any PR before it is merged. - -Once two members of the team have approved this, someone from the team will merge the PR. If you are part of the Calendso team, you should merge your own PRs once you have received both approvals. diff --git a/apps/docs/pages/event-types.mdx b/apps/docs/pages/event-types.mdx deleted file mode 100644 index 8d34cfb87a..0000000000 --- a/apps/docs/pages/event-types.mdx +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Event Types ---- - -# Event Types -Event types allow you to create different events for different occasions when booking a time with you in your calendar. These can be named differently, have different time durations and the choice of platform can change. - -## Creating an event type -1. Go to [Your Event Types](https://app.cal.com/event-types). -2. Click the button at the top right of your screen saying '+ New Event Type'. -3. Create the title of your new event. -4. Confirm the auto-generated event type URL. -5. Create a description on what your event will be used for. -6. Decide on the amount of minutes you wish for this event to last for. -7. Press 'Continue' -8. Your event has now been created! - -## Editing event types -1. Go to [Your Event Types](https://app.cal.com/event-types). -2. Click anywhere within the box of the event you would like to edit. - (From here you can edit the basic settings of your event) -3. To get the advanced options, at the bottom of your event setting click 'Show Advanced Settings' -4. After you have finished editing the event type, scroll to the bottom of your page and select 'Update' -5. Your event type has now been updated. - -## Deleting event types -1. Go to [Your Event Types](https://app.cal.com/event-types). -2. Click anywhere within the box of the event, just like you would if you were editing the event. -3. From this page, just to the right, a button saying 'delete' will appear. Click this and your event will be deleted! - -## How to block a time slot before/after a meeting -You can block out a time frame in your calendar only after the meeting. You can do this by selecting `Show advanced settings` of your Event Type. The setting is labeled `Time-slot intervals`. - -## Setting up specific availability for each type of Event -Head to `Show advanced settings` of your event. At the bottom you can set up specific availability for different Event Types. - -## Availability not showing on a certain day in your calendar -Head over to your event and once you click on `Show advanced settings`, make sure your time zones are correct. Also, check if you have any calendar events scheduled that could overlap with your availability. - -## People can't book me even though there is still a couple of hours left on my availability for today -Head over to your event and once you click on `Show advanced settings`, have a look at the `Minimum booking notice`. It probably overlaps with your availability so make sure that notice fits your desired time frame for meetings. For example, if someone wants to book a meeting with you at 16:15 and it’s already 15:30, your 90 minutes minimum booking notice doesn’t allow that meeting to be booked. - - diff --git a/apps/docs/pages/faq.mdx b/apps/docs/pages/faq.mdx deleted file mode 100644 index dcc176fc30..0000000000 --- a/apps/docs/pages/faq.mdx +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: FAQs ---- - -# Frequently asked questions - -## Does Cal.com support a custom domain? -This is possible with our self-hosted option. - -## Is there a possibility of multi-bookings for events where more people can book at the same time? -As it stands this is currently not possible. We always keep an eye on the limitations like these that our users point to us. We’ve had requests in the past for the multi-booking feature and this is on our priority list. - -## How to quickly block further bookings? -1. Click on the lower left corner of your dashboard where your username is displayed. -2. That initiates a dropdown menu. Click on `Set yourself as away`. - -This is a method to disable your Cal.com account which won't allow any bookings once initiated. However, bookings made before turning on *away mode* will still be booked. diff --git a/apps/docs/pages/import.mdx b/apps/docs/pages/import.mdx deleted file mode 100644 index dac0d5e3a2..0000000000 --- a/apps/docs/pages/import.mdx +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Import ---- - -import Callout from "nextra-theme-docs/callout"; - -# Import data from other scheduling tools - -When setting up your Cal.com account via the onboarding process, you can import data from other scheduling tools, such as Calendly or SavvyCal. All that you need to import your data is an access token, which you can retrieve from your Calendly/SavvyCal account. - -Once you've pasted your access token, we import your account data in less than a second. - -Naturally, we take security very seriously when it comes to importing your data from other accounts, so that's why we never store your access token, and use it once to query the Calendly/SavvyCal APIs, populate your account with the data it returns, and then your key is destroyed from memory. All of the importer code can be [freely viewed on GitHub](https://github.com/calcom/cal.com/tree/main/pages/api/import), so you can be assured we're not using your data for malicious purposes. - -## Calendly -The following steps will help you retrieve your Calendly access token, which you will need to present at the import screen of the onboarding process. -1. Go to the Calendly website and click on **My Account** in the top right -2. Now click on **Integrations** in the top right -3. Scroll to the bottom and click on **API & Webhooks** -4. Click the blue **Generate new token** button and type in anything you'd like as the app name -5. Press **Copy token** and then paste it into the Cal.com importer - - Even though we don't store your access token, you can press **Revoke** to destroy the access token from the **API & Webhooks** page once the import is complete. - - -## SavvyCal -The following steps will help you retrieve your SavvyCal access token, which you will need to present at the import screen of the onboarding process. -1. Head to the SavvyCal website and click **Settings** in the sidebar -2. Click the **Developers** tab, and under **Personal access tokens**, click the blue **Create a token** link -3. Give the token any name you'd like, and then confirm -4. Click to copy the token, and then paste the token into the Cal.com importer - - Even though we don't store your access token, you can press the trash icon to revoke the access token from the **Developers** tab once the import is complete. - diff --git a/apps/docs/pages/index.mdx b/apps/docs/pages/index.mdx deleted file mode 100644 index 94b8d51ebb..0000000000 --- a/apps/docs/pages/index.mdx +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: Home ---- - -import Bleed from "nextra-theme-docs/bleed"; - -# Cal.com Documentation - -Welcome to our product documentation, where you can explore advice and explanations for all of our features, as well as discover new tips and tricks to get the most out of your subscription. - -This is also the home of our design system documentation and developer docs. - -If you don't already know what Cal.com is about, please head over to [our website](https://cal.com), where you can learn more about the product before venturing into the documentation. - -Want to help make these docs even better? This site is fully open source, and the source code is available on [GitHub](https://github.com/calcom/cal.com/tree/main/apps/docs). You can also click the edit button at the bottom of any page to start editing the source code and start a pull request. - -![Telemedicine Example](https://cal.com/telemedicine-example.svg) diff --git a/apps/docs/pages/integrations/embed.mdx b/apps/docs/pages/integrations/embed.mdx deleted file mode 100644 index 7a876d8b45..0000000000 --- a/apps/docs/pages/integrations/embed.mdx +++ /dev/null @@ -1,262 +0,0 @@ ---- -title: Embed ---- - -import Anchor from "../../components/Anchor" - -# Embed - -The Embed allows your website visitors to book a meeting with you directly from your website. - -## Install on any website - -Install the following Vanilla JS Snippet to get embed to work on any website. After that you can choose any of the ways to show your Cal Link embedded on your website. - -```html - -``` - -## Install with a Framework - -### embed-react - -It provides a react component `` that can be used to show the embed inline at that place. - -```bash -yarn add @calcom/embed-react -``` - -### Any XYZ Framework - -You can use Vanilla JS Snippet to install - -Popular ways in which you can embed on your website - -Assuming that you have followed the steps of installing and initializing the snippet, you can show the embed in following ways: - -### Inline - -Show the embed inline inside a container element. It would take the width and height of the container element. - -
- _Vanilla JS_ - -```html - - -*Sample sandbox* -``` - -
- -#### - -
-_React_ - -```jsx -import Cal from "@calcom/embed-react"; - -const MyComponent = () => ( - -); -``` - -*Sample sandbox* - - -
- -### Popup on any existing element - -To show the embed as a popup on clicking an element, add `data-cal-link` attribute to the element. - -
- -Vanilla JS - -To show the embed as a popup on clicking an element, simply add `data-cal-link` attribute to the element. - -*Sample sandbox* - - - -
-
- React - ```jsx - import "@calcom/embed-react"; - - const MyComponent = ()=> { - return - } -``` - -*Sample sandbox* - - -
- -### Floating pop-up button - -```html - -``` - -*Sample sandbox* - - -## Supported Instructions - -Consider an instruction as a function with that name and that would be called with the given arguments. - -### `inline` - -Appends embed inline as the child of the element. - -```html - -```` - -- `elementOrSelector` - Give it either a valid CSS selector or an HTMLElement instance directly - -- `calLink` - Cal Link that you want to embed e.g. john. Just give the username. No need to give the full URL [https://cal.com/john](). It makes it easy to configure the calendar host once and use as many links you want with just usernames - -### `ui` - -Configure UI for embed. Make it look part of your webpage. - -```html - -``` - -- `styles` - It supports styling for `body` and `eventTypeListItem`. Right now we support just background on these two. - -### preload - -Usage: - -If you want to open cal link on some action. Make it pop open instantly by preloading it. - -```html - -``` - -- `calLink` - Cal Link that you want to embed e.g. john. Just give the username. No need to give the full URL [https://cal.com/john]() - -## Actions -You can listen to an action that occurs in embedded cal link as follows. You can think of them as DOM events. We are avoiding the term "events" to not confuse it with Cal Events. -```html - -``` - -Following are the list of supported actions. -- -| Action | Description | Properties | -|----------------------|------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| eventTypeSelected | When user chooses an event-type from the listing. | eventType:object // Event Type that has been selected" | -| bookingSuccessful | When the booking is successfully done. It might not be confirmed. | confirmed: boolean; //Whether confirmation from organizer is pending or not

eventType: "Object for Event Type that has been booked";

date: string; // Date of Event

duration: number; //Duration of booked Event

organizer: object //Organizer details like name, timezone, email | -| linkReady | Tells that the link is ready to be shown now. | None | -| linkFailed | Fired if link fails to load | code: number; // Error Code

msg: string; //Human Readable msg

data: object // More details to debug the error | -| __iframeReady | It is fired when the embedded iframe is ready to communicate with parent snippet. This is mostly for internal use by Embed Snippet | None | -| __windowLoadComplete | Tells that window load for iframe is complete | None | -| __dimensionChanged | Tells that dimensions of the content inside the iframe changed. | iframeWidth:number, iframeHeight:number | - -_Actions that start with __ are internal._ \ No newline at end of file diff --git a/apps/docs/pages/integrations/google.mdx b/apps/docs/pages/integrations/google.mdx deleted file mode 100644 index 06de91189a..0000000000 --- a/apps/docs/pages/integrations/google.mdx +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: Google ---- - -# Google Calendar -The Google Calendar integration checks for availability in your Google Calendars and creates bookings for you. - -## Removing Permissions for Cal to access your Google Account - -1. Go to your [Google Account](https://myaccount.google.com/). -2. On the left, click Data & privacy. -3. Scroll to "Data from apps and services you use." -4. Under "Download or delete your data," click Delete a Google service. You may need to sign in again. -5. Next to the product you want to remove, click Delete. -6. Follow the steps on the screen. - -To remove a product from your account that isn't listed in your Google Account, visit [Google Support](https://support.google.com) for more info on a specific product. - -## Obtaining the Google API Credentials -1. Open [Google API Console](https://console.cloud.google.com/apis/dashboard). If you don't have a project in your Google Cloud subscription, you'll need to create one before proceeding further. Under Dashboard pane, select Enable APIS and Services. -2. In the search box, type calendar and select the Google Calendar API search result. -3. Enable the selected API. -4. Next, go to the [OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent) from the side pane. Select the app type (Internal or External) and enter the basic app details on the first page. -5. In the second page on Scopes, select Add or Remove Scopes. Search for Calendar.event and select the scope with scope value `.../auth/calendar.events`, `.../auth/calendar.readonly`, `.../auth/calendar` and select Update. -6. In the third page (Test Users), add the Google account(s) you'll using. Make sure the details are correct on the last page of the wizard and your consent screen will be configured. -7. Now select [Credentials](https://console.cloud.google.com/apis/credentials) from the side pane and then select Create Credentials. Select the OAuth Client ID option. -8. Select Web Application as the Application Type. -9. Under Authorized redirect URI's, select Add URI and then add the two URIs `/api/integrations/googlecalendar/callback` and `/api/auth/callback/google` replacing CALENDSO URL with the URI at which your application runs. -10. The key will be created and you will be redirected back to the Credentials page. Select the newly generated client ID under OAuth 2.0 Client IDs. -11. Select Download JSON. Copy the contents of this file and paste the entire JSON string in the .env file as the value for GOOGLE_API_CREDENTIALS key. - -## Where to find the Google Meet integration? - -Google Meet is a part of the Google Calendar integration and it should be available once you've added your Google Calendar. Just select Google Meet as location for your Event Type: - -1. Go to your `Event Types`. -2. Click on the `Location` drop-down menu. -3. Select Google Meet as the location of your meeting. - -Once your Event Type slot is booked, it will automatically generate the Google Meet link for the meeting. diff --git a/apps/docs/pages/integrations/introduction.mdx b/apps/docs/pages/integrations/introduction.mdx deleted file mode 100644 index fc3af40f72..0000000000 --- a/apps/docs/pages/integrations/introduction.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Introduction ---- - -# Integrations - -## Connecting new calendars -1. Go to the [Cal App Store](https://app.cal.com/apps). -2. Located at the top right of the screen, press the button saying '+ Connect A New App' -3. Choose the account your calendar is connected too by clicking 'Add'. (e.g. Google, Office 365, Zoom) -4. You will be redirected to the log in page of the chosen account. -5. Allow Cal access to view and edit your calendars. -6. You will be sent back to the [Cal App Store](https://app.cal.com/apps/installed). From here you will now be able to see your connected calendar! - -## How to choose the primary Calendar? - -If you have two or more integrated calendars and you want your events to show in only one, you can define a primary calendar like this: - -1. Go to your [Installed](https://app.cal.com/apps/installed) page. -2. Next to your `Calendars` you will see a dropdown that says `Create events on:`. -3. Select your primary calendar. diff --git a/apps/docs/pages/integrations/meta.json b/apps/docs/pages/integrations/meta.json deleted file mode 100644 index 7b74194351..0000000000 --- a/apps/docs/pages/integrations/meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "introduction": "Introduction", - "google": "Google", - "microsoft": "Microsoft", - "zoom": "Zoom", - "stripe": "Stripe", - "zapier": "Zapier" - } - \ No newline at end of file diff --git a/apps/docs/pages/integrations/microsoft.mdx b/apps/docs/pages/integrations/microsoft.mdx deleted file mode 100644 index afdfeaf118..0000000000 --- a/apps/docs/pages/integrations/microsoft.mdx +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Microsoft ---- - -# Outlook/Microsoft 365 -The Outlook integration enables you to use your outlook.com or Microsoft 365 account to use for conflict checking and event bookings. - -## Obtaining Microsoft Graph Client ID and Secret -1. Open [Azure App Registration](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps) and select New registration -2. Name your application -3. Set **Who can use this application or access this API?** to **Accounts in any organizational directory (Any Azure AD directory - Multitenant)** -4. Set the **Web** redirect URI to `/api/integrations/office365calendar/callback` replacing CALENDSO URL with the URI at which your application runs. -5. Use **Application (client) ID** as the **MS_GRAPH_CLIENT_ID** attribute value in .env -6. Click **Certificates & secrets** create a new client secret and use the value as the **MS_GRAPH_CLIENT_SECRET** attribute - -## Removing Permissions for Cal to access your Microsoft Workplace Account - -Hover over Cal.com in the my apps portal, then select `manage your application`. - -The top part of permissions window shows what you personally consented to. Examples of apps permissions include the ability to access your calendar, contacts, or camera. - -You can revoke any of the permissions you consented to by selecting `Revoke Permissions`, however removing a permission may break some of the apps functionality. If you have problems after you remove permissions or accounts, contact your organization's Helpdesk for additional assistance. - -If you require more help, head over the Microsoft Documentation Page about [Managing Applications](https://docs.microsoft.com/en-us/azure/active-directory/user-help/my-applications-portal-permissions-saved-accounts) diff --git a/apps/docs/pages/integrations/slack.mdx b/apps/docs/pages/integrations/slack.mdx deleted file mode 100644 index 441a094674..0000000000 --- a/apps/docs/pages/integrations/slack.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -Title: Slack ---- - -# Slack - -## Connecting -Connecting the bot is easy - If you are a workspace admin, the install button will add the bot to the workspace and also authorize your account with the bot. If you are a normal user, the install button will connect your Slack account with Cal.com. This will allow you to perform commands in Slack. - -## Commands -`/today` - This command will display all meetings you have in your Cal.com profile for the current day. This will send a hidden message (not visible to anyone other than you) to the channel you issued the command in. - -`/create-event` - It will display a modal allowing you to simply create a meeting invite with anyone in Slack. Success/Error information will be displayed in a private direct message from the bot. - -`/links` - This command will post all your Cal.com meeting links into the current Slack channel you are in. **Note**: The bot needs to have permission to talk in the channel you are sending the message in. Otherwise, you won't be able to send your links. - -As this is the beggining stage of our Slack integration, we plan on adding more commands in the future that will further improve your Cal.com experience. - -## Self-Hosted -If you are using our self-hosted version, please refer to our documentation in -[cal.com/README.md](https://github.com/calcom/cal.com/blob/main/README.md#obtaining-slack-client-id-and-secret-and-signing-secret) diff --git a/apps/docs/pages/integrations/stripe.mdx b/apps/docs/pages/integrations/stripe.mdx deleted file mode 100644 index bb563abd86..0000000000 --- a/apps/docs/pages/integrations/stripe.mdx +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Stripe ---- - -# Stripe Payments - -The Stripe integration allows users to add payments to their bookings. - -## Adding the stripe integration - -1. Go to the [Cal App Store](https://app.cal.com/integrations). -2. Located near the top of this page will be the integration 'Stripe' and located to the right will be a button called 'Connect', press this. -3. You will be redirected to Stripe requesting for Cal.com to be authorized access. Press 'Accept'. - -This stripe integration has now successfully been added. - -## Removing the stripe integration - -1. Go to the [Cal App Store](https://app.cal.com/integrations). -2. Located near the top of this page will be the integration 'Stripe' and located to the right will be a button called 'Disconnect', press this. -3. A confirmation pop up box will appear, to confirm press the button saying 'Yes, disconnect integration'. - -This stripe integration has now successfully been disconnected. diff --git a/apps/docs/pages/integrations/zapier.mdx b/apps/docs/pages/integrations/zapier.mdx deleted file mode 100644 index f1cb8c9c0b..0000000000 --- a/apps/docs/pages/integrations/zapier.mdx +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Zapier ---- - -## Do you have a Zapier integration? - -We are currently working on it, but it isn’t live just yet. Until then, you can use our Webhooks integration and use Zapier's “Webhooks by Zapier”. diff --git a/apps/docs/pages/integrations/zoom.mdx b/apps/docs/pages/integrations/zoom.mdx deleted file mode 100644 index 94fd02e6b1..0000000000 --- a/apps/docs/pages/integrations/zoom.mdx +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Zoom -sidebar_position: 3 ---- - -# Zoom - -The Zoom integration automatically creates Zoom meetings for your bookings. - -## Connecting your Zoom account - -1. Go to the App Store page, and click the 'Add new integration' button. -2. Next to Zoom, go ahead and click the 'Add' button. -3. You will now be taken to Zoom to sign into your account and authorize Cal. - -## Disconnecting your Zoom account - -1. Go to the App Store page, and click on your Zoom integration in the list. -2. On the right hand side, click the Delete App button. - -This will remove the integration from Cal. Cal will not perform any actions on your account once the integration is removed. However, if you want to revoke Cal's permissions from your Zoom account, perform the following steps: - -1. Log into your Zoom account and navigate to the App Marketplace -2. Click Manage > Installed Apps or search for the Cal app -3. Click on the Cal app -4. Click Uninstall - -## How we interact with your Zoom account - -We only need the ability to create meetings, so when somebody books an event with you, Cal can communicate with Zoom and create the corresponding meeting. - -## Setting up Zoom on your self-hosted instance - -1. Open [Zoom Marketplace](https://marketplace.zoom.us/) and sign in with your Zoom account. -2. On the upper right, click "Develop" => "Build App". -3. On "OAuth", select "Create". -4. Name your App. -5. Choose "User-managed app" as the app type. -6. De-select the option to publish the app on the Zoom App Marketplace. -7. Click "Create". -8. Now copy the Client ID and Client Secret to your .env file into the `ZOOM_CLIENT_ID` and `ZOOM_CLIENT_SECRET` fields. -9. Set the Redirect URL for OAuth `/api/integrations/zoomvideo/callback` replacing CALENDSO URL with the URI at which your application runs. -10. Also add the redirect URL given above as an allowlist URL and enable "Subdomain check". Make sure it says "saved" below the form. -11. You don't need to provide basic information about your app. Instead click at "Scopes" and then at "+ Add Scopes". On the left, click the category "Meeting" and check the scope `meeting:write`. -12. Click "Done". -13. You're good to go. Now you can easily add your Zoom integration in the Cal settings. diff --git a/apps/docs/pages/meta.json b/apps/docs/pages/meta.json deleted file mode 100644 index 4d37384fa7..0000000000 --- a/apps/docs/pages/meta.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "index": "Home", - "self-hosting": "Self-hosting", - "availability": "Availability", - "bookings": "Bookings", - "event-types": "Event Types", - "teams": "Teams", - "integrations": "Integrations", - "public-api": "API", - "webhooks": "Webhooks", - "settings": "Settings", - "import": "Import", - "billing": "Billing", - "developer": "Developer", - "contributing": "Contributing", - "faq": "FAQs" -} diff --git a/apps/docs/pages/public-api.mdx b/apps/docs/pages/public-api.mdx deleted file mode 100644 index a5a2fa62a4..0000000000 --- a/apps/docs/pages/public-api.mdx +++ /dev/null @@ -1,20 +0,0 @@ -import Bleed from 'nextra-theme-docs/bleed' -import Head from "next/head"; -import IframeResizer from "iframe-resizer-react"; -import {useWindowSize} from "../lib/useWindowSize"; - - - - Public API | Cal.com - 768 ? "calc(100vw - 16rem)": "100vw", - minHeight: useWindowSize().width > 768 ? "100vh" : "200vh", - height: "auto", - border: 0, - }} - /> - diff --git a/apps/docs/pages/self-hosting/docker.mdx b/apps/docs/pages/self-hosting/docker.mdx deleted file mode 100644 index 068ed8be87..0000000000 --- a/apps/docs/pages/self-hosting/docker.mdx +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: Docker ---- - -# Docker - -The Docker configuration for Cal is an effort powered by people within the community. Cal does not provide official support for Docker, but we will accept fixes and documentation. Use at your own risk. - -The Docker configuration can be found [in our docker repository](https://github.com/calcom/docker). - -## Requirements - -Make sure you have `docker` & `docker-compose` installed on the server / system. - -## Getting Started - -### Option #1: Docker Hub - - ```bash - docker pull calendso/calendso - ``` - -or - -### Option #2: Cloning - -1. Clone calendso-docker - - ```bash - git clone --recursive https://github.com/calendso/docker.git calendso-docker - ``` - -2. Update `.env` if needed - -3. Build and start calendso - - ```bash - docker-compose up --build - ``` - -4. Start prisma studio - ```bash - docker-compose exec calendso npx prisma studio - ``` -5. Open a browser to [port 5555](http://localhost:5555) on your localhost to look at or modify the database content. - -6. Click on the `User` model to add a new user record. -7. Fill out the fields (remembering to encrypt your password with [BCrypt](https://bcrypt-generator.com/)) and click `Save 1 Record` to create your first user. -8. Open a browser to [port 3000](http://localhost:3000) on your localhost and login with your just created, first user. diff --git a/apps/docs/pages/self-hosting/install.mdx b/apps/docs/pages/self-hosting/install.mdx deleted file mode 100644 index 93e5cac0bf..0000000000 --- a/apps/docs/pages/self-hosting/install.mdx +++ /dev/null @@ -1,128 +0,0 @@ ---- -title: Installation ---- - -# Installation - -To get a local copy up and running, please follow these simple steps. - -## Prerequisites - -Here is what you need to be able to run Cal. - -- Node.js -- PostgreSQL -- Yarn _(recommended)_ - -> If you want to enable any of the available integrations, you may want to obtain additional credentials for each one. More details on this can be found below under the integrations category. - -## Setup - -1. Clone the repo - - ```sh - git clone https://github.com/calcom/cal.com.git - ``` - -1. Go to the project folder - - ```sh - cd cal.com - ``` - -1. Copy `.env.example` to `.env` - - ```sh - cp apps/web/.env.example apps/web/.env - ``` - -1. Install packages with yarn - - ```sh - yarn - ``` - -### Quick start with `yarn dx` - -> - **Requires Docker and Docker Compose to be installed** -> - Will start a local Postgres instance with a few test users - the credentials will be logged in the console - -```sh -yarn dx -``` - -### Manual setup - -1. Configure database in the `.env` file. Replace ``, ``, ``, `` with their applicable values - - ```text - DATABASE_URL='postgresql://:@:' - ``` - -
- - - If you don't know how to configure the DATABASE_URL, then follow the steps here to create a quick DB - using Heroku - - - 1. Create a free account with [Heroku](https://www.heroku.com/). - - 2. Create a new app. - - Create an App - - 3. In your new app, go to `Overview` and next to `Installed add-ons`, click `Configure Add-ons`. We need this to set up our database. - ![image](https://user-images.githubusercontent.com/16905768/115323232-a53ba480-a17f-11eb-98db-58e2f8c52426.png) - - 4. Once you clicked on `Configure Add-ons`, click on `Find more add-ons` and search for `postgres`. One of the options will be `Heroku Postgres` - click on that option. - ![image](https://user-images.githubusercontent.com/16905768/115323126-5beb5500-a17f-11eb-8030-7380310807a9.png) - - 5. Once the pop-up appears, click `Submit Order Form` - plan name should be `Hobby Dev - Free`. - - Submit Order Form - - 6. Once you completed the above steps, click on your newly created `Heroku Postgres` and go to its `Settings`. - ![image](https://user-images.githubusercontent.com/16905768/115323367-e92ea980-a17f-11eb-9ff4-dec95f2ec349.png) - - 7. In `Settings`, copy your URI to your Cal.com .env file and replace the `postgresql://:@:` with it. - ![image](https://user-images.githubusercontent.com/16905768/115323556-4591c900-a180-11eb-9808-2f55d2aa3995.png) - ![image](https://user-images.githubusercontent.com/16905768/115323697-7a9e1b80-a180-11eb-9f08-a742b1037f90.png) - - 8. To view your DB, once you add new data in Prisma, you can use [Heroku Data Explorer](https://heroku-data-explorer.herokuapp.com/). - -
- -1. Set a 32 character random string in your `apps/web/.env` file for the `CALENDSO_ENCRYPTION_KEY` (You can use a command like `openssl rand -base64 24` to generate one). -1. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`) - - ```sh - yarn workspace @calcom/prisma db-deploy - ``` - -1. Run (in development mode) - - ```sh - yarn dev - ``` - -### Setting up your first user - -1. Open [Prisma Studio](https://www.prisma.io/studio) to look at or modify the database content: - - ```sh - yarn db-studio - ``` - -1. Click on the `User` model to add a new user record. -1. Fill out the fields `email`, `username`, `password`, and set `metadata` to empty `{}` (remembering to encrypt your password with [BCrypt](https://bcrypt-generator.com/)) and click `Save 1 Record` to create your first user. - > New users are set on a `TRIAL` plan by default. You might want to adjust this behavior to your needs in the `prisma/schema.prisma` file. -1. Open a browser to [port 3000](http://localhost:3000) and login with your just created, first user. diff --git a/apps/docs/pages/self-hosting/meta.json b/apps/docs/pages/self-hosting/meta.json deleted file mode 100644 index 9bd1650cfb..0000000000 --- a/apps/docs/pages/self-hosting/meta.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "install": "Installation", - "upgrade": "Upgrade", - "docker": "Docker", - "vercel": "Vercel" -} diff --git a/apps/docs/pages/self-hosting/upgrade.mdx b/apps/docs/pages/self-hosting/upgrade.mdx deleted file mode 100644 index f22431fa01..0000000000 --- a/apps/docs/pages/self-hosting/upgrade.mdx +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: Upgrade ---- - -# Upgrading - -**Warning**: When performing database migrations, you may lose data if the migration is not done properly. - -1. Pull the current version: - ```sh - git pull - ``` -2. Apply database migrations by running one of the following commands: - - In a development environment, run: - - ```sh - yarn workspace @calcom/prisma db-migrate - ``` - - (this can clear your development database in some cases) - - In a production environment, run: - - ```sh - yarn workspace @calcom/prisma db-deploy - ``` - -3. Check the `.env.example` and compare it to your current `.env` file. In case there are any fields not present - in your current `.env`, add them there. - - For the current version, especially check if the variable `BASE_URL` is present and properly set in your environment, for example: - - ```text - BASE_URL='https://yourdomain.com' - ``` - -4. Start the server. In a development environment, just do: - ```sh - yarn dev - ``` - For a production build, run for example: - ```sh - yarn build - yarn start - ``` -5. Enjoy the new version. diff --git a/apps/docs/pages/self-hosting/vercel.mdx b/apps/docs/pages/self-hosting/vercel.mdx deleted file mode 100644 index 2dc4b97bb4..0000000000 --- a/apps/docs/pages/self-hosting/vercel.mdx +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Vercel ---- - -# Vercel - -## Requirements - -You need a PostgresDB database hosted somewhere. [Supabase](https://supabase.com/) offers a great free option. - -## Getting Started - -### Local settings - -1. Fork and clone the repository - - ```bash - git clone https://github.com//cal.com.git - ``` - -2. Copy the `.env.example` file in `apps/web`, rename it to `.env` and fill it with your settings ([See manual setup](https://github.com/calcom/cal.com#manual-setup) and [Obtaining the Google API Credentials](https://github.com/calcom/cal.com#obtaining-the-google-api-credentials)) - -3. Install packages with `yarn` - - ```bash - yarn install - ``` - -4. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`) - - ```sh - yarn workspace @calcom/prisma db-deploy - ``` - -5. Open [Prisma Studio](https://www.prisma.io/studio) to look at or modify the database content: - ``` - yarn db-studio - ``` -6. Click on the `User` model to add a new user record. -7. Fill out the fields (remembering to encrypt your password with [BCrypt](https://bcrypt-generator.com/)) and click `Save 1 Record` to create your first user. -8. Open a browser to [port 3000](http://localhost:3000) on your localhost and login with your just created, first user. - -### Deployment - -1. Create a new project on Vercel -1. Import from your forked repository -1. Set the Environment Variables -1. Set the root directory to `apps/web` -1. Override the build command to `cd ../.. && yarn build` -1. Hit Deploy diff --git a/apps/docs/pages/settings.mdx b/apps/docs/pages/settings.mdx deleted file mode 100644 index 83f3ca3180..0000000000 --- a/apps/docs/pages/settings.mdx +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: Settings ---- - -# Settings -## Setting up or making changes to your Profile - -1. Go to your [Profile Settings](https://app.cal.com/settings/profile). -2. From this page you are able to edit the following: -- Your URL Username -- Your Display Name -- Your About Information -- Change your Profile Picture -- Your Local Time Zone -- Your Chosen 'First Day Of The Week' -- Light or Dark Mode for your Booking Page -- Enabling or Disabling cal.com Branding on your Booking Page -3. Once you have completed all your changes, press that 'save' button located at the bottom right of your page. - -## Reset your password - -1. Go to [your account security settings](https://app.cal.com/settings/security). -2. Located at the top of your screen, you will see two boxes. -3. Enter your Old Password and New Password. -4. Click the button 'Save' located to the bottom right of your new password. -5. You have now successfully changed your password! - -## Change your email - -Go to [Profile Settings](https://app.cal.com/settings/profile). There, you will see the email associated with your account which you can then update. You’d just need to log out and back in to see the change take effect. - -## Enable 2FA - -1. Go to [Your Account Security Settings](https://app.cal.com/settings/security). -2. Located at the bottom of your screen, click on the tab labeled 'Enable Two-Factor Authentication'. -3. Enter your Password. -4. In your authenticator app, scan the QR Code presented on your screen. -5. Once Scanned, press 'Next' and then enter the code presented within your Authentication App -6. Press 'Enabled' - -You have now successfully enabled Two-Factor Authentication - -## Disable 2FA -1. Go to [Your Account Security Settings](https://app.cal.com/settings/security). -2. Located at the bottom of your screen, click on the tab labeled 'Disable Two-Factor Authentication'. -3. Enter your Password. - -You have now successfully disabled Two-Factor Authentication - -## How to delete my account - -You can delete your account from within the [Settings](https://app.cal.com/settings/profile) option. Just scroll all the way down and click on `Delete Account`. - -## How to change the language - -Go to your [Profile Settings](https://app.cal.com/settings/profile). Under `Language` you will see the dropdown menu and you can use it to select your desired language. diff --git a/apps/docs/pages/style.css b/apps/docs/pages/style.css deleted file mode 100644 index 55e436f61a..0000000000 --- a/apps/docs/pages/style.css +++ /dev/null @@ -1,15 +0,0 @@ -@font-face { - font-family: 'Cal Sans'; - src: url("https://cal.com/cal.ttf"); -} - -h1, -h2, -h3, -h4, -h5, -h6 { - font-family: 'Cal Sans'; - font-weight: normal; - letter-spacing: normal; -} diff --git a/apps/docs/pages/teams.mdx b/apps/docs/pages/teams.mdx deleted file mode 100644 index 3428294faa..0000000000 --- a/apps/docs/pages/teams.mdx +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: Teams ---- - -# Teams - -## How do I create a new team? - -1. Go to [Your Teams Settings](https://app.cal.com/settings/teams). -2. Press the button displayed on your screen called '+ New Team' -3. Enter the name you wish to call your team. -4. After entering the name press the 'Create Team' button located just under your new team name. -5. You have now created your new team. - -Creating a team will allow you to create new event types for the team, invite team members and much more! - -## How do I change my teams URL? - -1. Go to [Your Teams Settings](https://app.cal.com/settings/teams) and select the team you wish to edit. -2. Located at the top of your teams settings will be a team-url bar, from here you can input your requested teams url. -3. Once complete make sure you press 'save' at the bottom of the page. - -## How do I change my teams name? - -1. Go to [Your Teams Settings](https://app.cal.com/settings/teams) and select the team you wish to edit. -2. Located in the 2nd row of your teams settings will be a Team Name bar, from here you can input your requested team name. -3. Once complete make sure you press 'save' at the bottom of the page. - -## How do I add and remove a description of my team? - -1. Go to [Your Teams Settings](https://app.cal.com/settings/teams) and select the team you wish to edit. -2. Located below your team name entry box is a large text box labeled 'About', - -## How do I upload my team logo? - -1. Go to [Your Teams Settings](https://app.cal.com/settings/teams) and select the team you wish to edit. -2. Located under your teams subscription will be a box that says 'Upload a logo', press this box. -3. Press 'choose a file' and locate a file from your machine you wish to use as your team logo. -4. Once complete make sure you press 'save' at the bottom of the page. - -## How do I manage team members? - -1. Go to [Your Teams Settings](https://app.cal.com/settings/teams) and select the team you wish to edit. -2. Located at the bottom of your page is your team members, from here you can choose to add or remove team members and even change their role which could be Member, Admin or Owner. - -## How do I delete my team I created? - -1. Go to [Your Teams Settings](https://app.cal.com/settings/teams). -2. From here you will be able to see a list of teams you have created or apart of, located to the right of the team you wish to delete you can find three dots (...) -3. Press the 3 dots (...) then 'Disband Team' -4. This will brin gup a prompt confirming if you wish to disband your team, if your are sure you can press the button labeled 'Yes, disband team' - -Your team has now successfully been deleted. - -## Where can I find my team's Event Types? - -Once you open `Event Types` on your dashboard, you will find your team's Event Types below your individual ones. diff --git a/apps/docs/pages/webhooks.mdx b/apps/docs/pages/webhooks.mdx deleted file mode 100644 index 24659a2e6c..0000000000 --- a/apps/docs/pages/webhooks.mdx +++ /dev/null @@ -1,98 +0,0 @@ ---- -title: Webhooks ---- - -# Webhooks - -## Create a new Webhook - -1. Go to [Your Integrations](https://app.cal.com/integrations). -2. Scrolling down to the bottom of the page you will see a button called ‘New Webhook’. Press this button to open up the box which will ask for details on creating the new webhooks. -3. Select whatever event this triggers, this may be Create, Cancelled or Reschedule. -4. Once this is completed, insert the Subscriber URL then click 'Save' located at the bottom right of the box. - -## Edit an existing Webhook - -1. Go to [Your Integrations](https://app.cal.com/integrations). -2. Scrolling down to the bottom of the page you will see an icon to the right of your webhook, this will be labeled edit webhook. -3. Press the button and from here you can change any of the webhook settings. This could be changing the event that is triggered and this may be Create, Cancelled or Reschedule. Or you can change the Subscriber URL. - -## Delete an existing Webhook - -1. Go to [Your Integrations](https://app.cal.com/integrations). -2. Scrolling down to the bottom of the page you will see an icon to the right of your webhook, this will be labeled delete webhook. -3. Press the button and from here your webhook will no longer work and be deleted. - -## Webhook metadata - -Metadata is a way to pass extra information to Cal.com about a booking that is returned through a webhook. - -### Example - -The best way to explain this is with an example. Let's say you're a bank, and people register an account on your website, but you want them to book an onboarding call with your team to get set up. You could send them to your Cal.com booking link as part of your onboarding process, but when the webhook is returned, it may be difficult to match up which user booked a meeting with the user's account in your own database. Hence, you can pass a `user_id` value for instance as a URL parameter, which makes no difference to the booking process, but when the webhook is returned, you will receive the metadata as part of the webhook payload. - -Metadata is passed as a URL parameter on top of your booking link and follows the following syntax: - -```text -metadata[key_name]=value -``` - -For example, if your booking link is `cal.com/rick/quick-chat`, you can pass a user ID of 123 like so: - -```text -cal.com/rick/quick-chat?metadata[user_id]=123 -``` - -As a result, the webhook will be returned in this format: - -```text -\{ , metadata: \{ user_id: 123 \} \} -``` - -## Custom Webhooks template variable list - -Customizable webhooks are a great way reduce the development effort and in many cases remove the need for a developer to build an additional integration service. Using a custom template you can easily decide what data you receive in your webhook endpoint, manage the payload and setup related workflows accordingly. Here’s a breakdown of the payload that you would receive via an incoming webhook. - -### Webhook structure - -| Variable | Type | Description | -| ------------------- | -------- | -------------------------------------------------------------------------------------- | -| triggerEvent | String | The name of the trigger event [BOOKING_CREATED, BOOKING_RESHEDULED, BOOKING_CANCELLED] | -| createdAt | String | The time of the webhook trigger | -| type | String | The event-type slug | -| title | String | The event-type name | -| startTime | String | The event's start time | -| endTime | String | The event's end time | -| description? | String | The event's description as described in the event type | -| location? | String | Location of the event | -| organizer | Person | The organizer of the event | -| attendees | Person[] | The event booker & any guests | -| uid? | String | The UID of the booking | -| resheduleUid? | String | The UID for the rescheduling | -| cancellationReason? | String | Reason for cancellation | -| rejectionReason? | String | Reason for rejection | -| team?.name | String | Name of the team booked | -| team?.members | String[] | Members of the team booked | - -### Person structure - -| Variable | Type | Description | -| --------------- | ------ | --------------------------------------------------------------------- | -| name | String | Name of the individual | -| email | String | Email of the individual | -| timeZone | String | Timezone of the individual ("America/New_York", "Asia/Kolkata", etc.) | -| language.locale | String | Locale of the individual ("en", "fr", etc.) | - -### Example usage of variables for custom template: - -```sh -\{ - - "content": "A new event has been scheduled", - "type": "\{\{type\}\}", - "name": "\{\{title\}\}", - "organizer": "\{\{organizer.name\}\}", - "booker": "\{\{attendees.0.name\}\}" - -\} -``` diff --git a/apps/docs/public/android-chrome-192x192.png b/apps/docs/public/android-chrome-192x192.png deleted file mode 100644 index 395bfe3c60..0000000000 Binary files a/apps/docs/public/android-chrome-192x192.png and /dev/null differ diff --git a/apps/docs/public/android-chrome-256x256.png b/apps/docs/public/android-chrome-256x256.png deleted file mode 100644 index 83b5eb0ded..0000000000 Binary files a/apps/docs/public/android-chrome-256x256.png and /dev/null differ diff --git a/apps/docs/public/android-chrome-384x384.png b/apps/docs/public/android-chrome-384x384.png deleted file mode 100644 index 12d4935429..0000000000 Binary files a/apps/docs/public/android-chrome-384x384.png and /dev/null differ diff --git a/apps/docs/public/android-chrome-512x512.png b/apps/docs/public/android-chrome-512x512.png deleted file mode 100644 index f8f9a4852c..0000000000 Binary files a/apps/docs/public/android-chrome-512x512.png and /dev/null differ diff --git a/apps/docs/public/apple-touch-icon.png b/apps/docs/public/apple-touch-icon.png deleted file mode 100644 index ffa076315f..0000000000 Binary files a/apps/docs/public/apple-touch-icon.png and /dev/null differ diff --git a/apps/docs/public/browserconfig.xml b/apps/docs/public/browserconfig.xml deleted file mode 100644 index 3c58a03a73..0000000000 --- a/apps/docs/public/browserconfig.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - #292929 - - - diff --git a/apps/docs/public/cal.ttf b/apps/docs/public/cal.ttf deleted file mode 100644 index 4a2950a045..0000000000 Binary files a/apps/docs/public/cal.ttf and /dev/null differ diff --git a/apps/docs/public/demo.png b/apps/docs/public/demo.png deleted file mode 100644 index 3751514339..0000000000 Binary files a/apps/docs/public/demo.png and /dev/null differ diff --git a/apps/docs/public/favicon-16x16.png b/apps/docs/public/favicon-16x16.png deleted file mode 100644 index 92717c306b..0000000000 Binary files a/apps/docs/public/favicon-16x16.png and /dev/null differ diff --git a/apps/docs/public/favicon-32x32.png b/apps/docs/public/favicon-32x32.png deleted file mode 100644 index 5846956222..0000000000 Binary files a/apps/docs/public/favicon-32x32.png and /dev/null differ diff --git a/apps/docs/public/favicon.ico b/apps/docs/public/favicon.ico deleted file mode 100644 index 0760f8a241..0000000000 Binary files a/apps/docs/public/favicon.ico and /dev/null differ diff --git a/apps/docs/public/mstile-144x144.png b/apps/docs/public/mstile-144x144.png deleted file mode 100644 index 62f0fc9790..0000000000 Binary files a/apps/docs/public/mstile-144x144.png and /dev/null differ diff --git a/apps/docs/public/mstile-150x150.png b/apps/docs/public/mstile-150x150.png deleted file mode 100644 index f31d64926b..0000000000 Binary files a/apps/docs/public/mstile-150x150.png and /dev/null differ diff --git a/apps/docs/public/mstile-310x150.png b/apps/docs/public/mstile-310x150.png deleted file mode 100644 index 30025ecd6a..0000000000 Binary files a/apps/docs/public/mstile-310x150.png and /dev/null differ diff --git a/apps/docs/public/mstile-310x310.png b/apps/docs/public/mstile-310x310.png deleted file mode 100644 index 877f8974f1..0000000000 Binary files a/apps/docs/public/mstile-310x310.png and /dev/null differ diff --git a/apps/docs/public/mstile-70x70.png b/apps/docs/public/mstile-70x70.png deleted file mode 100644 index 56bace16e0..0000000000 Binary files a/apps/docs/public/mstile-70x70.png and /dev/null differ diff --git a/apps/docs/public/safari-pinned-tab.svg b/apps/docs/public/safari-pinned-tab.svg deleted file mode 100644 index 596052799d..0000000000 --- a/apps/docs/public/safari-pinned-tab.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - -Created by potrace 1.14, written by Peter Selinger 2001-2017 - - - - - - - diff --git a/apps/docs/public/site.webmanifest b/apps/docs/public/site.webmanifest deleted file mode 100644 index 31a1e10fdb..0000000000 --- a/apps/docs/public/site.webmanifest +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "Cal.com", - "short_name": "Cal.com", - "icons": [ - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#292929", - "background_color": "#292929", - "display": "standalone" -} diff --git a/apps/docs/theme.config.js b/apps/docs/theme.config.js deleted file mode 100644 index 25857c7491..0000000000 --- a/apps/docs/theme.config.js +++ /dev/null @@ -1,47 +0,0 @@ -import { SEO_IMG_DEFAULT, DOCS_URL } from "@calcom/lib/constants"; - -const themeConfig = { - github: "https://github.com/calcom/cal.com", - docsRepositoryBase: "https://github.com/calcom/cal.com/blob/main/apps/docs/pages", - titleSuffix: " | Cal.com", - logo:

Cal.com

, - head: ( - <> - - - - - - - - - - - - - - - - - - - - - - - ), - search: true, - prevLinks: true, - nextLinks: true, - footer: true, - footerEditLink: "Edit this page on GitHub", - footerText: <>© {new Date().getFullYear()} Cal.com, Inc. All rights reserved., -}; - -export default themeConfig; diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json deleted file mode 100644 index d1581064cf..0000000000 --- a/apps/docs/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "@calcom/tsconfig/nextjs.json", - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"], - "compilerOptions": { - "noImplicitAny": false - } -} diff --git a/apps/web/.prettierignore b/apps/web/.prettierignore index 6b0e06cdef..e0f842003c 100644 --- a/apps/web/.prettierignore +++ b/apps/web/.prettierignore @@ -1 +1,2 @@ public/embed +*.test.ts diff --git a/apps/web/package.json b/apps/web/package.json index 8a9b6c54e6..003d767777 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@calcom/web", - "version": "2.9.2", + "version": "2.9.4", "private": true, "scripts": { "analyze": "ANALYZE=true next build", @@ -156,7 +156,6 @@ "@types/stripe": "^8.0.417", "@types/uuid": "8.3.1", "autoprefixer": "^10.4.12", - "babel-jest": "^28.1.0", "copy-webpack-plugin": "^11.0.0", "detect-port": "^1.3.0", "env-cmd": "^10.1.0", @@ -165,7 +164,6 @@ "msw": "^0.42.3", "postcss": "^8.4.18", "tailwindcss": "^3.3.1", - "ts-jest": "^28.0.8", "ts-node": "^10.9.1", "typescript": "^4.9.4" }, diff --git a/apps/web/pages/[user].tsx b/apps/web/pages/[user].tsx index 6ef8f4c8b0..ebd7ce56a0 100644 --- a/apps/web/pages/[user].tsx +++ b/apps/web/pages/[user].tsx @@ -103,7 +103,7 @@ export default function User(props: inferSSRProps & E delete query.user; // So it doesn't display in the Link (and make tests fail) const nameOrUsername = user.name || user.username || ""; - /* + /* const telemetry = useTelemetry(); useEffect(() => { if (top !== window) { diff --git a/apps/web/pages/[user]/[type].tsx b/apps/web/pages/[user]/[type].tsx index 882ac3b440..f0347f9d35 100644 --- a/apps/web/pages/[user]/[type].tsx +++ b/apps/web/pages/[user]/[type].tsx @@ -162,7 +162,7 @@ async function getUserPageProps(context: GetStaticPropsContext) { return false; }).length > 0; - const hasPremiumUserName = hasKeyInMetadata(user, "isPremium"); + const hasPremiumUserName = hasKeyInMetadata(user, "isPremium") ? !!user.metadata.isPremium : false; return { props: { diff --git a/apps/web/pages/api/logo.ts b/apps/web/pages/api/logo.ts index 6abe10e3ae..cece787251 100644 --- a/apps/web/pages/api/logo.ts +++ b/apps/web/pages/api/logo.ts @@ -2,6 +2,9 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { z } from "zod"; import { IS_SELF_HOSTED, LOGO, LOGO_ICON, WEBAPP_URL } from "@calcom/lib/constants"; +import logger from "@calcom/lib/logger"; + +const log = logger.getChildLogger({ prefix: ["[api/logo]"] }); function removePort(url: string) { return url.replace(/:\d+$/, ""); @@ -51,7 +54,7 @@ async function getTeamLogos(subdomain: string) { appIconLogo: team.appIconLogo || `${WEBAPP_URL}${LOGO_ICON}`, }; } catch (error) { - if (error instanceof Error) console.error(error.message); + if (error instanceof Error) log.debug(error.message); return { appLogo: `${WEBAPP_URL}${LOGO}`, appIconLogo: `${WEBAPP_URL}${LOGO_ICON}`, diff --git a/apps/web/pages/auth/login.tsx b/apps/web/pages/auth/login.tsx index 1e6ea5a74f..575b4ce213 100644 --- a/apps/web/pages/auth/login.tsx +++ b/apps/web/pages/auth/login.tsx @@ -51,15 +51,17 @@ export default function Login({ }: inferSSRProps & WithNonceProps) { const { t } = useLocale(); const router = useRouter(); - const formSchema = z.object({ - email: z - .string() - .min(1, `${t("error_required_field")}`) - .email(`${t("enter_valid_email")}`), - password: z.string().min(1, `${t("error_required_field")}`), - }); + const formSchema = z + .object({ + email: z + .string() + .min(1, `${t("error_required_field")}`) + .email(`${t("enter_valid_email")}`), + password: z.string().min(1, `${t("error_required_field")}`), + }) + // Passthrough other fields like totpCode + .passthrough(); const methods = useForm({ resolver: zodResolver(formSchema) }); - const { register, formState } = methods; const [twoFactorRequired, setTwoFactorRequired] = useState(!!totpEmail || false); const [errorMessage, setErrorMessage] = useState(null); diff --git a/apps/web/pages/availability/[schedule].tsx b/apps/web/pages/availability/[schedule].tsx index 41861fdf52..68cdcc9feb 100644 --- a/apps/web/pages/availability/[schedule].tsx +++ b/apps/web/pages/availability/[schedule].tsx @@ -82,7 +82,7 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => { onChange={(ranges) => append({ ranges })} Trigger={ } /> diff --git a/apps/web/playwright/managed-event-types.e2e.ts b/apps/web/playwright/managed-event-types.e2e.ts index 3496deed51..b365a03089 100644 --- a/apps/web/playwright/managed-event-types.e2e.ts +++ b/apps/web/playwright/managed-event-types.e2e.ts @@ -39,10 +39,10 @@ test.describe("Managed Event Types tests", () => { await page.click("[data-testid=new-event-type-dropdown]"); await page.click("[data-testid=option-team-1]"); // Expecting we can add a managed event type as team owner - await expect(page.locator('input[value="MANAGED"]')).toBeVisible(); + await expect(page.locator('button[value="MANAGED"]')).toBeVisible(); // Actually creating a managed event type to test things further - await page.click('input[value="MANAGED"]'); + await page.click('button[value="MANAGED"]'); await page.fill("[name=title]", "managed"); await page.click("[type=submit]"); }); diff --git a/apps/web/public/static/locales/ar/common.json b/apps/web/public/static/locales/ar/common.json index e2dffafa48..8d5f241750 100644 --- a/apps/web/public/static/locales/ar/common.json +++ b/apps/web/public/static/locales/ar/common.json @@ -408,7 +408,7 @@ "add_time_availability": "إضافة فتحة زمنية جديدة", "add_an_extra_layer_of_security": "أضف إلى حسابك طبقة أمان إضافية للاحتياط من سرقة كلمة المرور الخاصة بك.", "2fa": "المصادقة من عاملين", - "2fa_disabled": "يمكن تمكين المصادقة الثنائية فقط لمصادقة البريد الإلكتروني وكلمة المرور", + "2fa_disabled": "يتم تمكين المصادقة الثنائية فقط لمصادقة البريد الإلكتروني وكلمة المرور", "enable_2fa": "تمكين المصادقة من عاملين", "disable_2fa": "تعطيل المصادقة من عاملين", "disable_2fa_recommendation": "إذا كنت بحاجة إلى تعطيل المصادقة من عاملين، فنوصيك بإعادة تمكينها في أقرب وقت ممكن.", @@ -589,17 +589,17 @@ "minutes": "الدقائق", "round_robin": "الترتيب الدوري", "round_robin_description": "نقل الاجتماعات بشكل دوري بين أعضاء الفريق المتعددين.", - "managed_event": "الحدث المدار", + "managed_event": "حدث تم إدارته", "username_placeholder": "اسم المستخدم", - "managed_event_description": "إنشاء وتوزيع أنواع الأحداث بكميات كبيرة على أعضاء الفريق", + "managed_event_description": "إنشاء أنواع الأحداث بشكل مجمّع وتوزيعها على أعضاء الفريق", "managed": "مدارة", "managed_event_url_clarification": "سيتم ملء \"اسم المستخدم\" باسم المستخدم للأعضاء المعينين", "assign_to": "تعيين إلى", "add_members": "إضافة أعضاء...", "count_members_one": "{{count}} عضو", - "count_members_other": "{{count}} أعضاء", + "count_members_other": "{{count}} من الأعضاء", "no_assigned_members": "لا يوجد أعضاء معينين", - "assigned_to": "تم تعيينه إلى", + "assigned_to": "تم التعيين إلى", "start_assigning_members_above": "بدء تعيين الأعضاء أعلاه", "locked_fields_admin_description": "لن يتمكن الأعضاء من تعديل هذا", "locked_fields_member_description": "قام مشرف الفريق بقفل هذا الخيار", @@ -754,10 +754,10 @@ "minimum_booking_notice": "الحد الأدنى من الوقت للحجز", "slot_interval": "الفترات الزمنية بين عمليات الحجز", "slot_interval_default": "استخدام طول الحدث (الوضع الافتراضي)", - "delete_event_type": "حذف نوع الحدث؟", - "delete_managed_event_type": "حذف نوع الحدث المُدار؟", - "delete_event_type_description": "لن يكون بإمكان أي شخص قمت بمشاركة الرابط معه الوصول إلى الرابط.", - "delete_managed_event_type_description": "
  • سيتم أيضًا حذف أنواع الأحداث الخاصة بالأعضاء المعينين في هذا النوع من الأحداث.
  • لن يتمكن أي شخص شاركوا رابطهم معه من الحجز باستخدامه.
", + "delete_event_type": "هل تريد حذف نوع الحدث؟", + "delete_managed_event_type": "هل تريد حذف نوع الحدث المُدار؟", + "delete_event_type_description": "لن يتمكن أي شخص قمت بمشاركة الرابط معه الججز باستخدامه.", + "delete_managed_event_type_description": "
  • سيتم أيضًا حذف أنواع الأحداث الخاصة بالأعضاء المعينين في هذا النوع من الأحداث.
  • لن يتمكن أي شخص شاركوا الرابط معه من الحجز باستخدامه.
", "confirm_delete_event_type": "نعم، قم بالحذف", "delete_account": "حذف الحساب", "confirm_delete_account": "نعم، احذف الحساب", @@ -1177,7 +1177,7 @@ "event_advanced_tab_title": "متقدم", "event_setup_multiple_duration_error": "إعداد الحدث: تستلزم فترات متعددة خيارًا واحدًا على الأقل.", "event_setup_multiple_duration_default_error": "إعداد الحدث: الرجاء تحديد مدة افتراضية صالحة.", - "event_setup_booking_limits_error": "يجب أن تكون حدود الحجز بترتيب تصاعدي. [يوم،أسبوع،شهر،سنة]", + "event_setup_booking_limits_error": "يجب أن تكون حدود الحجز بترتيب تصاعدي. [يوم، أسبوع، شهر، سنة]", "event_setup_duration_limits_error": "يجب أن تكون حدود المدة بترتيب تصاعدي. [يوم، أسبوع، شهر، سنة]", "select_which_cal": "اختر أي رزنامة تريد إضافة الحجز إليها", "custom_event_name": "اسم حدث مخصص", @@ -1317,7 +1317,7 @@ "exchange_authentication_standard": "المصادقة الأساسية", "exchange_authentication_ntlm": "مصادقة NTLM", "exchange_compression": "ضغط Gzip", - "exchange_version": "نسخة التبادل", + "exchange_version": "إصدار Exchange", "exchange_version_2007_SP1": "2007 SP1", "exchange_version_2010": "2010", "exchange_version_2010_SP1": "2010 SP1", @@ -1395,7 +1395,7 @@ "billing_help_title": "هل تحتاج إلى أي شيء آخر؟", "billing_help_description": "إذا كنت تحتاج إلى أي مساعدة إضافية بخصوص الفوترة، فإن فريق الدعم لدينا في انتظارك لتقديم المساعدة.", "billing_help_cta": "الاتصال بالدعم", - "ignore_special_characters_booking_questions": "تجاهل المحارف الخاصة في معرّف سؤال الحجز لديك. استخدم فقط الأحرف والأرقام", + "ignore_special_characters_booking_questions": "تجاهل الأحرف الخاصة في معرف سؤال الحجز لديك. استخدم الأحرف والأرقام فقط", "retry": "إعادة المحاولة", "fetching_calendars_error": "حدثت مشكلة أثناء جلب التقويمات الخاصة بك. يرجى <1>إعادة المحاولة أو الاتصال بدعم العملاء.", "calendar_connection_fail": "فشل الاتصال بالتقويم", @@ -1485,10 +1485,10 @@ "admin_has_disabled": "قام مشرفًا بتعطيل {{appName}}", "disabled_app_affects_event_type": "قام مشرفًا بتعطيل {{appName}} مما يؤثر على نوع الحدث {{eventType}} لديك", "event_replaced_notice": "استبدل المشرف أحد أنواع الأحداث لديك", - "email_subject_slug_replacement": "استبدل مسؤول فريق الحدث لديك /{{slug}}", - "email_body_slug_replacement_notice": "استبدل مسؤول في فريق {{teamName}} نوع الحدث لديك/{{slug}} بنوع الحدث المُدار الذي يتحكم فيه.", - "email_body_slug_replacement_info": "سيستمر رابطك في العمل، ولكن بعض إعداداته قد تغيرت. يمكنك مراجعتها في أنواع الأحداث.", - "email_body_slug_replacement_suggestion": "يرجى الاتصال بالمسؤول إذا كان لديك أي أسئلة حول نوع الحدث.

جدولة سعيدة،
فريق Cal.com", + "email_subject_slug_replacement": "استبدل مشرف في فريق الحدث لديك /{{slug}}", + "email_body_slug_replacement_notice": "استبدل مشرف في فريق {{teamName}} نوع الحدث لديك/{{slug}} بنوع الحدث المُدار الذي يتحكم فيه.", + "email_body_slug_replacement_info": "سيستمر الرابط في العمل، ولكن ربما تم تغيير بعض إعداداته. يمكنك مراجعتها في أنواع الأحداث.", + "email_body_slug_replacement_suggestion": "يرجى الاتصال بالمشرف إذا كان لديك أي أسئلة حول نوع الحدث.

جدولة سعيدة،
فريق Cal.com", "disable_payment_app": "قام المشرف بتعطيل {{appName}} الذي يؤثر على نوع الحدث {{title}} لديك. لا يزال بإمكان الحاضرين حجز هذا النوع من الأحداث، ولكن لن يُطالبوا بالدفع. يمكنك إخفاء نوع الحدث لمنع حدوث هذا إلى أن يقوم المشرف بتغيير طريقة الدفع لك.", "payment_disabled_still_able_to_book": "لا يزال بإمكان الحاضرين حجز هذا النوع من الأحداث، ولكن لن يُطالبوا بالدفع. يمكنك إخفاء نوع الحدث لمنع حدوث هذا إلى أن يقوم المشرف بتغيير طريقة الدفع لك.", "app_disabled_with_event_type": "عطّل المشرف {{appName}} مما يؤثر على نوع الحدث {{title}} لديك.", @@ -1551,7 +1551,7 @@ "create_your_first_team_webhook_description": "قم بإنشاء أول شبكة ويب هوك لهذا النوع من فعاليات الفريق", "create_webhook_team_event_type": "قم بإنشاء ويب هوك لهذا النوع من فعاليات الفريق", "disable_success_page": "تعطيل صفحة النجاح (يعمل فقط إذا كان لديك رابط إعادة توجيه)", - "invalid_admin_password": "تم تعيينك كمشرف ولكن ليس لديك كلمة مرور بطول 15 مِحرَفاً على الأقل", + "invalid_admin_password": "أنت مشرف ولكن ليس لديك كلمة مرور بطول 15 حرفًا على الأقل أو لا توجد مصادقة ثنائية حتى الآن", "change_password_admin": "قم بتغيير كلمة المرور للحصول على وصول المشرف", "username_already_taken": "اسم المستخدم هذا مستخدم مسبقًا", "assignment": "تعيين", @@ -1685,14 +1685,14 @@ "add_1_option_per_line": "إضافة خيار واحد لكل سطر", "select_a_router": "حدد مساراً", "add_a_new_route": "إضافة مسار جديد", - "make_informed_decisions": "اتخاذ قرارات على اطّلاع باستخدام الرؤى", - "make_informed_decisions_description": "تقدّم لوحة الرؤى لدينا جميع الأنشطة لفريقك وتظهر لك الاتجاهات السائدة التي تمكّنك من جدولة الفريق واتخاذ القرار بشكل أفضل.", - "view_bookings_across": "عرض الحجز أمام جميع الأعضاء", - "view_bookings_across_description": "شاهد من يتلقى معظم الحجوزات وتأكد من إجراء أفضل عملية توزيع عبر فريقك", - "identify_booking_trends": "تحديد الاتجاهات السائدة للحجز", + "make_informed_decisions": "اتخذ قرارات مستنيرة باستخدام Insights", + "make_informed_decisions_description": "تُظهر لوحة معلومات Insights جميع أنشطة فريقك وتعرض لك الاتجاهات الرائجة التي تتيح جدولة الفريق واتخاذ القرارات بشكل أفضل.", + "view_bookings_across": "عرض حجوزات جميع الأعضاء", + "view_bookings_across_description": "تعرف على من يتلقى أكبر عدد من الحجوزات وتأكد من التوزيع الأفضل بين أعضاء فريقك", + "identify_booking_trends": "تحديد اتجاهات الحجز الرائجة", "identify_booking_trends_description": "شاهد أي الأوقات في الأسبوع وخلال اليوم تحظى بشعبية لدى من يقومون بالحجز لديك", - "spot_popular_event_types": "أنواع الأحداث الشائعة", - "spot_popular_event_types_description": "شاهد أي من نوع من الأحداث لديك يتلقى معظم النقرات والحجز", + "spot_popular_event_types": "اكتشف أنواع الأحداث الشائعة", + "spot_popular_event_types_description": "تعرف أي أنواع من الأحداث لديك تتلقى أكبر عدد من النقرات والحجوزات", "no_responses_yet": "لا توجد ردود بعد", "this_will_be_the_placeholder": "سيكون هذا هو العنصر النائب", "this_meeting_has_not_started_yet": "لم يبدأ هذا الاجتماع بعد", @@ -1704,13 +1704,13 @@ "can_you_try_again": "هل يمكنك المحاولة مرة أخرى في وقت مختلف؟", "verify": "التحقق", "timezone_variable": "المنطقة الزمنية", - "timezone_info": "المنطقة الزمنية للشخص المتلقي", + "timezone_info": "المنطقة الزمنية للشخص الذي يتلقى الحجز", "event_end_time_variable": "وقت انتهاء الحدث", - "event_end_time_info": "زمن نهاية الحدث", - "cancel_url_variable": "إلغاء عنوان URL", - "cancel_url_info": "عنوان URL لإلغاء الحجز", - "reschedule_url_variable": "إعادة جدولة عنوان URL", - "reschedule_url_info": "عنوان URL لإعادة جدولة الحجز", + "event_end_time_info": "وقت نهاية الحدث", + "cancel_url_variable": "رابط الإلغاء", + "cancel_url_info": "رابط إلغاء الحجز", + "reschedule_url_variable": "رابط إعادة الجدولة", + "reschedule_url_info": "رابط لإعادة جدولة الحجز", "invalid_event_name_variables": "هناك متغيّر غير صالح في اسم الحدث", "select_all": "تحديد الكل", "default_conferencing_bulk_title": "التحديث الجماعي لمجموعة أنواع الأحداث الحالية", @@ -1720,65 +1720,65 @@ "members_default_schedule_description": "سنستخدم جدول الإتاحة الافتراضي لكل عضو، وسيكون بإمكانه تعديله أو تغييره.", "requires_at_least_one_schedule": "يجب أن يكون لديك جدول واحد على الأقل", "default_conferencing_bulk_description": "تحديث المواقع لأنواع الأحداث المحددة", - "locked_for_members": "مقفل أمام الأعضاء", - "locked_apps_description": "سيكون الأعضاء قادرين على رؤية التطبيقات النشطة، ولكن من دون القدرة على تعديل أي إعدادات للتطبيق", - "locked_webhooks_description": "سيكون الأعضاء قادرين على رؤية قوالب الويب النشطة، ولكن من دون القدرة على تعديل أي إعدادات للقوالب", + "locked_for_members": "مقفل للأعضاء", + "locked_apps_description": "سيتمكن الأعضاء من رؤية التطبيقات النشطة، ولكن لن يتمكنوا من تعديل أي إعدادات للتطبيق", + "locked_webhooks_description": "سيكون الأعضاء قادرين على رؤية webhooks النشطة، ولكن من دون القدرة على تعديل أي إعدادات Webhook", "locked_workflows_description": "سيكون الأعضاء قادرين على رؤية مسارات العمل النشطة، ولكن من دون القدرة على تعديل أي إعدادات لمسار العمل", "app_not_connected": "أنت غير متصل بحساب {{appName}}.", "connect_now": "اتصل الآن", - "managed_event_dialog_confirm_button_one": "استبدال وإشعار العضو {{count}}", - "managed_event_dialog_confirm_button_other": "استبدال وإشعار الأعضاء {{count}}", + "managed_event_dialog_confirm_button_one": "الاستبدال وإخطار {{count}} عضو", + "managed_event_dialog_confirm_button_other": "الاستبدال وإخطار {{count}} من الأعضاء", "managed_event_dialog_title_one": "عنوان url /{{slug}} موجود بالفعل من أجل {{count}} عضو. هل تريد استبداله؟", "managed_event_dialog_title_other": "عنوان url /{{slug}} موجود بالفعل لـ {{count}} أعضاء. هل تريد استبداله؟", "managed_event_dialog_information_one": "{{names}} يستخدم بالفعل عنوان url /{{slug}}.", "managed_event_dialog_information_other": "{{names}} يستخدم بالفعل عنوان url /{{slug}}.", - "managed_event_dialog_clarification": "سنقوم بإعلامهم إذا اخترت استبداله. قم بالعودة وإزالتهم إذا كنت لا تريد الكتابة فوقها.", + "managed_event_dialog_clarification": "إذا اخترت استبداله، فسنخطرهم بذلك. ارجع وقم بإزالتهم إذا كنت لا تريد استبداله.", "review_event_type": "مراجعة نوع الحدث", - "looking_for_more_analytics": "هل تبحث عن تحليلات إضافية؟", - "looking_for_more_insights": "هل تبحث عن رؤى أكثر؟", - "add_filter": "إضافة عامل فرز", - "select_user": "اختر مستخدم", - "select_event_type": "اختر نوع الحدث", - "select_date_range": "اختر نطاق التاريخ", + "looking_for_more_analytics": "هل تبحث عن المزيد من التحليلات؟", + "looking_for_more_insights": "هل تبحث عن المزيد من Insights؟", + "add_filter": "إضافة عامل تصفية", + "select_user": "اختر المستخدم", + "select_event_type": "حدد نوع الحدث", + "select_date_range": "حدد نطاق التاريخ", "popular_events": "الأحداث الشائعة", - "no_event_types_found": "لم يتم العثور على أنواع الأحداث", + "no_event_types_found": "لم يتم العثور على أنواع للأحداث", "average_event_duration": "متوسط مدة الحدث", - "most_booked_members": "أكثر الأعضاء حجزاً", - "least_booked_members": "أقل الأعضاء حجزاً", + "most_booked_members": "الأعضاء الأكثر حجزًا", + "least_booked_members": "الأعضاء الأقل حجزًا", "events_created": "تم إنشاء الأحداث", "events_completed": "تم اكتمال الأحداث", "events_cancelled": "تم إلغاء الأحداث", - "events_rescheduled": "تم إعادة جدولة الأحداث", + "events_rescheduled": "تمت إعادة جدولة الأحداث", "from_last_period": "من آخر فترة", "from_to_date_period": "من: {{startDate}} إلى: {{endDate}}", - "analytics_for_organisation": "الرؤى", + "analytics_for_organisation": "Insights", "subtitle_analytics": "اعرف المزيد عن نشاط فريقك", "redirect_url_warning": "سيؤدي إضافة إعادة توجيه إلى تعطيل صفحة النجاح. تأكد من ذكر \"تأكيد الحجز\" في صفحة النجاح المخصصة.", - "event_trends": "الاتجاهات السائدة للحدث", - "clear_filters": "مسح عوامل الفرز", + "event_trends": "الرائج في الأحداث", + "clear_filters": "مسح عوامل التصفية", "hold": "تعليق", - "on_booking_option": "تحصيل الدفعة عن الحجز", - "hold_option": "فرض رسوم لقاء عدم الحضور", + "on_booking_option": "تحصيل الدفع عن الحجز", + "hold_option": "فرض رسوم عدم الحضور", "card_held": "تم تعليق البطاقة", - "charge_card": "الدفع باستخدام البطاقة", - "card_charged": "تم استقطاع الدفعة من البطاقة", - "no_show_fee_amount": "رسم عدم الحضور {{amount, currency}}", - "no_show_fee": "رسم عدم الحضور", + "charge_card": "خصم الدفع من البطاقة", + "card_charged": "تم خصم الدفع من البطاقة", + "no_show_fee_amount": "رسوم عدم الحضور {{amount, currency}}", + "no_show_fee": "رسوم عدم الحضور", "submit_card": "تقديم بطاقة", "submit_payment_information": "تقديم معلومات الدفع", "meeting_awaiting_payment_method": "اجتماعك في انتظار طريقة الدفع", - "no_show_fee_charged_email_subject": "تم تحصيل رسم عدم حضور مقداره {{amount, currency}} من أجل {{title}} في {{date}}", - "no_show_fee_charged_text_body": "تم تحصيل رسم عدم حضور", - "no_show_fee_charged_subtitle": "تم تحصيل رسم عدم حضور مقداره {{amount, currency}} من أجل الحدث التالي", - "error_charging_card": "حدثت مشكلة في استقطاع رسم عدم الحضور. يرجى المحاولة مرة أخرى لاحقاً.", - "collect_no_show_fee": "تحصيل رسم عدم الحضور", - "no_show_fee_charged": "لم يتم تحصيل رسوم عدم حضور", - "insights": "الرؤى", - "testing_workflow_info_message": "عند اختبار مسار العمل هذا، كن على علم بأنه يمكن جدولة رسائل البريد الإلكتروني والرسائل القصيرة فقط قبل ساعة واحدة على الأقل", - "insights_no_data_found_for_filter": "لم يتم العثور على بيانات لعامل الفرز المحدد أو التواريخ المحددة.", - "acknowledge_booking_no_show_fee": "أقر بأنه إذا لم أحضر هذا الحدث فإنه لن يتم استخدام بطاقتي للدقع لرسوم الحدث {{amount, currency}}.", + "no_show_fee_charged_email_subject": "تم تحصيل رسوم عدم حضور يبلغ {{amount, currency}} من أجل {{title}} في {{date}}", + "no_show_fee_charged_text_body": "تم تحصيل رسوم عدم حضور", + "no_show_fee_charged_subtitle": "تم تحصيل رسوم عدم حضور يبلغ {{amount, currency}} للحدث التالي", + "error_charging_card": "حدثت مشكلة في خصم رسوم عدم الحضور. يرجى المحاولة مرة أخرى لاحقًا.", + "collect_no_show_fee": "تحصيل رسوم عدم الحضور", + "no_show_fee_charged": "تم تحصيل رسوم عدم حضور", + "insights": "Insights", + "testing_workflow_info_message": "عند اختبار سير العمل هذا، يجب أن تدرك أنه لا يمكن جدولة رسائل البريد الإلكتروني والرسائل القصيرة إلا قبل ساعة واحدة على الأقل", + "insights_no_data_found_for_filter": "لم يتم العثور على بيانات لعامل التصفية المحدد أو التواريخ المحددة.", + "acknowledge_booking_no_show_fee": "أقر بأنه إذا لم أحضر هذا الحدث فسيتم سحب رسوم عدم حضور من بطاقتي تبلغ {{amount, currency}}.", "card_details": "تفاصيل البطاقة", - "seats_and_no_show_fee_error": "لا يمكن حالياً تمكين المقاعد وفرض رسوم عدم الحضور", + "seats_and_no_show_fee_error": "لا يمكن حاليًا تمكين المقاعد وفرض رسوم عدم الحضور", "complete_your_booking": "أكمل الحجز", "complete_your_booking_subject": "أكمل الحجز: {{title}} في {{date}}", "email_invite_team": "تم دعوة {{email}}" diff --git a/apps/web/public/static/locales/cs/common.json b/apps/web/public/static/locales/cs/common.json index 6cb1c60076..65d984d757 100644 --- a/apps/web/public/static/locales/cs/common.json +++ b/apps/web/public/static/locales/cs/common.json @@ -757,7 +757,7 @@ "delete_event_type": "Odstranit typ události?", "delete_managed_event_type": "Odstranit typ spravované události?", "delete_event_type_description": "Každý, komu jste tento odkaz nasdíleli, pomocí něj již nebude moci provádět rezervace.", - "delete_managed_event_type_description": "
  • Členům přiřazeným k tomuto typu události budou odstraněny i příslušné typy událostí.
  • Každý, komu jste tento odkaz nasdíleli, pomocí něj již ńebude moci moci provádět rezervace.
", + "delete_managed_event_type_description": "
  • Členům přiřazeným k tomuto typu události budou odstraněny i příslušné typy událostí.
  • Každý, komu jste tento odkaz nasdíleli, pomocí něj již nebude moci moci provádět rezervace.
", "confirm_delete_event_type": "Ano, odstranit", "delete_account": "Smazat účet", "confirm_delete_account": "Ano, smazat účet", @@ -1200,7 +1200,7 @@ "start_of_week": "Začátek týdne", "recordings_title": "Nahrávky", "recording": "Nahrávka", - "happy_scheduling": "Veselé plánování", + "happy_scheduling": "Hodně štěstí s plánováním", "select_calendars": "Vyberte kalendáře, u kterých chcete kontrolovat konflikty v zájmu prevence dvojích rezervací.", "check_for_conflicts": "Zkontrolovat konflikty", "view_recordings": "Zobrazit nahrávky", @@ -1326,7 +1326,7 @@ "exchange_version_2013_SP1": "2013 SP1", "exchange_version_2015": "2015", "exchange_version_2016": "2016", - "routing_forms_description": "Zde si můžete prohlédnout všechny vytvořené formuláře a trasy.", + "routing_forms_description": "Vytvořit formuláře pro přesměrování účastníků do správných destinací", "routing_forms_send_email_owner": "Odeslat e-mail vlastníkovi", "routing_forms_send_email_owner_description": "Po odeslání formuláře bude vlastníkovi odeslán e-mail", "add_new_form": "Přidat nový formulář", @@ -1488,7 +1488,7 @@ "email_subject_slug_replacement": "Správce týmu nahradil vaši událost /{{slug}}", "email_body_slug_replacement_notice": "Správce v týmu {{teamName}} nahradil váš typ události /{{slug}} spravovaným typem události, který ovládá.", "email_body_slug_replacement_info": "Váš odkaz bude dále fungovat, ale některá příslušná nastavení se mohla změnit. Můžete si je prohlédnout v části Typy událostí.", - "email_body_slug_replacement_suggestion": "Pokud máte k typu události případné dotazy, obraťte se na svého správce.

Veselé plánování,
tým Cal.com", + "email_body_slug_replacement_suggestion": "Pokud máte k typu události dotazy, obraťte se na svého správce.

Hodně štěstí s plánováním.
tým Cal.com", "disable_payment_app": "Administrátor zakázal {{appName}}, což ovlivňuje váš typ události {{title}}. Účastníci si stále mohou rezervovat tento typ události, ale nebudou vyzváni k platbě. Abyste tomu zabránili, můžete tento typ události skrýt, dokud správce tento způsob platby znovu nepovolí.", "payment_disabled_still_able_to_book": "Účastníci si stále mohou rezervovat tento typ události, ale nebudou vyzváni k platbě. Abyste tomu zabránili, můžete tento typ události skrýt, dokud správce tento způsob platby znovu nepovolí.", "app_disabled_with_event_type": "Administrátor zakázal {{appName}}, což ovlivňuje váš typ události {{title}}.", @@ -1691,7 +1691,7 @@ "view_bookings_across_description": "Podívejte se, kdo přijímá nejvíce rezervací, a zajistěte co nejlepší rozložení v rámci celého týmu", "identify_booking_trends": "Identifikujte trendy v rezervacích", "identify_booking_trends_description": "Zjistěte, které části týdne a které časy během dne jsou u vašich rezervací oblíbené", - "spot_popular_event_types": "Vytipujete si oblíbené typy událostí", + "spot_popular_event_types": "Identifikujte oblíbené typy událostí", "spot_popular_event_types_description": "Zjistěte, které typy vašich událostí získávají nejvíce kliknutí a rezervací", "no_responses_yet": "Zatím žádné odpovědi", "this_will_be_the_placeholder": "Toto bude zástupný znak", @@ -1717,17 +1717,17 @@ "members_default_schedule": "Výchozí rozvrh člena", "set_by_admin": "Nastaveno správcem týmu", "members_default_location": "Výchozí umístění člena", - "members_default_schedule_description": "Použijeme výchozí rozvrh dostupnosti každého člena. Bude ho moct upravovat nebo měnit.", + "members_default_schedule_description": "Použijeme výchozí rozvrh dostupnosti každého člena. Bude ho moci upravovat nebo měnit.", "requires_at_least_one_schedule": "Musíte mít alespoň jeden rozvrh", "default_conferencing_bulk_description": "Aktualizujte místa pro vybrané typy událostí", "locked_for_members": "Zamčeno pro členy", - "locked_apps_description": "Členové budou vidět aktivní aplikace, ale nastavení aplikací upravovat moci nebudou", - "locked_webhooks_description": "Členové budou vidět aktivní webhooky, ale nastavení webhooků upravovat moci nebudou", - "locked_workflows_description": "Členové budou vidět aktivní pracovní postupy, ale nastavení pracovních postupů upravovat moci nebudou", + "locked_apps_description": "Členové uvidí aktivní aplikace, ale nastavení aplikací upravovat nemohou", + "locked_webhooks_description": "Členové uvidí aktivní webhooky, ale nastavení webhooků upravovat nemohou", + "locked_workflows_description": "Členové uvidí aktivní pracovní postupy, ale nastavení pracovních postupů upravovat nemohou", "app_not_connected": "Nemáte připojený účet {{appName}}.", "connect_now": "Připojte se", "managed_event_dialog_confirm_button_one": "Nahradit a upozornit {{count}} člena", - "managed_event_dialog_confirm_button_other": "Nahradit a upozornit členy ({{count}})", + "managed_event_dialog_confirm_button_other": "Nahradit a upozornit členy {{count}}", "managed_event_dialog_title_one": "Adresa URL /{{slug}} již existuje pro {{count}} člena. Chcete ji nahradit?", "managed_event_dialog_title_other": "Adresa URL /{{slug}} již existuje pro členy ({{count}}). Chcete ji nahradit?", "managed_event_dialog_information_one": "{{names}} již používá adresa URL /{{slug}}.", @@ -1767,10 +1767,10 @@ "submit_card": "Předložit kartu", "submit_payment_information": "Odeslat platební údaje", "meeting_awaiting_payment_method": "Vaše schůzka čeká na způsob platby", - "no_show_fee_charged_email_subject": "Poplatek za nedostavení se ve výši {{amount, currency}} stržen za {{title} dne {{date}}.", + "no_show_fee_charged_email_subject": "Poplatek za nedostavení se ve výši {{amount, currency}} stržen za {{title}} dne {{date}}.", "no_show_fee_charged_text_body": "Byl stržen poplatek za nedostavení se", "no_show_fee_charged_subtitle": "V souvislosti s následující událostí byl stržen poplatek za nedostavení se ve výši {{amount, currency}}", - "error_charging_card": "Při strhávání poplatku za nedostavení se došlo k chybě. Zkuste to prosím později.", + "error_charging_card": "Při strhávání poplatku za nedostavení se došlo k chybě. Zkuste to později.", "collect_no_show_fee": "Vybrat poplatek za nedostavení se", "no_show_fee_charged": "Poplatek za nedostavení se stržen", "insights": "Insights", diff --git a/apps/web/public/static/locales/de/common.json b/apps/web/public/static/locales/de/common.json index 1a290e85b1..586004142c 100644 --- a/apps/web/public/static/locales/de/common.json +++ b/apps/web/public/static/locales/de/common.json @@ -757,8 +757,8 @@ "delete_event_type": "Ereignistyp löschen?", "delete_managed_event_type": "Verwalteten Ereignistyp löschen?", "delete_event_type_description": "Jede Person, mit der Sie diesen Link geteilt haben, kann nicht mehr über den Link buchen.", - "delete_managed_event_type_description": "
  • Diesem Ereignistypen zugewiesene Mitglieder werden ebenfalls ihre Ereignistypen gelöscht bekommen.
  • Jeder, mit dem sie ihren Link geteilt haben, wird nicht mehr über diesen Link buchen können.
", - "confirm_delete_event_type": "Ja, löschen", + "delete_managed_event_type_description": "
  • Die Ereignistypen der diesem Ereignistypen zugewiesenen Mitglieder werden ebenfalls gelöscht.
  • Jeder, mit dem sie ihren Link geteilt haben, wird nicht mehr über diesen Link buchen können.
", + "confirm_delete_event_type": "
  • Die Ereignistypen der diesem Ereignistypen zugewiesenen Mitglieder werden ebenfalls gelöscht.
  • Jeder, mit dem sie ihren Link geteilt haben, wird nicht mehr über diesen Link buchen können.
", "delete_account": "Account löschen", "confirm_delete_account": "Ja, Account löschen", "delete_account_confirmation_message": "Sind Sie sicher, dass Sie Ihr {{appName}}-Konto löschen möchten? Sie können dann nicht mehr über Ihre geteilten Links gebucht werden und alle Einstellungen werden gelöscht.", @@ -1326,7 +1326,7 @@ "exchange_version_2013_SP1": "2013 SP1", "exchange_version_2015": "2015", "exchange_version_2016": "2016", - "routing_forms_description": "Hier sehen Sie alle Formulare und Weiterleitungen, die Sie erstellt haben.", + "routing_forms_description": "Erstellen Sie Formulare , um Teilnehmer zu den richtigen Stellen weiterzuleiten", "routing_forms_send_email_owner": "E-Mail an Eigentümer senden", "routing_forms_send_email_owner_description": "Sendet eine E-Mail an den Eigentümer, wenn das Formular abgeschickt wird", "add_new_form": "Neues Formular hinzufügen", @@ -1685,8 +1685,8 @@ "add_1_option_per_line": "1 Option pro Zeile hinzufügen", "select_a_router": "Weiterleitungsformular auswählen", "add_a_new_route": "Neue Route hinzufügen", - "make_informed_decisions": "Treffen Sie mit Einblicke fundierte Entscheidungen", - "make_informed_decisions_description": "Unser Einsichten-Dashboard deckt alle Aktivitäten in Ihrem Team ab und zeigt Ihnen Trends, die eine bessere Teamplanung und Entscheidungsfindung ermöglichen.", + "make_informed_decisions": "Treffen Sie mit Insights fundierte Entscheidungen", + "make_informed_decisions_description": "Unser Insights-Dashboard deckt alle Aktivitäten in Ihrem Team ab und zeigt Ihnen Trends, die eine bessere Teamplanung und Entscheidungsfindung ermöglichen.", "view_bookings_across": "Buchungen für alle Mitglieder anzeigen", "view_bookings_across_description": "Sehen Sie, wer am häufigsten gebucht wird und sorgen Sie für die beste Verteilung in Ihrem Team", "identify_booking_trends": "Buchungstrends erkennen", @@ -1704,8 +1704,8 @@ "can_you_try_again": "Können Sie es zu einem anderen Zeitpunkt erneut versuchen?", "verify": "Bestätigen", "timezone_variable": "Zeitzone", - "timezone_info": "Die Zeitzone der empfangenen Person", - "event_end_time_variable": "Ereignis-Endzeitpunkt", + "timezone_info": "Die Zeitzone der empfangenden Person", + "event_end_time_variable": "Endzeitpunkt des Ereignisses", "event_end_time_info": "Der Endzeitpunkt des Termins", "cancel_url_variable": "Absage-URL", "cancel_url_info": "Die URL, um die Buchung abzusagen", @@ -1732,7 +1732,7 @@ "managed_event_dialog_title_other": "Die URL /{{slug}} ist bereits für {{count}} Mitglieder vorhanden. Möchten Sie sie ersetzen?", "managed_event_dialog_information_one": "{{names}} verwendet bereits die /{{slug}}-URL.", "managed_event_dialog_information_other": "{{names}} verwenden bereits die /{{slug}}-URL.", - "managed_event_dialog_clarification": "Wenn Sie ihn ersetzen, werden wir sie benachrichtigen. Gehen Sie zurück und entfernen Sie sie, wenn Sie ihn nicht überschreiben möchten.", + "managed_event_dialog_clarification": "Wenn Sie die URL ersetzen, werden wir sie benachrichtigen. Gehen Sie zurück und entfernen Sie sie, wenn Sie sie nicht überschreiben möchten.", "review_event_type": "Ereignistyp überprüfen", "looking_for_more_analytics": "Suchen Sie nach weiteren Analysedaten?", "looking_for_more_insights": "Suchen Sie nach weiteren Einsichten?", @@ -1749,9 +1749,9 @@ "events_completed": "Ereignisse abgeschlossen", "events_cancelled": "Ereignisse abgesagt", "events_rescheduled": "Ereignisse neu geplant", - "from_last_period": "vom letzten Zeitraum", + "from_last_period": "seit dem letzten Zeitraum", "from_to_date_period": "Von: {{startDate}} Bis: {{endDate}}", - "analytics_for_organisation": "Einsichten", + "analytics_for_organisation": "Insights", "subtitle_analytics": "Erfahren Sie mehr über die Aktivität Ihres Teams", "redirect_url_warning": "Das Hinzufügen einer Umleitung wird die Erfolgsseite deaktivieren. Erwähnen Sie \"Buchung bestätigt\" auf Ihrer benutzerdefinierten Erfolgsseite.", "event_trends": "Ereignistrends", @@ -1773,12 +1773,12 @@ "error_charging_card": "Beim Berechnen der Nichterscheinungsgebühr ist etwas schiefgelaufen. Bitte versuchen Sie es später erneut.", "collect_no_show_fee": "Gebühr für das Nichterscheinen anfordern", "no_show_fee_charged": "Gebühr für das Nichterscheinen berechnet", - "insights": "Einsichten", + "insights": "Insights", "testing_workflow_info_message": "Bitte beachten Sie beim Testen dieses Workflows, dass E-Mails und SMS spätestens 1 Stunde im Voraus geplant werden können", "insights_no_data_found_for_filter": "Keine Daten für den ausgewählten Filter oder das ausgewählte Datum gefunden.", "acknowledge_booking_no_show_fee": "Ich bestätige, dass meiner Karte eine Gebühr von {{amount, currency}} berechnet wird, sofern ich nicht an diesem Ereignis teilnehme.", "card_details": "Karteninformationen", - "seats_and_no_show_fee_error": "Aktuell können keine Plätze aktiviert werden oder eine Gebühr für das Nichterscheinen berechnet werden", + "seats_and_no_show_fee_error": "Aktuell können keine Plätze aktiviert oder eine Gebühr für das Nichterscheinen berechnet werden", "complete_your_booking": "Schließen Sie Ihre Buchung ab", "complete_your_booking_subject": "Schließen Sie Ihre Buchung ab: {{title}} am {{date}}", "email_invite_team": "{{email}} wurde eingeladen" diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index c3f458020e..4aecb5157e 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1831,6 +1831,7 @@ "disable_attendees_confirmation_emails_description": "At least one workflow is active on this event type that sends an email to the attendees when the event is booked.", "disable_host_confirmation_emails": "Disable default confirmation emails for host", "disable_host_confirmation_emails_description": "At least one workflow is active on this event type that sends an email to the host when the event is booked.", + "add_an_override": "Add an override", "import_from_google_workspace":"Import users from Google Workspace", "connect_google_workspace":"Connect Google Workspace", "google_workspace_admin_tooltip":"You must be a Workspace Admin to use this feature", diff --git a/apps/web/public/static/locales/it/common.json b/apps/web/public/static/locales/it/common.json index 5e11627d95..478366836b 100644 --- a/apps/web/public/static/locales/it/common.json +++ b/apps/web/public/static/locales/it/common.json @@ -601,7 +601,7 @@ "no_assigned_members": "Nessun membro assegnato", "assigned_to": "Assegnato a", "start_assigning_members_above": "Inizia ad assegnare membri in alto", - "locked_fields_admin_description": "I membri non saranno in grado di apportare modifiche qui", + "locked_fields_admin_description": "I membri non potranno fare modifiche", "locked_fields_member_description": "Questa opzione è stata bloccata dall'amministratore del team", "url": "URL", "hidden": "Nascosto", @@ -757,7 +757,7 @@ "delete_event_type": "Eliminare il tipo di evento?", "delete_managed_event_type": "Eliminare il tipo di evento gestito?", "delete_event_type_description": "Gli utenti con cui hai condiviso il link non saranno più in grado di usarlo per prenotare.", - "delete_managed_event_type_description": "
  • Questo tipo di evento sarà eliminato anche ai membri a esso assegnati.
  • Gli utenti con cui hai condiviso il link non saranno più in grado di usarlo per prenotare.
", + "delete_managed_event_type_description": "
  • Verranno eliminati anche i tipi di evento dei membri assegnati a questo evento.
  • Gli utenti con cui hanno condiviso il link non saranno più in grado di usarlo per prenotare.
", "confirm_delete_event_type": "Sì, elimina", "delete_account": "Elimina account", "confirm_delete_account": "Sì, elimina account", @@ -1148,7 +1148,7 @@ "current_username": "Nome utente corrente", "example_1": "Esempio 1", "example_2": "Esempio 2", - "booking_question_identifier": "Identificatore domanda alla prenotazione", + "booking_question_identifier": "Identificatore della domanda posta in fase di prenotazione", "company_size": "Dimensione dell'azienda", "what_help_needed": "Per cosa ti serve assistenza?", "variable_format": "Formato variabile", @@ -1326,7 +1326,7 @@ "exchange_version_2013_SP1": "2013 SP1", "exchange_version_2015": "2015", "exchange_version_2016": "2016", - "routing_forms_description": "Qui puoi vedere tutti i moduli e tutti i percorsi che hai creato.", + "routing_forms_description": "Crea moduli per indirizzare i partecipanti alle destinazioni corrette", "routing_forms_send_email_owner": "Invia e-mail al proprietario", "routing_forms_send_email_owner_description": "Invia un'e-mail al proprietario quando il modulo viene inviato", "add_new_form": "Aggiungi nuovo modulo", @@ -1395,7 +1395,7 @@ "billing_help_title": "Hai bisogno di altro?", "billing_help_description": "Se hai bisogno di ulteriore aiuto per la fatturazione, il nostro team di supporto è qui per aiutarti.", "billing_help_cta": "Contatta l'assistenza", - "ignore_special_characters_booking_questions": "Ignora i caratteri speciali nell'identificatore della domanda da porre nella pagina di prenotazione. Usa solo lettere e numeri", + "ignore_special_characters_booking_questions": "Ignora i caratteri speciali nell'identificatore della domanda posta in fase di prenotazione. Usa solo lettere e numeri", "retry": "Riprova", "fetching_calendars_error": "Si è verificato un problema durante il recupero dei calendari. <1>Riprova o contatta l'assistenza clienti.", "calendar_connection_fail": "Impossibile connettersi al calendario", @@ -1488,7 +1488,7 @@ "email_subject_slug_replacement": "Un amministratore di team ha sostituito il tuo evento /{{slug}}", "email_body_slug_replacement_notice": "Un amministratore del team {{teamName}} ha sostituito il tuo tipo di evento /{{slug}} con un tipo di evento gestito da lui controllato.", "email_body_slug_replacement_info": "Il tuo link continuerà a funzionare, ma alcune delle sue impostazioni potrebbero essere state cambiate. Puoi verificarlo nella sezione Tipi di evento.", - "email_body_slug_replacement_suggestion": "Per qualsiasi domanda sui tipi di evento, contatta il nostro amministratore.

Ti auguriamo una piacevole esperienza di pianificazione,
Il team Cal.com", + "email_body_slug_replacement_suggestion": "Per qualsiasi domanda sul tipo di evento, contatta il tuo amministratore.

Ti auguriamo una piacevole esperienza di pianificazione,
Il team Cal.com", "disable_payment_app": "L'amministratore ha disabilitato {{appName}}, il che ha effetto sul tuo tipo di evento {{title}}. I partecipanti sono ancora in grado di prenotare questo tipo di evento ma non gli verrà richiesto di pagare. Per evitarlo, è possibile nascondere questo tipo di evento fino a che l'amministratore non riattivi il tuo metodo di pagamento.", "payment_disabled_still_able_to_book": "I partecipanti sono ancora in grado di prenotare questo tipo di evento ma non gli verrà richiesto di pagare. Per evitarlo, è possibile nascondere questo tipo di evento fino a che l'amministratore non riattivi il tuo metodo di pagamento.", "app_disabled_with_event_type": "L'amministratore ha disabilitato {{appName}}, il che ha effetto sul tuo tipo di evento {{title}}.", @@ -1551,7 +1551,7 @@ "create_your_first_team_webhook_description": "Crea il primo webhook per questo tipo di evento del team", "create_webhook_team_event_type": "Crea un webhook per questo tipo di evento del team", "disable_success_page": "Disabilita pagina di conferma dell'esito (funziona solo se si dispone di un URL di reindirizzamento)", - "invalid_admin_password": "Sei un amministratore, ma la tua password non ha la lunghezza minima di 15 caratteri, né utilizzi l'autenticazione a due fattori", + "invalid_admin_password": "Sei un amministratore, ma la tua password non ha la lunghezza minima di 15 caratteri, oppure non utilizzi ancora l'autenticazione a due fattori", "change_password_admin": "Cambia password per ottenere i diritti di accesso come amministratore", "username_already_taken": "Nome utente già in uso", "assignment": "Assegnazione", @@ -1705,8 +1705,8 @@ "verify": "Verifica", "timezone_variable": "Timezone", "timezone_info": "Il fuso orario della persona che riceve la prenotazione", - "event_end_time_variable": "Ora di fine evento", - "event_end_time_info": "Ora di fine dell'evento", + "event_end_time_variable": "Ora di conclusione dell'evento", + "event_end_time_info": "Ora di conclusione dell'evento", "cancel_url_variable": "URL di annullamento", "cancel_url_info": "URL per annullare la prenotazione", "reschedule_url_variable": "URL di riprogrammazione", @@ -1717,13 +1717,13 @@ "members_default_schedule": "Pianificazione predefinita membro", "set_by_admin": "Impostato dall'amministratore del team", "members_default_location": "Posizione predefinita del membro", - "members_default_schedule_description": "Useremo il programma di disponibilità predefinito di ciascun membro. I membri saranno in grado di modificarlo.", + "members_default_schedule_description": "Useremo il programma di disponibilità predefinito di ciascun membro. I membri potranno modificarlo.", "requires_at_least_one_schedule": "È necessario avere almeno un programma", "default_conferencing_bulk_description": "Aggiorna le posizioni per i tipi di eventi selezionati", "locked_for_members": "Bloccato per i membri", - "locked_apps_description": "I membri saranno in grado di vedere le app attive, ma non saranno in grado di modificare le impostazioni delle app", - "locked_webhooks_description": "I membri saranno in grado di vedere i webhook attivi, ma non saranno in grado di modificare le impostazioni dei webhook", - "locked_workflows_description": "I membri saranno in grado di vedere i flussi di lavoro attivi, ma non saranno in grado di modificare le impostazioni dei flussi di lavoro", + "locked_apps_description": "I membri potranno vedere le app attive, ma non potranno modificare le impostazioni delle app", + "locked_webhooks_description": "I membri potranno vedere i webhook attivi, ma non potranno modificare le impostazioni dei webhook", + "locked_workflows_description": "I membri potranno vedere i flussi di lavoro attivi, ma non potranno modificare le impostazioni dei flussi di lavoro", "app_not_connected": "Non è stato connesso un account di {{appName}}.", "connect_now": "Connetti ora", "managed_event_dialog_confirm_button_one": "Sostituisci e invia notifica a {{count}} membro", @@ -1732,8 +1732,8 @@ "managed_event_dialog_title_other": "L'URL /{{slug}} esiste già per {{count}} membri. Sostituirlo?", "managed_event_dialog_information_one": "{{names}} utilizza già l'URL /{{slug}}.", "managed_event_dialog_information_other": "{{names}} utilizzano già l'URL /{{slug}}.", - "managed_event_dialog_clarification": "Se si decide di sostituirlo, ai membri ne verrà inviata una notifica. Tornare indietro e rimuoverli se non si desidera sovrascriverlo.", - "review_event_type": "Controlla tipo di evento", + "managed_event_dialog_clarification": "Se decidi di sostituirlo, i membri ne saranno avvisati. Torna indietro e rimuovili se non desideri sovrascriverlo.", + "review_event_type": "Rivedi tipo di evento", "looking_for_more_analytics": "Hai bisogno di più dati analitici?", "looking_for_more_insights": "Hai bisogno di maggiori approfondimenti?", "add_filter": "Aggiungi filtro", @@ -1774,7 +1774,7 @@ "collect_no_show_fee": "Riscuoti penale per mancata presentazione", "no_show_fee_charged": "Penale per mancata presentazione addebitata", "insights": "Insights", - "testing_workflow_info_message": "Durante il test di questo flusso di lavoro, tieni presente che le e-mail e i messaggi SMS possono essere programmati con almeno 1 ora di anticipo", + "testing_workflow_info_message": "Durante il test di questo flusso di lavoro, tieni presente che le e-mail e i messaggi SMS devono essere programmati con almeno 1 ora di anticipo", "insights_no_data_found_for_filter": "Nessun dato trovato per il filtro selezionato o le date selezionate.", "acknowledge_booking_no_show_fee": "Confermo che in caso di mia mancata partecipazione a questo evento, una penale per mancata presentazione di {{amount, currency}} verrà addebitata sulla mia carta.", "card_details": "Dati della carta", diff --git a/apps/web/public/static/locales/ja/common.json b/apps/web/public/static/locales/ja/common.json index 55de430dd0..ea468da241 100644 --- a/apps/web/public/static/locales/ja/common.json +++ b/apps/web/public/static/locales/ja/common.json @@ -408,7 +408,7 @@ "add_time_availability": "新しい時間帯を追加", "add_an_extra_layer_of_security": "パスワードが盗まれる場合に備えて、アカウントのセキュリティを強化します。", "2fa": "二要素認証", - "2fa_disabled": "二要素認証はメールとパスワードの認証にのみ有効化できます", + "2fa_disabled": "二要素認証はメール認証とパスワード認証のみ有効にできます", "enable_2fa": "二要素認証を有効にする", "disable_2fa": "二要素認証を無効にする", "disable_2fa_recommendation": "2FA を無効にする必要がある場合は、その必要がなくなった後すぐに有効にしなおすことをおすすめします。", @@ -591,7 +591,7 @@ "round_robin_description": "複数のチームメンバー間のミーティングを定期化する。", "managed_event": "管理イベント", "username_placeholder": "ユーザー名", - "managed_event_description": "イベントタイプを一括して作成し、チームメンバーに割り当てる", + "managed_event_description": "イベントタイプを作成し、チームメンバーに一括配布する", "managed": "管理", "managed_event_url_clarification": "「ユーザー名」には、割り当てられたメンバーのユーザー名が入ります", "assign_to": "割り当て先", @@ -601,7 +601,7 @@ "no_assigned_members": "割り当てられたメンバーはいません", "assigned_to": "割り当て先", "start_assigning_members_above": "上記のメンバーへの割り当てを開始する", - "locked_fields_admin_description": "メンバーはこれを編集できません", + "locked_fields_admin_description": "メンバーはこれを編集することはできません", "locked_fields_member_description": "このオプションはチーム管理者によってロックされました", "url": "URL", "hidden": "隠れている", @@ -756,8 +756,8 @@ "slot_interval_default": "イベントの長さを使用する (デフォルト)", "delete_event_type": "イベントタイプを削除しますか?", "delete_managed_event_type": "管理イベントタイプを削除しますか?", - "delete_event_type_description": "これまでにこのリンクを共有された人は、リンクを使って予約することができなくなります。", - "delete_managed_event_type_description": "
  • このイベントタイプを割り当てられたメンバーも、イベントタイプが削除されます。
  • これまでにこのリンクを共有された人は皆、このリンクを使った予約ができなくなります。
", + "delete_event_type_description": "これまでにこのリンクを共有したユーザーは、このリンクを使用して予約することができなくなります。", + "delete_managed_event_type_description": "
  • このイベントタイプに割り当てられたメンバーは、そのイベントタイプも削除されます。
  • リンクを共有した人は、そのリンクを使用して予約することができなくなります。
", "confirm_delete_event_type": "はい、削除します", "delete_account": "アカウントを削除する", "confirm_delete_account": "はい。アカウントを削除します", @@ -1148,7 +1148,7 @@ "current_username": "現在のユーザー名", "example_1": "例 1", "example_2": "例 2", - "booking_question_identifier": "予約時の質問の識別子", + "booking_question_identifier": "予約質問識別子", "company_size": "会社の規模", "what_help_needed": "何かお困りですか?", "variable_format": "変数の形式", @@ -1177,7 +1177,7 @@ "event_advanced_tab_title": "詳細設定", "event_setup_multiple_duration_error": "イベント設定:複数の期間には少なくとも1つのオプションが必要です。", "event_setup_multiple_duration_default_error": "イベント設定:有効なデフォルト期間を選択してください。", - "event_setup_booking_limits_error": "予約の上限の日時表記は昇順にする必要があります。[日、週、月、年]", + "event_setup_booking_limits_error": "予約上限の日時表記は昇順にする必要があります。[日、週、月、年]", "event_setup_duration_limits_error": "期間の上限は昇順にする必要があります。[日、週、月、年]", "select_which_cal": "予約を追加するカレンダーを選択してください", "custom_event_name": "カスタムイベント名", @@ -1395,7 +1395,7 @@ "billing_help_title": "他にもお手伝いできることはありませんか?", "billing_help_description": "請求に関してサポートが必要な場合には、サポートチームがお手伝いさせていただきます。", "billing_help_cta": "サポートに問い合わせる", - "ignore_special_characters_booking_questions": "予約時の質問の識別子に含まれている特殊文字はすべて無視し、アルファベットと数字のみを使用してください", + "ignore_special_characters_booking_questions": "予約質問識別子に含まれている特殊文字はすべて無視し、アルファベットと数字のみを使用してください", "retry": "再試行", "fetching_calendars_error": "カレンダーの取得中にエラーが発生しました。<1>もう一度お試しいただくか、カスタマーサポートまでお問い合わせください。", "calendar_connection_fail": "カレンダーの接続に失敗しました", @@ -1551,7 +1551,7 @@ "create_your_first_team_webhook_description": "このチームイベントの種類の初めての webhook を作成します", "create_webhook_team_event_type": "このチームイベントの種類の webhook を作成します", "disable_success_page": "予約完了ページを無効化する (リダイレクト URL がある場合にのみ有効)", - "invalid_admin_password": "あなたは管理者ですが、パスワードの長さが足りないか(15文字以上必要です)、まだ二要素認証をしていません", + "invalid_admin_password": "あなたは管理者ですが、パスワードの長さが15文字以上ではないか、二要素認証をまだしていません", "change_password_admin": "パスワードを変更して管理者アクセス権を取得する", "username_already_taken": "ユーザー名が既に使用されています", "assignment": "割り当て", @@ -1686,7 +1686,7 @@ "select_a_router": "ルーターを選択", "add_a_new_route": "新しいルートを追加", "make_informed_decisions": "Insights を使って情報に基づいた決定をする", - "make_informed_decisions_description": "Insights ダッシュボードではチーム全体のアクティビティがひと目でわかります。傾向もわかるので、チームはより良いスケジューリングと決定ができます。", + "make_informed_decisions_description": "Insights ダッシュボードは、チーム内のすべてのアクティビティを表示し、チームのスケジューリングや意思決定をより良いものにするための傾向を示します。", "view_bookings_across": "メンバー全体の予約を表示", "view_bookings_across_description": "誰が最も予約されているか確認し、チーム全体で最適な配分ができるようにします", "identify_booking_trends": "予約の傾向を明らかにする", @@ -1715,12 +1715,12 @@ "select_all": "すべて選択", "default_conferencing_bulk_title": "既存のイベントの種類を一括更新する", "members_default_schedule": "メンバーのデフォルトスケジュール", - "set_by_admin": "チーム管理者により設定", + "set_by_admin": "チーム管理者による設定", "members_default_location": "メンバーのデフォルトの場所", "members_default_schedule_description": "各メンバーのデフォルトの空き状況スケジュールを使用します。メンバーはスケジュールを編集したり、変更したりできます。", "requires_at_least_one_schedule": "少なくとも 1 つのスケジュールが必要です", "default_conferencing_bulk_description": "選択したイベントの種類の場所を更新します", - "locked_for_members": "メンバーにはロックされています", + "locked_for_members": "メンバーに対してロックされています", "locked_apps_description": "メンバーは有効なアプリを確認できますが、アプリの設定を編集することはできません", "locked_webhooks_description": "メンバーは有効なウェブフックを確認できますが、ウェブフックの設定を編集することはできません", "locked_workflows_description": "メンバーは有効なワークフローを確認できますが、ワークフローの設定を編集することはできません", @@ -1766,7 +1766,7 @@ "no_show_fee": "予約不参加料金", "submit_card": "カードを送信", "submit_payment_information": "支払い情報を送信", - "meeting_awaiting_payment_method": "会議は支払い方法待ちです", + "meeting_awaiting_payment_method": "会議の支払いを待っています", "no_show_fee_charged_email_subject": "{{date}} の {{title}} に {{amount, currency}} の予約不参加料金が請求されました", "no_show_fee_charged_text_body": "予約不参加料金が請求されました", "no_show_fee_charged_subtitle": "次のイベントに {{amount, currency}} の予約不参加料金が請求されました", diff --git a/apps/web/public/static/locales/nl/common.json b/apps/web/public/static/locales/nl/common.json index b10182ceb6..c0514414ee 100644 --- a/apps/web/public/static/locales/nl/common.json +++ b/apps/web/public/static/locales/nl/common.json @@ -755,7 +755,7 @@ "slot_interval": "Tijdslot intervallen", "slot_interval_default": "Gebruik evenement lengte (standaard)", "delete_event_type": "Gebeurtenistype verwijderen?", - "delete_managed_event_type": "Beheerd gebeurtenistype verwijderen?", + "delete_managed_event_type": "Type beheerde gebeurtenis verwijderen?", "delete_event_type_description": "Iedereen met wie u deze link hebt gedeeld, kan niet meer boeken via deze link.", "delete_managed_event_type_description": "
  • De gebeurtenistypen van leden die aan dit gebeurtenistype zijn toegewezen, worden ook verwijderd.
  • Iedereen met wie zij hun link hebben gedeeld, kan niet meer boeken via deze link.
", "confirm_delete_event_type": "Ja, verwijderen", @@ -1148,7 +1148,7 @@ "current_username": "Huidige gebruikersnaam", "example_1": "Voorbeeld 1", "example_2": "Voorbeeld 2", - "booking_question_identifier": "Boekingsvraagidentificatiecode", + "booking_question_identifier": "Boekingsvraag-ID", "company_size": "Bedrijfsgrootte", "what_help_needed": "Waar heeft u hulp bij nodig?", "variable_format": "Variabele indeling", @@ -1280,7 +1280,7 @@ "download_responses_description": "Download alle reacties op uw formulier in CSV-indeling.", "download": "Downloaden", "download_recording": "Opname downloaden", - "recording_from_your_recent_call": "Een opname van uw recente gesprek op Cal.com is klaar voor download", + "recording_from_your_recent_call": "Een opname van uw recente gesprek op Cal.com is klaar om te downloaden", "create_your_first_form": "Maak uw eerste formulier", "create_your_first_form_description": "Met routeringsformulieren kunt u kwalificerende vragen stellen en doorverwijzen naar de juiste persoon of het juiste type gebeurtenis.", "create_your_first_webhook": "Maak uw eerste webhook", @@ -1326,7 +1326,7 @@ "exchange_version_2013_SP1": "2013 SP1", "exchange_version_2015": "2015", "exchange_version_2016": "2016", - "routing_forms_description": "U kunt hier alle formulieren en routes zien die u heeft gemaakt.", + "routing_forms_description": "Maak formulieren om deelnemers naar de juiste bestemmingen te verwijzen", "routing_forms_send_email_owner": "E-mail naar eigenaar versturen", "routing_forms_send_email_owner_description": "Verstuurt een e-mail naar de eigenaar wanneer het formulier is verzonden", "add_new_form": "Nieuw formulier toevoegen", @@ -1395,7 +1395,7 @@ "billing_help_title": "Nog iets nodig?", "billing_help_description": "Als u verder nog hulp nodig heeft bij de facturering, is ons ondersteuningsteam er om u te helpen.", "billing_help_cta": "Contact opnemen met de ondersteuning", - "ignore_special_characters_booking_questions": "Negeer speciale tekens in uw boekingsvraagidentificatiecode. Gebruik alleen letters en cijfers", + "ignore_special_characters_booking_questions": "Negeer speciale tekens in uw boekingsvraag-ID. Gebruik alleen letters en cijfers", "retry": "Opnieuw proberen", "fetching_calendars_error": "Er is een probleem opgetreden bij het ophalen van uw agenda's. <1>Probeer het opnieuw of neem contact op met de klantenservice.", "calendar_connection_fail": "Agendaverbinding mislukt", @@ -1486,9 +1486,9 @@ "disabled_app_affects_event_type": "Een beheerder heeft {{appName}} uitgeschakeld die van invloed is op uw gebeurtenistype {{eventType}}", "event_replaced_notice": "Een beheerder heeft een van uw gebeurtenistypen vervangen", "email_subject_slug_replacement": "Een teambeheerder heeft uw gebeurtenis vervangen /{{slug}}", - "email_body_slug_replacement_notice": "Een beheerder van het team {{teamName}} heeft uw gebeurtenistype /{{slug}} vervangen door een beheerd gebeurtenistype dat de beheerder beheert.", + "email_body_slug_replacement_notice": "Een beheerder van het team {{teamName}} heeft uw gebeurtenistype /{{slug}} vervangen door een type beheerde gebeurtenis dat deze beheert.", "email_body_slug_replacement_info": "Uw link blijft werken, maar sommige instellingen ervoor kunnen gewijzigd zijn. U kunt deze bekijken in gebeurtenistypen.", - "email_body_slug_replacement_suggestion": "Neem contact op met uw beheerder als u vragen heeft over het gebeurtenistype.

Veel plezier met plannen,
Het Cal.com-team", + "email_body_slug_replacement_suggestion": "Neem contact op met uw beheerder als u vragen hebt over het gebeurtenistype.

Veel plezier met plannen,
Het Cal.com-team", "disable_payment_app": "De beheerder heeft {{appName}} uitgeschakeld die van invloed is op uw gebeurtenistype {{title}}. Deelnemers kunnen dit tyoe gebeurtenissen nog steeds boeken, maar worden niet gevraagd te betalen. U kunt het gebeurtenistype verbergen om dit te voorkomen totdat uw beheerde de betaalmethode opnieuw inschakelt.", "payment_disabled_still_able_to_book": "Deelnemers kunnen dit type gebeurtenis nog steeds boeken, maar worden niet gevraagd te betalen. U kunt het gebeurtenistype verbergen om dit te voorkomen totdat uw beheerde de betaalmethode opnieuw inschakelt.", "app_disabled_with_event_type": "De beheerder heeft {{appName}} uitgeschakeld die van invloed is op uw gebeurtenistype {{title}}.", @@ -1551,7 +1551,7 @@ "create_your_first_team_webhook_description": "Maak uw eerste webhook voor dit teamgebeurtenistype", "create_webhook_team_event_type": "Maak een webhook voor dit teamgebeurtenistype", "disable_success_page": "Succespagina uitschakelen (werkt alleen als u een omleidings-URL heeft)", - "invalid_admin_password": "U bent beheerder, maar u heeft geen wachtwoord van minimaal 15 tekens of nog geen tweestapsverificatie", + "invalid_admin_password": "U bent beheerder, maar u hebt geen wachtwoord van minimaal 15 tekens of nog geen tweestapsverificatie", "change_password_admin": "Wijzig het wachtwoord om beheerderstoegang te krijgen", "username_already_taken": "De gebruikersnaam is al in gebruik", "assignment": "Toewijzing", @@ -1661,7 +1661,7 @@ "not_enough_seats": "Onvoldoende plaatsen", "form_builder_field_already_exists": "Een veld met deze naam bestaat al", "form_builder_field_add_subtitle": "Pas de gestelde vragen op de boekingspagina aan", - "show_on_booking_page": "Op boekingspagina weergeven", + "show_on_booking_page": "Weergeven op boekingspagina", "get_started_zapier_templates": "Aan de slag met Zapier-sjablonen", "team_is_unpublished": "{{team}} is niet gepubliceerd", "team_is_unpublished_description": "Deze teamlink is momenteel niet beschikbaar. Neem contact op met de teameigenaar of vraag hem het te publiceren.", @@ -1685,13 +1685,13 @@ "add_1_option_per_line": "Voeg 1 optie per regel toe", "select_a_router": "Selecteer een router", "add_a_new_route": "Voeg een nieuwe route toe", - "make_informed_decisions": "Maak geïnformeerde beslissingen met Insights", + "make_informed_decisions": "Neem geïnformeerde beslissingen met Insights", "make_informed_decisions_description": "Ons Insights-dashboard geeft alle activiteiten in uw team weer en toont u trends die betere teamplanning en besluitvorming mogelijk maken.", - "view_bookings_across": "Boekingen van alle leden weergeven", + "view_bookings_across": "Geef boekingen van alle leden weer", "view_bookings_across_description": "Bekijk wie het meest geboekt wordt en zorg voor de beste verdeling over uw team", - "identify_booking_trends": "Boekingstrends identificeren", + "identify_booking_trends": "Identificeer boekingstrends", "identify_booking_trends_description": "Bekijk welke tijden van de week en welke tijden overdag populair zijn voor uw boekers", - "spot_popular_event_types": "Populaire gebeurtenistypen spotten", + "spot_popular_event_types": "Spot populaire gebeurtenistypen", "spot_popular_event_types_description": "Bekijk welke van uw gebeurtenistypen de meeste kliks en boekingen ontvangen", "no_responses_yet": "Nog geen reacties", "this_will_be_the_placeholder": "Dit wordt de plaatshouder", @@ -1717,14 +1717,14 @@ "members_default_schedule": "Standaardschema lid", "set_by_admin": "Ingesteld door teambeheerder", "members_default_location": "Standaardlocatie lid", - "members_default_schedule_description": "Wij gebruiken de standaard beschikbaarheidsplanning van elk lid. Ze kunnen dit bewerken of wijzigen.", + "members_default_schedule_description": "Wij gebruiken de standaard beschikbaarheidsplanning van elk lid. Leden kunnen dit bewerken of wijzigen.", "requires_at_least_one_schedule": "U moet ten minste één planning hebben", "default_conferencing_bulk_description": "Werk de locaties voor de geselecteerde gebeurtenistypen bij", "locked_for_members": "Vergrendeld voor leden", "locked_apps_description": "Leden kunnen de actieve apps zien, maar kunnen geen appinstellingen bewerken", "locked_webhooks_description": "Leden kunnen de actieve webhooks zien, maar kunnen geen webhookinstellingen bewerken", "locked_workflows_description": "Leden kunnen de actieve werkstromen zien, maar kunnen geen werkstroominstellingen bewerken", - "app_not_connected": "U heeft geen {{appName}}-account gekoppeld.", + "app_not_connected": "U hebt geen {{appName}}-account gekoppeld.", "connect_now": "Nu koppelen", "managed_event_dialog_confirm_button_one": "{{count}} lid vervangen en informeren", "managed_event_dialog_confirm_button_other": "{{count}} leden vervangen en informeren", @@ -1732,20 +1732,20 @@ "managed_event_dialog_title_other": "De URL /{{slug}} bestaat al voor {{count}} leden. Wilt u deze vervangen?", "managed_event_dialog_information_one": "{{names}} gebruikt al de URL /{{slug}}.", "managed_event_dialog_information_other": "{{names}} gebruiken al de URL /{{slug}}.", - "managed_event_dialog_clarification": "Als u ervoor kiest om het te vervangen, informeren wij hen daar over. Ga terug en verwijder ze als u ze niet wilt overschrijven.", + "managed_event_dialog_clarification": "Als u ervoor kiest om de URL te vervangen, informeren wij de leden daarover. Ga terug en verwijder ze als u de URL niet wilt overschrijven.", "review_event_type": "Gebeurtenistype bekijken", "looking_for_more_analytics": "Op zoek naar meer analyses?", "looking_for_more_insights": "Op zoek naar meer Insights?", "add_filter": "Filter toevoegen", - "select_user": "Gebruiker selecteren", - "select_event_type": "Gebeurtenistype selecteren", - "select_date_range": "Datumbereik selecteren", + "select_user": "Selecteer gebruiker", + "select_event_type": "Selecteer gebeurtenistype", + "select_date_range": "Selecteer datumbereik", "popular_events": "Populaire gebeurtenissen", "no_event_types_found": "Geen gebeurtenistypen gevonden", "average_event_duration": "Gemiddelde gebeurtenisduur", "most_booked_members": "Meest geboekte leden", "least_booked_members": "Minst geboekte leden", - "events_created": "Gemaakt gebeurtenissen", + "events_created": "Gemaakte gebeurtenissen", "events_completed": "Voltooide gebeurtenissen", "events_cancelled": "Geannuleerde gebeurtenissen", "events_rescheduled": "Verplaatste gebeurtenissen", @@ -1757,21 +1757,21 @@ "event_trends": "Gebeurtenistrends", "clear_filters": "Filters wissen", "hold": "Vasthouden", - "on_booking_option": "Ontvang betaling bij boeking", + "on_booking_option": "Betaling ontvangen bij boeken", "hold_option": "Afwezigheidsvergoeding in rekening brengen", - "card_held": "Kaart vastgehouden", + "card_held": "Kaartbetaling vastgehouden", "charge_card": "In rekening brengen op kaart", "card_charged": "In rekening gebracht op kaart", "no_show_fee_amount": "{{amount, currency}} afwezigheidsvergoeding", "no_show_fee": "Afwezigheidsvergoeding", - "submit_card": "Kaart versturen", - "submit_payment_information": "Betaalgegevens versturen", + "submit_card": "Kaart verzenden", + "submit_payment_information": "Betaalgegevens verzenden", "meeting_awaiting_payment_method": "Uw vergadering wacht op een betaalmethode", "no_show_fee_charged_email_subject": "Afwezigheidsvergoeding van {{amount, currency}} in rekening gebracht voor {{title}} om {{date}}", "no_show_fee_charged_text_body": "Afwezigheidsvergoeding is in rekening gebracht", "no_show_fee_charged_subtitle": "Er is een afwezigheidsvergoeding van {{amount, currency}} in rekening gebracht voor de volgende gebeurtenis", "error_charging_card": "Er is iets misgegaan met het in rekening brengen van de afwezigheidsvergoeding. Probeer het later opnieuw.", - "collect_no_show_fee": "Afwezigheidsvergoeding ontvangen", + "collect_no_show_fee": "Afwezigheidsvergoeding innen", "no_show_fee_charged": "Afwezigheidsvergoeding in rekening gebracht", "insights": "Insights", "testing_workflow_info_message": "Let er bij het testen van deze werkstroom op dat e-mails en sms minimaal 1 uur van tevoren kunnen gepland moeten worden", diff --git a/apps/web/public/static/locales/pl/common.json b/apps/web/public/static/locales/pl/common.json index c31a6bacce..160ded39c7 100644 --- a/apps/web/public/static/locales/pl/common.json +++ b/apps/web/public/static/locales/pl/common.json @@ -408,7 +408,7 @@ "add_time_availability": "Dodaj nowy przedział czasowy", "add_an_extra_layer_of_security": "Dodaj dodatkową warstwę zabezpieczeń do swojego konta na wypadek kradzieży hasła.", "2fa": "Uwierzytelnianie dwuetapowe", - "2fa_disabled": "Uwierzytelnianie dwuskładnikowe może zostać włączone tylko do uwierzytelniania adresu e-mail i hasła.", + "2fa_disabled": "Uwierzytelnianie dwuskładnikowe można włączyć tylko w celu uwierzytelniania adresu e-mail i hasła", "enable_2fa": "Włącz uwierzytelnianie dwuskładnikowe", "disable_2fa": "Wyłącz uwierzytelnianie dwuetapowe", "disable_2fa_recommendation": "Jeśli chcesz wyłączyć 2FA, zalecamy ponowne włączenie go tak szybko, jak to możliwe.", @@ -590,19 +590,19 @@ "round_robin": "Round Robin", "round_robin_description": "Cykl spotkań między wieloma członkami zespołu.", "managed_event": "Zarządzane wydarzenie", - "username_placeholder": "username", - "managed_event_description": "Masowo twórz i udostępniaj typy wydarzeń członkom zespołu", + "username_placeholder": "nazwa-użytkownika", + "managed_event_description": "Zbiorczo twórz typy wydarzeń i rozpowszechniaj je wśród członków zespołu", "managed": "Zarządzane", - "managed_event_url_clarification": "Pole „username” zostanie uzupełnione nazwami przypisanych użytkowników.", + "managed_event_url_clarification": "Pole „username” zostanie wypełnione nazwą przypisanego użytkownika", "assign_to": "Przypisz do", "add_members": "Dodaj członków...", - "count_members_one": "Liczba członków: {{count}}", - "count_members_other": "Liczba członków: {{count}}", + "count_members_one": "{{count}} członek", + "count_members_other": "{{count}} członków", "no_assigned_members": "Brak przypisanych członków", "assigned_to": "Przypisano do:", "start_assigning_members_above": "Rozpocznij przypisywanie członków powyżej", - "locked_fields_admin_description": "Członkowie nie będą mogli tego edytować.", - "locked_fields_member_description": "Ta opcja została zablokowana przez administratora zespołu.", + "locked_fields_admin_description": "Członkowie nie będą mogli tego edytować", + "locked_fields_member_description": "Ta opcja została zablokowana przez administratora zespołu", "url": "Adres URL", "hidden": "Ukryte", "readonly": "Tylko do odczytu", @@ -1154,7 +1154,7 @@ "variable_format": "Format zmiennej", "webhook_subscriber_url_reserved": "Adres URL subskrybenta Webhook jest już zdefiniowany.", "custom_input_as_variable_info": "Ignoruj wszystkie znaki specjalne etykiety dodatkowych danych wejściowych (używaj tylko liter i cyfr), użyj wielkich liter w przypadku wszystkich liter i zastąp spacje znakami podkreślenia.", - "using_booking_questions_as_variables": "Jak mogę używać pytań dotyczących rezerwacji jako zmiennych?", + "using_booking_questions_as_variables": "Jak używać pytań dotyczących rezerwacji jako zmiennych?", "download_desktop_app": "Pobierz aplikację komputerową", "set_ping_link": "Ustaw link do aplikacji Ping", "rate_limit_exceeded": "Przekroczono limit prób", @@ -1177,7 +1177,7 @@ "event_advanced_tab_title": "Zaawansowane", "event_setup_multiple_duration_error": "Konfiguracja wydarzeń: wiele czasów trwania wymaga co najmniej 1 opcji.", "event_setup_multiple_duration_default_error": "Konfiguracja wydarzenia: wybierz prawidłowy domyślny czas trwania.", - "event_setup_booking_limits_error": "Limity rezerwacji muszą być podane w kolejności rosnącej. [dzień,tydzień,miesiąc,rok]", + "event_setup_booking_limits_error": "Limity rezerwacji muszą być podane w kolejności rosnącej. [dzień, tydzień, miesiąc, rok]", "event_setup_duration_limits_error": "Limity czasu trwania muszą być podane w kolejności rosnącej. [dzień, tydzień, miesiąc, rok]", "select_which_cal": "Wybierz kalendarz, do którego chcesz dodać rezerwacje.", "custom_event_name": "Niestandardowa nazwa wydarzenia", @@ -1280,7 +1280,7 @@ "download_responses_description": "Pobierz wszystkie odpowiedzi do formularza w formacie CSV.", "download": "Pobierz", "download_recording": "Pobierz nagranie", - "recording_from_your_recent_call": "Nagranie z ostatniego połączenia w witrynie Cal.com jest gotowe do pobrania.", + "recording_from_your_recent_call": "Nagranie z ostatniego połączenia w witrynie Cal.com jest gotowe do pobrania", "create_your_first_form": "Utwórz pierwszy formularz", "create_your_first_form_description": "Za pomocą formularzy przekierowujących możesz zadawać pytania profilujące i przekierowywać użytkowników do odpowiednich osób lub typów wydarzeń.", "create_your_first_webhook": "Utwórz pierwszy Webhook", @@ -1326,7 +1326,7 @@ "exchange_version_2013_SP1": "2013 SP1", "exchange_version_2015": "2015", "exchange_version_2016": "2016", - "routing_forms_description": "Możesz tutaj zobaczyć wszystkie utworzone przez siebie formularze i przekierowania.", + "routing_forms_description": "Utwórz formularze kierujące uczestników do właściwych miejsc docelowych", "routing_forms_send_email_owner": "Wyślij wiadomość e-mail do właściciela", "routing_forms_send_email_owner_description": "Wysyła wiadomość e-mail do właściciela, gdy formularz zostanie przesłany.", "add_new_form": "Dodaj nowy formularz", @@ -1484,11 +1484,11 @@ "duration_limit_reached": "Osiągnięto limit czasu trwania tego typu wydarzenia", "admin_has_disabled": "Administrator wyłączył aplikację {{appName}}", "disabled_app_affects_event_type": "Administrator wyłączył aplikację {{appName}}, która ma wpływ na Twój typ wydarzenia ({{eventType}})", - "event_replaced_notice": "Administrator zastąpił jeden z Twoich typów wydarzeń.", + "event_replaced_notice": "Administrator zastąpił jeden z Twoich typów wydarzeń", "email_subject_slug_replacement": "Administrator zespołu zastąpił Twoje wydarzenie /{{slug}}", "email_body_slug_replacement_notice": "Administrator zespołu {{teamName}} zastąpił Twój typ wydarzenia /{{slug}} kontrolowanym przez siebie typem zarządzanego wydarzenia.", "email_body_slug_replacement_info": "Twój link nadal będzie działał, ale niektóre z jego ustawień mogły ulec zmianie. Możesz zobaczyć szczegóły w typach wydarzeń.", - "email_body_slug_replacement_suggestion": "Jeśli masz jakiekolwiek pytania dotyczące typu wydarzenia, skontaktuj się z administratorem.

Miłego planowania
Zespół Cal.com", + "email_body_slug_replacement_suggestion": "Jeśli masz jakiekolwiek pytania dotyczące typu wydarzenia, skontaktuj się z administratorem.

Miłego planowania,
Zespół Cal.com", "disable_payment_app": "Administrator wyłączył aplikację {{appName}}, która ma wpływ na Twój typ wydarzenia ({{title}}). Uczestnicy nadal mogą rezerwować wydarzenie tego typu, ale nie zostaną poproszeni o płatność. Możesz ukryć ten typ wydarzenia, aby temu zapobiec, póki administrator nie włączy ponownie Twojej metody płatności.", "payment_disabled_still_able_to_book": "Uczestnicy nadal mogą rezerwować wydarzenie tego typu, ale nie zostaną poproszeni o płatność. Możesz ukryć ten typ wydarzenia, aby temu zapobiec, póki administrator nie włączy ponownie Twojej metody płatności.", "app_disabled_with_event_type": "Administrator wyłączył aplikację {{appName}}, która ma wpływ na Twój typ wydarzenia ({{title}}).", @@ -1661,7 +1661,7 @@ "not_enough_seats": "Za mało miejsc", "form_builder_field_already_exists": "Pole o tej nazwie już istnieje", "form_builder_field_add_subtitle": "Dostosuj pytania zadawane na stronie rezerwacji", - "show_on_booking_page": "Wyświetl na stronie rezerwacji", + "show_on_booking_page": "Pokaż na stronie rezerwacji", "get_started_zapier_templates": "Zacznij korzystać z szablonów Zapier", "team_is_unpublished": "Zespół {{team}} nie został opublikowany", "team_is_unpublished_description": "Link tego zespołu jest obecnie niedostępny. Skontaktuj się z właścicielem zespołu lub poproś o jego opublikowanie.", @@ -1687,12 +1687,12 @@ "add_a_new_route": "Dodaj nowe przekierowanie", "make_informed_decisions": "Podejmuj świadome decyzje dzięki funkcji Insights", "make_informed_decisions_description": "Panel Insights zawiera informacje o całej aktywności zespołu i trendach umożliwiających lepsze planowanie i podejmowanie decyzji w zespole.", - "view_bookings_across": "Zobacz rezerwacje wszystkich członków", - "view_bookings_across_description": "Zobacz, kto ma najwięcej rezerwacji i upewnij się, że Twój zespół jest równomiernie obciążony pracą.", + "view_bookings_across": "Wyświetl rezerwacje wszystkich członków", + "view_bookings_across_description": "Zobacz, kto ma najwięcej rezerwacji i upewnij się, że Twój zespół jest równomiernie obciążony", "identify_booking_trends": "Identyfikuj trendy w rezerwacjach", - "identify_booking_trends_description": "Zobacz, które dni tygodnia i pory dnia są popularne wśród rezerwujących.", + "identify_booking_trends_description": "Zobacz, które dni tygodnia i pory dnia są popularne wśród rezerwujących", "spot_popular_event_types": "Przekonaj się, które typy wydarzeń są popularne", - "spot_popular_event_types_description": "Zobacz, które typy wydarzeń są najczęściej klikane i rezerwowane.", + "spot_popular_event_types_description": "Zobacz, które typy wydarzeń są najczęściej klikane i rezerwowane", "no_responses_yet": "Nie ma jeszcze odpowiedzi", "this_will_be_the_placeholder": "To będzie symbol zastępczy", "this_meeting_has_not_started_yet": "To spotkanie jeszcze się nie rozpoczęło", @@ -1707,53 +1707,53 @@ "timezone_info": "Strefa czasowa osoby rezerwowanej", "event_end_time_variable": "Czas zakończenia wydarzenia", "event_end_time_info": "Czas zakończenia wydarzenia", - "cancel_url_variable": "Adres URL do anulowania", - "cancel_url_info": "Adres URL do anulowania rezerwacji", - "reschedule_url_variable": "Adres URL do zmiany terminu", - "reschedule_url_info": "Adres URL do zmiany terminu rezerwacji", + "cancel_url_variable": "Adres URL umożliwiający anulowanie", + "cancel_url_info": "Adres URL umożliwiający anulowanie rezerwacji", + "reschedule_url_variable": "Adres URL umożliwiający zmianę terminu", + "reschedule_url_info": "Adres URL umożliwiający zmianę terminu rezerwacji", "invalid_event_name_variables": "W Twojej nazwie wydarzenia występuje nieprawidłowa zmienna.", "select_all": "Zaznacz wszystko", "default_conferencing_bulk_title": "Zbiorcza aktualizacja istniejących typów wydarzeń", - "members_default_schedule": "Domyślny harmonogram użytkownika", - "set_by_admin": "Ustalone przez administratora zespołu", + "members_default_schedule": "Domyślny harmonogram członka", + "set_by_admin": "Ustawia administrator zespołu", "members_default_location": "Domyślna lokalizacja członka", "members_default_schedule_description": "Będziemy używać domyślnego harmonogramu dostępności każdego członka. Każdy z nich będzie mógł go edytować lub zmienić.", - "requires_at_least_one_schedule": "Musisz mieć co najmniej jeden harmonogram.", + "requires_at_least_one_schedule": "Musisz mieć co najmniej jeden harmonogram", "default_conferencing_bulk_description": "Aktualizuj lokalizacje wybranych typów wydarzeń", - "locked_for_members": "Zablokowane dla członków", - "locked_apps_description": "Członkowie będą mogli zobaczyć aktywne aplikacje, ale nie będą mogli zmieniać ich ustawień.", - "locked_webhooks_description": "Członkowie będą mogli zobaczyć aktywne webhooki, ale nie będą mogli zmieniać ich ustawień.", - "locked_workflows_description": "Członkowie będą mogli zobaczyć aktywne przepływy pracy, ale nie będą mogli zmieniać ich ustawień.", - "app_not_connected": "Nie połączono konta aplikacji {{appName}}.", - "connect_now": "Połącz teraz", + "locked_for_members": "Zablokowany dla członków", + "locked_apps_description": "Członkowie będą mogli sprawdzić aktywne aplikacje, ale nie będą mogli edytować ich ustawień", + "locked_webhooks_description": "Członkowie będą mogli sprawdzić aktywne webhooki, ale nie będą mogli edytować ich ustawień", + "locked_workflows_description": "Członkowie będą mogli sprawdzić aktywne przepływy pracy, ale nie będą mogli edytować ich ustawień", + "app_not_connected": "Nie podłączono konta {{appName}}.", + "connect_now": "Podłącz teraz", "managed_event_dialog_confirm_button_one": "Zastąp i poinformuj {{count}} członka", - "managed_event_dialog_confirm_button_other": "Zastąp i poinformuj {{count}} członków", + "managed_event_dialog_confirm_button_other": "Zastąp i powiadom {{count}} członków", "managed_event_dialog_title_one": "Adres URL /{{slug}} już istnieje dla {{count}} członka. Czy chcesz go zastąpić?", "managed_event_dialog_title_other": "Adres URL /{{slug}} już istnieje dla {{count}} członków. Czy chcesz go zastąpić?", "managed_event_dialog_information_one": "{{names}} używa już adresu URL /{{slug}}.", "managed_event_dialog_information_other": "{{names}} używają już adresu URL /{{slug}}.", - "managed_event_dialog_clarification": "Jeśli zdecydujesz się go zastąpić, powiadomimy te osoby. Wróć i usuń je, jeśli nie chcesz nadpisywać adresu.", - "review_event_type": "Zobacz szczegóły typu wydarzenia", + "managed_event_dialog_clarification": "Jeśli zdecydujesz się go zastąpić, powiadomimy te osoby. Wróć i usuń je, jeśli nie chcesz zastępować adresu.", + "review_event_type": "Sprawdź typ wydarzenia", "looking_for_more_analytics": "Szukasz dokładniejszych danych analitycznych?", - "looking_for_more_insights": "Potrzebujesz dokładniejszych Statystyk?", + "looking_for_more_insights": "Szukasz dokładniejszych danych Insights?", "add_filter": "Dodaj filtr", "select_user": "Wybierz użytkownika", "select_event_type": "Wybierz typ wydarzenia", "select_date_range": "Wybierz zakres dat", "popular_events": "Popularne wydarzenia", - "no_event_types_found": "Nie znaleziono typów wydarzeń", + "no_event_types_found": "Nie znaleziono żadnych typów wydarzeń", "average_event_duration": "Średni czas trwania wydarzenia", "most_booked_members": "Najczęściej rezerwowani członkowie", "least_booked_members": "Najrzadziej rezerwowani członkowie", "events_created": "Utworzone wydarzenia", - "events_completed": "Zakończone wydarzenia", + "events_completed": "Ukończone wydarzenia", "events_cancelled": "Anulowane wydarzenia", "events_rescheduled": "Przełożone wydarzenia", "from_last_period": "od ostatniego okresu", "from_to_date_period": "Od: {{startDate}} Do: {{endDate}}", "analytics_for_organisation": "Insights", "subtitle_analytics": "Dowiedz się więcej o aktywności Twojego zespołu", - "redirect_url_warning": "Dodanie przekierowania sprawi, że strona powodzenia zostanie wyłączona. Upewnij się, że na Twojej niestandardowej stronie powodzenia umieścisz informację o potwierdzeniu rezerwacji.", + "redirect_url_warning": "Dodanie przekierowania wyłączy stronę powodzenia. Upewnij się, że na Twojej niestandardowej stronie powodzenia znajduje się wzmianka o potwierdzeniu rezerwacji.", "event_trends": "Trendy wydarzeń", "clear_filters": "Wyczyść filtry", "hold": "Zablokowano", @@ -1774,10 +1774,10 @@ "collect_no_show_fee": "Pobierz opłatę za niestawienie się", "no_show_fee_charged": "Pobrano opłatę za niestawienie się", "insights": "Insights", - "testing_workflow_info_message": "Podczas testowania tego przepływu pracy pamiętaj, że wiadomości e-mail i SMS mogą zostać zaplanowane najpóźniej godzinę przed terminem ich dostarczenia.", - "insights_no_data_found_for_filter": "Nie znaleziono danych dotyczących wybranego filtra lub wskazanych dat.", - "acknowledge_booking_no_show_fee": "Rozumiem, że jeśli nie wezmę udziału w wydarzeniu, z mojej karty pobrana zostanie opłata w wysokości {{amount, currency}}.", - "card_details": "Dane karty płatniczej", + "testing_workflow_info_message": "Podczas testowania tego przepływu pracy pamiętaj, że wiadomości e-mail i SMS można zaplanować jedynie z co najmniej 1-godzinnym wyprzedzeniem", + "insights_no_data_found_for_filter": "Nie znaleziono danych dotyczących wybranego filtru lub wskazanych dat.", + "acknowledge_booking_no_show_fee": "Rozumiem, że jeśli nie wezmę udziału w wydarzeniu, z mojej karty pobrana zostanie opłata za niestawienie się w wysokości {{amount, currency}}.", + "card_details": "Dane karty", "seats_and_no_show_fee_error": "Obecnie nie można udostępnić miejsc ani naliczyć opłaty za niestawienie się.", "complete_your_booking": "Ukończ rezerwację", "complete_your_booking_subject": "Ukończ rezerwację: {{title}} dnia {{date}}", diff --git a/apps/web/public/static/locales/pt/common.json b/apps/web/public/static/locales/pt/common.json index 65c83e4aa3..b3eb0917ff 100644 --- a/apps/web/public/static/locales/pt/common.json +++ b/apps/web/public/static/locales/pt/common.json @@ -71,7 +71,7 @@ "event_still_awaiting_approval": "Um evento ainda aguarda a sua aprovação", "booking_submitted_subject": "Reserva submetida: {{title}} em {{date}}", "download_recording_subject": "Transferir gravação: {{title}} em {{date}}", - "download_your_recording": "Transferir a sua gravação", + "download_your_recording": "Transfira a sua gravação", "your_meeting_has_been_booked": "A sua reunião foi reservada", "event_type_has_been_rescheduled_on_time_date": "Seu {{title}} foi remarcado para {{date}}.", "event_has_been_rescheduled": "O seu evento foi reagendado.", @@ -593,7 +593,7 @@ "username_placeholder": "nome-de-utilizador", "managed_event_description": "Criar e distribuir tipos de eventos em conjunto para os membros da equipa", "managed": "Gerido", - "managed_event_url_clarification": "\"username\" será substituído pelo nome de utilizador dos membros atribuídos", + "managed_event_url_clarification": "\"nome-de-utilizador\" será substituído pelo nome de utilizador dos membros atribuídos", "assign_to": "Atribuir a", "add_members": "Adicionar membros...", "count_members_one": "{{count}} membro", @@ -1551,7 +1551,7 @@ "create_your_first_team_webhook_description": "Crie o seu primeiro webhook para este tipo de evento de equipa", "create_webhook_team_event_type": "Crie um webhook para este tipo de evento de equipa", "disable_success_page": "Desativar a Página de sucesso (só funciona se tiver um URL de redirecionamento)", - "invalid_admin_password": "Você é um administrador, mas ainda não tem uma palavra-passe com pelo menos 15 caracteres", + "invalid_admin_password": "Você é administrador, mas ainda não tem uma palavra-passe com pelo menos 15 caracteres", "change_password_admin": "Altere a palavra-passe para ter acesso de administrador", "username_already_taken": "O nome de utilizador já está a ser utilizado", "assignment": "Atribuição", @@ -1743,8 +1743,8 @@ "popular_events": "Eventos populares", "no_event_types_found": "Não foram encontrados tipos de eventos", "average_event_duration": "Duração média do evento", - "most_booked_members": "Membros mais reservados", - "least_booked_members": "Membros menos reservados", + "most_booked_members": "Membros com mais reservas", + "least_booked_members": "Membros com menos reservas", "events_created": "Eventos criados", "events_completed": "Eventos concluídos", "events_cancelled": "Eventos cancelados", @@ -1756,10 +1756,10 @@ "redirect_url_warning": "Ao adicionar um redirecionamento irá desativar a página de sucesso. Certifique-se que indica que \"A reserva foi confirmada\" na sua página de sucesso personalizada.", "event_trends": "Tendências de evento", "clear_filters": "Limpar filtros", - "hold": "Cativo", + "hold": "Reter", "on_booking_option": "Receber o pagamento na reserva", "hold_option": "Cobrar taxa de não-comparência", - "card_held": "Cartão cativo", + "card_held": "Cartão retido", "charge_card": "Debitar no cartão", "card_charged": "Cartão debitado", "no_show_fee_amount": "Taxa de não-comparência de {{amount, currency}}", diff --git a/apps/web/public/static/locales/ro/common.json b/apps/web/public/static/locales/ro/common.json index 91fcf55e5f..d3a3097b55 100644 --- a/apps/web/public/static/locales/ro/common.json +++ b/apps/web/public/static/locales/ro/common.json @@ -594,13 +594,13 @@ "managed_event_description": "Creați și distribuiți membrilor echipei tipuri de evenimente în masă", "managed": "Configurat", "managed_event_url_clarification": "Câmpul „username” va fi completat cu numele de utilizator ale membrilor desemnați", - "assign_to": "Atribuiți către", + "assign_to": "Alocați la", "add_members": "Adăugare membri...", "count_members_one": "{{count}} membru", "count_members_other": "{{count}} (de) membri", - "no_assigned_members": "Niciun membru desemnat", - "assigned_to": "Atribuit către", - "start_assigning_members_above": "Începeți să desemnați membri mai sus", + "no_assigned_members": "Niciun membru alocat", + "assigned_to": "Alocat la", + "start_assigning_members_above": "Începeți să alocați membri mai sus", "locked_fields_admin_description": "Membrii nu vor putea modifica acest câmp", "locked_fields_member_description": "Această opțiune a fost blocată de administratorul echipei", "url": "URL", @@ -757,7 +757,7 @@ "delete_event_type": "Ștergeți tipul de eveniment?", "delete_managed_event_type": "Ștergeți tipul de eveniment gestionat?", "delete_event_type_description": "Nicio persoană căreia i-ați transmis acest link nu va mai putea efectua rezervări prin intermediul lui.", - "delete_managed_event_type_description": "
  • Membrii alocați acestui tip de eveniment își vor vedea și tipurile de evenimente șterse.
  • Nicio persoană căreia i-au transmis linkul nu va mai putea efectua rezervări prin intermediul lui.
", + "delete_managed_event_type_description": "
  • Pentru membrii alocați la acest tip de eveniment vor fi șterse și tipurile de evenimente.
  • Nicio persoană căreia i-au transmis linkul nu va mai putea efectua rezervări prin intermediul lui.
", "confirm_delete_event_type": "Da, șterge", "delete_account": "Ștergeți contul", "confirm_delete_account": "Da, ștergeți contul", @@ -1326,7 +1326,7 @@ "exchange_version_2013_SP1": "2013 SP1", "exchange_version_2015": "2015", "exchange_version_2016": "2016", - "routing_forms_description": "Puteți vedea aici toate formularele și parcursurile pe care le-ați creat.", + "routing_forms_description": "Creați formulare pentru a direcționa participanții către destinațiile corecte", "routing_forms_send_email_owner": "Trimiteți un e-mail către proprietar", "routing_forms_send_email_owner_description": "Trimite un e-mail către proprietar atunci când este trimis formularul", "add_new_form": "Adăugați un formular nou", @@ -1395,7 +1395,7 @@ "billing_help_title": "Aveți nevoie de altceva?", "billing_help_description": "Dacă aveți nevoie de ajutor suplimentar pentru facturare, echipa noastră de asistență este aici pentru a vă ajuta.", "billing_help_cta": "Contactați serviciul de asistență", - "ignore_special_characters_booking_questions": "Ignorați caracterele speciale din identificatorul de întrebări despre rezervare. Utilizați doar litere și cifre", + "ignore_special_characters_booking_questions": "Ignorați caracterele speciale în identificatorul întrebării despre rezervare. Utilizați doar litere și cifre", "retry": "Reîncercați", "fetching_calendars_error": "A survenit o problemă legată de recuperarea calendarelor dvs. <1>Încercați din nou sau contactați serviciul de asistență pentru clienți.", "calendar_connection_fail": "Conexiune calendar nereușită", @@ -1486,8 +1486,8 @@ "disabled_app_affects_event_type": "Un administrator a dezactivat {{appName}} care afectează tipul tău de eveniment {{eventType}}", "event_replaced_notice": "Un administrator a înlocuit unul dintre tipurile dvs. de evenimente", "email_subject_slug_replacement": "Un administrator al echipei v-a înlocuit evenimentul /{{slug}}", - "email_body_slug_replacement_notice": "Un administrator din echipa {{teamName}} v-a înlocuit tipul de eveniment /{{slug}} cu un tip de eveniment gestionat pe care îl controlează.", - "email_body_slug_replacement_info": "Linkul dvs. va funcționa în continuare, dar este posibil ca unele setări să se fi schimbat. Îl puteți examina în tipurile de evenimente.", + "email_body_slug_replacement_notice": "Un administrator din echipa {{teamName}} v-a înlocuit tipul de eveniment /{{slug}} cu un tip de eveniment configurat pe care îl controlează.", + "email_body_slug_replacement_info": "Linkul dvs. va funcționa în continuare, dar este posibil ca unele setări să se fi schimbat. Îl puteți consulta în tipurile de evenimente.", "email_body_slug_replacement_suggestion": "Dacă aveți întrebări despre tipul de eveniment, luați legătura cu administratorul dvs.

Programări cu spor,
Echipa Cal.com", "disable_payment_app": "Administratorul a dezactivat {{appName}} care afectează tipul tău de eveniment {{title}}. Participanții încă pot să facă rezervări pentru acest tip de eveniment, dar nu li se va solicita să plătească. Poți ascunde tipul de eveniment pentru a preveni acest lucru până când administratorul tău reactivează metoda ta de plată.", "payment_disabled_still_able_to_book": "Participanții încă pot să facă rezervări pentru acest tip de eveniment, dar nu li se va solicita să plătească. Poți ascunde tipul de eveniment pentru a preveni acest lucru până când administratorul tău reactivează metoda ta de plată.", @@ -1685,14 +1685,14 @@ "add_1_option_per_line": "Adăugați o opțiune pe linie", "select_a_router": "Selectați un parcurs", "add_a_new_route": "Adăugați un parcurs nou", - "make_informed_decisions": "Ia decizii informate cu Insights", - "make_informed_decisions_description": "Tabloul de bord Insights prezintă toate activitățile din echipa ta și îți arată tendințele care te ajută să îți programezi mai bine echipa și să iei decizii mai bune.", - "view_bookings_across": "Vezi rezervările tuturor membrilor", - "view_bookings_across_description": "Vezi cine primește cele mai multe rezervări și asigură cea mai bună distribuție în echipa ta", - "identify_booking_trends": "Identifică tendințele legate de rezervare", - "identify_booking_trends_description": "Vezi ce zile din săptămână și ore din timpul zilei sunt populare pentru persoanele care fac rezervări", - "spot_popular_event_types": "Identifică tipuri de evenimente populare", - "spot_popular_event_types_description": "Vezi care dintre tipurile de evenimente primesc cele mai multe clicuri și rezervări", + "make_informed_decisions": "Luați decizii informate cu Insights", + "make_informed_decisions_description": "Tabloul de bord Insights prezintă toate activitățile din echipa dvs. și vă arată tendințele care vă ajută să faceți programări mai bune pentru echipă și să luați decizii mai bune.", + "view_bookings_across": "Vizualizați rezervările tuturor membrilor", + "view_bookings_across_description": "Vedeți cine primește cele mai multe rezervări și asigurați cea mai bună distribuție în echipa dvs.", + "identify_booking_trends": "Identificați tendințele legate de rezervări", + "identify_booking_trends_description": "Vedeți ce zile din săptămână și ore din timpul zilei sunt populare pentru persoanele care fac rezervări", + "spot_popular_event_types": "Identificați tipurile de evenimente populare", + "spot_popular_event_types_description": "Vedeți care dintre tipurile de evenimente primesc cele mai multe clicuri și rezervări", "no_responses_yet": "Nu există încă răspunsuri", "this_will_be_the_placeholder": "Acesta va fi substituentul", "this_meeting_has_not_started_yet": "Această întâlnire nu a început încă", @@ -1716,7 +1716,7 @@ "default_conferencing_bulk_title": "Actualizare în masă tipuri de evenimente existente", "members_default_schedule": "Programul implicit al membrului", "set_by_admin": "Setat de administratorul echipei", - "members_default_location": "Localizarea implicită a membrului", + "members_default_location": "Locația implicită a membrului", "members_default_schedule_description": "Vom utiliza programul implicit de disponibilitate al fiecărui membru. Aceștia îl vor putea edita sau schimba.", "requires_at_least_one_schedule": "Este necesar să aveți cel puțin un program", "default_conferencing_bulk_description": "Actualizați locațiile pentru tipurile de evenimente selectate", @@ -1733,13 +1733,13 @@ "managed_event_dialog_information_one": "{{names}} folosește deja URL-ul /{{slug}}.", "managed_event_dialog_information_other": "{{names}} folosesc deja URL-ul /{{slug}}.", "managed_event_dialog_clarification": "Îi vom anunța dacă alegeți să îl înlocuiți. Reveniți și ștergeți-i dacă nu doriți să îl suprascrieți.", - "review_event_type": "Evaluați tipul de eveniment", - "looking_for_more_analytics": "Cauți mai multe analize?", + "review_event_type": "Revizuiți tipul de eveniment", + "looking_for_more_analytics": "Căutați mai multe analize?", "looking_for_more_insights": "Căutați mai multe perspective?", - "add_filter": "Adaugă filtru", - "select_user": "Selectează utilizatorul", - "select_event_type": "Selectează tipul de eveniment", - "select_date_range": "Selectează intervalul de date", + "add_filter": "Adăugare filtru", + "select_user": "Selectare utilizator", + "select_event_type": "Selectare tip eveniment", + "select_date_range": "Selectare interval de date", "popular_events": "Evenimente populare", "no_event_types_found": "Nu s-a găsit niciun tip de eveniment", "average_event_duration": "Durata medie a evenimentului", @@ -1764,8 +1764,8 @@ "card_charged": "Card debitat", "no_show_fee_amount": "Taxă de neprezentare în valoare de {{amount, currency}}", "no_show_fee": "Taxă de neprezentare", - "submit_card": "Trimiteți cardul", - "submit_payment_information": "Trimiteți informațiile de plată", + "submit_card": "Trimitere card", + "submit_payment_information": "Trimitere informații de plată", "meeting_awaiting_payment_method": "Ședința va începe după efectuarea plății", "no_show_fee_charged_email_subject": "Taxă de neprezentare în valoare de {{amount, currency}}, percepută pentru {{title}}, pe {{date}}", "no_show_fee_charged_text_body": "Taxa de neprezentare a fost percepută", @@ -1774,7 +1774,7 @@ "collect_no_show_fee": "Colectare taxă de neprezentare", "no_show_fee_charged": "Taxă de neprezentare percepută", "insights": "Insights", - "testing_workflow_info_message": "La testarea acestui flux de lucru, țineți cont de faptul că e-mailurile și SMS-urile pot fi programate cu cel puțin o oră în prealabil", + "testing_workflow_info_message": "La testarea acestui flux de lucru, țineți cont de faptul că e-mailurile și SMS-urile pot fi programate doar cu cel puțin o oră în prealabil", "insights_no_data_found_for_filter": "Nu s-au găsit date pentru filtrul selectat sau pentru datele selectate.", "acknowledge_booking_no_show_fee": "Dacă nu particip la acest eveniment, accept să mi se perceapă de pe card o taxă de neprezentare în valoare de {{amount, currency}}.", "card_details": "Detalii card", diff --git a/apps/web/public/static/locales/ru/common.json b/apps/web/public/static/locales/ru/common.json index 94d93fa4bf..bdc34e79d0 100644 --- a/apps/web/public/static/locales/ru/common.json +++ b/apps/web/public/static/locales/ru/common.json @@ -70,7 +70,7 @@ "event_awaiting_approval_subject": "Ожидает подтверждения: {{title}} на {{date}}", "event_still_awaiting_approval": "Событие все еще ждет вашего подтверждения", "booking_submitted_subject": "Бронирование отправлено: {{title}}, {{date}}", - "download_recording_subject": "Скачать запись: {{title}} от {{date}}", + "download_recording_subject": "Скачайте запись: {{title}}, {{date}}", "download_your_recording": "Скачать запись", "your_meeting_has_been_booked": "Ваша встреча была забронирована", "event_type_has_been_rescheduled_on_time_date": "Ваше {{title}} было перенесено на {{date}}.", @@ -408,7 +408,7 @@ "add_time_availability": "Добавить новый интервал времени", "add_an_extra_layer_of_security": "Добавьте дополнительный уровень безопасности в свою учетную запись на случай кражи пароля.", "2fa": "Двухфакторная авторизация", - "2fa_disabled": "Двухфакторную аутентификацию можно включить только для аутентификации по электронной почте и паролю", + "2fa_disabled": "Двухфакторную авторизацию можно включить только при использовании авторизации по электронной почте и паролю", "enable_2fa": "Включить двухфакторную авторизацию", "disable_2fa": "Отключить двухфакторную авторизацию", "disable_2fa_recommendation": "Если вам нужно отключить двухфакторную авторизацию, мы рекомендуем включить её как можно скорее.", @@ -591,15 +591,15 @@ "round_robin_description": "Цикл встреч между несколькими членами команды.", "managed_event": "Управляемое событие", "username_placeholder": "имя пользователя", - "managed_event_description": "Создавайте пакеты типов событий и рассылайте их участнкиам команды", - "managed": "Управляемые", - "managed_event_url_clarification": "в \"username\" указывается имя пользователя, присвоенное участнику", - "assign_to": "Назначить", + "managed_event_description": "Создавайте типы событий и массово рассылайте их участникам команды", + "managed": "Управляемое", + "managed_event_url_clarification": "в «username» указываются имена пользователей, которым будет назначен тип события", + "assign_to": "Назначить участнику", "add_members": "Добавить участников...", - "count_members_one": "{{count}} участник", - "count_members_other": "{{count}} участника(ов)", + "count_members_one": "Участники: {{count}}", + "count_members_other": "Участники: {{count}}", "no_assigned_members": "Нет назначенных участников", - "assigned_to": "Назначено", + "assigned_to": "Назначено участнику", "start_assigning_members_above": "Начните назначать участников", "locked_fields_admin_description": "Участники не смогут это редактировать", "locked_fields_member_description": "Эта опция заблокирована администратором команды", @@ -755,9 +755,9 @@ "slot_interval": "Интервалы времени", "slot_interval_default": "Использовать длину события (по умолчанию)", "delete_event_type": "Удалить тип события?", - "delete_managed_event_type": "Удалить тип управляемого события?", - "delete_event_type_description": "Если вы поделились с кем-то ссылкой, с ее помощью больше нельзя оформлять бронирование.", - "delete_managed_event_type_description": "
  • У участников, назначенных для этого типа события, будут удалены их типы событий.
  • Если они поделились с кем-то ссылкой, с ее помощью больше нельзя будет оформлять бронирование.
", + "delete_managed_event_type": "Удалить управляемый тип события?", + "delete_event_type_description": "Пользователи, с которыми вы поделились этой ссылкой, больше не смогут оформлять бронирование с ее помощью.", + "delete_managed_event_type_description": "
  • После того как вы назначите участникам данный тип события, их прежние типы событий будут удалены.
  • Пользователи, с которыми участники поделились своей ссылкой, больше не смогут оформлять бронирование с ее помощью.
", "confirm_delete_event_type": "Да, удалить", "delete_account": "Удалить аккаунт", "confirm_delete_account": "Да, удалить аккаунт", @@ -1177,7 +1177,7 @@ "event_advanced_tab_title": "Расширенные", "event_setup_multiple_duration_error": "Настройка события: чтобы выбрать несколько вариантов продолжительности, требуется как минимум 1 параметр.", "event_setup_multiple_duration_default_error": "Настройка события: выберите допустимую продолжительность по умолчанию.", - "event_setup_booking_limits_error": "Лимиты бронирования должны указываться по возрастанию [день, неделя, месяц, год].", + "event_setup_booking_limits_error": "Лимиты бронирования должны указываться от меньшей единицы к большей [день, неделя, месяц, год]", "event_setup_duration_limits_error": "Продолжительность бронирования должна указываться от меньшей единицы к большей [день, неделя, месяц, год]", "select_which_cal": "Выберите, в какой календарь добавить бронирование", "custom_event_name": "Пользовательское имя события", @@ -1276,11 +1276,11 @@ "attendee_name_info": "Участник, на которого оформляется бронирование", "to": "Кому", "workflow_turned_on_successfully": "Состояние рабочего процесса «{{workflowName}}» изменено на «{{offOn}}»", - "download_responses": "Скачивайте ответы", - "download_responses_description": "Скачивайте все ответы пользователей, заполнивших форму, в формате CSV.", + "download_responses": "Скачать ответы", + "download_responses_description": "Скачать все ответы пользователей, заполнивших форму, в формате CSV.", "download": "Скачать", "download_recording": "Скачать запись", - "recording_from_your_recent_call": "Запись вашего недавнего звонка на Cal.com готова для загрузки", + "recording_from_your_recent_call": "Запись вашего недавнего звонка на Cal.com готова для скачивания", "create_your_first_form": "Создайте первую форму", "create_your_first_form_description": "Формы маршрутизации позволяют задавать пользователям уточняющие вопросы, чтобы перенаправлять их к соответствующему лицу или типу события.", "create_your_first_webhook": "Создайте свой первый вебхук", @@ -1326,7 +1326,7 @@ "exchange_version_2013_SP1": "2013 SP1", "exchange_version_2015": "2015", "exchange_version_2016": "2016", - "routing_forms_description": "Здесь показываются все созданные вами формы и маршруты.", + "routing_forms_description": "Создавайте формы, чтобы направлять участников к нужному сотруднику", "routing_forms_send_email_owner": "Отправляйте письма владельцу", "routing_forms_send_email_owner_description": "Отправляйте владельцу формы электронное письмо, когда пользователь ее заполняет", "add_new_form": "Добавить новую форму", @@ -1395,7 +1395,7 @@ "billing_help_title": "Нужно что-нибудь еще?", "billing_help_description": "Если вам нужна дальнейшая помощь с оплатой счета, наша служба поддержки готова помочь.", "billing_help_cta": "Обратиться в службу поддержки", - "ignore_special_characters_booking_questions": "Не используйте специальные символы в идентификаторе вопроса о бронировании. Используйте только буквы и цифры", + "ignore_special_characters_booking_questions": "Не используйте в идентификаторе вопроса о бронировании специальные символы. Используйте только буквы и цифры", "retry": "Повторить", "fetching_calendars_error": "Ошибка при загрузке календарей. <1>Попробуйте еще раз или обратитесь в службу поддержки.", "calendar_connection_fail": "Не удалось подключиться к календарю", @@ -1484,11 +1484,11 @@ "duration_limit_reached": "Достигнут лимит продолжительности бронирования для этого типа события", "admin_has_disabled": "Администратор отключил {{appName}}", "disabled_app_affects_event_type": "Администратор отключил {{appName}}, которое влияет на тип мероприятия {{eventType}}", - "event_replaced_notice": "Администратор заменил вам один из типов событий", - "email_subject_slug_replacement": "Администратор команды заменил вам событие /{{slug}}", - "email_body_slug_replacement_notice": "Администратор команды {{teamName}} заменил вам тип события /{{slug}} на тип управляемого им события.", - "email_body_slug_replacement_info": "Ваша ссылка работает, но, возможно, для нее изменились некоторые настройки. Вы можете просмотреть ее в типах событий.", - "email_body_slug_replacement_suggestion": "Если у вас есть вопросы о типе события, свяжитесь с администратором.

Счастливого планирования,
команда Cal.com", + "event_replaced_notice": "Администратор заменил один из ваших типов событий", + "email_subject_slug_replacement": "Администратор команды заменил ваше событие /{{slug}}", + "email_body_slug_replacement_notice": "Администратор команды {{teamName}} заменил ваш тип события /{{slug}} на управляемый им тип события.", + "email_body_slug_replacement_info": "Ссылка по-прежнему будет работать, но некоторые настройки могли измениться. Вы можете просмотреть их в списке типов событий.", + "email_body_slug_replacement_suggestion": "Если у вас есть вопросы о данном типе события, свяжитесь с администратором.

Счастливого планирования!
Команда Cal.com", "disable_payment_app": "Приложение {{appName}}, используемое для событий типа «{{title}}», отключено администратором. Участники по-прежнему могут бронировать данный тип событий, но не будут получать сообщение о необходимости произвести оплату. Чтобы этого избежать, вы можете скрыть тип событий до тех пор, пока администратор снова не активирует способ оплаты.", "payment_disabled_still_able_to_book": "Участники все еще могут забронировать этот тип мероприятия, но они не получат сообщение о том, что нужно произвести оплату. Чтобы этого избежать, можно скрыть тип мероприятия, пока администратор не активирует способ оплаты снова.", "app_disabled_with_event_type": "Администратор отключил {{appName}}, которое влияет на тип мероприятия {{title}}.", @@ -1551,7 +1551,7 @@ "create_your_first_team_webhook_description": "Создайте первый вебхук для этого командного типа событий", "create_webhook_team_event_type": "Создать вебхук для этого командного типа событий", "disable_success_page": "Отключить страницу успешного бронирования (работает, только если у вас есть URL-адрес перенаправления)", - "invalid_admin_password": "У вас есть права администратора, но ваш пароль короче 15 символов, либо у вас еще нет 2FA", + "invalid_admin_password": "У вас есть права администратора, но ваш пароль короче 15 символов или вы не настроили двухфакторную авторизацию", "change_password_admin": "Измените пароль, чтобы получить административный доступ", "username_already_taken": "Имя пользователя уже занято", "assignment": "Назначение", @@ -1584,7 +1584,7 @@ "ee_enterprise_license": "Enterprise-лицензия «/ee»", "enterprise_booking_fee": "От {{enterprise_booking_fee}}/месяц", "enterprise_license_includes": "Всё для коммерческого использования", - "no_need_to_keep_your_code_open_source": "Необязательно оставлять исходный код открытым", + "no_need_to_keep_your_code_open_source": "Нет необходимости оставлять исходный код открытым", "repackage_rebrand_resell": "Простая переупаковка, ребрендинг и перепродажа", "a_vast_suite_of_enterprise_features": "Множество функций для предприятий", "free_license_fee": "0,00 $/месяц", @@ -1686,12 +1686,12 @@ "select_a_router": "Выбрать форму маршрутизации", "add_a_new_route": "Добавить новый маршрут", "make_informed_decisions": "Принимайте обоснованные решения с помощью Insights", - "make_informed_decisions_description": "На нашей панели мониторинга Insights видна вся работа вашей команды, а выявленные ею тенденции позволяют лучше планировать и принимать решения.", - "view_bookings_across": "Просмотреть бронирования для всех участников", - "view_bookings_across_description": "Выясните, кто участвует в мероприятиях больше всех, и обеспечьте оптимальное распределение задач в команде", - "identify_booking_trends": "Определяйте тренды бронирования", - "identify_booking_trends_description": "Посмотрите, в какое время и в какие дни бронирование оформляют чаще всего", - "spot_popular_event_types": "Находите популярные типы событий", + "make_informed_decisions_description": "На нашей панели мониторинга Insights видна вся работа вашей команды, а выявляемые ею тенденции позволяют лучше планировать и принимать решения.", + "view_bookings_across": "Просматривайте бронирования для всех участников", + "view_bookings_across_description": "Узнайте, кто участвует в мероприятиях больше всех, и обеспечьте оптимальное распределение задач в команде", + "identify_booking_trends": "Выявляйте тренды бронирования", + "identify_booking_trends_description": "Следите за тем, в какое время и в какие дни бронирование оформляют чаще всего", + "spot_popular_event_types": "Определяйте наиболее популярные типы событий", "spot_popular_event_types_description": "Узнайте, какие типы событий собирают больше всего кликов и бронирований", "no_responses_yet": "Ответов пока нет", "this_will_be_the_placeholder": "Это текст-заполнитель", @@ -1717,7 +1717,7 @@ "members_default_schedule": "Расписание участника по умолчанию", "set_by_admin": "Устанавливается администратором команды", "members_default_location": "Местоположение участника по умолчанию", - "members_default_schedule_description": "Для каждого участника мы используем установленный по умолчанию график доступности. Пользователи могут редактировать или менять его.", + "members_default_schedule_description": "Для каждого участника будет использоваться его график доступности по умолчанию. Участники смогут редактировать или менять его.", "requires_at_least_one_schedule": "У вас должно быть по крайней мере одно расписание", "default_conferencing_bulk_description": "Обновить местоположение для выбранных типов событий", "locked_for_members": "Заблокировано для участников", @@ -1725,14 +1725,14 @@ "locked_webhooks_description": "Участники смогут видеть активные вебхуки, но не смогут редактировать их настройки", "locked_workflows_description": "Участники смогут видеть активные рабочие процессы, но не смогут редактировать их настройки", "app_not_connected": "Вы не подключили аккаунт {{appName}}.", - "connect_now": "Подключить сейчас", + "connect_now": "Подключить", "managed_event_dialog_confirm_button_one": "Заменить и уведомить {{count}} участника", - "managed_event_dialog_confirm_button_other": "Заменить и уведомить {{count}} участников", + "managed_event_dialog_confirm_button_other": "Заменить и уведомить участников ({{count}})", "managed_event_dialog_title_one": "URL /{{slug}} для {{count}} участника уже существует. Заменить?", "managed_event_dialog_title_other": "URL /{{slug}} для {{count}} участников уже существует. Заменить?", "managed_event_dialog_information_one": "{{names}} уже используют /{{slug}} url.", "managed_event_dialog_information_other": "{{names}} уже используют /{{slug}} url.", - "managed_event_dialog_clarification": "В случае замены мы уведомим пользователей. Чтобы не выполнять перезапись, вернитесь назад и удалите их.", + "managed_event_dialog_clarification": "В случае замены мы уведомим пользователей. Если вы не хотите выполнять перезапись, вернитесь назад и удалите типы событий.", "review_event_type": "Пересмотреть тип события", "looking_for_more_analytics": "Нужно больше аналитики?", "looking_for_more_insights": "Нужно больше Insights?", @@ -1743,43 +1743,43 @@ "popular_events": "Популярные события", "no_event_types_found": "Типы событий не найдены", "average_event_duration": "Средняя продолжительность события", - "most_booked_members": "Участники с максимальным числом бронирований", - "least_booked_members": "Участники с минимальным числом бронирований", + "most_booked_members": "Участники с наибольшим числом бронирований", + "least_booked_members": "Участники с наименьшим числом бронирований", "events_created": "Созданные события", "events_completed": "Завершенные события", "events_cancelled": "Отмененные события", "events_rescheduled": "Перенесенные события", "from_last_period": "с последнего периода", - "from_to_date_period": "С: {{startDate}} До: {{endDate}}", + "from_to_date_period": "С: {{startDate}} По: {{endDate}}", "analytics_for_organisation": "Insights", "subtitle_analytics": "Узнайте больше о работе команды", - "redirect_url_warning": "При добавлении перенаправления страница успешного бронирования будет отключена. Не забудьте упомянуть «Бронирование подтверждено» на пользовательской странице успешного бронирования.", + "redirect_url_warning": "При использовании перенаправления страница успешного бронирования будет отключена. Не забудьте упомянуть «Бронирование подтверждено» на собственной странице оповещения об успешном бронировании.", "event_trends": "Тренды событий", - "clear_filters": "Очистить фильтры", - "hold": "Подождите", - "on_booking_option": "Получить оплату при бронировании", - "hold_option": "Взимать комиссию за отсутствие на событии", - "card_held": "Карта сохранена", - "charge_card": "Изменить карту", - "card_charged": "Оплата с карты списана", - "no_show_fee_amount": "{{amount, currency}} комиссия за отсутствие на событии", - "no_show_fee": "Комиссия за отсутствие на событии", + "clear_filters": "Сбросить фильтры", + "hold": "Заморозить", + "on_booking_option": "Списывать оплату при бронировании", + "hold_option": "Взимать комиссию за неявку", + "card_held": "Сумма заморожена", + "charge_card": "Списать оплату с карты", + "card_charged": "Оплата списана с карты", + "no_show_fee_amount": "{{amount, currency}} комиссия за неявку", + "no_show_fee": "Комиссия за неявку", "submit_card": "Ввести карту", "submit_payment_information": "Отправить платежную информацию", "meeting_awaiting_payment_method": "Выберите способ оплаты для встречи", - "no_show_fee_charged_email_subject": "Комиссия за отсутствие на событии {{amount, currency}} списана с {{title}} от {{date}}", - "no_show_fee_charged_text_body": "Комиссия за отсутствие на событии зачислена", - "no_show_fee_charged_subtitle": "Комиссия за отсутствие на следующем событии зачислена в размере {{amount, currency}}", - "error_charging_card": "Не удалось зачислить комиссию за отсутствие на событии. Повторите попытку позже.", - "collect_no_show_fee": "Получить комиссию за отсуствие на событии", - "no_show_fee_charged": "Зачислена комиссия за отсутствие на событии", + "no_show_fee_charged_email_subject": "Списание комиссии за неявку на событие {{title}} {{date}} в размере {{amount, currency}}", + "no_show_fee_charged_text_body": "Комиссия за неявку списана", + "no_show_fee_charged_subtitle": "Списана комиссия в размере {{amount, currency}} за неявку на следующее событие", + "error_charging_card": "Не удалось списать комиссию за неявку. Повторите попытку позже.", + "collect_no_show_fee": "Списать комиссию за неявку", + "no_show_fee_charged": "Комиссия за неявку списана", "insights": "Insights", - "testing_workflow_info_message": "При тестировании этого рабочего процесса помните, что отправку писем и SMS можно запланировать только за 1 час до начала", - "insights_no_data_found_for_filter": "Не найдены данные для выбранного фильтра или выбранных дат.", - "acknowledge_booking_no_show_fee": "Я понимаю, что в случае моего отказа посетить это мероприятие с моей карты будет списана комиссия за отсутствие на мероприятии в размере {{amount, currency}}.", + "testing_workflow_info_message": "При тестировании этого рабочего процесса помните, что отправку писем и SMS можно запланировать не менее чем за 1 час до начала", + "insights_no_data_found_for_filter": "Данные, соответствующие выбранному фильтру или диапазону дат, не найдены.", + "acknowledge_booking_no_show_fee": "Я подтверждаю, что в случае моей неявки на событие с моей карты будет списана комиссия в размере {{amount, currency}}.", "card_details": "Реквизиты карты", - "seats_and_no_show_fee_error": "В данный момент не удается активировать места и зачислить комиссию за отсутствие на событии", - "complete_your_booking": "Завершите бронирование", - "complete_your_booking_subject": "Завершите бронирование: {{title}} от {{date}}", - "email_invite_team": "{{email}} приглашен(а)" + "seats_and_no_show_fee_error": "Списание платы за неявку при использовании выбора места в настоящее время не поддерживается", + "complete_your_booking": "Завершить бронирование", + "complete_your_booking_subject": "Завершите бронирование: {{title}}, {{date}}", + "email_invite_team": "Участник {{email}} приглашен(а)" } diff --git a/apps/web/public/static/locales/sr/common.json b/apps/web/public/static/locales/sr/common.json index f49bc95a0c..daaedbb2e9 100644 --- a/apps/web/public/static/locales/sr/common.json +++ b/apps/web/public/static/locales/sr/common.json @@ -408,7 +408,7 @@ "add_time_availability": "Dodaj novi vremenski interval", "add_an_extra_layer_of_security": "Dodajte dodatni nivo sigurnosti svom nalogu u slučaju da vam je lozinka ukradena.", "2fa": "Dvofaktorska Autentikacija", - "2fa_disabled": "Dvostruka potvrda identiteta pože biti omogućena samo za potvrdu identiteta putem imejla i lozinke", + "2fa_disabled": "Dvostruka potvrda identiteta može biti omogućena samo za potvrdu identiteta putem imejla i lozinke", "enable_2fa": "Omogući dvofaktorsku autentikaciju", "disable_2fa": "Onemogući dvofaktorsku autentikaciju", "disable_2fa_recommendation": "Ako morate da onemogućite 2FA, predlažemo da je omogućite što pre.", @@ -590,17 +590,17 @@ "round_robin": "Round Robin", "round_robin_description": "Ciklusirajte sastanke između više članova tima.", "managed_event": "Upravljani događaj", - "username_placeholder": "korisničko ime", + "username_placeholder": "username", "managed_event_description": "Kreirajte i distribuirajte veliku količinu tipova događaja članovima tima", "managed": "Upravljano", - "managed_event_url_clarification": "„korisničko ime“ će biti ispunjeno korisničkim imenom dodeljenog člana", + "managed_event_url_clarification": "„username“ će biti ispunjeno korisničkim imenom dodeljenog člana", "assign_to": "Dodelite", "add_members": "Dodajte članove...", "count_members_one": "{{count}} član", "count_members_other": "{{count}} članova", "no_assigned_members": "Nema dodeljenih članova", "assigned_to": "Dodeljeno", - "start_assigning_members_above": "Počnite da dodeljujete korisnicima iznad", + "start_assigning_members_above": "Počnite da dodeljujete članove iznad", "locked_fields_admin_description": "Članovi neće moći da uređuju ovo", "locked_fields_member_description": "Ova opcija je zaključana od strane administratora tima", "url": "URL", @@ -757,7 +757,7 @@ "delete_event_type": "Izbriši tip događaja?", "delete_managed_event_type": "Izbriši upravljani tip događaja?", "delete_event_type_description": "Niko sa kime ste podelili link neće više moći da rezerviše pomoću njega.", - "delete_managed_event_type_description": "
  • Članovima dodeljenim ovom tipu događaja će takođe biti obrisani njihovi tipovi događaja.
  • Niko sa kim su podelili svoj link neće više moći da zakaže pomoću njega.
", + "delete_managed_event_type_description": "
  • Članovima dodeljenim ovom tipu događaja će takođe biti izbrisani njihovi tipovi događaja.
  • Niko sa kim su podelili svoj link neće više moći da rezerviše pomoću njega.
", "confirm_delete_event_type": "Da, izbriši", "delete_account": "Izbriši nalog", "confirm_delete_account": "Da, izbriši nalog", @@ -1317,16 +1317,16 @@ "exchange_authentication_standard": "Osnovna potvrda identiteta", "exchange_authentication_ntlm": "NTLM potvrda identiteta", "exchange_compression": "GZip kompresija", - "exchange_version": "Verzija za razmenu", - "exchange_version_2007_SP1": "2007. SP1", - "exchange_version_2010": "2010.", - "exchange_version_2010_SP1": "2010. SP1", - "exchange_version_2010_SP2": "2010. SP2", - "exchange_version_2013": "2013.", - "exchange_version_2013_SP1": "2013. SP1", - "exchange_version_2015": "2015.", - "exchange_version_2016": "2016.", - "routing_forms_description": "Možete da vidite sve formulare i rute koje ste kreirali ovde.", + "exchange_version": "Exchange verzija", + "exchange_version_2007_SP1": "2007 SP1", + "exchange_version_2010": "2010", + "exchange_version_2010_SP1": "2010 SP1", + "exchange_version_2010_SP2": "2010 SP2", + "exchange_version_2013": "2013", + "exchange_version_2013_SP1": "2013 SP1", + "exchange_version_2015": "2015", + "exchange_version_2016": "2016", + "routing_forms_description": "Kreirajte formulare da biste uputili učesnike na ispravna odredišta", "routing_forms_send_email_owner": "Pošaljite imejl vlasniku", "routing_forms_send_email_owner_description": "Pošaljite imejl vlasniku kada pošaljete obrazac", "add_new_form": "Dodaj novi formular", @@ -1486,7 +1486,7 @@ "disabled_app_affects_event_type": "Administrator je onemogućio {{appName}}, što utiče na vaš tip događaja {{eventType}}", "event_replaced_notice": "Administrator je zamenio jedan od vaših tipova događaja", "email_subject_slug_replacement": "Administrator tima je zamenio vaš događaj /{{slug}}", - "email_body_slug_replacement_notice": "Administrator iz {{teamName}} tima je zamenio vaš tip događaja {{slug}} upravljanim tipom događaja koji oni kontrolišu.", + "email_body_slug_replacement_notice": "Administrator iz {{teamName}} tima je zamenio vaš tip događaja {{slug}} tipom događaja kojim on upravlja.", "email_body_slug_replacement_info": "Vaš link će nastaviti da funkcioniše, ali neka od njegovih podešavanja su se možda promenila. Možete da ih pregledate u tipovima događaja.", "email_body_slug_replacement_suggestion": "Ako imate bilo kakvih pitanja u vezi sa tipom događaja, obratite se svom administratoru.

Srećno planiranje,
Cal.com tim", "disable_payment_app": "Administrator je onemogućio {{appName}}, što utiče na vaš tip događaja {{title}}. Učesnici još uvek mogu da zakažu ovaj tip događaja, ali od njih neće biti zatraženo da plate. Možete da sakrijete ovaj tip događaja da biste to sprečili dok vaš administrator ponovo ne omogući vaš način plaćanja.", @@ -1584,7 +1584,7 @@ "ee_enterprise_license": "“/ee” Licenca za preduzeća", "enterprise_booking_fee": "Već od {{enterprise_booking_fee}}/mesečno", "enterprise_license_includes": "Sve za komercijalnu upotrebu", - "no_need_to_keep_your_code_open_source": "Nije potrebno da vaš kôd bude otvoren", + "no_need_to_keep_your_code_open_source": "Nije potrebno da vaš kôd bude open-source", "repackage_rebrand_resell": "Prepakujte, rebrendirajte i lako ponovo prodajte", "a_vast_suite_of_enterprise_features": "Ogroman izbor funkcija za preduzeća", "free_license_fee": "0,00 $/mesečno", @@ -1685,7 +1685,7 @@ "add_1_option_per_line": "Dodajte 1 opciju za svaku liniju", "select_a_router": "Izaberite ruter", "add_a_new_route": "Dodaj novo preusmeravanje", - "make_informed_decisions": "Donesite informisane odluke uz Insights", + "make_informed_decisions": "Donosite pravilne odluke pomoću Insights", "make_informed_decisions_description": "Kontrolna tabla Insights prikazuje sve aktivnosti vašeg tima i prikazuje trendove koji omogućavaju bolje timsko zakazivanje i odlučivanje.", "view_bookings_across": "Pogledajte rezervacije za sve članove", "view_bookings_across_description": "Pogledajte ko je dobio najviše rezervacija i uverite se da su najbolje podeljene u timu", @@ -1710,20 +1710,20 @@ "cancel_url_variable": "URL za otkazivanje", "cancel_url_info": "URL za otkazivanje zakazivanja", "reschedule_url_variable": "URL za promenu termina", - "reschedule_url_info": "URL za promenu termina", + "reschedule_url_info": "URL za promenu zakazanog termina", "invalid_event_name_variables": "U nazivu vašeg događaja se nalazi nevažeća promenljiva", "select_all": "Izaberi sve", "default_conferencing_bulk_title": "Masovno ažuriranje postojećih tipova događaja", - "members_default_schedule": "Podrazumevani raspored članova", + "members_default_schedule": "Podrazumevani raspored člana", "set_by_admin": "Postavio administrator tima", - "members_default_location": "Podrazumevana lokacija članova", + "members_default_location": "Podrazumevana lokacija člana", "members_default_schedule_description": "Koristićemo podrazumevani raspored dostupnosti za svakog člana. Oni će moći da ga uređuju ili promene.", "requires_at_least_one_schedule": "Potrebno je da imate barem jedan raspored", "default_conferencing_bulk_description": "Ažuriranje lokacija za izabrane tipove događaja", "locked_for_members": "Zaključano za članove", - "locked_apps_description": "Članovi će moći da vide aktivne aplikacije, ali neće moći da uređuju bilo kakva podešavanja aplikacije", - "locked_webhooks_description": "Članovi će moći da vide aktivne webhook-ove, ali neće moći da uređuju bilo kakva podešavanja webhook-ova", - "locked_workflows_description": "Članovi će moći da vide aktivne radne tokove, ali neće moći da uređuju bilo kakva podešavanja radnih tokova", + "locked_apps_description": "Članovi će moći da vide aktivne aplikacije, ali neće moći da uređuju podešavanja aplikacije", + "locked_webhooks_description": "Članovi će moći da vide aktivne webhook-ove, ali neće moći da uređuju podešavanja webhook-ova", + "locked_workflows_description": "Članovi će moći da vide aktivne radne tokove, ali neće moći da uređuju podešavanja radnih tokova", "app_not_connected": "Niste povezali {{appName}} nalog.", "connect_now": "Povežite odmah", "managed_event_dialog_confirm_button_one": "Zameni i obavesti {{count}} člana", @@ -1733,9 +1733,9 @@ "managed_event_dialog_information_one": "{{names}} već koristi /{{slug}} URL.", "managed_event_dialog_information_other": "{{names}} već koriste /{{slug}} URL.", "managed_event_dialog_clarification": "Ako izaberete da ga zamenite, obavestićemo ih. Vratite se i uklonite ih ako ne želite da zamenite postojeće.", - "review_event_type": "Pregledajte tip događaja", + "review_event_type": "Pregledaj tip događaja", "looking_for_more_analytics": "Tražite još analitike?", - "looking_for_more_insights": "Tražite još uvida?", + "looking_for_more_insights": "Tražite još Insights podataka?", "add_filter": "Dodajte filter", "select_user": "Izaberite korisnika", "select_event_type": "Izaberite tip događaja", @@ -1751,40 +1751,40 @@ "events_rescheduled": "Pomereni događaji", "from_last_period": "iz prošlog perioda", "from_to_date_period": "Od: {{startDate}} Do: {{endDate}}", - "analytics_for_organisation": "Uvidi", + "analytics_for_organisation": "Insights", "subtitle_analytics": "Saznajte više o aktivnosti vašeg tima", - "redirect_url_warning": "Dodavanje preusmeravanja će onemogućiti stranicu uspeha. Uverite se da spomenete „Zakazivanje potvrđeno“ na vašoj prilagođenoj stranici uspeha.", + "redirect_url_warning": "Dodavanje preusmeravanja će onemogućiti stranicu uspeha. Obavezno navedite „Zakazivanje potvrđeno“ na vašoj prilagođenoj stranici uspeha.", "event_trends": "Trendovi događaja", "clear_filters": "Izbriši filtere", "hold": "Zadrži", - "on_booking_option": "Sakupite plaćanje za zakazivanje", - "hold_option": "Naplatite nadoknadu za izostanak", - "card_held": "Karta zadržana", - "charge_card": "Naplati karticu", - "card_charged": "Kartica naplaćena", - "no_show_fee_amount": "{{amount, currency}} nadoknada za izostanak", - "no_show_fee": "Nadoknada za izostanak", - "submit_card": "Pošaljite karticu", - "submit_payment_information": "Pošaljite informacije o plaćanju", + "on_booking_option": "Prikupi uplatu za zakazivanje", + "hold_option": "Naplati naknadu za izostanak", + "card_held": "Rezervisana sredstva na kartici", + "charge_card": "Naplati sa kartice", + "card_charged": "Naplaćeno sa kartice", + "no_show_fee_amount": "{{amount, currency}} naknada za izostanak", + "no_show_fee": "Naknada za izostanak", + "submit_card": "Pošalji karticu", + "submit_payment_information": "Pošalji informacije o plaćanju", "meeting_awaiting_payment_method": "Vaš sastanak čeka na način plaćanja", - "no_show_fee_charged_email_subject": "Nadoknada za izostanak u iznosu od {{amount, currency}} je naplaćena za {{title}} dana {{date}}", + "no_show_fee_charged_email_subject": "Naknada za izostanak u iznosu od {{amount, currency}} je naplaćena za {{title}} dana {{date}}", "no_show_fee_charged_text_body": "Naplaćena je naknada za izostanak", "no_show_fee_charged_subtitle": "Naknada za izostanak u iznosu od {{amount, currency}} je naplaćena za sledeći događaj", "error_charging_card": "Došlo je do greške prilikom naplate naknade za izostanak. Pokušajte ponovo kasnije.", - "collect_no_show_fee": "Sakupite naknadu za izostanak", + "collect_no_show_fee": "Prikupi naknadu za izostanak", "no_show_fee_charged": "Naplaćena je naknada za izostanak", - "insights": "Uvidi", + "insights": "Insights", "testing_workflow_info_message": "Kada testirate ovaj radni tok, imajte na umu da imejlovi i SMS-ovi mogu da budu zakazani najmanje 1 sat unapred", "insights_no_data_found_for_filter": "Nisu pronađeni podaci za izabrani filter ili izabrane datume.", - "acknowledge_booking_no_show_fee": "Jasno mi je da će mi biti naplaćena naknada za izostanak putem kartice u iznosu od {{amount, currency}} ukoliko ne dođem na ovaj događaj.", + "acknowledge_booking_no_show_fee": "Jasno mi je da će mi biti naplaćena naknada za izostanak putem kartice u iznosu od {{amount, currency}} ukoliko ne prisustvujem ovom događaju.", "card_details": "Informacije o kartici", - "seats_and_no_show_fee_error": "Omogućavanje mesta i naplata naknade za izostanak trenutno nije moguće", + "seats_and_no_show_fee_error": "Trenutno nije moguće aktiviranje mesta i naplata naknade za izostanak", "complete_your_booking": "Završite svoje zakazivanje", "complete_your_booking_subject": "Završite svoje zakazivanje: {{title}} dana {{date}}", "currency_string": "{{amount, currency}}", "charge_card_dialog_body": "Upravo ćete naplatiti polazniku {{amount, currency}}. Jeste li sigurni da želite da nastavite?", "charge_attendee": "Naplatite polazniku {{amount, currency}}", "payment_app_commission": "Zahtevajte plaćanje ({{paymentFeePercentage}}% + {{fee, currency}} provizija po transakciji)", - "email_invite_team": "{{email}} je pozvan", + "email_invite_team": "{{email}} je pozvan/a", "image_size_limit_exceed": "Otpremljena slika ne sme da prevazilazi ograničenje od 5mb" } diff --git a/apps/web/public/static/locales/sv/common.json b/apps/web/public/static/locales/sv/common.json index 06f6308874..8f0e6fd018 100644 --- a/apps/web/public/static/locales/sv/common.json +++ b/apps/web/public/static/locales/sv/common.json @@ -70,8 +70,8 @@ "event_awaiting_approval_subject": "Inväntar godkännande: {{title}} den {{date}}", "event_still_awaiting_approval": "En bokning väntar fortfarande på ditt godkännande", "booking_submitted_subject": "Bokning skickad: {{title}} {{date}}", - "download_recording_subject": "Ladda ner inspelning: {{title}} {{date}}", - "download_your_recording": "Ladda ner din inspelning", + "download_recording_subject": "Ladda ned inspelning: {{title}} {{date}}", + "download_your_recording": "Ladda ned din inspelning", "your_meeting_has_been_booked": "Ditt möte har bokats", "event_type_has_been_rescheduled_on_time_date": "Din {{title}} har ändrats till {{date}}.", "event_has_been_rescheduled": "Uppdatering - Din bokning har blivit omplanerad", @@ -593,9 +593,9 @@ "username_placeholder": "användarnamn", "managed_event_description": "Skapa och distribuera evenemangstyper i bulk till teammedlemmar", "managed": "Hanterad", - "managed_event_url_clarification": "\"användarnamn\" kommer att fyllas i med användarnamnet för de medlemmar som tilldelats", + "managed_event_url_clarification": "\"användarnamn\" fylls i med användarnamnet för de medlemmar som tilldelats", "assign_to": "Tilldela till", - "add_members": "Lägg till medlemmar...", + "add_members": "Lägg till medlemmar ...", "count_members_one": "{{count}} medlem", "count_members_other": "{{count}} medlemmar", "no_assigned_members": "Inga tilldelade medlemmar", @@ -756,8 +756,8 @@ "slot_interval_default": "Använd händelsens längd (standard)", "delete_event_type": "Radera händelsetyp?", "delete_managed_event_type": "Radera hanterad händelsetyp?", - "delete_event_type_description": "Alla som du har delat den här länken med kommer inte längre att kunna boka med hjälp av den.", - "delete_managed_event_type_description": "
  • Medlemmar som tilldelats denna händelsetyp kommer också att få sina händelsetyper raderade.
  • Alla som de har delat sin länk med kommer inte längre att kunna boka med den.
", + "delete_event_type_description": "Alla som du delat den här länken med kan inte längre boka med den.", + "delete_managed_event_type_description": "
  • Händelsetyper raderas även för medlemmar som tilldelats denna händelsetyp.
  • Alla som de delat sin länk med kan inte längre boka med den.
", "confirm_delete_event_type": "Ja, radera", "delete_account": "Radera konto", "confirm_delete_account": "Ja, radera konto", @@ -1200,7 +1200,7 @@ "start_of_week": "Början av veckan", "recordings_title": "Inspelningar", "recording": "Inspelning", - "happy_scheduling": "Glad schemaläggning", + "happy_scheduling": "Lycka till med schemaläggningen", "select_calendars": "Välj vilka kalendrar du vill kontrollera för konflikter för att förhindra dubbelbokningar.", "check_for_conflicts": "Kontrollera om konflikter", "view_recordings": "Visa inspelningar", @@ -1279,7 +1279,7 @@ "download_responses": "Ladda ned svar", "download_responses_description": "Ladda ned alla svar till ditt formulär i CSV-format.", "download": "Ladda ned", - "download_recording": "Ladda ner inspelning", + "download_recording": "Ladda ned inspelning", "recording_from_your_recent_call": "En inspelning från ditt senaste samtal på Cal.com är redo för nedladdning", "create_your_first_form": "Skapa ditt första formulär", "create_your_first_form_description": "Med omdirigeringsformulär kan du ställa kvalificerade frågor och dirigera till rätt person eller händelsetyp.", @@ -1326,7 +1326,7 @@ "exchange_version_2013_SP1": "2013 SP1", "exchange_version_2015": "2015", "exchange_version_2016": "2016", - "routing_forms_description": "Du kan se alla former och rutter som du har skapat här.", + "routing_forms_description": "Skapa formulär för att dirigera deltagare till rätt mål", "routing_forms_send_email_owner": "Skicka e-post till ägare", "routing_forms_send_email_owner_description": "Skickar ett e-postmeddelande till ägaren när formuläret skickas", "add_new_form": "Lägg till nytt formulär", @@ -1486,9 +1486,9 @@ "disabled_app_affects_event_type": "En administratör har inaktiverat {{appName}} vilket påverkar din händelsetyp {{eventType}}", "event_replaced_notice": "En administratör har ersatt en av dina händelsetyper", "email_subject_slug_replacement": "En teamadministratör har ersatt din händelse /{{slug}}", - "email_body_slug_replacement_notice": "En administratör i {{teamName}}-teamet har ersatt din händelsetyp /{{slug}} med en hanterad händelsetyp som de kontrollerar.", - "email_body_slug_replacement_info": "Din länk kommer att fortsätta fungera, men vissa inställningar för den kan ha ändrats. Du kan granska den i händelsetyper.", - "email_body_slug_replacement_suggestion": "Om du har några frågor om händelsetypen, kontakta din administratör.

Glad schemaläggning,
Cal.com-teamet", + "email_body_slug_replacement_notice": "En administratör i {{teamName}}-teamet har ersatt din händelsetyp /{{slug}} med en hanterad händelsetyp som hen kontrollerar.", + "email_body_slug_replacement_info": "Din länk fortsätter att fungera, men vissa inställningar för den kan ha ändrats. Du kan granska den i händelsetyper.", + "email_body_slug_replacement_suggestion": "Kontakta din administratör om du har några frågor om händelsetypen.

Lycka till med schemaläggningen!
Cal.com-teamet", "disable_payment_app": "Administratören har inaktiverat {{appName}} vilket påverkar din händelsetyp {{title}}. Deltagarna kan fortfarande boka den här händelsetypen, men de uppmanas inte att betala. Du kan dölja händelsetypen för att förhindra detta tills din administratör återaktiverar betalningsmetoden.", "payment_disabled_still_able_to_book": "Deltagarna kan fortfarande boka den här händelsetypen, men de uppmanas inte att betala. Du kan dölja händelsetypen för att förhindra detta tills din administratör återaktiverar betalningsmetoden.", "app_disabled_with_event_type": "Administratören har inaktiverat {{appName}} vilket påverkar din händelsetyp {{title}}.", @@ -1551,7 +1551,7 @@ "create_your_first_team_webhook_description": "Skapa din första webhook för denna teamhändelsetyp", "create_webhook_team_event_type": "Skapa en webhook för denna teamhändelsetyp", "disable_success_page": "Inaktivera framgångssida (fungerar bara om du har en omdirigerings-URL)", - "invalid_admin_password": "Du är administratör men du har inte ett lösenord med minst 15 tecken eller ingen 2FA än", + "invalid_admin_password": "Du är administratör men ditt lösenord har färre än 15 tecken eller ingen 2FA än", "change_password_admin": "Ändra lösenord för att få administratörsåtkomst", "username_already_taken": "Användarnamnet används redan", "assignment": "Tilldelning", @@ -1584,7 +1584,7 @@ "ee_enterprise_license": "\"/ee\" Enterprise-licens", "enterprise_booking_fee": "Från {{enterprise_booking_fee}}/månad", "enterprise_license_includes": "Allt för ett kommersiellt användningsfall", - "no_need_to_keep_your_code_open_source": "Inget behov av att behålla din kod som öppen källkod", + "no_need_to_keep_your_code_open_source": "Behöver inte behålla din kod som öppen källkod", "repackage_rebrand_resell": "Lätt att ompaketera, omprofilera och sälja vidare", "a_vast_suite_of_enterprise_features": "En omfattande uppsättning Enterprise-funktioner", "free_license_fee": "0,00 USD/månad", @@ -1685,12 +1685,12 @@ "add_1_option_per_line": "Lägg till ett alternativ per rad", "select_a_router": "Välj en omdirigering", "add_a_new_route": "Lägg till ny omdirigering", - "make_informed_decisions": "Ta välgrundade beslut med Insights", - "make_informed_decisions_description": "Vår instrumentpanel i Insights visar all aktivitet i ditt team och visar trender som gör det möjligt att schemalägga teamet och fatta beslut på ett bättre sätt.", + "make_informed_decisions": "Fatta välgrundade beslut med Insights", + "make_informed_decisions_description": "Vår instrumentpanel i Insights visar alla aktiviteter för ditt team och visar trender så att du kan schemalägga teamet och fatta beslut på ett bättre sätt.", "view_bookings_across": "Visa bokningar för alla medlemmar", - "view_bookings_across_description": "Se vem som får flest bokningar och se till att du får den bästa fördelningen i ditt team", + "view_bookings_across_description": "Se vem som får flest bokningar och se till att teamet får den bästa fördelningen", "identify_booking_trends": "Identifiera bokningstrender", - "identify_booking_trends_description": "Se vilka tider i veckan och vilka tider under dagen som är populära för dina bokare", + "identify_booking_trends_description": "Se vilka tider i veckan och under dagen som är populära för dina bokare", "spot_popular_event_types": "Hitta populära händelsetyper", "spot_popular_event_types_description": "Se vilka av dina händelsetyper som får flest klick och bokningar", "no_responses_yet": "Inga svar ännu", @@ -1704,26 +1704,26 @@ "can_you_try_again": "Kan du försöka igen med en annan tid?", "verify": "Verifiera", "timezone_variable": "Tidszon", - "timezone_info": "Tidszonen för den person som tar emot", + "timezone_info": "Tidszon för mottagande person", "event_end_time_variable": "Händelsens sluttid", "event_end_time_info": "Händelsens sluttid", "cancel_url_variable": "URL för att avbryta", - "cancel_url_info": "Webbadressen fö ratt avbryta bokningen", + "cancel_url_info": "URL för att avbryta bokningen", "reschedule_url_variable": "URL för ombokning", - "reschedule_url_info": "Webbadressen för att ändra bokningen", + "reschedule_url_info": "URL för att ändra bokningen", "invalid_event_name_variables": "Det finns en ogiltig variabel i ditt händelsenamn", "select_all": "Välj alla", "default_conferencing_bulk_title": "Massuppdatering av befintliga händelsetyper", "members_default_schedule": "Medlemmens standardschema", - "set_by_admin": "Ställ in av teamadministratör", + "set_by_admin": "Ställs in av teamadministratör", "members_default_location": "Medlemmens förvalda plats", - "members_default_schedule_description": "Vi kommer att använda varje medlems standardschema för tillgänglighet. De kommer att kunna redigera eller ändra det.", + "members_default_schedule_description": "Vi kommer att använda varje medlems standardschema för tillgänglighet. De kan redigera eller ändra det.", "requires_at_least_one_schedule": "Du måste ha minst ett schema", "default_conferencing_bulk_description": "Uppdatera platserna för de valda händelsetyperna", "locked_for_members": "Låst för medlemmar", - "locked_apps_description": "Medlemmarna kommer att kunna se de aktiva apparna, men de kommer inte att kunna ändra appinställningarna", - "locked_webhooks_description": "Medlemmarna kommer att kunna se aktiva webhooks men inte kunna redigera inställningar för webhooks", - "locked_workflows_description": "Medlemmarna kommer att kunna se de aktiva arbetsflödena, men de kommer inte att kunna ändra några arbetsflödesinställningar", + "locked_apps_description": "Medlemmarna kan se de aktiva apparna men kan inte ändra appinställningarna", + "locked_webhooks_description": "Medlemmarna kan se aktiva webhooks men kan inte redigera inställningar för webhooks", + "locked_workflows_description": "Medlemmarna kan se aktiva arbetsflöden men kan inte ändra några arbetsflödesinställningar", "app_not_connected": "Du har inte anslutit ett {{appName}}-konto.", "connect_now": "Anslut nu", "managed_event_dialog_confirm_button_one": "Ersätt och meddela {{count}} medlem", @@ -1732,53 +1732,53 @@ "managed_event_dialog_title_other": "Webbadressen /{{slug}} finns redan för {{count}} medlemmar. Vill du ersätta den?", "managed_event_dialog_information_one": "{{names}} använder redan webbadressen /{{slug}}.", "managed_event_dialog_information_other": "{{names}} använder redan webbadressen /{{slug}}.", - "managed_event_dialog_clarification": "Om du väljer att byta ut den kommer vi att meddela dem. Gå tillbaka och ta bort dem om du inte vill skriva över den.", + "managed_event_dialog_clarification": "Vi meddelar dem om du byter ut den. Gå tillbaka och ta bort dem om du inte vill skriva över den.", "review_event_type": "Granska händelsetyp", - "looking_for_more_analytics": "Vill du ha fler analyser?", - "looking_for_more_insights": "Vill du ha fler insikter?", + "looking_for_more_analytics": "Vill du ha mer analys?", + "looking_for_more_insights": "Vill du ha mer Insights?", "add_filter": "Lägg till filter", "select_user": "Välj användare", "select_event_type": "Välj händelsetyp", "select_date_range": "Välj datumintervall", "popular_events": "Populära händelser", - "no_event_types_found": "Inga händelsetyper hittade", - "average_event_duration": "Genomsnittlig händelselängd", + "no_event_types_found": "Inga händelsetyper hittades", + "average_event_duration": "Genomsnittlig varaktighet för händelse", "most_booked_members": "Mest bokade medlemmar", "least_booked_members": "Minst bokade medlemmar", "events_created": "Skapade händelser", "events_completed": "Slutförda händelser", - "events_cancelled": "Händelser avbrutna", - "events_rescheduled": "Händelser ombokade", + "events_cancelled": "Avbrutna händelser", + "events_rescheduled": "Ombokade händelser", "from_last_period": "från senaste perioden", "from_to_date_period": "Från: {{startDate}} till: {{endDate}}", - "analytics_for_organisation": "Insikter", + "analytics_for_organisation": "Insights", "subtitle_analytics": "Läs mer om ditt teams aktivitet", - "redirect_url_warning": "Att lägga till en omdirigering kommer att inaktivera framgångssidan. Se till att nämna \"Bokning bekräftad\" på din anpassade framgångssida.", + "redirect_url_warning": "Framgångssidan inaktiveras om du lägger till en omdirigering. Nämn \"Bokning bekräftad\" på din anpassade framgångssida.", "event_trends": "Händelsetrender", "clear_filters": "Rensa filter", - "hold": "Vänta", + "hold": "Spärr", "on_booking_option": "Samla in betalning vid bokning", - "hold_option": "Ta ut en avgift för utebliven ankomst", - "card_held": "Kort som innehas", + "hold_option": "Avgift för utebliven närvaro", + "card_held": "Kort spärrat", "charge_card": "Debitera kort", "card_charged": "Kort debiterat", - "no_show_fee_amount": "{{amount, currency}} i avgift för utebliven ankomst", - "no_show_fee": "Avgift för utebliven ankomst", + "no_show_fee_amount": "Avgift på {{amount, currency}} för utebliven närvaro", + "no_show_fee": "Avgift för utebliven närvaro", "submit_card": "Använd kort", "submit_payment_information": "Ange betalningsinformation", "meeting_awaiting_payment_method": "Ditt möte väntar på en betalningsmetod", - "no_show_fee_charged_email_subject": "Avgift för utebliven ankomst på {{amount, currency}} debiteras för {{title}} {{date}}", - "no_show_fee_charged_text_body": "Avgift för utebliven ankomst debiterades", - "no_show_fee_charged_subtitle": "Avgift för utebliven ankomst på {{amount, currency}} debiterades för följande händelse", - "error_charging_card": "Något gick fel när avgiften för utebliven ankomst debiterades. Försök igen senare.", - "collect_no_show_fee": "Samla in avgift för utebliven ankomst", - "no_show_fee_charged": "Avgift för utebliven ankomst debiterades", - "insights": "Insikter", - "testing_workflow_info_message": "När det här arbetsflödet testas, var medveten om att e-postmeddelanden och SMS endast kan schemaläggas minst en timme i förväg", - "insights_no_data_found_for_filter": "Inga data hittades för valda filter eller valda datum.", - "acknowledge_booking_no_show_fee": "Jag bekräftar att om jag inte deltar i händelsen kommer en avgift på {{amount, currency}} för utebliven ankomst att debiteras mitt kort.", + "no_show_fee_charged_email_subject": "Avgift på {{amount, currency}} för utebliven närvaro debiteras för {{title}} {{date}}", + "no_show_fee_charged_text_body": "Avgift för utebliven närvaro har debiterats", + "no_show_fee_charged_subtitle": "Avgift på {{amount, currency}} för utebliven närvaro har debiterats för följande händelse", + "error_charging_card": "Något gick fel när avgiften för utebliven närvaro debiterades. Försök igen senare.", + "collect_no_show_fee": "Samla in avgift för utebliven närvaro", + "no_show_fee_charged": "Avgift för utebliven närvaro har debiterats", + "insights": "Insights", + "testing_workflow_info_message": "När det här arbetsflödet testas ska du vara medveten om att e-postmeddelanden och SMS endast kan schemaläggas minst en timme i förväg", + "insights_no_data_found_for_filter": "Inga data hittades för valt filter eller valda datum.", + "acknowledge_booking_no_show_fee": "Jag bekräftar att en avgift på {{amount, currency}} för utebliven närvaro debiteras mitt kort om jag inte deltar i händelsen.", "card_details": "Kortuppgifter", - "seats_and_no_show_fee_error": "För närvarande går det inte att aktivera platser och ta ut en avgift för utebliven ankomst", + "seats_and_no_show_fee_error": "För närvarande går det inte att aktivera platser och ta ut en avgift för utebliven närvaro", "complete_your_booking": "Slutför din bokning", "complete_your_booking_subject": "Slutför din bokning: {{title}} {{date}}", "email_invite_team": "{{email}} har bjudits in" diff --git a/apps/web/public/static/locales/tr/common.json b/apps/web/public/static/locales/tr/common.json index b3d91b918c..1de3b1816e 100644 --- a/apps/web/public/static/locales/tr/common.json +++ b/apps/web/public/static/locales/tr/common.json @@ -593,7 +593,7 @@ "username_placeholder": "kullanıcı adı", "managed_event_description": "Etkinlik türlerini toplu olarak oluşturun ve ekip üyelerine dağıtın", "managed": "Yönetildi", - "managed_event_url_clarification": "\"username\" alanı, atanan üyelerin kullanıcı adı ile doldurulacaktır", + "managed_event_url_clarification": "\"username\" alanı, atanan üyelerin kullanıcı adı ile doldurulur", "assign_to": "Şuraya ata:", "add_members": "Üye ekle...", "count_members_one": "{{count}} üye", @@ -1200,7 +1200,7 @@ "start_of_week": "Haftanın başlangıcı", "recordings_title": "Kayıtlar", "recording": "Kayıt", - "happy_scheduling": "Mutlu Planlama", + "happy_scheduling": "İyi Planlamalar", "select_calendars": "Çifte rezervasyonu önlemek için çakışmaları kontrol etmek istediğiniz takvimleri seçin.", "check_for_conflicts": "Çakışmaları kontrol edin", "view_recordings": "Kayıtları görüntüle", @@ -1280,7 +1280,7 @@ "download_responses_description": "Formunuza gelen tüm yanıtları CSV formatında indirin.", "download": "İndir", "download_recording": "Kaydı İndir", - "recording_from_your_recent_call": "Cal.com'daki son görüşmenize ait bir kayıt indirilmeye hazır", + "recording_from_your_recent_call": "Cal.com'daki son aramanızın kaydı indirilmeye hazır", "create_your_first_form": "İlk formunuzu oluşturun", "create_your_first_form_description": "Yönlendirme Formları ile yeterlik soruları sorabilir ve bunları doğru kişiye veya etkinlik türüne yönlendirebilirsiniz.", "create_your_first_webhook": "İlk Web kancanızı oluşturun", @@ -1380,7 +1380,7 @@ "add_limit": "Kısıtlama Ekleyin", "team_name_required": "Ekip adı gereklidir", "show_attendees": "Katılımcı bilgilerini misafirler arasında paylaşın", - "how_booking_questions_as_variables": "Rezervasyonla ilgili sorular değişken olarak nasıl kullanılır?", + "how_booking_questions_as_variables": "Rezervasyon soruları değişken olarak nasıl kullanılır?", "format": "Biçim", "uppercase_for_letters": "Tüm harfleri büyük harf olarak kullanın", "replace_whitespaces_underscores": "Boşlukları alt çizgi ile değiştir", @@ -1395,7 +1395,7 @@ "billing_help_title": "Başka bir şeye ihtiyacınız var mı?", "billing_help_description": "Faturalandırma konusunda daha fazla yardıma ihtiyacınız olursa destek ekibimiz size yardımcı olmaya hazır.", "billing_help_cta": "Destek ekibimize ulaşın", - "ignore_special_characters_booking_questions": "Rezervasyon sorusu tanımlayıcınızdaki özel karakterleri kullanmayın. Yalnızca harfler ve sayılar kullanın", + "ignore_special_characters_booking_questions": "Rezervasyon sorusu tanımlayıcınızdaki özel karakterleri yok sayın. Yalnızca harf ve sayıları kullanın", "retry": "Yeniden dene", "fetching_calendars_error": "Takvimleriniz alınırken bir sorun oluştu. Lütfen <1>tekrar deneyin veya müşteri destek ekibiyle iletişme geçin.", "calendar_connection_fail": "Takvim bağlantısı yapılamadı", @@ -1485,10 +1485,10 @@ "admin_has_disabled": "Bir yönetici {{appName}} adlı uygulamayı devre dışı bıraktı", "disabled_app_affects_event_type": "Bir yönetici {{eventType}} etkinlik türünüzü etkileyen {{appName}} adlı uygulamayı devre dışı bıraktı", "event_replaced_notice": "Bir yönetici, etkinlik türlerinizden birini değiştirdi", - "email_subject_slug_replacement": "Bir ekip yöneticisi etkinliğinizi /{{slug}} değiştirdi", + "email_subject_slug_replacement": "Bir ekip yöneticisi, etkinliğinizi /{{slug}} değiştirdi", "email_body_slug_replacement_notice": "{{teamName}} ekibindeki bir yönetici, /{{slug}} etkinlik türünüzü kontrol ettiği yönetilen bir etkinlik türüyle değiştirdi.", "email_body_slug_replacement_info": "Bağlantınız çalışmaya devam edecek, ancak bazı ayarları değişmiş olabilir. Bağlantıyı etkinlik kategorilerinde inceleyebilirsiniz.", - "email_body_slug_replacement_suggestion": "Etkinlik türüyle ilgili herhangi bir sorunuz varsa lütfen yöneticinizle iletişime geçin.

Mutlu planlama,
Cal.com ekibi", + "email_body_slug_replacement_suggestion": "Etkinlik türüyle ilgili herhangi bir sorunuz varsa lütfen yöneticinizle iletişime geçin.

İyi planlamalar,
Cal.com ekibi", "disable_payment_app": "Yönetici, {{title}} etkinlik türünüzü etkileyebilecek {{appName}} uygulamasını devre dışı bıraktı. Katılımcılar yine de bu tür bir etkinlik için rezervasyon yaptırabilirler ancak herhangi bir ödeme yapmalarına gerek yoktur. Bu durumu önlemek için yöneticiniz ödeme yönteminizi yeniden etkinleştirene kadar etkinlik türünü gizleyebilirsiniz.", "payment_disabled_still_able_to_book": "Katılımcılar yine de bu tür bir etkinlik için rezervasyon yaptırabilirler, ancak herhangi bir ödeme yapmalarına gerek yoktur. Bu durumu önlemek için yöneticiniz ödeme yönteminizi yeniden etkinleştirene kadar etkinlik türünü gizleyebilirsiniz.", "app_disabled_with_event_type": "Yönetici, etkinlik {{title}} türünüzü etkileyen {{appName}} uygulamasını devre dışı bıraktı.", @@ -1686,7 +1686,7 @@ "select_a_router": "Yönlendirici seçin", "add_a_new_route": "Yeni bir Yönlendirme ekle", "make_informed_decisions": "Insights ile bilinçli kararlar verin", - "make_informed_decisions_description": "lnsights panomuz, ekibinizdeki tüm etkinlikleri analiz eder ve size daha iyi ekip planlaması yapma ve karar verme imkanı sağlayan eğilimleri gösterir.", + "make_informed_decisions_description": "lnsights panomuz, ekibinizdeki tüm etkinlikleri analiz eder ve size daha iyi ekip planlaması yapma ve karar verme imkanı sağlayan trendleri gösterir.", "view_bookings_across": "Tüm üyelerin rezervasyonlarını görüntüleyin", "view_bookings_across_description": "En çok rezervasyonu kimin aldığını görün ve tüm ekibiniz için en iyi dağılımı sağlayın", "identify_booking_trends": "Rezervasyon trendlerini belirleyin", @@ -1750,7 +1750,7 @@ "events_cancelled": "İptal Edilen Etkinlikler", "events_rescheduled": "Yeniden Planlanan Etkinlikler", "from_last_period": "son dönemden itibaren", - "from_to_date_period": "İlk tarih: {{startDate}} Son tarih: {{endDate}}", + "from_to_date_period": "Başlangıç: {{startDate}} Bitiş: {{endDate}}", "analytics_for_organisation": "Insights", "subtitle_analytics": "Ekibinizin aktivitesi hakkında daha fazla bilgi edinin", "redirect_url_warning": "Yönlendirme eklemek başarı sayfasını devre dışı bırakır. Özel başarı sayfanızda \"Rezervasyon Onaylandı\" ifadesinden bahsetmeyi unutmayın.", diff --git a/apps/web/public/static/locales/uk/common.json b/apps/web/public/static/locales/uk/common.json index 19ce9f99b3..770f29bf04 100644 --- a/apps/web/public/static/locales/uk/common.json +++ b/apps/web/public/static/locales/uk/common.json @@ -70,7 +70,7 @@ "event_awaiting_approval_subject": "Очікування на схвалення: {{title}}, {{date}}", "event_still_awaiting_approval": "Захід досі чекає на ваше схвалення", "booking_submitted_subject": "Бронювання надіслано: {{title}}, {{date}}", - "download_recording_subject": "Завантажити запис: {{title}} о {{date}}", + "download_recording_subject": "Завантажити запис: {{title}} від {{date}}", "download_your_recording": "Завантажте запис", "your_meeting_has_been_booked": "Вашу нараду заброньовано", "event_type_has_been_rescheduled_on_time_date": "Ваш {{title}} перенесено на {{date}}.", @@ -408,7 +408,7 @@ "add_time_availability": "Додати нове часове вікно", "add_an_extra_layer_of_security": "Підвищте рівень захисту свого облікового запису на випадок, якщо у вас викрадуть пароль.", "2fa": "Двоетапна автентифікація", - "2fa_disabled": "Двоетапну автентифікацію можна ввімкнути лише для автентифікації за допомогою ел. пошти й пароля", + "2fa_disabled": "Двоетапну автентифікацію можна ввімкнути лише для входу за допомогою електронної пошти й пароля", "enable_2fa": "Увімкнути двоетапну автентифікацію", "disable_2fa": "Вимкнути двоетапну автентифікацію", "disable_2fa_recommendation": "Якщо вам потрібно вимкнути двоетапну перевірку, рекомендуємо потім увімкнути її якомога швидше.", @@ -593,16 +593,16 @@ "username_placeholder": "ім’я користувача", "managed_event_description": "Створюйте й надсилайте типи заходів усім учасникам команди пакетом", "managed": "Керований", - "managed_event_url_clarification": "у полі «ім’я користувача» буде вказано імена користувачів призначених учасників", - "assign_to": "Призначити", + "managed_event_url_clarification": "Поле «username» буде заповнено іменами користувачів призначених учасників", + "assign_to": "Кому призначити:", "add_members": "Додати учасників…", "count_members_one": "{{count}} учасник", - "count_members_other": "{{count}} учасники(-ів)", + "count_members_other": "Учасників: {{count}}", "no_assigned_members": "Немає призначених учасників", - "assigned_to": "Призначено", + "assigned_to": "Кому призначено:", "start_assigning_members_above": "Почніть призначати учасників вище", - "locked_fields_admin_description": "Учасники не зможуть редагувати це", - "locked_fields_member_description": "Адміністратор команди заблокував цю опцію", + "locked_fields_admin_description": "Учасники не зможуть змінити це", + "locked_fields_member_description": "Адміністратор команди заблокував цей варіант", "url": "URL", "hidden": "Приховано", "readonly": "Тільки читання", @@ -756,8 +756,8 @@ "slot_interval_default": "Використовувати тривалість заходу (за замовчуванням)", "delete_event_type": "Видалити тип заходу?", "delete_managed_event_type": "Видалити захід керованого типу?", - "delete_event_type_description": "Користувачі, з якими ви поділилися цим посиланням, більше не зможуть здійснити бронювання з його допомогою.", - "delete_managed_event_type_description": "
  • В учасників, призначених для цього типу заходу, також буде видалено їхні типи заходів.
  • Користувачі, з якими вони поділилися своїм посиланням, більше не зможуть здійснити бронювання з його допомогою.
", + "delete_event_type_description": "Користувачі, з якими ви поділилися цим посиланням, більше не зможуть скористатися ним, щоб створити бронювання.", + "delete_managed_event_type_description": "
  • Типи заходів учасників, призначених для цього типу заходу, також буде видалено.
  • Користувачі, з якими вони поділилися своїм посиланням, більше не зможуть скористатися ним, щоб створити бронювання.
", "confirm_delete_event_type": "Так, видалити", "delete_account": "Видалити обліковий запис", "confirm_delete_account": "Так, видалити обліковий запис", @@ -1148,13 +1148,13 @@ "current_username": "Поточне ім’я користувача", "example_1": "Приклад 1", "example_2": "Приклад 2", - "booking_question_identifier": "Ідентифікатор запитання щодо бронювання", + "booking_question_identifier": "Ідентифікатор запитання про бронювання", "company_size": "Розмір компанії", "what_help_needed": "З чим саме потрібна допомога?", "variable_format": "Формат змінної", "webhook_subscriber_url_reserved": "URL-адресу для абонента у вебгуку вже визначено", "custom_input_as_variable_info": "Усі спеціальні символи в додатковому введеному підписі пропускаються (використовуються тільки літери та цифри), використовуються лише великі літери, пробіли замінюються на підкреслення.", - "using_booking_questions_as_variables": "Як використовувати запитання щодо бронювання як змінні?", + "using_booking_questions_as_variables": "Як використовувати запитання про бронювання як змінні?", "download_desktop_app": "Завантажити додаток для ПК", "set_ping_link": "Установити Ping-посилання", "rate_limit_exceeded": "Перевищено ліміт кількості запитів", @@ -1177,7 +1177,7 @@ "event_advanced_tab_title": "Додатково", "event_setup_multiple_duration_error": "Налаштування заходу: якщо мають бути доступні різні варіанти, потрібно вказати принаймні один.", "event_setup_multiple_duration_default_error": "Налаштування заходу: виберіть припустиму тривалість за замовчуванням.", - "event_setup_booking_limits_error": "Ліміти на бронювання мають бути впорядковані за зростанням. [день,тиждень,місяць,рік]", + "event_setup_booking_limits_error": "Ліміти бронювань має бути впорядковано за зростанням. [день, тиждень, місяць, рік]", "event_setup_duration_limits_error": "Ліміти тривалості мають бути впорядковані за зростанням. [день, тиждень, місяць, рік]", "select_which_cal": "Виберіть календар, у який додаватимуться бронювання", "custom_event_name": "Користувацька назва події", @@ -1200,7 +1200,7 @@ "start_of_week": "Початок тижня", "recordings_title": "Записи", "recording": "Запис", - "happy_scheduling": "Щасливого планування", + "happy_scheduling": "Вдалого планування", "select_calendars": "Виберіть, які календарі потрібно перевірити на наявність конфліктів, щоб уникнути подвійних бронювань.", "check_for_conflicts": "Перевірка конфліктів", "view_recordings": "Переглянути записи", @@ -1317,7 +1317,7 @@ "exchange_authentication_standard": "Проста автентифікація", "exchange_authentication_ntlm": "Автентифікація NTLM", "exchange_compression": "Стиснення GZip", - "exchange_version": "Версія обміну", + "exchange_version": "Версія Exchange", "exchange_version_2007_SP1": "2007 SP1", "exchange_version_2010": "2010", "exchange_version_2010_SP1": "2010 SP1", @@ -1326,7 +1326,7 @@ "exchange_version_2013_SP1": "2013 SP1", "exchange_version_2015": "2015", "exchange_version_2016": "2016", - "routing_forms_description": "Тут можна переглянути всі створені вами форми й переспрямування.", + "routing_forms_description": "Створюйте форми, щоб правильно скеровувати відвідувачів", "routing_forms_send_email_owner": "Надіслати власнику електронний лист", "routing_forms_send_email_owner_description": "Коли надсилається форма, власник отримує електронний лист", "add_new_form": "Додати нову форму", @@ -1380,7 +1380,7 @@ "add_limit": "Додати ліміт", "team_name_required": "Назва команди обов’язкова", "show_attendees": "Поширювати інформацію про учасників серед гостей", - "how_booking_questions_as_variables": "Як використовувати запитання щодо бронювання як змінні?", + "how_booking_questions_as_variables": "Як використовувати запитання про бронювання як змінні?", "format": "Формат", "uppercase_for_letters": "Використовувати всі великі літери", "replace_whitespaces_underscores": "Заміняти пробіли підкресленнями", @@ -1395,7 +1395,7 @@ "billing_help_title": "Потрібно щось інше?", "billing_help_description": "Якщо вам потрібна додаткова допомога з виставленням рахунків, наша служба підтримки готова її надати.", "billing_help_cta": "Звернутися в службу підтримки", - "ignore_special_characters_booking_questions": "Пропускайте спеціальні символи в ідентифікаторі запитання щодо бронювання. Використовуйте лише літери й цифри.", + "ignore_special_characters_booking_questions": "Пропускайте спеціальні символи в ідентифікаторі запитання про бронювання та використовуйте лише літери й цифри", "retry": "Повторити", "fetching_calendars_error": "Під час отримання календарів виникла проблема. <1>Повторіть спробу або зверніться в службу підтримки.", "calendar_connection_fail": "Не вдалося підключитися до календаря", @@ -1487,8 +1487,8 @@ "event_replaced_notice": "Адміністратор замінив один із типів ваших заходів", "email_subject_slug_replacement": "Адміністратор команди замінив ваш захід /{{slug}}", "email_body_slug_replacement_notice": "Адміністратор команди {{teamName}} замінив ваш тип заходу/{{slug}} на керований тип заходу, який він контролює.", - "email_body_slug_replacement_info": "Ваше посилання продовжуватиме працювати, але деякі параметри можуть бути змінені. Дізнайтеся більше в типах подій.", - "email_body_slug_replacement_suggestion": "Якщо у вас виникли запитання щодо типу заходу, зверніться до свого адміністратора.

Щасливого планування!
Команда Cal.com", + "email_body_slug_replacement_info": "Ваше посилання й надалі працювати, але деякі параметри можуть змінитися. Перегляньте їх у типах подій.", + "email_body_slug_replacement_suggestion": "Якщо у вас є запитання про тип заходу, зверніться до свого адміністратора.

Вдалого планування!
Команда Cal.com", "disable_payment_app": "Адміністратор вимкнув додаток {{appName}}, що впливає на ваш тип заходу «{{title}}». Учасники все одно можуть бронювати події такого типу, але від них не вимагатиметься оплата. Ви можете приховати цей тип заходу, щоб уникнути цього, і дочекатись активації способу оплати адміністратором.", "payment_disabled_still_able_to_book": "Учасники все одно можуть бронювати події такого типу, але від них не вимагатиметься оплата. Ви можете приховати цей тип заходу, щоб уникнути цього, і дочекатись активації способу оплати адміністратором.", "app_disabled_with_event_type": "Адміністратор вимкнув додаток {{appName}}, що впливає на ваш тип заходу «{{title}}».", @@ -1584,7 +1584,7 @@ "ee_enterprise_license": "“/ee” Ліцензія для плану Enterprise", "enterprise_booking_fee": "Від {{enterprise_booking_fee}}/міс.", "enterprise_license_includes": "Усе для комерційного використання", - "no_need_to_keep_your_code_open_source": "Немає потреби залишати код відкритим", + "no_need_to_keep_your_code_open_source": "Немає потреби залишати вихідний код відкритим", "repackage_rebrand_resell": "Легко здійснювати перепакування, ребрендинг і перепродаж", "a_vast_suite_of_enterprise_features": "Великий набір корпоративних функцій", "free_license_fee": "$0,00/міс.", @@ -1661,7 +1661,7 @@ "not_enough_seats": "Недостатньо місць", "form_builder_field_already_exists": "Поле з такою назвою вже існує", "form_builder_field_add_subtitle": "Налаштуйте запитання, відповіді на які потрібно буде надати на сторінці бронювання", - "show_on_booking_page": "Показати на сторінці бронювання", + "show_on_booking_page": "Показувати на сторінці бронювання", "get_started_zapier_templates": "Розпочніть роботу із шаблонами Zapier", "team_is_unpublished": "Публікацію команди «{{team}}» скасовано", "team_is_unpublished_description": "Посилання на команду зараз недоступне. Зверніться до власника команди або попросіть його опублікувати команду.", @@ -1685,14 +1685,14 @@ "add_1_option_per_line": "Додати 1 варіант на рядок", "select_a_router": "Вибрати форму переспрямування", "add_a_new_route": "Додати нове переспрямування", - "make_informed_decisions": "Приймайте обґрунтовані рішення за допомогою аналітики", - "make_informed_decisions_description": "У розділі «Аналітика» показано всю діяльність вашої команди й тенденції, які дають змогу краще планувати час команди й приймати рішення.", + "make_informed_decisions": "Ухвалюйте обґрунтовані рішення за допомогою Insights", + "make_informed_decisions_description": "На інформаційній панелі Insights можна відстежувати всю роботу своєї команди й тенденції, які дають змогу ефективніше планувати час і ухвалювати рішення.", "view_bookings_across": "Переглядайте бронювання всіх учасників", - "view_bookings_across_description": "Дізнайтеся, хто отримує найбільше бронювань і забезпечте найкращий розподіл у команді", - "identify_booking_trends": "Визначте тенденції бронювання", - "identify_booking_trends_description": "Дізнайтесь, які дні тижня і який час популярні в користувачів, які здійснюють бронювання", - "spot_popular_event_types": "Визначте популярні типи заходів", - "spot_popular_event_types_description": "Дізнайтеся, які типи заходів отримують найбільшу кількість переходів і бронювань", + "view_bookings_across_description": "Дізнайтеся, у кого найбільше бронювань, і забезпечте оптимальний розподіл часу в команді", + "identify_booking_trends": "Визначайте тенденції бронювання", + "identify_booking_trends_description": "Дізнайтеся, які дні тижня і години дня найчастіше вибирають користувачі, які створюють бронювання", + "spot_popular_event_types": "Визначайте популярні типи заходів", + "spot_popular_event_types_description": "Дізнайтеся, які типи заходів мають найбільшу кількість переходів і бронювань", "no_responses_yet": "Поки немає відповідей", "this_will_be_the_placeholder": "Це заповнювач", "this_meeting_has_not_started_yet": "Ця нарада ще не почалася", @@ -1715,13 +1715,13 @@ "select_all": "Вибрати все", "default_conferencing_bulk_title": "Масове оновлення наявних типів заходів", "members_default_schedule": "Розклад учасника за замовчуванням", - "set_by_admin": "Встановлено адміністратором команди", + "set_by_admin": "Визначено адміністратором команди", "members_default_location": "Розташування учасника за замовчуванням", "members_default_schedule_description": "Ми використовуватимемо розклад кожного учасника за замовчуванням. Вони зможуть редагувати або змінювати його.", "requires_at_least_one_schedule": "У вас має бути принаймні один розклад", "default_conferencing_bulk_description": "Оновіть розташування для вибраних типів заходів", "locked_for_members": "Заблоковано для учасників", - "locked_apps_description": "Учасники зможуть бачити активні додатки, але не зможуть редагувати налаштування додатка", + "locked_apps_description": "Учасники бачитимуть активні додатки, але не зможуть змінювати їх налаштування", "locked_webhooks_description": "Учасники бачитимуть активні вебгуки, але не зможуть змінювати їх налаштування", "locked_workflows_description": "Учасники бачитимуть активні робочі процеси, але не зможуть змінювати їх налаштування", "app_not_connected": "Ви не під’єднали обліковий запис {{appName}}.", @@ -1732,14 +1732,14 @@ "managed_event_dialog_title_other": "URL-адреса /{{slug}} вже існує для кількох учасників ({{count}}). Хочете замінити її?", "managed_event_dialog_information_one": "{{names}} вже використовує URL-адресу /{{slug}}.", "managed_event_dialog_information_other": "{{names}} вже використовують URL-адресу /{{slug}}.", - "managed_event_dialog_clarification": "Якщо ви захочете замінити її, ми повідомимо їм. Поверніться і вилучіть їх, якщо ви не хочете перезаписувати її.", + "managed_event_dialog_clarification": "Якщо ви захочете замінити її, ми повідомимо учасників. Поверніться і вилучіть їх, замість того щоб перезаписувати.", "review_event_type": "Переглянути тип заходу", - "looking_for_more_analytics": "Вам потрібно більше аналітичних даних?", - "looking_for_more_insights": "Вам потрібно більше аналітики?", + "looking_for_more_analytics": "Потрібно більше аналітичних даних?", + "looking_for_more_insights": "Потрібно більше аналітики?", "add_filter": "Додати фільтр", - "select_user": "Виберіть користувача", - "select_event_type": "Виберіть тип заходу", - "select_date_range": "Виберіть діапазон дат", + "select_user": "Вибрати користувача", + "select_event_type": "Вибрати тип заходу", + "select_date_range": "Вибрати діапазон дат", "popular_events": "Популярні заходи", "no_event_types_found": "Типи заходів не знайдено", "average_event_duration": "Середня тривалість заходу", @@ -1751,35 +1751,35 @@ "events_rescheduled": "Перенесені заходи", "from_last_period": "після останнього періоду", "from_to_date_period": "З: {{startDate}} До: {{endDate}}", - "analytics_for_organisation": "Аналітика", - "subtitle_analytics": "Дізнайтеся більше про активність вашої команди", - "redirect_url_warning": "Додавання переспрямування вимкне сторінку з результатом бронювання. Переконайтеся, що ви додали текст «Бронювання підтверджено» на свою сторінку з результатом бронювання.", + "analytics_for_organisation": "Insights", + "subtitle_analytics": "Дізнайтеся більше про активність своєї команди", + "redirect_url_warning": "Якщо додати переспрямування, сторінка з результатом бронювання не з’являтиметься. Переконайтеся, що ви додали текст «Бронювання підтверджено» на свою сторінку з результатом бронювання.", "event_trends": "Тенденції заходів", "clear_filters": "Очистити фільтри", - "hold": "Утримати", - "on_booking_option": "Отримуйте оплату за бронювання", - "hold_option": "Стягнути плату за відсутність", - "card_held": "Здійснено попередню авторизацію", + "hold": "Блокування", + "on_booking_option": "Отримувати плату за бронювання", + "hold_option": "Стягувати плату за відсутність", + "card_held": "Кошти на картці заблоковано", "charge_card": "Списати з картки", - "card_charged": "Списано кошти з картки", + "card_charged": "Кошти списано з картки", "no_show_fee_amount": "Плата за відсутність у розмірі {{amount, currency}}", "no_show_fee": "Плата за відсутність", - "submit_card": "Надіслати картку", - "submit_payment_information": "Надіслати платіжну інформацію", - "meeting_awaiting_payment_method": "Потрібно вказати спосіб оплати для вашої наради", - "no_show_fee_charged_email_subject": "Стягнуто плату за відсутність на заході «{{title}}» у розмірі {{amount, currency}} о {{date}}", - "no_show_fee_charged_text_body": "Стягнуто плату за відсутність", - "no_show_fee_charged_subtitle": "За нижчезазначений захід було стягнуто плату за відсутність у розмірі {{amount, currency}}", - "error_charging_card": "Сталася помилка під час стягнення плати за відсутність. Спробуйте ще раз пізніше.", + "submit_card": "Додати картку", + "submit_payment_information": "Зберегти платіжну інформацію", + "meeting_awaiting_payment_method": "Виберіть спосіб оплати для своєї наради", + "no_show_fee_charged_email_subject": "Плату за відсутність на заході «{{title}}» у розмірі {{amount, currency}} стягнуто {{date}}", + "no_show_fee_charged_text_body": "Плату за відсутність стягнуто", + "no_show_fee_charged_subtitle": "Плату за відсутність у розмірі {{amount, currency}} стягнуто для вказаного нижче заходу", + "error_charging_card": "Сталася помилка під час стягнення плати за відсутність. Спробуйте пізніше.", "collect_no_show_fee": "Стягнути плату за відсутність", "no_show_fee_charged": "Стягнуто плату за відсутність", - "insights": "Аналітика", - "testing_workflow_info_message": "Тестуючи цей робочий процес, зважайте на те, що надсилання електронних листів і SMS-повідомлень можна запланувати мінімум за 1 годину", - "insights_no_data_found_for_filter": "Не знайдено жодних даних для вибраного фільтру або вибраних дат.", - "acknowledge_booking_no_show_fee": "Я підтверджую, що в разі моєї відсутності на цьому заході з моєї картки буде стягнуто плату за відсутність у розмірі {{amount, currency}}.", - "card_details": "Інформація про картку", - "seats_and_no_show_fee_error": "Наразі неможливо ввімкнути місця і стягнути плату за відсутність", + "insights": "Insights", + "testing_workflow_info_message": "Тестуючи цей робочий процес, зважайте на те, що надсилання електронних листів і SMS-повідомлень можна запланувати щонайменше за 1 годину", + "insights_no_data_found_for_filter": "Не знайдено жодних даних для вибраного фільтра або вибраних дат.", + "acknowledge_booking_no_show_fee": "Я усвідомлюю, що в разі моєї відсутності на цьому заході з моєї картки буде стягнуто плату в розмірі {{amount, currency}}.", + "card_details": "Карткові реквізити", + "seats_and_no_show_fee_error": "Зараз неможливо активувати місця і стягнути плату за відсутність", "complete_your_booking": "Завершіть бронювання", - "complete_your_booking_subject": "Завершіть бронювання: {{title}} {{date}}", + "complete_your_booking_subject": "Завершіть бронювання: {{title}} від {{date}}", "email_invite_team": "{{email}} запрошено" } diff --git a/apps/web/public/static/locales/zh-CN/common.json b/apps/web/public/static/locales/zh-CN/common.json index 7a7766e92c..0595aa3f5c 100644 --- a/apps/web/public/static/locales/zh-CN/common.json +++ b/apps/web/public/static/locales/zh-CN/common.json @@ -408,7 +408,7 @@ "add_time_availability": "添加新的时间段", "add_an_extra_layer_of_security": "在您的密码被盗时,能给您的账户添加一层额外的安全保护", "2fa": "两步验证", - "2fa_disabled": "只能为电子邮件和密码身份验证启用双因素身份验证", + "2fa_disabled": "只能为电子邮件和密码身份验证启用两步验证", "enable_2fa": "启用两步验证", "disable_2fa": "禁用两步验证", "disable_2fa_recommendation": "如果您需要临时禁用两步验证,我们建议尽快重新启用它。", @@ -591,7 +591,7 @@ "round_robin_description": "和多个团队成员之间轮流举行会议。", "managed_event": "托管活动", "username_placeholder": "用户名", - "managed_event_description": "批量创建事件类型并分发给团队成员", + "managed_event_description": "批量创建活动类型并分发给团队成员", "managed": "托管", "managed_event_url_clarification": "“用户名”将填写所分配成员的用户名", "assign_to": "分配给", @@ -600,7 +600,7 @@ "count_members_other": "{{count}} 个成员", "no_assigned_members": "没有已分配的成员", "assigned_to": "已分配给", - "start_assigning_members_above": "开始分配上面的成员", + "start_assigning_members_above": "开始分配以上成员", "locked_fields_admin_description": "成员将无法编辑此内容", "locked_fields_member_description": "此选项已被团队管理员锁定", "url": "链接", @@ -755,9 +755,9 @@ "slot_interval": "时间段间隔", "slot_interval_default": "使用活动时长 (默认)", "delete_event_type": "删除活动类型?", - "delete_managed_event_type": "创建托管活动类型?", + "delete_managed_event_type": "删除托管活动类型?", "delete_event_type_description": "您将此链接共享给的任何人都将无法再使用它进行预约。", - "delete_managed_event_type_description": "
  • 分配给此活动类型的成员也将删除其活动类型。
  • 他们将链接共享给的任何人都将无法再使用它进行预约。
", + "delete_managed_event_type_description": "
  • 分配给此活动类型的成员其活动类型也将被删除。
  • 如果他们将自己的链接共享给其他人,这些人也都将无法再使用该链接进行预约。
", "confirm_delete_event_type": "是,删除", "delete_account": "删除账户", "confirm_delete_account": "是,删除账户", @@ -904,6 +904,7 @@ "duplicate": "复制", "offer_seats": "提供位置", "offer_seats_description": "提供位置以供预约 (这将自动禁止访客和选择加入预约)。", + "seats_available_one": "可用位置", "seats_available_other": "可用位置", "number_of_seats": "每个预约的位置数目", "enter_number_of_seats": "输入位置数目", @@ -1178,7 +1179,7 @@ "event_advanced_tab_title": "高级", "event_setup_multiple_duration_error": "活动设置: 多个时长需要至少 1 个选项。", "event_setup_multiple_duration_default_error": "活动设置: 请选择有效的默认时长。", - "event_setup_booking_limits_error": "预约限制必须按升序排列。[日,周,月,年]", + "event_setup_booking_limits_error": "预约限制必须按升序排列:[日、周、月、年]", "event_setup_duration_limits_error": "时长限制必须按升序排列。[日, 周, 月, 年]", "select_which_cal": "选择要添加预约的日历", "custom_event_name": "自定义活动名称", @@ -1279,7 +1280,7 @@ "attendee_name_info": "预约人的姓名", "to": "至", "workflow_turned_on_successfully": "{{workflowName}} 工作流程已成功{{offOn}}", - "download_responses": "下载回应", + "download_responses": "下载回复", "download_responses_description": "以 CSV 格式下载对表格的所有回复。", "download": "下载", "download_recording": "下载录制内容", @@ -1329,7 +1330,7 @@ "exchange_version_2013_SP1": "2013 SP1", "exchange_version_2015": "2015", "exchange_version_2016": "2016", - "routing_forms_description": "您可以在此处看到您创建的所有表格和途径。", + "routing_forms_description": "创建表格以将参与者引导到正确的目的地", "routing_forms_send_email_owner": "向所有者发送电子邮件", "routing_forms_send_email_owner_description": "提交表单后向所有者发送电子邮件", "add_new_form": "添加新表格", @@ -1398,7 +1399,7 @@ "billing_help_title": "还有其他需要?", "billing_help_description": "如果您在支付帐单时需要任何进一步的帮助,我们的支持团队随时为您提供。", "billing_help_cta": "联系支持", - "ignore_special_characters_booking_questions": "忽略预约问题标识符中的特殊字符。仅使用字母和数字", + "ignore_special_characters_booking_questions": "忽略您的预约问题标识符中的特殊字符。仅使用字母和数字", "retry": "重试", "fetching_calendars_error": "获取您的日历时出现问题。请<1>重试或联系客户支持。", "calendar_connection_fail": "日历连接失败", @@ -1487,10 +1488,10 @@ "duration_limit_reached": "已达到此活动类型的时长限制", "admin_has_disabled": "管理员已禁用 {{appName}}", "disabled_app_affects_event_type": "管理员已禁用会影响活动类型 {{eventType}} 的 {{appName}}", - "event_replaced_notice": "管理员已替换您的一个活动类型", - "email_subject_slug_replacement": "团队管理员已替换您的活动/{{slug}}", - "email_body_slug_replacement_notice": "{{teamName}} 团队的管理员已将您的活动类型 {{slug}} 替换为他们控制的托管活动类型。", - "email_body_slug_replacement_info": "您的链接将继续有效,但其某些设置可能已更改。您可以在活动类型中查看它。", + "event_replaced_notice": "一位管理员已替换您的其中一个活动类型", + "email_subject_slug_replacement": "一位团队管理员已替换您的活动/{{slug}}", + "email_body_slug_replacement_notice": "{{teamName}} 团队的一位管理员已将您的活动类型 {{slug}} 替换为他们控制的托管活动类型。", + "email_body_slug_replacement_info": "您的链接将继续有效,但其某些设置可能已更改。您可以在活动类型中进行查看。", "email_body_slug_replacement_suggestion": "如果您对活动类型有任何疑问,请联系您的管理员。

祝日程安排愉快,
Cal.com 团队", "disable_payment_app": "管理员已禁用会影响活动类型 {{title}} 的 {{appName}}。参与者仍然能预约此类型的活动,但不会被提示付款。您可以隐藏该活动类型来防止出现这种情况,直到管理员重新启用您的支付方式。", "payment_disabled_still_able_to_book": "参与者仍然能预约此类型的活动,但不会被提示付款。您可以隐藏该活动类型来防止出现这种情况,直到管理员重新启用您的支付方式。", @@ -1689,13 +1690,13 @@ "select_a_router": "选择途径", "add_a_new_route": "添加新途径", "make_informed_decisions": "使用 Insights 做出明智的决策", - "make_informed_decisions_description": "我们的 Insights 仪表板会显示您团队中的所有活动,并向您展示趋势,以便更好地进行团队日程安排和制定决策。", + "make_informed_decisions_description": "我们的 Insights 仪表板会显示您团队的所有活动,并向您展示趋势,以便您更好地进行团队日程安排和制定决策。", "view_bookings_across": "查看所有成员的预约", "view_bookings_across_description": "查看谁收到的预约最多,并确保在团队中实现最佳分配", "identify_booking_trends": "确定预约趋势", - "identify_booking_trends_description": "查看一周中的哪些时间和一天中的哪些时间受预约者欢迎", + "identify_booking_trends_description": "查看一周中和一天中最受预约者欢迎的时段", "spot_popular_event_types": "发现热门的活动类型", - "spot_popular_event_types_description": "查看哪些活动类型获得最多点击和预约", + "spot_popular_event_types_description": "查看获得最多点击和预约的活动类型", "no_responses_yet": "尚无回复", "this_will_be_the_placeholder": "这将是占位符", "this_meeting_has_not_started_yet": "此会议尚未开始", @@ -1717,18 +1718,18 @@ "invalid_event_name_variables": "活动名称中存在无效变量", "select_all": "全选", "default_conferencing_bulk_title": "批量更新现有活动类型", - "members_default_schedule": "成员的默认时间表", + "members_default_schedule": "成员的默认日程安排", "set_by_admin": "由团队管理员设置", "members_default_location": "成员的默认位置", - "members_default_schedule_description": "我们将使用每个成员的默认可预约时间表。他们将能够编辑或更改它。", - "requires_at_least_one_schedule": "您需要至少有一个时间表", + "members_default_schedule_description": "我们将使用每位成员的默认可用日程安排。他们可对其进行编辑或更改。", + "requires_at_least_one_schedule": "您需要至少有一个日程安排", "default_conferencing_bulk_description": "更新选定活动类型的位置", - "locked_for_members": "为成员锁定", + "locked_for_members": "已为成员锁定", "locked_apps_description": "成员将能够查看活动的应用,但不能编辑任何应用设置", "locked_webhooks_description": "成员将能够查看活动的 Webhook,但不能编辑任何 Webhook 设置", "locked_workflows_description": "成员将能够查看活动的工作流程,但不能编辑任何工作流程设置", "locked_by_admin": "被团队管理员锁定", - "app_not_connected": "您尚未连接 {{appName}} 账户。", + "app_not_connected": "您尚未连接 {{appName}} 帐户。", "connect_now": "立即连接", "managed_event_dialog_confirm_button_one": "替换并通知 {{count}} 个成员", "managed_event_dialog_confirm_button_other": "替换并通知 {{count}} 个成员", @@ -1738,8 +1739,8 @@ "managed_event_dialog_information_other": "{{names}} 已经在使用 /{{slug}} 链接。", "managed_event_dialog_clarification": "如果您选择将其替换,我们会通知他们。如果您不想将其覆盖,请返回并移除。", "review_event_type": "查看活动类型", - "looking_for_more_analytics": "寻找更多分析?", - "looking_for_more_insights": "寻找更多洞察?", + "looking_for_more_analytics": "寻找更多的分析?", + "looking_for_more_insights": "寻找更多的 Insights?", "add_filter": "添加筛选器", "select_user": "选择用户", "select_event_type": "选择活动类型", @@ -1749,11 +1750,11 @@ "average_event_duration": "平均活动持续时间", "most_booked_members": "预约最多的成员", "least_booked_members": "预约最少的成员", - "events_created": "创建的活动", - "events_completed": "完成的活动", - "events_cancelled": "取消的活动", - "events_rescheduled": "重新安排的活动", - "from_last_period": "从上一期间", + "events_created": "已创建的活动", + "events_completed": "已完成的活动", + "events_cancelled": "已取消的活动", + "events_rescheduled": "已重新安排的活动", + "from_last_period": "从上一时段开始", "from_to_date_period": "开始日期: {{startDate}} 结束日期: {{endDate}}", "analytics_for_organisation": "Insights", "subtitle_analytics": "详细了解团队的活动", @@ -1771,16 +1772,16 @@ "submit_card": "提交卡", "submit_payment_information": "提交付款信息", "meeting_awaiting_payment_method": "您的会议正在等待付款方式", - "no_show_fee_charged_email_subject": "已为 {{date}} 的 {{title}} 收取 {{amount, currency}} 的失约费", + "no_show_fee_charged_email_subject": "已针对 {{date}} 的 {{title}} 收取 {{amount, currency}} 的失约费", "no_show_fee_charged_text_body": "已收取失约费", - "no_show_fee_charged_subtitle": "已为以下活动收取 {{amount, currency}} 的失约费", + "no_show_fee_charged_subtitle": "已针对以下活动收取 {{amount, currency}} 的失约费", "error_charging_card": "收取失约费时出错。请稍后再试。", "collect_no_show_fee": "收取失约费", "no_show_fee_charged": "已收取失约费", "insights": "Insights", - "testing_workflow_info_message": "测试此工作流程时请注意,电子邮件和短信只能提前至少 1 小时安排", + "testing_workflow_info_message": "测试此工作流程时请注意,电子邮件和短信必须提前至少 1 小时安排", "insights_no_data_found_for_filter": "未找到所选筛选器或所选日期的数据。", - "acknowledge_booking_no_show_fee": "我确认,如果我不参加此活动,我的卡将被收取 {{amount, currency}} 的失约费。", + "acknowledge_booking_no_show_fee": "我同意,如果我不参加此活动,将从我的卡中收取 {{amount, currency}} 的失约费。", "card_details": "卡详细信息", "seats_and_no_show_fee_error": "当前无法启用位置和收取失约费", "complete_your_booking": "完成您的预约", @@ -1790,6 +1791,7 @@ "disable_attendees_confirmation_emails_description": "该活动类型中至少有一个工作流程处于活动状态,当该活动被预约时,将向参与者发送电子邮件。", "disable_host_confirmation_emails": "禁用主持人的默认确认电子邮件", "disable_host_confirmation_emails_description": "该活动类型中至少有一个工作流程处于活动状态,当该活动被预约时,将向主持人发送电子邮件。", + "add_an_override": "添加替代", "first_event_type_webhook_description": "为此活动类型创建第一个 Webhook", "create_for": "创建" } diff --git a/apps/web/public/static/locales/zh-TW/common.json b/apps/web/public/static/locales/zh-TW/common.json index a772357ba3..9a432e9315 100644 --- a/apps/web/public/static/locales/zh-TW/common.json +++ b/apps/web/public/static/locales/zh-TW/common.json @@ -593,12 +593,12 @@ "username_placeholder": "使用者名稱", "managed_event_description": "大量建立並分配活動類型給團隊成員", "managed": "受管", - "managed_event_url_clarification": "「使用者名稱」會填入獲指派成員的使用者名稱", + "managed_event_url_clarification": "「使用者名稱」會填入所指派成員的使用者名稱", "assign_to": "指派給", - "add_members": "新增成員……", + "add_members": "新增成員…", "count_members_one": "{{count}} 名成員", "count_members_other": "{{count}} 名成員", - "no_assigned_members": "未指派成員", + "no_assigned_members": "無已指派成員", "assigned_to": "已指派給", "start_assigning_members_above": "開始指派以上成員", "locked_fields_admin_description": "成員將無法編輯此欄位", @@ -756,8 +756,8 @@ "slot_interval_default": "使用活動長度(預設)", "delete_event_type": "要刪除活動類型嗎?", "delete_managed_event_type": "要刪除受管活動類型嗎?", - "delete_event_type_description": "與您共用此連結的任何使用者將無法再使用此連結預約。", - "delete_managed_event_type_description": "
  • 對於獲指派此活動類型的成員,他們的活動類型也將遭到刪除。
  • 與他們共用此連結的任何使用者將無法再使用此連結預約。
", + "delete_event_type_description": "和您共用此連結的任何使用者都無法再使用此連結預約。", + "delete_managed_event_type_description": "
  • 對於獲指派此活動類型的成員,其活動類型也將被刪除。
  • 和他們共用此連結的任何使用者都無法再使用此連結預約。
", "confirm_delete_event_type": "是,刪除", "delete_account": "刪除帳號", "confirm_delete_account": "是,刪除帳號", @@ -1154,7 +1154,7 @@ "variable_format": "變數格式", "webhook_subscriber_url_reserved": "已定義 Webhook 訂閱者網址", "custom_input_as_variable_info": "忽略額外輸入標籤的所有特殊字元 (只使用字母和數字),所有字母一律使用大寫,並以底線取代空格。", - "using_booking_questions_as_variables": "要如何將預約問題當作變數?", + "using_booking_questions_as_variables": "如何將預約問題當作變數?", "download_desktop_app": "下載桌面版應用程式", "set_ping_link": "設定 Ping 連結", "rate_limit_exceeded": "已超過限速", @@ -1326,7 +1326,7 @@ "exchange_version_2013_SP1": "2013 SP1", "exchange_version_2015": "2015", "exchange_version_2016": "2016", - "routing_forms_description": "您可以在這裡查看您已建立的所有表單和引導路徑。", + "routing_forms_description": "建立表單以將參與者引導到正確的目的地", "routing_forms_send_email_owner": "傳送電子郵件給擁有者", "routing_forms_send_email_owner_description": "表單提交後傳送電子郵件給擁有者", "add_new_form": "新增新表單", @@ -1487,7 +1487,7 @@ "event_replaced_notice": "管理員已取代您其中一個活動類型", "email_subject_slug_replacement": "團隊管理員已取代您的活動/{{slug}}", "email_body_slug_replacement_notice": "{{teamName}} 團隊管理員已使用其所控管的受管活動類型來取代您的活動類型/{{slug}}。", - "email_body_slug_replacement_info": "您的連結會繼續正常運作,但該連結的部分設定可能已變更。您可以在活動類型中檢閱相關設定。", + "email_body_slug_replacement_info": "您的連結會繼續正常運作,但該連結的某些設定可能已變更。您可以在活動類型中查看。", "email_body_slug_replacement_suggestion": "若您對於活動類型有任何疑問,請聯絡您的管理員。

祝排程愉快。
Cal.com 團隊", "disable_payment_app": "管理員已停用會影響 {{title}} 活動類型的 {{appName}}。與會者仍可預約此類型活動,但不會收到付費提示。您也可以隱藏此活動類型來避免這種情況,直到管理員再次啟用您的付款方式為止。", "payment_disabled_still_able_to_book": "與會者仍可預約此類型活動,但不會收到付費提示。您也可以隱藏此活動類型來避免這種情況,直到管理員再次啟用您的付款方式。", @@ -1551,7 +1551,7 @@ "create_your_first_team_webhook_description": "為此團隊活動類型建立第一個 Webhook", "create_webhook_team_event_type": "為此團隊活動類型建立 Webhook", "disable_success_page": "停用「成功」頁面 (只適用於您有重新導向網址時)", - "invalid_admin_password": "您是管理員,但您的密碼長度未符合至少 15 個字元的限制,或還未啟用兩階段驗證", + "invalid_admin_password": "您是管理員,但您的密碼長度未符合至少 15 個字元的限制,或是尚未啟用兩階段驗證", "change_password_admin": "變更密碼以獲得管理員存取權", "username_already_taken": "已有人使用此使用者名稱", "assignment": "指派", @@ -1689,10 +1689,10 @@ "make_informed_decisions_description": "我們的 Insights 儀表版會顯示您團隊內部的所有活動,並顯示有助於團隊排程和決策的趨勢。", "view_bookings_across": "查看所有成員的預約", "view_bookings_across_description": "查看誰收到最多預約,並確保預約妥善分配給所有團隊成員", - "identify_booking_trends": "判斷出預約趨勢", - "identify_booking_trends_description": "查看當週和當天最受預約者青睞的時段", - "spot_popular_event_types": "探索熱門活動類型", - "spot_popular_event_types_description": "查看您哪個活動類型的點閱次數和預約次數最多", + "identify_booking_trends": "找出預約趨勢", + "identify_booking_trends_description": "查看當週和當天最受預約者歡迎的時段", + "spot_popular_event_types": "找出熱門活動類型", + "spot_popular_event_types_description": "查看哪種活動類型的點閱次數及預約次數最多", "no_responses_yet": "還沒有回應", "this_will_be_the_placeholder": "此處會是佔位符", "this_meeting_has_not_started_yet": "此會議尚未開始", @@ -1709,16 +1709,16 @@ "event_end_time_info": "活動結束時間", "cancel_url_variable": "取消網址", "cancel_url_info": "取消此預約的網址", - "reschedule_url_variable": "重新預定網址", - "reschedule_url_info": "重新預定此預約的網址", + "reschedule_url_variable": "重新安排網址", + "reschedule_url_info": "重新安排此預約的網址", "invalid_event_name_variables": "您的活動名稱包含無效的變數", "select_all": "全部選擇", "default_conferencing_bulk_title": "大量更新現有活動類型", "members_default_schedule": "成員的預設行程表", "set_by_admin": "由團隊管理員設定", "members_default_location": "成員的預設位置", - "members_default_schedule_description": "我們會使用每位成員的預設開放時間行程表。他們將能編輯或變更該行程表。", - "requires_at_least_one_schedule": "您需要擁有至少一個行程表", + "members_default_schedule_description": "我們會使用每位成員的預設開放時間行程表。他們可以編輯或變更該行程表。", + "requires_at_least_one_schedule": "您必須擁有至少一個行程表", "default_conferencing_bulk_description": "更新所選活動類型的地點", "locked_for_members": "已為成員鎖定", "locked_apps_description": "成員將能查看啟用的應用程式,但無法編輯任何應用程式設定", @@ -1733,7 +1733,7 @@ "managed_event_dialog_information_one": "{{names}} 已在使用 /{{slug}} 網址。", "managed_event_dialog_information_other": "{{names}} 已在使用 /{{slug}} 網址。", "managed_event_dialog_clarification": "如果您選擇取代,我們會通知他們;如果不想覆寫既有網址,請返回並移除。", - "review_event_type": "檢閱活動類型", + "review_event_type": "查看活動類型", "looking_for_more_analytics": "在找更多分析嗎?", "looking_for_more_insights": "在找更多 Insights 嗎?", "add_filter": "新增篩選條件", @@ -1749,7 +1749,7 @@ "events_completed": "已完成的活動", "events_cancelled": "已取消的活動", "events_rescheduled": "已重新預定的活動", - "from_last_period": "從最後一個時期", + "from_last_period": "從上一個時段", "from_to_date_period": "開始:{{startDate}} 結束:{{endDate}}", "analytics_for_organisation": "Insights", "subtitle_analytics": "進一步瞭解您的團隊活動", diff --git a/apps/web/test/lib/checkBookingLimits.test.ts b/apps/web/test/lib/checkBookingLimits.test.ts index 2dad1bc828..281380e2df 100644 --- a/apps/web/test/lib/checkBookingLimits.test.ts +++ b/apps/web/test/lib/checkBookingLimits.test.ts @@ -1,10 +1,11 @@ +import { describe, expect, it } from "vitest"; +import prismaMock from "../../../../tests/libs/__mocks__/prisma"; + import dayjs from "@calcom/dayjs"; import { validateIntervalLimitOrder } from "@calcom/lib"; import { checkBookingLimits, checkBookingLimit } from "@calcom/lib/server"; import type { IntervalLimit } from "@calcom/types/Calendar"; -import { prismaMock } from "../../../../tests/config/singleton"; - type Mockdata = { id: number; startDate: Date; diff --git a/apps/web/test/lib/checkDurationLimits.test.ts b/apps/web/test/lib/checkDurationLimits.test.ts index 20c0f12b00..6186563b76 100644 --- a/apps/web/test/lib/checkDurationLimits.test.ts +++ b/apps/web/test/lib/checkDurationLimits.test.ts @@ -1,8 +1,10 @@ +import { describe, expect, it } from "vitest"; +import prismaMock from "../../../../tests/libs/__mocks__/prisma"; + import dayjs from "@calcom/dayjs"; import { validateIntervalLimitOrder } from "@calcom/lib"; import { checkDurationLimit, checkDurationLimits } from "@calcom/lib/server"; -import { prismaMock } from "../../../../tests/config/singleton"; type MockData = { id: number; diff --git a/apps/web/test/lib/getAggregateWorkingHours.test.ts b/apps/web/test/lib/getAggregateWorkingHours.test.ts index 47041f36b0..f7c96ffb6f 100644 --- a/apps/web/test/lib/getAggregateWorkingHours.test.ts +++ b/apps/web/test/lib/getAggregateWorkingHours.test.ts @@ -1,5 +1,5 @@ -import { expect, it } from "@jest/globals"; import MockDate from "mockdate"; +import { expect, it } from "vitest"; import { getAggregateWorkingHours } from "@calcom/core/getAggregateWorkingHours"; @@ -28,9 +28,9 @@ const HAWAII_AND_NEWYORK_TEAM = [ /* TODO: Make this test more "professional" */ it("Sydney and Shiraz can live in harmony 🙏", async () => { expect(getAggregateWorkingHours(HAWAII_AND_NEWYORK_TEAM, "COLLECTIVE")).toMatchInlineSnapshot(` - Array [ - Object { - "days": Array [ + [ + { + "days": [ 3, 4, 5, @@ -38,16 +38,16 @@ it("Sydney and Shiraz can live in harmony 🙏", async () => { "endTime": 360, "startTime": 780, }, - Object { - "days": Array [ + { + "days": [ 6, ], "endTime": 180, "startTime": 0, "userId": 2, }, - Object { - "days": Array [ + { + "days": [ 2, 3, 4, @@ -55,8 +55,8 @@ it("Sydney and Shiraz can live in harmony 🙏", async () => { "endTime": 1260, "startTime": 780, }, - Object { - "days": Array [ + { + "days": [ 5, ], "endTime": 1260, @@ -66,9 +66,9 @@ it("Sydney and Shiraz can live in harmony 🙏", async () => { `); expect(getAggregateWorkingHours(HAWAII_AND_NEWYORK_TEAM, "ROUND_ROBIN")).toMatchInlineSnapshot(` - Array [ - Object { - "days": Array [ + [ + { + "days": [ 1, 2, 3, @@ -79,8 +79,8 @@ it("Sydney and Shiraz can live in harmony 🙏", async () => { "startTime": 780, "userId": 1, }, - Object { - "days": Array [ + { + "days": [ 3, 4, 5, @@ -89,16 +89,16 @@ it("Sydney and Shiraz can live in harmony 🙏", async () => { "startTime": 0, "userId": 1, }, - Object { - "days": Array [ + { + "days": [ 6, ], "endTime": 180, "startTime": 0, "userId": 2, }, - Object { - "days": Array [ + { + "days": [ 2, 3, 4, @@ -107,8 +107,8 @@ it("Sydney and Shiraz can live in harmony 🙏", async () => { "startTime": 780, "userId": 3, }, - Object { - "days": Array [ + { + "days": [ 5, ], "endTime": 1439, diff --git a/apps/web/test/lib/getAvailabilityFromSchedule.test.ts b/apps/web/test/lib/getAvailabilityFromSchedule.test.ts index bac272f260..c4a26cc8b2 100644 --- a/apps/web/test/lib/getAvailabilityFromSchedule.test.ts +++ b/apps/web/test/lib/getAvailabilityFromSchedule.test.ts @@ -1,6 +1,6 @@ -import { expect, it } from "@jest/globals"; import type { Availability } from "@prisma/client"; import MockDate from "mockdate"; +import { expect, it } from "vitest"; import dayjs from "@calcom/dayjs"; import { getAvailabilityFromSchedule } from "@calcom/lib/availability"; diff --git a/apps/web/test/lib/getSchedule.test.ts b/apps/web/test/lib/getSchedule.test.ts index f9aab9ea93..9b45d7dbf0 100644 --- a/apps/web/test/lib/getSchedule.test.ts +++ b/apps/web/test/lib/getSchedule.test.ts @@ -1,15 +1,16 @@ -/** - * !: Stops the `jose` dependency from bundling the browser version and breaking tests - * @jest-environment node - */ import type { EventType as PrismaEventType, User as PrismaUser, Booking as PrismaBooking, App as PrismaApp, } from "@prisma/client"; + +import CalendarManagerMock from "../../../../tests/libs/__mocks__/CalendarManager"; +import prismaMock from "../../../../tests/libs/__mocks__/prisma"; + import { diff } from "jest-diff"; import { v4 as uuidv4 } from "uuid"; +import { describe, expect, vi, beforeEach, afterEach, test } from "vitest"; import logger from "@calcom/lib/logger"; import prisma from "@calcom/prisma"; @@ -18,14 +19,13 @@ import type { BookingStatus } from "@calcom/prisma/enums"; import type { Slot } from "@calcom/trpc/server/routers/viewer/slots/types"; import { getSchedule } from "@calcom/trpc/server/routers/viewer/slots/util"; -import { prismaMock, CalendarManagerMock } from "../../../../tests/config/singleton"; - // TODO: Mock properly prismaMock.eventType.findUnique.mockResolvedValue(null); prismaMock.user.findMany.mockResolvedValue([]); -jest.mock("@calcom/lib/constants", () => ({ +vi.mock("@calcom/lib/constants", () => ({ IS_PRODUCTION: true, + WEBAPP_URL: "http://localhost:3000" })); declare global { @@ -233,6 +233,13 @@ describe("getSchedule", () => { const { dateString: plus1DateString } = getDate({ dateIncrement: 1 }); const { dateString: plus2DateString } = getDate({ dateIncrement: 2 }); + CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([ + { + start: `${plus2DateString}T04:45:00.000Z`, + end: `${plus2DateString}T23:00:00.000Z`, + }, + ]); + const scenarioData = { hosts: [], eventTypes: [ @@ -261,12 +268,6 @@ describe("getSchedule", () => { // An event with one accepted booking createBookingScenario(scenarioData); - addBusyTimesInGoogleCalendar([ - { - start: `${plus2DateString}T04:45:00.000Z`, - end: `${plus2DateString}T23:00:00.000Z`, - }, - ]); const scheduleForDayWithAGoogleCalendarBooking = await getSchedule({ eventTypeId: 1, eventTypeSlug: "", @@ -492,7 +493,7 @@ describe("getSchedule", () => { // FIXME: Fix minimumBookingNotice is respected test test.skip("minimumBookingNotice is respected", async () => { - jest.useFakeTimers().setSystemTime( + vi.useFakeTimers().setSystemTime( (() => { const today = new Date(); // Beginning of the day in current timezone of the system @@ -571,13 +572,20 @@ describe("getSchedule", () => { dateString: todayDateString, } ); - jest.useRealTimers(); + vi.useRealTimers(); }); test("afterBuffer and beforeBuffer tests - Non Cal Busy Time", async () => { const { dateString: plus2DateString } = getDate({ dateIncrement: 2 }); const { dateString: plus3DateString } = getDate({ dateIncrement: 3 }); + CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([ + { + start: `${plus3DateString}T04:00:00.000Z`, + end: `${plus3DateString}T05:59:59.000Z`, + }, + ]); + const scenarioData = { eventTypes: [ { @@ -607,13 +615,6 @@ describe("getSchedule", () => { createBookingScenario(scenarioData); - addBusyTimesInGoogleCalendar([ - { - start: `${plus3DateString}T04:00:00.000Z`, - end: `${plus3DateString}T05:59:59.000Z`, - }, - ]); - const scheduleForEventOnADayWithNonCalBooking = await getSchedule({ eventTypeId: 1, eventTypeSlug: "", @@ -641,6 +642,13 @@ describe("getSchedule", () => { const { dateString: plus2DateString } = getDate({ dateIncrement: 2 }); const { dateString: plus3DateString } = getDate({ dateIncrement: 3 }); + CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([ + { + start: `${plus3DateString}T04:00:00.000Z`, + end: `${plus3DateString}T05:59:59.000Z`, + }, + ]); + const scenarioData = { eventTypes: [ { @@ -679,13 +687,6 @@ describe("getSchedule", () => { createBookingScenario(scenarioData); - addBusyTimesInGoogleCalendar([ - { - start: `${plus3DateString}T04:00:00.000Z`, - end: `${plus3DateString}T05:59:59.000Z`, - }, - ]); - const scheduleForEventOnADayWithCalBooking = await getSchedule({ eventTypeId: 1, eventTypeSlug: "", @@ -712,6 +713,8 @@ describe("getSchedule", () => { const { dateString: plus1DateString } = getDate({ dateIncrement: 1 }); const { dateString: plus2DateString } = getDate({ dateIncrement: 2 }); + CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([]); + const scenarioData = { eventTypes: [ { @@ -1380,15 +1383,3 @@ const getDate = (param: { dateIncrement?: number; monthIncrement?: number; yearI dateString: `${year}-${month}-${date}`, }; }; - -/** - * TODO: Improve this to validate the arguments passed to getBusyCalendarTimes if they are valid or not. - */ -function addBusyTimesInGoogleCalendar( - busy: { - start: string; - end: string; - }[] -) { - CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue(busy); -} diff --git a/apps/web/test/lib/getTimezone.test.ts b/apps/web/test/lib/getTimezone.test.ts index bd3701355e..2f5f5d0d58 100644 --- a/apps/web/test/lib/getTimezone.test.ts +++ b/apps/web/test/lib/getTimezone.test.ts @@ -1,4 +1,4 @@ -import { expect, it } from "@jest/globals"; +import { expect, it } from "vitest"; import { filterByCities, addCitiesToDropdown, handleOptionLabel } from "@calcom/lib/timezone"; @@ -34,25 +34,25 @@ const option = { }; it("should return empty array for an empty string", () => { - expect(filterByCities("", cityData)).toMatchInlineSnapshot(`Array []`); + expect(filterByCities("", cityData)).toMatchInlineSnapshot(`[]`); }); it("should filter cities for a valid city name", () => { expect(filterByCities("San Francisco", cityData)).toMatchInlineSnapshot(` - Array [ - Object { + [ + { "city": "San Francisco", "timezone": "America/Argentina/Cordoba", }, - Object { + { "city": "San Francisco de Macoris", "timezone": "America/Santo_Domingo", }, - Object { + { "city": "San Francisco Gotera", "timezone": "America/El_Salvador", }, - Object { + { "city": "San Francisco", "timezone": "America/Los_Angeles", }, @@ -62,7 +62,7 @@ it("should filter cities for a valid city name", () => { it("should return appropriate timezone(s) for a given city name array", () => { expect(addCitiesToDropdown(cityData)).toMatchInlineSnapshot(` - Object { + { "America/Argentina/Cordoba": "San Francisco", "America/El_Salvador": "San Francisco Gotera", "America/Los_Angeles": "San Francisco", diff --git a/apps/web/test/lib/getWorkingHours.test.ts b/apps/web/test/lib/getWorkingHours.test.ts index acc45c6dd4..26346aa188 100644 --- a/apps/web/test/lib/getWorkingHours.test.ts +++ b/apps/web/test/lib/getWorkingHours.test.ts @@ -1,5 +1,5 @@ -import { expect, it } from "@jest/globals"; import MockDate from "mockdate"; +import { expect, it } from "vitest"; import dayjs from "@calcom/dayjs"; import { getWorkingHours } from "@calcom/lib/availability"; diff --git a/apps/web/test/lib/handleChildrenEventTypes.test.ts b/apps/web/test/lib/handleChildrenEventTypes.test.ts index d02d168100..95a7ab0aac 100644 --- a/apps/web/test/lib/handleChildrenEventTypes.test.ts +++ b/apps/web/test/lib/handleChildrenEventTypes.test.ts @@ -1,11 +1,12 @@ import type { EventType } from "@prisma/client"; -import type { Prisma } from "@prisma/client"; +import { describe, expect, it, vi } from "vitest"; import updateChildrenEventTypes from "@calcom/features/ee/managed-event-types/lib/handleChildrenEventTypes"; import { buildEventType } from "@calcom/lib/test/builder"; +import type { Prisma } from "@calcom/prisma/client"; import type { CompleteEventType, CompleteWorkflowsOnEventTypes } from "@calcom/prisma/zod"; -import { prismaMock } from "../../../../tests/config/singleton"; +import prismaMock from "../../../../tests/libs/__mocks__/prisma"; const mockFindFirstEventType = (data?: Partial) => { const eventType = buildEventType(data as Partial); @@ -13,13 +14,13 @@ const mockFindFirstEventType = (data?: Partial) => { return eventType; }; -jest.mock("@calcom/emails/email-manager", () => { +vi.mock("@calcom/emails/email-manager", () => { return { sendSlugReplacementEmail: () => ({}), }; }); -jest.mock("@calcom/lib/server/i18n", () => { +vi.mock("@calcom/lib/server/i18n", () => { return { getTranslation: (key: string) => key, }; diff --git a/apps/web/test/lib/parseZone.test.ts b/apps/web/test/lib/parseZone.test.ts index 0883283da9..b8a312f296 100644 --- a/apps/web/test/lib/parseZone.test.ts +++ b/apps/web/test/lib/parseZone.test.ts @@ -1,3 +1,5 @@ +import { expect, it } from "vitest"; + import { parseZone } from "@calcom/lib/parse-zone"; const EXPECTED_DATE_STRING = "2021-06-20T11:59:59+02:00"; diff --git a/apps/web/test/lib/slots.test.ts b/apps/web/test/lib/slots.test.ts index 4bc5f2ebc1..df07104ae9 100644 --- a/apps/web/test/lib/slots.test.ts +++ b/apps/web/test/lib/slots.test.ts @@ -1,5 +1,5 @@ -import { expect, it } from "@jest/globals"; import MockDate from "mockdate"; +import { describe, expect, it } from "vitest"; import dayjs from "@calcom/dayjs"; import { MINUTES_DAY_END, MINUTES_DAY_START } from "@calcom/lib/availability"; diff --git a/apps/web/test/lib/team-event-types.test.ts b/apps/web/test/lib/team-event-types.test.ts index d4e61bdcb6..84275f4c42 100644 --- a/apps/web/test/lib/team-event-types.test.ts +++ b/apps/web/test/lib/team-event-types.test.ts @@ -1,8 +1,10 @@ +import { expect, it } from "vitest"; + +import prismaMock from "../../../../tests/libs/__mocks__/prisma"; + import { getLuckyUser } from "@calcom/lib/server"; import { buildUser } from "@calcom/lib/test/builder"; -import { prismaMock } from "../../../../tests/config/singleton"; - it("can find lucky user with maximize availability", async () => { const user1 = buildUser({ id: 1, diff --git a/git-init.sh b/git-init.sh index 045c134681..a480c0c3d4 100755 --- a/git-init.sh +++ b/git-init.sh @@ -5,4 +5,4 @@ exit 0 } -./git-setup.sh api website console +./git-setup.sh website console diff --git a/git-setup.sh b/git-setup.sh index 9524ce21af..f9deccf6d5 100755 --- a/git-setup.sh +++ b/git-setup.sh @@ -2,7 +2,7 @@ # If no project name is given if [ $# -eq 0 ]; then # Display usage and stop - echo "Usage: git-setup.sh " + echo "Usage: git-setup.sh " exit 1 fi # Get remote url to support either https or ssh @@ -30,13 +30,7 @@ for module in "$@"; do # We forcefully added the subdmoule which was in .gitignore, so unstage it. git restore --staged apps/$module else - # If the module is the API, display a link to request access - if [ "$module" = "api" ]; then - echo "You don't have access to: '${module}' module. You can request access in: https://console.cal.com" - else - # If the module is not the API, display normal message - echo "You don't have access to: '${module}' module." - fi + echo "You don't have access to: '${module}' module." fi done git restore --staged .gitmodules diff --git a/jest.config.ts b/jest.config.ts deleted file mode 100644 index 9fe67b3e5a..0000000000 --- a/jest.config.ts +++ /dev/null @@ -1,119 +0,0 @@ -import type { Config } from "jest"; - -// Added +2 to ensure we need to do some conversions in our tests -process.env.TZ = "GMT+2"; - -const config: Config = { - preset: "ts-jest", - verbose: true, - projects: [ - { - displayName: "@calcom/web", - roots: ["/apps/web"], - moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], - modulePathIgnorePatterns: [ - // - "/apps/web/test/__fixtures__", - "/apps/web/node_modules", - "/apps/web/dist", - ], - clearMocks: true, - setupFilesAfterEnv: ["/tests/config/singleton.ts"], - setupFiles: ["/apps/web/test/jest-setup.js"], - testMatch: ["**/test/lib/**/*.(spec|test).(ts|tsx|js)", "**/__tests__/**/*.(spec|test).(ts|tsx|js)"], - testPathIgnorePatterns: ["/apps/web/.next", "/apps/web/playwright/"], - transform: { - "^.+\\.(js|jsx|ts|tsx)$": ["babel-jest", { presets: ["next/babel"] }], - }, - transformIgnorePatterns: ["/node_modules/", "^.+\\.module\\.(css|sass|scss)$"], - testEnvironment: "jsdom", - resolver: `/apps/web/test/jest-resolver.js`, - moduleNameMapper: { - "^@components(.*)$": "/apps/web/components$1", - "^@lib(.*)$": "/apps/web/lib$1", - "^@server(.*)$": "/apps/web/server$1", - }, - }, - { - displayName: "@calcom/lib", - roots: ["/packages/lib"], - testEnvironment: "node", - moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], - transform: { - "^.+\\.tsx?$": "ts-jest", - }, - globals: { - "ts-jest": { - tsconfig: "/packages/lib/tsconfig.test.json", - }, - }, - }, - { - displayName: "@calcom/closecom", - roots: ["/packages/app-store/closecom"], - testMatch: ["**/test/lib/**/*.(spec|test).(ts|tsx|js)"], - transform: { - "^.+\\.ts?$": "ts-jest", - }, - transformIgnorePatterns: ["/node_modules/", "^.+\\.module\\.(css|sass|scss)$"], - testEnvironment: "jsdom", - setupFiles: ["/packages/app-store/closecom/test/globals.ts"], - }, - { - displayName: "@calcom/routing-forms", - roots: ["/packages/app-store/routing-forms"], - testMatch: ["**/test/lib/**/*.(spec|test).(ts|tsx|js)"], - transform: { - "^.+\\.ts?$": "ts-jest", - }, - transformIgnorePatterns: ["/node_modules/", "^.+\\.module\\.(css|sass|scss)$"], - testEnvironment: "jsdom", - }, - { - displayName: "@calcom/features", - roots: ["/packages/features"], - testMatch: ["**/*.(spec|test).(ts|tsx|js)"], - transform: { - "^.+\\.ts?$": "ts-jest", - }, - transformIgnorePatterns: ["/node_modules/", "^.+\\.module\\.(css|sass|scss)$"], - testEnvironment: "jsdom", - moduleDirectories: ["node_modules", ""], - globals: { - "ts-jest": { - tsconfig: "/packages/features/tsconfig.json", - }, - }, - }, - // FIXME: Prevent this breaking Jest when API module is missing - // { - // displayName: "@calcom/api", - // roots: ["/apps/api"], - // testMatch: ["**/test/lib/**/*.(spec|test).(ts|tsx|js)"], - // setupFilesAfterEnv: ["/tests/config/singleton.ts"], - // transform: { - // "^.+\\.ts?$": "ts-jest", - // }, - // globals: { - // "ts-jest": { - // tsconfig: "/apps/api/tsconfig.json", - // }, - // }, - // transformIgnorePatterns: ["/node_modules/", "^.+\\.module\\.(css|sass|scss)$"], - // testEnvironment: "node", - // clearMocks: true, - // moduleNameMapper: { - // "^@lib/(.*)$": "/apps/api/lib/$1", - // "^@api/(.*)$": "/apps/api/pages/api/$1", - // }, - // // setupFilesAfterEnv: ["/apps/api/jest.setup.ts"], // Uncomment when API becomes public - // }, - ], - watchPlugins: [ - "jest-watch-typeahead/filename", - "jest-watch-typeahead/testname", - "jest-watch-select-projects", - ], -}; - -export default config; diff --git a/package.json b/package.json index 8864cab3f3..4f37729332 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,6 @@ "dev:swagger": "turbo run dev --scope=\"@calcom/api\" --scope=\"@calcom/swagger\"", "dev:website": "turbo run dev --scope=\"@calcom/web\" --scope=\"@calcom/website\"", "dev": "turbo run dev --scope=\"@calcom/web\"", - "docs-build": "turbo run build --scope=\"@calcom/docs\" --include-dependencies", - "docs-dev": "turbo run dev --scope=\"@calcom/docs\"", - "docs-start": "turbo run start --scope=\"@calcom/docs\"", "build-storybook": "turbo run build --scope=\"@calcom/storybook\"", "dx": "turbo run dx", "i-dev": "infisical run -- turbo run dev --scope=\"@calcom/web\"", @@ -59,7 +56,7 @@ "prepare": "husky install", "prisma": "yarn workspace @calcom/prisma prisma", "start": "turbo run start --scope=\"@calcom/web\"", - "tdd": "jest --watch", + "tdd": "vitest watch", "e2e": "NEXT_PUBLIC_IS_E2E=1 yarn playwright test --project=@calcom/web", "e2e:app-store": "QUICK=true yarn playwright test --project=@calcom/app-store", "e2e:embed": "QUICK=true yarn playwright test --project=@calcom/embed-core", @@ -69,7 +66,7 @@ "test-e2e:embed": "yarn db-seed && yarn e2e:embed", "test-e2e:embed-react": "yarn db-seed && yarn e2e:embed-react", "test-playwright": "yarn playwright test --config=playwright.config.ts", - "test": "jest", + "test": "vitest run", "type-check": "turbo run type-check", "type-check:ci": "turbo run type-check:ci --log-prefix=none", "web": "yarn workspace @calcom/web" @@ -80,19 +77,17 @@ "@playwright/test": "^1.31.2", "@snaplet/copycat": "^0.3.0", "@types/dompurify": "^2.4.0", - "@types/jest": "^28.1.7", + "c8": "^7.13.0", "dotenv-checker": "^1.1.5", - "husky": "^8.0.1", - "jest": "^28.1.0", - "jest-environment-jsdom": "^28.1.3", - "jest-mock-extended": "^2.0.7", - "jest-watch-select-projects": "^2.0.0", - "jest-watch-typeahead": "^2.0.0", + "husky": "^8.0.0", + "jest-diff": "^29.5.0", + "jsdom": "^22.0.0", "lint-staged": "^12.5.0", "prettier": "^2.8.6", - "ts-jest": "^28.0.8", "tsc-absolute": "^1.0.0", - "typescript": "^4.9.4" + "typescript": "^4.9.4", + "vitest": "^0.31.1", + "vitest-mock-extended": "^1.1.3" }, "dependencies": { "city-timezones": "^1.2.1", diff --git a/packages/app-store/closecom/package.json b/packages/app-store/closecom/package.json index 332a337fb6..050c6de58d 100644 --- a/packages/app-store/closecom/package.json +++ b/packages/app-store/closecom/package.json @@ -6,8 +6,8 @@ "main": "./index.ts", "description": "Close is the inside sales CRM of choice for startups and SMBs. Make more calls, send more emails and close more deals starting today.", "scripts": { - "test": "jest", - "test:coverage": "jest --coverage" + "test": "vitest", + "test:coverage": "vitest run --coverage" }, "dependencies": { "@calcom/lib": "*", diff --git a/packages/app-store/closecom/test/globals.ts b/packages/app-store/closecom/test/globals.ts index cc3469f1e5..357d308a35 100644 --- a/packages/app-store/closecom/test/globals.ts +++ b/packages/app-store/closecom/test/globals.ts @@ -1,14 +1,16 @@ -jest.mock("@calcom/lib/logger", () => ({ +import { vi } from "vitest"; + +vi.mock("@calcom/lib/logger", () => ({ default: { getChildLogger: () => ({ - debug: jest.fn(), - error: jest.fn(), - log: jest.fn(), + debug: vi.fn(), + error: vi.fn(), + log: vi.fn(), }), }, })); -jest.mock("@calcom/lib/crypto", () => ({ +vi.mock("@calcom/lib/crypto", () => ({ symmetricDecrypt: () => `{ "userApiKey": "test" }`, diff --git a/packages/app-store/closecom/test/lib/CalendarService.test.ts b/packages/app-store/closecom/test/lib/CalendarService.test.ts index ee9d6b2284..da7c598c20 100644 --- a/packages/app-store/closecom/test/lib/CalendarService.test.ts +++ b/packages/app-store/closecom/test/lib/CalendarService.test.ts @@ -1,3 +1,5 @@ +import { expect, vi, afterEach, test } from "vitest"; + import CloseCom from "@calcom/lib/CloseCom"; import { getCloseComContactIds, @@ -7,7 +9,7 @@ import { } from "@calcom/lib/CloseComeUtils"; import type { CalendarEvent } from "@calcom/types/Calendar"; -jest.mock("@calcom/lib/CloseCom", () => ({ +vi.mock("@calcom/lib/CloseCom", () => ({ default: class { constructor() { /* Mock */ @@ -16,7 +18,7 @@ jest.mock("@calcom/lib/CloseCom", () => ({ })); afterEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); }); // getCloseComLeadId diff --git a/packages/app-store/routing-forms/test/lib/jsonLogicToPrisma.test.ts b/packages/app-store/routing-forms/test/lib/jsonLogicToPrisma.test.ts index 107c4d65c5..10407d4e12 100644 --- a/packages/app-store/routing-forms/test/lib/jsonLogicToPrisma.test.ts +++ b/packages/app-store/routing-forms/test/lib/jsonLogicToPrisma.test.ts @@ -1,9 +1,9 @@ -import { expect, it, describe } from "@jest/globals"; +import { describe, expect, it, afterEach, vi } from "vitest"; import { jsonLogicToPrisma } from "../../jsonLogicToPrisma"; afterEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); }); describe("jsonLogicToPrisma - Single Query", () => { diff --git a/packages/app-store/stripepayment/LICENSE b/packages/app-store/stripepayment/LICENSE index 60f0a2a1d0..86aae54b29 100644 --- a/packages/app-store/stripepayment/LICENSE +++ b/packages/app-store/stripepayment/LICENSE @@ -1,4 +1,4 @@ -The Cal.com Enterprise Edition (EE) license (the “EE License”) +The Cal.com Commercial License (EE) license (the “EE License”) Copyright (c) 2020-present Cal.com, Inc With regard to the Cal.com Software: @@ -8,7 +8,7 @@ used in production, if you (and any entity that you represent) have agreed to, and are in compliance with, the Cal.com Subscription Terms available at https://cal.com/terms (the “EE Terms”), or other agreements governing the use of the Software, as mutually agreed by you and Cal.com, Inc ("Cal.com"), -and otherwise have a valid Cal.com Enterprise Edition subscription ("EE Subscription") +and otherwise have a valid Cal.com Commercial License subscription ("EE Subscription") for the correct number of hosts as defined in the EE Terms ("Hosts"). Subject to the foregoing sentence, you are free to modify this Software and publish patches to the Software. You agree that Cal.com and/or its licensors (as applicable) retain all right, title and interest in diff --git a/packages/emails/src/templates/AttendeeWasRequestedToRescheduleEmail.tsx b/packages/emails/src/templates/AttendeeWasRequestedToRescheduleEmail.tsx index 256e77596b..fe45ce2aca 100644 --- a/packages/emails/src/templates/AttendeeWasRequestedToRescheduleEmail.tsx +++ b/packages/emails/src/templates/AttendeeWasRequestedToRescheduleEmail.tsx @@ -3,23 +3,27 @@ import { OrganizerScheduledEmail } from "./OrganizerScheduledEmail"; export const AttendeeWasRequestedToRescheduleEmail = ( props: { metadata: { rescheduleLink: string } } & React.ComponentProps -) => ( - - {props.calEvent.organizer.language.translate("request_reschedule_subtitle", { - organizer: props.calEvent.organizer.name, - })} - - } - headerType="calendarCircle" - subject="rescheduled_event_type_subject" - callToAction={ - - - - } - {...props} - /> -); +) => { + const t = props.attendee.language.translate; + return ( + + {t("request_reschedule_subtitle", { + organizer: props.calEvent.organizer.name, + })} + + } + headerType="calendarCircle" + subject="rescheduled_event_type_subject" + callToAction={ + + + + } + {...props} + /> + ); +}; diff --git a/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts b/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts index 6b4ca01e82..881c9890e5 100644 --- a/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts +++ b/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts @@ -1,10 +1,10 @@ -import type { DateArray, Person } from "ics"; +import type { DateArray } from "ics"; import { createEvent } from "ics"; import dayjs from "@calcom/dayjs"; import { getManageLink } from "@calcom/lib/CalEventParser"; import { APP_NAME } from "@calcom/lib/constants"; -import type { CalendarEvent } from "@calcom/types/Calendar"; +import type { CalendarEvent, Person } from "@calcom/types/Calendar"; import { renderEmail } from ".."; import OrganizerScheduledEmail from "./organizer-scheduled-email"; @@ -14,6 +14,7 @@ export default class AttendeeWasRequestedToRescheduleEmail extends OrganizerSche constructor(calEvent: CalendarEvent, metadata: { rescheduleLink: string }) { super({ calEvent }); this.metadata = metadata; + this.t = this.calEvent.attendees[0].language.translate; } protected getNodeMailerPayload(): Record { const toAddresses = [this.calEvent.attendees[0].email]; @@ -31,7 +32,7 @@ export default class AttendeeWasRequestedToRescheduleEmail extends OrganizerSche })}`, html: renderEmail("AttendeeWasRequestedToRescheduleEmail", { calEvent: this.calEvent, - attendee: this.calEvent.organizer, + attendee: this.calEvent.attendees[0], metadata: this.metadata, }), text: this.getTextBody(), diff --git a/packages/embeds/embed-core/LICENSE b/packages/embeds/embed-core/LICENSE index 1f8841cbc2..cedfba0c36 100644 --- a/packages/embeds/embed-core/LICENSE +++ b/packages/embeds/embed-core/LICENSE @@ -1,4 +1,4 @@ -The Cal.com Enterprise Edition (EE) license (the “EE License”) +The Cal.com Commercial License (EE) license (the “EE License”) Copyright (c) 2020-present Cal.com, Inc With regard to the Cal.com Software: @@ -8,7 +8,7 @@ used in production, if you (and any entity that you represent) have agreed to, and are in compliance with, the Cal.com Subscription Terms available at https://cal.com/terms (the “EE Terms”), or other agreements governing the use of the Software, as mutually agreed by you and Cal.com, Inc ("Cal.com"), -and otherwise have a valid Cal.com Enterprise Edition subscription ("EE Subscription") +and otherwise have a valid Cal.com Commercial License subscription ("EE Subscription") for the correct number of hosts as defined in the EE Terms ("Hosts"). Subject to the foregoing sentence, you are free to modify this Software and publish patches to the Software. You agree that Cal.com and/or its licensors (as applicable) retain all right, title and interest in diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts b/packages/embeds/embed-core/playwright/tests/action-based.e2e.ts similarity index 100% rename from packages/embeds/embed-core/playwright/tests/action-based.test.ts rename to packages/embeds/embed-core/playwright/tests/action-based.e2e.ts diff --git a/packages/embeds/embed-react/LICENSE b/packages/embeds/embed-react/LICENSE index 1f8841cbc2..cedfba0c36 100644 --- a/packages/embeds/embed-react/LICENSE +++ b/packages/embeds/embed-react/LICENSE @@ -1,4 +1,4 @@ -The Cal.com Enterprise Edition (EE) license (the “EE License”) +The Cal.com Commercial License (EE) license (the “EE License”) Copyright (c) 2020-present Cal.com, Inc With regard to the Cal.com Software: @@ -8,7 +8,7 @@ used in production, if you (and any entity that you represent) have agreed to, and are in compliance with, the Cal.com Subscription Terms available at https://cal.com/terms (the “EE Terms”), or other agreements governing the use of the Software, as mutually agreed by you and Cal.com, Inc ("Cal.com"), -and otherwise have a valid Cal.com Enterprise Edition subscription ("EE Subscription") +and otherwise have a valid Cal.com Commercial License subscription ("EE Subscription") for the correct number of hosts as defined in the EE Terms ("Hosts"). Subject to the foregoing sentence, you are free to modify this Software and publish patches to the Software. You agree that Cal.com and/or its licensors (as applicable) retain all right, title and interest in diff --git a/packages/embeds/embed-react/playwright/tests/basic.test.ts b/packages/embeds/embed-react/playwright/tests/basic.e2e.ts similarity index 100% rename from packages/embeds/embed-react/playwright/tests/basic.test.ts rename to packages/embeds/embed-react/playwright/tests/basic.e2e.ts diff --git a/packages/embeds/embed-snippet/LICENSE b/packages/embeds/embed-snippet/LICENSE index 1f8841cbc2..cedfba0c36 100644 --- a/packages/embeds/embed-snippet/LICENSE +++ b/packages/embeds/embed-snippet/LICENSE @@ -1,4 +1,4 @@ -The Cal.com Enterprise Edition (EE) license (the “EE License”) +The Cal.com Commercial License (EE) license (the “EE License”) Copyright (c) 2020-present Cal.com, Inc With regard to the Cal.com Software: @@ -8,7 +8,7 @@ used in production, if you (and any entity that you represent) have agreed to, and are in compliance with, the Cal.com Subscription Terms available at https://cal.com/terms (the “EE Terms”), or other agreements governing the use of the Software, as mutually agreed by you and Cal.com, Inc ("Cal.com"), -and otherwise have a valid Cal.com Enterprise Edition subscription ("EE Subscription") +and otherwise have a valid Cal.com Commercial License subscription ("EE Subscription") for the correct number of hosts as defined in the EE Terms ("Hosts"). Subject to the foregoing sentence, you are free to modify this Software and publish patches to the Software. You agree that Cal.com and/or its licensors (as applicable) retain all right, title and interest in diff --git a/packages/features/bookings/components/EventTypeFilter.tsx b/packages/features/bookings/components/EventTypeFilter.tsx index 371101ad28..7aa377e38d 100644 --- a/packages/features/bookings/components/EventTypeFilter.tsx +++ b/packages/features/bookings/components/EventTypeFilter.tsx @@ -1,12 +1,14 @@ import { useSession } from "next-auth/react"; -import { Fragment, useState, useEffect } from "react"; +import { Fragment, useState } from "react"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { RouterOutputs } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react"; import { AnimatedPopover } from "@calcom/ui"; +import { Checkbox } from "@calcom/ui"; import { groupBy } from "../groupBy"; +import { useFilterQuery } from "../lib/useFilterQuery"; export type IEventTypesFilters = RouterOutputs["viewer"]["eventTypes"]["listWithTeam"]; export type IEventTypeFilter = IEventTypesFilters[0]; @@ -27,24 +29,32 @@ type GroupedEventTypeState = Record< export const EventTypeFilter = () => { const { t } = useLocale(); const { data: user } = useSession(); - const eventTypes = trpc.viewer.eventTypes.listWithTeam.useQuery(); + const { data: query, pushItemToKey, removeItemByKeyAndValue } = useFilterQuery(); + const [groupedEventTypes, setGroupedEventTypes] = useState(); - // Will be handled up the tree to redirect - useEffect(() => { - if (!eventTypes.data) return; - // Group event types by team - const grouped = groupBy( - eventTypes.data.filter((el) => el.team), - (item) => item?.team?.name || "" - ); // Add the team name - const individualEvents = eventTypes.data.filter((el) => !el.team); - // push indivdual events to the start of grouped array - setGroupedEventTypes({ user_own_event_types: individualEvents, ...grouped }); - }, [eventTypes.data, user]); - if (!user) return null; + const eventTypes = trpc.viewer.eventTypes.listWithTeam.useQuery(undefined, { + onSuccess: (data) => { + // Will be handled up the tree to redirect + // Group event types by team + const grouped = groupBy( + data.filter((el) => el.team), + (item) => item?.team?.name || "" + ); // Add the team name + const individualEvents = data.filter((el) => !el.team); + // push indivdual events to the start of grouped array + setGroupedEventTypes( + individualEvents.length > 0 ? { user_own_event_types: individualEvents, ...grouped } : grouped + ); + }, + enabled: !!user, + }); - return ( + if (!eventTypes.data) return null; + + const isNotEmpty = eventTypes.data.length > 0; + + return eventTypes.status === "success" && isNotEmpty ? (
{groupedEventTypes && @@ -54,25 +64,23 @@ export const EventTypeFilter = () => { {teamName === "user_own_event_types" ? t("individual") : teamName}
{groupedEventTypes[teamName].map((eventType) => ( - -
-

- {eventType.title} -

-
- -
-
-
+
+ { + if (e.target.checked) { + pushItemToKey("eventTypeIds", eventType.id); + } else if (!e.target.checked) { + removeItemByKeyAndValue("eventTypeIds", eventType.id); + } + }} + description={eventType.title} + /> +
))} ))}
- ); + ) : null; }; diff --git a/packages/features/bookings/components/FiltersContainer.tsx b/packages/features/bookings/components/FiltersContainer.tsx index 9299875861..b30604cf38 100644 --- a/packages/features/bookings/components/FiltersContainer.tsx +++ b/packages/features/bookings/components/FiltersContainer.tsx @@ -1,9 +1,11 @@ -import { Fragment, ReactNode } from "react"; +import type { ReactNode } from "react"; +import { Fragment } from "react"; +import { EventTypeFilter } from "./EventTypeFilter"; import { PeopleFilter } from "./PeopleFilter"; import { TeamsMemberFilter } from "./TeamFilter"; -type FilterTypes = "teams" | "people"; +type FilterTypes = "teams" | "people" | "eventType"; type Filter = { name: FilterTypes; @@ -25,6 +27,12 @@ const filters: Filter[] = [ controllingQueryParams: ["usersId"], showByDefault: true, }, + { + name: "eventType", + component: , + controllingQueryParams: ["eventTypeId"], + showByDefault: true, + }, ]; export function FiltersContainer() { diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index f09e7135b8..a1a9fc73d1 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -304,11 +304,11 @@ const getEventTypesFromDB = async (eventTypeId: number) => { return { ...eventType, - metadata: EventTypeMetaDataSchema.parse(eventType.metadata), - recurringEvent: parseRecurringEvent(eventType.recurringEvent), - customInputs: customInputSchema.array().parse(eventType.customInputs || []), - locations: (eventType.locations ?? []) as LocationObject[], - bookingFields: getBookingFieldsWithSystemFields(eventType), + metadata: EventTypeMetaDataSchema.parse(eventType?.metadata || {}), + recurringEvent: parseRecurringEvent(eventType?.recurringEvent), + customInputs: customInputSchema.array().parse(eventType?.customInputs || []), + locations: (eventType?.locations ?? []) as LocationObject[], + bookingFields: getBookingFieldsWithSystemFields(eventType || {}), }; }; @@ -488,7 +488,7 @@ function getBookingData({ } } }); - + debugger; const reqBody = bookingDataSchema.parse(req.body); if ("customInputs" in reqBody) { if (reqBody.customInputs) { @@ -584,7 +584,7 @@ async function handler( ...eventType, bookingFields: getBookingFieldsWithSystemFields(eventType), }; - + debugger; const { recurringCount, allRecurringDates, @@ -673,7 +673,7 @@ async function handler( ...user, isFixed, })) - : eventType.users; + : eventType.users || []; // loadUsers allows type inferring let users: (Awaited>[number] & { isFixed?: boolean; diff --git a/packages/features/ee/package.json b/packages/features/ee/package.json index 18043f1a38..7244d62522 100644 --- a/packages/features/ee/package.json +++ b/packages/features/ee/package.json @@ -1,6 +1,6 @@ { "name": "@calcom/ee", - "description": "Cal.com Enterprise Edition features", + "description": "Cal.com Commercial License features", "version": "0.0.0", "private": true, "license": "See license in LICENSE", diff --git a/packages/features/ee/sso/components/ConnectionInfo.tsx b/packages/features/ee/sso/components/ConnectionInfo.tsx index 43f32f0fdf..c2a3a85fb2 100644 --- a/packages/features/ee/sso/components/ConnectionInfo.tsx +++ b/packages/features/ee/sso/components/ConnectionInfo.tsx @@ -51,7 +51,7 @@ export default function ConnectionInfo({ ) : ( )} -
+
@@ -99,7 +99,7 @@ const SAMLInfo = ({ acsUrl, entityId }: { acsUrl: string | null; entityId: strin }} type="button" className="rounded-l-none py-[19px] text-base "> - + {t("copy")} @@ -121,7 +121,7 @@ const SAMLInfo = ({ acsUrl, entityId }: { acsUrl: string | null; entityId: strin }} type="button" className="rounded-l-none py-[19px] text-base "> - + {t("copy")} @@ -157,7 +157,7 @@ const OIDCInfo = ({ callbackUrl }: { callbackUrl: string | null }) => { }} type="button" className="rounded-l-none py-[19px] text-base "> - + {t("copy")} diff --git a/packages/features/eventtypes/components/CreateEventTypeDialog.tsx b/packages/features/eventtypes/components/CreateEventTypeDialog.tsx index 2487ebbe57..2726bc74e4 100644 --- a/packages/features/eventtypes/components/CreateEventTypeDialog.tsx +++ b/packages/features/eventtypes/components/CreateEventTypeDialog.tsx @@ -254,6 +254,9 @@ export default function CreateEventTypeDialog({ /> )} { + form.setValue("schedulingType", val); + }} className={classNames( "mt-1 flex gap-4", isAdmin && flags["managed-event-types"] && "flex-col" diff --git a/packages/features/settings/layouts/SettingsLayout.tsx b/packages/features/settings/layouts/SettingsLayout.tsx index 857ac8df87..258cede522 100644 --- a/packages/features/settings/layouts/SettingsLayout.tsx +++ b/packages/features/settings/layouts/SettingsLayout.tsx @@ -190,7 +190,7 @@ const SettingsSidebarContainer = ({ return tab.name !== "teams" ? (
-
+
{tab && tab.icon && ( )} @@ -201,7 +201,7 @@ const SettingsSidebarContainer = ({ alt="User Avatar" /> )} -

{t(tab.name)}

+

{t(tab.name)}

@@ -222,11 +222,11 @@ const SettingsSidebarContainer = ({
-
+
{tab && tab.icon && ( )} -

{t(tab.name)}

+

{t(tab.name)}

{teams && @@ -248,7 +248,7 @@ const SettingsSidebarContainer = ({ }>
setTeamMenuState([ ...teamMenuState, diff --git a/packages/lib/cva/cva.test.ts b/packages/lib/cva/cva.test.ts index d73a113b4e..31c29eb4ac 100644 --- a/packages/lib/cva/cva.test.ts +++ b/packages/lib/cva/cva.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from "vitest"; + import { applyStyleToMultipleVariants } from "./cva"; describe("CVA Utils", () => { diff --git a/packages/lib/defaultAvatarImage.test.ts b/packages/lib/defaultAvatarImage.test.ts index 6119ae5804..14d7ca2392 100644 --- a/packages/lib/defaultAvatarImage.test.ts +++ b/packages/lib/defaultAvatarImage.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from "vitest"; + import { defaultAvatarSrc, getPlaceholderAvatar } from "./defaultAvatarImage"; describe("Default Avatar Image tests", () => { diff --git a/packages/lib/getBrandColours.test.ts b/packages/lib/getBrandColours.test.ts index b1152a2e1c..8a9b253eee 100644 --- a/packages/lib/getBrandColours.test.ts +++ b/packages/lib/getBrandColours.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from "vitest"; + import { createColorMap } from "./getBrandColours"; describe("useGetBrandingColours", () => { diff --git a/packages/lib/hooks/useHasPaidPlan.ts b/packages/lib/hooks/useHasPaidPlan.ts index e0509d4d95..9111f4a43b 100644 --- a/packages/lib/hooks/useHasPaidPlan.ts +++ b/packages/lib/hooks/useHasPaidPlan.ts @@ -12,7 +12,8 @@ export function useHasPaidPlan() { const isLoading = isLoadingTeamQuery || isLoadingUserQuery; - const isCurrentUsernamePremium = user && user.metadata && hasKeyInMetadata(user, "isPremium"); + const isCurrentUsernamePremium = + user && hasKeyInMetadata(user, "isPremium") ? !!user.metadata.isPremium : false; const hasPaidPlan = hasTeamPlan?.hasTeamPlan || isCurrentUsernamePremium; diff --git a/packages/lib/random.test.ts b/packages/lib/random.test.ts index b8e02bc1af..d978b895e9 100644 --- a/packages/lib/random.test.ts +++ b/packages/lib/random.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from "vitest"; + import { randomString } from "./random"; describe("Random util tests", () => { diff --git a/packages/lib/test/CalEventParser.test.ts b/packages/lib/test/CalEventParser.test.ts index f146d6c363..c90a8daa15 100644 --- a/packages/lib/test/CalEventParser.test.ts +++ b/packages/lib/test/CalEventParser.test.ts @@ -1,4 +1,5 @@ import { faker } from "@faker-js/faker"; +import { describe, expect, it, vi } from "vitest"; import { getLocation, @@ -8,11 +9,11 @@ import { } from "../CalEventParser"; import { buildCalendarEvent, buildVideoCallData } from "./builder"; -jest.mock("@calcom/lib/constants", () => ({ +vi.mock("@calcom/lib/constants", () => ({ WEBAPP_URL: "http://localhost:3000", })); -jest.mock("short-uuid", () => ({ +vi.mock("short-uuid", () => ({ __esModule: true, default: () => ({ fromUUID: () => "FAKE_UUID" }), })); diff --git a/packages/lib/text.test.ts b/packages/lib/text.test.ts index 1424775365..4d60a46f14 100644 --- a/packages/lib/text.test.ts +++ b/packages/lib/text.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from "vitest"; + import { truncate } from "./text"; describe("Text util tests", () => { diff --git a/packages/lib/weekday.test.ts b/packages/lib/weekday.test.ts index 81f22321fc..076a618562 100644 --- a/packages/lib/weekday.test.ts +++ b/packages/lib/weekday.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from "vitest"; + import { nameOfDay, weekdayNames } from "./weekday"; describe("Weekday tests", () => { diff --git a/packages/trpc/server/routers/viewer/bookings/get.handler.ts b/packages/trpc/server/routers/viewer/bookings/get.handler.ts index 7420dbbd97..a3e90a2a8f 100644 --- a/packages/trpc/server/routers/viewer/bookings/get.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/get.handler.ts @@ -102,6 +102,15 @@ export const getHandler = async ({ ctx, input }: GetOptions) => { }, ], }, + eventTypeIds: { + AND: [ + { + eventTypeId: { + in: input.filters?.eventTypeIds, + }, + }, + ], + }, }; const filtersCombined: Prisma.BookingWhereInput[] = diff --git a/packages/trpc/server/routers/viewer/workflows/getWorkflowActionOptions.handler.ts b/packages/trpc/server/routers/viewer/workflows/getWorkflowActionOptions.handler.ts index ad12d6ddd6..a4159afcf3 100644 --- a/packages/trpc/server/routers/viewer/workflows/getWorkflowActionOptions.handler.ts +++ b/packages/trpc/server/routers/viewer/workflows/getWorkflowActionOptions.handler.ts @@ -17,7 +17,8 @@ type GetWorkflowActionOptionsOptions = { export const getWorkflowActionOptionsHandler = async ({ ctx }: GetWorkflowActionOptionsOptions) => { const { user } = ctx; - const isCurrentUsernamePremium = user && user.metadata && hasKeyInMetadata(user, "isPremium"); + const isCurrentUsernamePremium = + user && hasKeyInMetadata(user, "isPremium") ? !!user.metadata.isPremium : false; let isTeamsPlan = false; if (!isCurrentUsernamePremium) { diff --git a/packages/trpc/server/routers/viewer/workflows/update.handler.ts b/packages/trpc/server/routers/viewer/workflows/update.handler.ts index bb44f10f2a..64c7fe0ed9 100644 --- a/packages/trpc/server/routers/viewer/workflows/update.handler.ts +++ b/packages/trpc/server/routers/viewer/workflows/update.handler.ts @@ -66,7 +66,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { throw new TRPCError({ code: "UNAUTHORIZED" }); } - const isCurrentUsernamePremium = user && user.metadata && hasKeyInMetadata(user, "isPremium"); + const isCurrentUsernamePremium = hasKeyInMetadata(user, "isPremium") ? !!user.metadata.isPremium : false; let isTeamsPlan = false; if (!isCurrentUsernamePremium) { diff --git a/packages/ui/components/card/Card.tsx b/packages/ui/components/card/Card.tsx index 153f1325e4..b7c0dcdb2a 100644 --- a/packages/ui/components/card/Card.tsx +++ b/packages/ui/components/card/Card.tsx @@ -152,12 +152,14 @@ export function Card({ {learnMore.text} )} - + {actionButton?.child && ( + + )}
)}
diff --git a/packages/ui/components/form/checkbox/Checkbox.tsx b/packages/ui/components/form/checkbox/Checkbox.tsx index 06b2268c3b..f35022e315 100644 --- a/packages/ui/components/form/checkbox/Checkbox.tsx +++ b/packages/ui/components/form/checkbox/Checkbox.tsx @@ -1,3 +1,4 @@ +import { useId } from "@radix-ui/react-id"; import type { InputHTMLAttributes } from "react"; import React, { forwardRef } from "react"; @@ -16,6 +17,7 @@ type Props = InputHTMLAttributes & { const CheckboxField = forwardRef( ({ label, description, error, disabled, ...rest }, ref) => { const descriptionAsLabel = !label || rest.descriptionAsLabel; + const id = useId(); return (
{label && ( @@ -26,7 +28,7 @@ const CheckboxField = forwardRef( className: classNames("flex text-sm font-medium text-emphasis"), ...(!descriptionAsLabel ? { - htmlFor: rest.id, + htmlFor: rest.id ? rest.id : id, } : {}), }, @@ -52,6 +54,7 @@ const CheckboxField = forwardRef( ref={ref} type="checkbox" disabled={disabled} + id={rest.id ? rest.id : id} className={classNames( "text-primary-600 focus:ring-primary-500 border-default bg-default h-4 w-4 rounded ltr:mr-2 rtl:ml-2", !error && disabled diff --git a/packages/ui/form/radio-area/RadioAreaGroup.tsx b/packages/ui/form/radio-area/RadioAreaGroup.tsx index f0185fc691..9bb68f24ad 100644 --- a/packages/ui/form/radio-area/RadioAreaGroup.tsx +++ b/packages/ui/form/radio-area/RadioAreaGroup.tsx @@ -1,59 +1,57 @@ -import React from "react"; +import { useId } from "@radix-ui/react-id"; +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"; +import type { ReactNode } from "react"; import classNames from "@calcom/lib/classNames"; -type RadioAreaProps = React.InputHTMLAttributes & { classNames?: { container?: string } }; +type RadioAreaProps = RadioGroupPrimitive.RadioGroupItemProps & { + children: ReactNode; + classNames?: { container?: string }; +}; -const RadioArea = React.forwardRef( - ({ children, className, classNames: innerClassNames, ...props }, ref) => { - return ( - - ); - } -); -type MaybeArray = T[] | T; -type ChildrenOfType = MaybeArray< - React.ReactElement> ->; -interface RadioAreaGroupProps extends Omit, "onChange" | "children"> { - onChange?: (value: string) => void; - children: ChildrenOfType; -} +const RadioArea = ({ children, className, classNames: innerClassNames, ...props }: RadioAreaProps) => { + const radioAreaId = useId(); + const id = props.id ?? radioAreaId; -const RadioAreaGroup = ({ children, className, onChange, ...passThroughProps }: RadioAreaGroupProps) => { - const childrenWithProps = React.Children.map(children, (child) => { - if (onChange && React.isValidElement(child)) { - return React.cloneElement(child, { - onChange: (e: React.ChangeEvent) => { - onChange(e.target.value); - }, - }); - } - return child; - }); return ( -
- {childrenWithProps} +
+ + + +
); }; -RadioAreaGroup.displayName = "RadioAreaGroup"; -RadioArea.displayName = "RadioArea"; +const RadioAreaGroup = ({ + children, + className, + onValueChange, + ...passThroughProps +}: RadioGroupPrimitive.RadioGroupProps) => { + return ( + + {children} + + ); +}; const Item = RadioArea; const Group = RadioAreaGroup; diff --git a/packages/ui/form/radio-area/Select.tsx b/packages/ui/form/radio-area/Select.tsx deleted file mode 100644 index 6834d8c9b2..0000000000 --- a/packages/ui/form/radio-area/Select.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible"; -import React from "react"; -import type { FieldValues, Path, UseFormReturn } from "react-hook-form"; - -import classNames from "@calcom/lib/classNames"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { ChevronDown } from "@calcom/ui/components/icon"; - -import { RadioArea, RadioAreaGroup } from "./RadioAreaGroup"; - -interface OptionProps - extends Pick, "value" | "label" | "className"> { - description?: string; -} - -export type FieldPath = Path; -interface RadioAreaSelectProps - extends Omit, "onChange" | "form"> { - options: OptionProps[]; // allow options to be passed programmatically, like options={} - onChange?: (value: string) => void; - form: UseFormReturn; - name: FieldPath; -} - -export const Select = function RadioAreaSelect( - props: RadioAreaSelectProps -) { - const { t } = useLocale(); - const { - options, - form, - disabled = !options.length, // if not explicitly disabled and the options length is empty, disable anyway - placeholder = t("select"), - } = props; - - const getLabel = (value: string | ReadonlyArray | number | undefined) => - options.find((option: OptionProps) => option.value === value)?.label; - - return ( - - - {getLabel(props.value) ?? placeholder} - - - - - {options.map((option) => ( - - {option.label} -

{option.description}

-
- ))} -
-
-
- ); -}; - -export default Select; diff --git a/packages/ui/form/radio-area/index.ts b/packages/ui/form/radio-area/index.ts index b6a8b4b6cc..1f2d98b9cf 100644 --- a/packages/ui/form/radio-area/index.ts +++ b/packages/ui/form/radio-area/index.ts @@ -1,3 +1,2 @@ export * as RadioGroup from "./RadioAreaGroup"; -export { default as Select } from "./Select"; export { Group, Indicator, Label, Radio, RadioField } from "./Radio"; diff --git a/tests/config/singleton.ts b/tests/config/singleton.ts deleted file mode 100644 index 3fa1bd82f1..0000000000 --- a/tests/config/singleton.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { PrismaClient } from "@prisma/client"; -import type { DeepMockProxy } from "jest-mock-extended"; -import { mockDeep, mockReset } from "jest-mock-extended"; - -import * as CalendarManager from "@calcom/core/CalendarManager"; -import prisma from "@calcom/prisma"; - -jest.mock("@calcom/core/CalendarManager"); - -jest.mock("@calcom/prisma", () => ({ - __esModule: true, - default: mockDeep(), -})); - -beforeEach(() => { - mockReset(prismaMock); -}); - -export const prismaMock = prisma as unknown as DeepMockProxy; -export const CalendarManagerMock = CalendarManager as unknown as DeepMockProxy; diff --git a/tests/libs/__mocks__/CalendarManager.ts b/tests/libs/__mocks__/CalendarManager.ts new file mode 100644 index 0000000000..8a0cdd17e8 --- /dev/null +++ b/tests/libs/__mocks__/CalendarManager.ts @@ -0,0 +1,13 @@ +import { beforeEach, vi } from "vitest"; +import { mockReset, mockDeep } from "vitest-mock-extended"; + +import type * as CalendarManager from "@calcom/core/CalendarManager"; + +vi.mock("@calcom/core/CalendarManager", () => CalendarManagerMock); + +beforeEach(() => { + mockReset(CalendarManagerMock); +}); + +const CalendarManagerMock = mockDeep(); +export default CalendarManagerMock; diff --git a/tests/libs/__mocks__/prisma.ts b/tests/libs/__mocks__/prisma.ts new file mode 100644 index 0000000000..a510d2abd1 --- /dev/null +++ b/tests/libs/__mocks__/prisma.ts @@ -0,0 +1,16 @@ +import type { PrismaClient } from "@prisma/client"; +import { beforeEach, vi } from "vitest"; +import { mockDeep, mockReset } from "vitest-mock-extended"; + +vi.mock("@calcom/prisma", () => ({ + default: prisma, + availabilityUserSelect: vi.fn(), + userSelect: vi.fn(), +})); + +beforeEach(() => { + mockReset(prisma); +}); + +const prisma = mockDeep(); +export default prisma; diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000000..6eea9436fc --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + coverage: { + provider: "c8", + }, + }, +}); diff --git a/vitest.workspace.ts b/vitest.workspace.ts new file mode 100644 index 0000000000..97d8cbcce6 --- /dev/null +++ b/vitest.workspace.ts @@ -0,0 +1,20 @@ +import { defineWorkspace } from "vitest/config"; + +// defineWorkspace provides a nice type hinting DX +export default defineWorkspace([ + { + test: { + include: ["packages/**/*.{test,spec}.{ts,js}", "apps/**/*.{test,spec}.{ts,js}"], + // TODO: Ignore the api until tests are fixed + exclude: ["apps/api/**/*", "**/node_modules/**/*"], + }, + }, + { + test: { + name: "@calcom/closecom", + include: ["packages/app-store/closecom/**/*.{test,spec}.{ts,js}"], + environment: "jsdom", + setupFiles: ["packages/app-store/closecom/test/globals.ts"], + }, + }, +]); diff --git a/yarn.lock b/yarn.lock index 813bbfe0c7..242bbf36c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1220,7 +1220,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.7": version: 7.16.7 resolution: "@babel/code-frame@npm:7.16.7" dependencies: @@ -1313,7 +1313,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.10": +"@babel/core@npm:^7.12.10": version: 7.18.6 resolution: "@babel/core@npm:7.18.6" dependencies: @@ -1416,7 +1416,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.12.11, @babel/generator@npm:^7.18.7, @babel/generator@npm:^7.7.2": +"@babel/generator@npm:^7.12.11, @babel/generator@npm:^7.18.7": version: 7.18.7 resolution: "@babel/generator@npm:7.18.7" dependencies: @@ -2163,15 +2163,6 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.16.7, @babel/parser@npm:^7.17.3, @babel/parser@npm:^7.17.8": - version: 7.17.8 - resolution: "@babel/parser@npm:7.17.8" - bin: - parser: ./bin/babel-parser.js - checksum: 1771808491982cc47baa888a997aef6b58308e3844c8c00f730f8fd97defe57d32cdbf46075cd49aaee310fa31f3d2c80a0d41b41a4ee0ff336ee09e2ff6c222 - languageName: node - linkType: hard - "@babel/parser@npm:^7.12.11, @babel/parser@npm:^7.18.6, @babel/parser@npm:^7.18.8": version: 7.18.8 resolution: "@babel/parser@npm:7.18.8" @@ -2190,6 +2181,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.14.7, @babel/parser@npm:^7.16.7, @babel/parser@npm:^7.17.3, @babel/parser@npm:^7.17.8": + version: 7.17.8 + resolution: "@babel/parser@npm:7.17.8" + bin: + parser: ./bin/babel-parser.js + checksum: 1771808491982cc47baa888a997aef6b58308e3844c8c00f730f8fd97defe57d32cdbf46075cd49aaee310fa31f3d2c80a0d41b41a4ee0ff336ee09e2ff6c222 + languageName: node + linkType: hard + "@babel/parser@npm:^7.17.9": version: 7.17.9 resolution: "@babel/parser@npm:7.17.9" @@ -2508,18 +2508,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-bigint@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-bigint@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": ^7.8.0 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 3a10849d83e47aec50f367a9e56a6b22d662ddce643334b087f9828f4c3dd73bdc5909aaeabe123fed78515767f9ca43498a0e621c438d1cd2802d7fae3c9648 - languageName: node - linkType: hard - -"@babel/plugin-syntax-class-properties@npm:^7.12.13, @babel/plugin-syntax-class-properties@npm:^7.8.3": +"@babel/plugin-syntax-class-properties@npm:^7.12.13": version: 7.12.13 resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" dependencies: @@ -2607,17 +2596,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-meta@npm:^7.8.3": - version: 7.10.4 - resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" - dependencies: - "@babel/helper-plugin-utils": ^7.10.4 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 166ac1125d10b9c0c430e4156249a13858c0366d38844883d75d27389621ebe651115cb2ceb6dc011534d5055719fa1727b59f39e1ab3ca97820eef3dcab5b9b - languageName: node - linkType: hard - "@babel/plugin-syntax-json-strings@npm:^7.8.3": version: 7.8.3 resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" @@ -2662,7 +2640,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4, @babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": +"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4": version: 7.10.4 resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" dependencies: @@ -2684,7 +2662,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-numeric-separator@npm:^7.10.4, @babel/plugin-syntax-numeric-separator@npm:^7.8.3": +"@babel/plugin-syntax-numeric-separator@npm:^7.10.4": version: 7.10.4 resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" dependencies: @@ -2739,7 +2717,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-top-level-await@npm:^7.14.5, @babel/plugin-syntax-top-level-await@npm:^7.8.3": +"@babel/plugin-syntax-top-level-await@npm:^7.14.5": version: 7.14.5 resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" dependencies: @@ -2750,7 +2728,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-typescript@npm:^7.18.6, @babel/plugin-syntax-typescript@npm:^7.7.2": +"@babel/plugin-syntax-typescript@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-syntax-typescript@npm:7.18.6" dependencies: @@ -3491,7 +3469,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.10.5, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.7": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.7": version: 7.17.8 resolution: "@babel/runtime@npm:7.17.8" dependencies: @@ -3565,7 +3543,7 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.16.7, @babel/template@npm:^7.3.3": +"@babel/template@npm:^7.16.7": version: 7.16.7 resolution: "@babel/template@npm:7.16.7" dependencies: @@ -3616,7 +3594,7 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.18.6, @babel/traverse@npm:^7.18.8, @babel/traverse@npm:^7.7.2": +"@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.18.6, @babel/traverse@npm:^7.18.8": version: 7.18.8 resolution: "@babel/traverse@npm:7.18.8" dependencies: @@ -3742,7 +3720,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:7.17.0, @babel/types@npm:^7.0.0, @babel/types@npm:^7.16.7, @babel/types@npm:^7.17.0, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3": +"@babel/types@npm:7.17.0, @babel/types@npm:^7.16.7, @babel/types@npm:^7.17.0": version: 7.17.0 resolution: "@babel/types@npm:7.17.0" dependencies: @@ -3925,9 +3903,7 @@ __metadata: "@calcom/tsconfig": "*" "@calcom/types": "*" "@sentry/nextjs": ^7.20.0 - babel-jest: ^28.1.0 bcryptjs: ^2.4.3 - jest: ^28.1.0 memory-cache: ^0.2.0 next: ~13.2.1 next-api-middleware: ^1.0.1 @@ -4038,8 +4014,8 @@ __metadata: "@types/react": 18.0.26 "@types/react-dom": 18.0.9 eslint: ^8.34.0 - eslint-config-next: ~13.2.1 - next: ~13.2.1 + eslint-config-next: ^13.2.1 + next: ^13.2.1 next-auth: ^4.20.1 react: ^18.2.0 react-dom: ^18.2.0 @@ -4196,20 +4172,6 @@ __metadata: languageName: unknown linkType: soft -"@calcom/docs@workspace:apps/docs": - version: 0.0.0-use.local - resolution: "@calcom/docs@workspace:apps/docs" - dependencies: - "@calcom/config": "*" - iframe-resizer-react: ^1.1.0 - next: ^13.2.1 - nextra: ^1.1.0 - nextra-theme-docs: ^1.2.2 - react: ^18.2.0 - react-dom: ^18.2.0 - languageName: unknown - linkType: soft - "@calcom/ee@workspace:packages/features/ee": version: 0.0.0-use.local resolution: "@calcom/ee@workspace:packages/features/ee" @@ -4984,7 +4946,6 @@ __metadata: accept-language-parser: ^1.5.0 async: ^3.2.4 autoprefixer: ^10.4.12 - babel-jest: ^28.1.0 bcryptjs: ^2.4.3 classnames: ^2.3.1 copy-webpack-plugin: ^11.0.0 @@ -5049,7 +5010,6 @@ __metadata: superjson: 1.9.1 tailwindcss: ^3.3.1 tailwindcss-radix: ^2.6.0 - ts-jest: ^28.0.8 ts-node: ^10.9.1 turndown: ^7.1.1 typescript: ^4.9.4 @@ -5798,6 +5758,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/android-arm64@npm:0.17.19" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.15.18": version: 0.15.18 resolution: "@esbuild/android-arm@npm:0.15.18" @@ -5812,6 +5779,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/android-arm@npm:0.17.19" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/android-x64@npm:0.16.17" @@ -5819,6 +5793,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/android-x64@npm:0.17.19" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/darwin-arm64@npm:0.16.17" @@ -5826,6 +5807,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/darwin-arm64@npm:0.17.19" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/darwin-x64@npm:0.16.17" @@ -5833,6 +5821,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/darwin-x64@npm:0.17.19" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/freebsd-arm64@npm:0.16.17" @@ -5840,6 +5835,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/freebsd-arm64@npm:0.17.19" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/freebsd-x64@npm:0.16.17" @@ -5847,6 +5849,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/freebsd-x64@npm:0.17.19" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/linux-arm64@npm:0.16.17" @@ -5854,6 +5863,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-arm64@npm:0.17.19" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/linux-arm@npm:0.16.17" @@ -5861,6 +5877,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-arm@npm:0.17.19" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/linux-ia32@npm:0.16.17" @@ -5868,6 +5891,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-ia32@npm:0.17.19" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.14.54": version: 0.14.54 resolution: "@esbuild/linux-loong64@npm:0.14.54" @@ -5889,6 +5919,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-loong64@npm:0.17.19" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/linux-mips64el@npm:0.16.17" @@ -5896,6 +5933,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-mips64el@npm:0.17.19" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/linux-ppc64@npm:0.16.17" @@ -5903,6 +5947,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-ppc64@npm:0.17.19" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/linux-riscv64@npm:0.16.17" @@ -5910,6 +5961,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-riscv64@npm:0.17.19" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/linux-s390x@npm:0.16.17" @@ -5917,6 +5975,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-s390x@npm:0.17.19" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/linux-x64@npm:0.16.17" @@ -5924,6 +5989,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-x64@npm:0.17.19" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/netbsd-x64@npm:0.16.17" @@ -5931,6 +6003,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/netbsd-x64@npm:0.17.19" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/openbsd-x64@npm:0.16.17" @@ -5938,6 +6017,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/openbsd-x64@npm:0.17.19" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/sunos-x64@npm:0.16.17" @@ -5945,6 +6031,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/sunos-x64@npm:0.17.19" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/win32-arm64@npm:0.16.17" @@ -5952,6 +6045,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/win32-arm64@npm:0.17.19" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/win32-ia32@npm:0.16.17" @@ -5959,6 +6059,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/win32-ia32@npm:0.17.19" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.16.17": version: 0.16.17 resolution: "@esbuild/win32-x64@npm:0.16.17" @@ -5966,6 +6073,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/win32-x64@npm:0.17.19" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint/eslintrc@npm:^1.0.5": version: 1.3.0 resolution: "@eslint/eslintrc@npm:1.3.0" @@ -7003,197 +7117,12 @@ __metadata: languageName: node linkType: hard -"@jest/console@npm:^28.1.3": - version: 28.1.3 - resolution: "@jest/console@npm:28.1.3" +"@jest/schemas@npm:^29.4.3": + version: 29.4.3 + resolution: "@jest/schemas@npm:29.4.3" dependencies: - "@jest/types": ^28.1.3 - "@types/node": "*" - chalk: ^4.0.0 - jest-message-util: ^28.1.3 - jest-util: ^28.1.3 - slash: ^3.0.0 - checksum: fe50d98d26d02ce2901c76dff4bd5429a33c13affb692c9ebf8a578ca2f38a5dd854363d40d6c394f215150791fd1f692afd8e730a4178dda24107c8dfd9750a - languageName: node - linkType: hard - -"@jest/core@npm:^28.1.3": - version: 28.1.3 - resolution: "@jest/core@npm:28.1.3" - dependencies: - "@jest/console": ^28.1.3 - "@jest/reporters": ^28.1.3 - "@jest/test-result": ^28.1.3 - "@jest/transform": ^28.1.3 - "@jest/types": ^28.1.3 - "@types/node": "*" - ansi-escapes: ^4.2.1 - chalk: ^4.0.0 - ci-info: ^3.2.0 - exit: ^0.1.2 - graceful-fs: ^4.2.9 - jest-changed-files: ^28.1.3 - jest-config: ^28.1.3 - jest-haste-map: ^28.1.3 - jest-message-util: ^28.1.3 - jest-regex-util: ^28.0.2 - jest-resolve: ^28.1.3 - jest-resolve-dependencies: ^28.1.3 - jest-runner: ^28.1.3 - jest-runtime: ^28.1.3 - jest-snapshot: ^28.1.3 - jest-util: ^28.1.3 - jest-validate: ^28.1.3 - jest-watcher: ^28.1.3 - micromatch: ^4.0.4 - pretty-format: ^28.1.3 - rimraf: ^3.0.0 - slash: ^3.0.0 - strip-ansi: ^6.0.0 - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - checksum: cb79f34bafc4637e7130df12257f5b29075892a2be2c7f45c6d4c0420853e80b5dae11016e652530eb234f4c44c00910cdca3c2cd86275721860725073f7d9b4 - languageName: node - linkType: hard - -"@jest/environment@npm:^28.1.3": - version: 28.1.3 - resolution: "@jest/environment@npm:28.1.3" - dependencies: - "@jest/fake-timers": ^28.1.3 - "@jest/types": ^28.1.3 - "@types/node": "*" - jest-mock: ^28.1.3 - checksum: 14c496b84aef951df33128cea68988e9de43b2e9d62be9f9c4308d4ac307fa345642813679f80d0a4cedeb900cf6f0b6bb2b92ce089528e8721f72295fdc727f - languageName: node - linkType: hard - -"@jest/expect-utils@npm:^28.1.3": - version: 28.1.3 - resolution: "@jest/expect-utils@npm:28.1.3" - dependencies: - jest-get-type: ^28.0.2 - checksum: 808ea3a68292a7e0b95490fdd55605c430b4cf209ea76b5b61bfb2a1badcb41bc046810fe4e364bd5fe04663978aa2bd73d8f8465a761dd7c655aeb44cf22987 - languageName: node - linkType: hard - -"@jest/expect@npm:^28.1.3": - version: 28.1.3 - resolution: "@jest/expect@npm:28.1.3" - dependencies: - expect: ^28.1.3 - jest-snapshot: ^28.1.3 - checksum: 4197f6fdddc33dc45ba4e838f992fc61839c421d7aed0dfe665ef9c2f172bb1df8a8cac9cecee272b40e744a326da521d5e182709fe82a0b936055bfffa3b473 - languageName: node - linkType: hard - -"@jest/fake-timers@npm:^28.1.3": - version: 28.1.3 - resolution: "@jest/fake-timers@npm:28.1.3" - dependencies: - "@jest/types": ^28.1.3 - "@sinonjs/fake-timers": ^9.1.2 - "@types/node": "*" - jest-message-util: ^28.1.3 - jest-mock: ^28.1.3 - jest-util: ^28.1.3 - checksum: cec14d5b14913a54dce64a62912c5456235f5d90b509ceae19c727565073114dae1aaf960ac6be96b3eb94789a3a758b96b72c8fca7e49a6ccac415fbc0321e1 - languageName: node - linkType: hard - -"@jest/globals@npm:^28.1.3": - version: 28.1.3 - resolution: "@jest/globals@npm:28.1.3" - dependencies: - "@jest/environment": ^28.1.3 - "@jest/expect": ^28.1.3 - "@jest/types": ^28.1.3 - checksum: 3504bb23de629d466c6f2b6b75d2e1c1b10caccbbcfb7eaa82d22cc37711c8e364c243929581184846605c023b475ea6c42c2e3ea5994429a988d8d527af32cd - languageName: node - linkType: hard - -"@jest/reporters@npm:^28.1.3": - version: 28.1.3 - resolution: "@jest/reporters@npm:28.1.3" - dependencies: - "@bcoe/v8-coverage": ^0.2.3 - "@jest/console": ^28.1.3 - "@jest/test-result": ^28.1.3 - "@jest/transform": ^28.1.3 - "@jest/types": ^28.1.3 - "@jridgewell/trace-mapping": ^0.3.13 - "@types/node": "*" - chalk: ^4.0.0 - collect-v8-coverage: ^1.0.0 - exit: ^0.1.2 - glob: ^7.1.3 - graceful-fs: ^4.2.9 - istanbul-lib-coverage: ^3.0.0 - istanbul-lib-instrument: ^5.1.0 - istanbul-lib-report: ^3.0.0 - istanbul-lib-source-maps: ^4.0.0 - istanbul-reports: ^3.1.3 - jest-message-util: ^28.1.3 - jest-util: ^28.1.3 - jest-worker: ^28.1.3 - slash: ^3.0.0 - string-length: ^4.0.1 - strip-ansi: ^6.0.0 - terminal-link: ^2.0.0 - v8-to-istanbul: ^9.0.1 - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - checksum: a7440887ce837922cbeaa64c3232eb48aae02aa9123f29fc4280ad3e1afe4b35dcba171ba1d5fd219037c396c5152d9c2d102cff1798dd5ae3bd33ac4759ae0a - languageName: node - linkType: hard - -"@jest/schemas@npm:^28.1.3": - version: 28.1.3 - resolution: "@jest/schemas@npm:28.1.3" - dependencies: - "@sinclair/typebox": ^0.24.1 - checksum: 3cf1d4b66c9c4ffda58b246de1ddcba8e6ad085af63dccdf07922511f13b68c0cc480a7bc620cb4f3099a6f134801c747e1df7bfc7a4ef4dceefbdea3e31e1de - languageName: node - linkType: hard - -"@jest/source-map@npm:^28.1.2": - version: 28.1.2 - resolution: "@jest/source-map@npm:28.1.2" - dependencies: - "@jridgewell/trace-mapping": ^0.3.13 - callsites: ^3.0.0 - graceful-fs: ^4.2.9 - checksum: b82a5c2e93d35d86779c61a02ccb967d1b5cd2e9dd67d26d8add44958637cbbb99daeeb8129c7653389cb440dc2a2f5ae4d2183dc453c67669ff98938b775a3a - languageName: node - linkType: hard - -"@jest/test-result@npm:^28.1.3": - version: 28.1.3 - resolution: "@jest/test-result@npm:28.1.3" - dependencies: - "@jest/console": ^28.1.3 - "@jest/types": ^28.1.3 - "@types/istanbul-lib-coverage": ^2.0.0 - collect-v8-coverage: ^1.0.0 - checksum: 957a5dd2fd2e84aabe86698f93c0825e96128ccaa23abf548b159a9b08ac74e4bde7acf4bec48479243dbdb27e4ea1b68c171846d21fb64855c6b55cead9ef27 - languageName: node - linkType: hard - -"@jest/test-sequencer@npm:^28.1.3": - version: 28.1.3 - resolution: "@jest/test-sequencer@npm:28.1.3" - dependencies: - "@jest/test-result": ^28.1.3 - graceful-fs: ^4.2.9 - jest-haste-map: ^28.1.3 - slash: ^3.0.0 - checksum: 13f8905e6d1ec8286694146f7be3cf90eff801bbdea5e5c403e6881444bb390ed15494c7b9948aa94bd7e9c9a851e0d3002ed6e7371d048b478596e5b23df953 + "@sinclair/typebox": ^0.25.16 + checksum: ac754e245c19dc39e10ebd41dce09040214c96a4cd8efa143b82148e383e45128f24599195ab4f01433adae4ccfbe2db6574c90db2862ccd8551a86704b5bebd languageName: node linkType: hard @@ -7220,29 +7149,6 @@ __metadata: languageName: node linkType: hard -"@jest/transform@npm:^28.1.3": - version: 28.1.3 - resolution: "@jest/transform@npm:28.1.3" - dependencies: - "@babel/core": ^7.11.6 - "@jest/types": ^28.1.3 - "@jridgewell/trace-mapping": ^0.3.13 - babel-plugin-istanbul: ^6.1.1 - chalk: ^4.0.0 - convert-source-map: ^1.4.0 - fast-json-stable-stringify: ^2.0.0 - graceful-fs: ^4.2.9 - jest-haste-map: ^28.1.3 - jest-regex-util: ^28.0.2 - jest-util: ^28.1.3 - micromatch: ^4.0.4 - pirates: ^4.0.4 - slash: ^3.0.0 - write-file-atomic: ^4.0.1 - checksum: dadf618936e0aa84342f07f532801d5bed43cdf95d1417b929e4f8782c872cff1adc84096d5a287a796d0039a2691c06d8450cce5a713a8b52fbb9f872a1e760 - languageName: node - linkType: hard - "@jest/types@npm:^26.6.2": version: 26.6.2 resolution: "@jest/types@npm:26.6.2" @@ -7269,20 +7175,6 @@ __metadata: languageName: node linkType: hard -"@jest/types@npm:^28.1.3": - version: 28.1.3 - resolution: "@jest/types@npm:28.1.3" - dependencies: - "@jest/schemas": ^28.1.3 - "@types/istanbul-lib-coverage": ^2.0.0 - "@types/istanbul-reports": ^3.0.0 - "@types/node": "*" - "@types/yargs": ^17.0.8 - chalk: ^4.0.0 - checksum: 1e258d9c063fcf59ebc91e46d5ea5984674ac7ae6cae3e50aa780d22b4405bf2c925f40350bf30013839eb5d4b5e521d956ddf8f3b7c78debef0e75a07f57350 - languageName: node - linkType: hard - "@jimp/bmp@npm:^0.16.1": version: 0.16.1 resolution: "@jimp/bmp@npm:0.16.1" @@ -7826,16 +7718,6 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.13": - version: 0.3.15 - resolution: "@jridgewell/trace-mapping@npm:0.3.15" - dependencies: - "@jridgewell/resolve-uri": ^3.0.3 - "@jridgewell/sourcemap-codec": ^1.4.10 - checksum: 38917e9c2b014d469a9f51c016ed506acbe44dd16ec2f6f99b553ebf3764d22abadbf992f2367b6d2b3511f3eae8ed3a8963f6c1030093fda23efd35ecab2bae - languageName: node - linkType: hard - "@jridgewell/trace-mapping@npm:^0.3.14, @jridgewell/trace-mapping@npm:^0.3.17": version: 0.3.17 resolution: "@jridgewell/trace-mapping@npm:0.3.17" @@ -8181,18 +8063,6 @@ __metadata: languageName: node linkType: hard -"@mdx-js/loader@npm:^2.0.0-next.9": - version: 2.1.1 - resolution: "@mdx-js/loader@npm:2.1.1" - dependencies: - "@mdx-js/mdx": ^2.0.0 - source-map: ^0.7.0 - peerDependencies: - webpack: ">=4" - checksum: 0834bc33e86d274a9ef55b8c5d3ad96c498779361f7daf4bc8f7e18ba3da6fbddf9ac0a2f4eb5854008daf8618932677bb4a8e0bb63dce8f09047324b917585e - languageName: node - linkType: hard - "@mdx-js/mdx@npm:^1.6.22": version: 1.6.22 resolution: "@mdx-js/mdx@npm:1.6.22" @@ -8220,32 +8090,7 @@ __metadata: languageName: node linkType: hard -"@mdx-js/mdx@npm:^2.0.0": - version: 2.1.5 - resolution: "@mdx-js/mdx@npm:2.1.5" - dependencies: - "@types/estree-jsx": ^1.0.0 - "@types/mdx": ^2.0.0 - estree-util-build-jsx: ^2.0.0 - estree-util-is-identifier-name: ^2.0.0 - estree-util-to-js: ^1.1.0 - estree-walker: ^3.0.0 - hast-util-to-estree: ^2.0.0 - markdown-extensions: ^1.0.0 - periscopic: ^3.0.0 - remark-mdx: ^2.0.0 - remark-parse: ^10.0.0 - remark-rehype: ^10.0.0 - unified: ^10.0.0 - unist-util-position-from-estree: ^1.0.0 - unist-util-stringify-position: ^3.0.0 - unist-util-visit: ^4.0.0 - vfile: ^5.0.0 - checksum: 51b8b40aafbbeee618fd289ef6e0311e28e2598c514016aefb094237fa9cdf3e1c1407267a08dc7f8da9673de354b5b946f3ceb7ba5735bdf869d18c2a49117d - languageName: node - linkType: hard - -"@mdx-js/react@npm:^1.6.16, @mdx-js/react@npm:^1.6.22": +"@mdx-js/react@npm:^1.6.22": version: 1.6.22 resolution: "@mdx-js/react@npm:1.6.22" peerDependencies: @@ -8351,15 +8196,6 @@ __metadata: languageName: node linkType: hard -"@next/eslint-plugin-next@npm:13.2.4": - version: 13.2.4 - resolution: "@next/eslint-plugin-next@npm:13.2.4" - dependencies: - glob: 7.1.7 - checksum: 95f9a84922ec1c73aa51b794f9d2dc49c165a1c793db17cab4558097d31aaf65c2e5e3390d0691c056fac105562d6dc974b13b1df79981f8b7faba005f6eb3b5 - languageName: node - linkType: hard - "@next/swc-android-arm-eabi@npm:13.2.3": version: 13.2.3 resolution: "@next/swc-android-arm-eabi@npm:13.2.3" @@ -10076,33 +9912,6 @@ __metadata: languageName: node linkType: hard -"@reach/skip-nav@npm:^0.11.2": - version: 0.11.2 - resolution: "@reach/skip-nav@npm:0.11.2" - dependencies: - "@reach/utils": 0.11.2 - tslib: ^2.0.0 - peerDependencies: - react: ^16.8.0 - react-dom: ^16.8.0 - checksum: 7c28dce5eeaaf51bbb1ec5a0302f83a423c4e6819bcd1051316250069c19c4924c0d8a712e0ee5bfd9420ddfdb17b0e07e514934c268e1c0758dedfff93ef2ec - languageName: node - linkType: hard - -"@reach/utils@npm:0.11.2": - version: 0.11.2 - resolution: "@reach/utils@npm:0.11.2" - dependencies: - "@types/warning": ^3.0.0 - tslib: ^2.0.0 - warning: ^4.0.3 - peerDependencies: - react: ^16.8.0 - react-dom: ^16.8.0 - checksum: affb4dca8285965d3560bcd516a13c99afb047041f119f8bfdedab4dd64b03b969275d9abaf22a64e1e24a1f8675d148df31cddae1e74008407daa8944343d53 - languageName: node - linkType: hard - "@reach/utils@npm:0.16.0": version: 0.16.0 resolution: "@reach/utils@npm:0.16.0" @@ -10528,17 +10337,10 @@ __metadata: languageName: node linkType: hard -"@sinclair/typebox@npm:^0.24.1": - version: 0.24.19 - resolution: "@sinclair/typebox@npm:0.24.19" - checksum: dff2a3ebe9accc83e95fe3152f94de6ba5e4aba6833fba5436c334ba7e377b992f86228fd6f285cd6890d6419245dc89e06e17b74228289308c46224ab7ebd68 - languageName: node - linkType: hard - -"@sindresorhus/is@npm:^0.7.0": - version: 0.7.0 - resolution: "@sindresorhus/is@npm:0.7.0" - checksum: decc50f6fe80b75c981bcff0a585c05259f5e04424a46a653ac9a7e065194145c463ca81001e3a229bd203f59474afadb5b1fa0af5507723f87f2dd45bd3897c +"@sinclair/typebox@npm:^0.25.16": + version: 0.25.24 + resolution: "@sinclair/typebox@npm:0.25.24" + checksum: 10219c58f40b8414c50b483b0550445e9710d4fe7b2c4dccb9b66533dd90ba8e024acc776026cebe81e87f06fa24b07fdd7bc30dd277eb9cc386ec50151a3026 languageName: node linkType: hard @@ -10549,24 +10351,6 @@ __metadata: languageName: node linkType: hard -"@sinonjs/commons@npm:^1.7.0": - version: 1.8.3 - resolution: "@sinonjs/commons@npm:1.8.3" - dependencies: - type-detect: 4.0.8 - checksum: 6159726db5ce6bf9f2297f8427f7ca5b3dff45b31e5cee23496f1fa6ef0bb4eab878b23fb2c5e6446381f6a66aba4968ef2fc255c1180d753d4b8c271636a2e5 - languageName: node - linkType: hard - -"@sinonjs/fake-timers@npm:^9.1.2": - version: 9.1.2 - resolution: "@sinonjs/fake-timers@npm:9.1.2" - dependencies: - "@sinonjs/commons": ^1.7.0 - checksum: 7d3aef54e17c1073101cb64d953157c19d62a40e261a30923fa1ee337b049c5f29cc47b1f0c477880f42b5659848ba9ab897607ac8ea4acd5c30ddcfac57fca6 - languageName: node - linkType: hard - "@snaplet/copycat@npm:^0.3.0": version: 0.3.0 resolution: "@snaplet/copycat@npm:0.3.0" @@ -12686,15 +12470,6 @@ __metadata: languageName: node linkType: hard -"@types/acorn@npm:^4.0.0": - version: 4.0.6 - resolution: "@types/acorn@npm:4.0.6" - dependencies: - "@types/estree": "*" - checksum: 60e1fd28af18d6cb54a93a7231c7c18774a9a8739c9b179e9e8750dca631e10cbef2d82b02830ea3f557b1d121e6406441e9e1250bd492dc81d4b3456e76e4d4 - languageName: node - linkType: hard - "@types/aria-query@npm:^4.2.0": version: 4.2.2 resolution: "@types/aria-query@npm:4.2.2" @@ -12709,47 +12484,6 @@ __metadata: languageName: node linkType: hard -"@types/babel__core@npm:^7.1.14": - version: 7.1.19 - resolution: "@types/babel__core@npm:7.1.19" - dependencies: - "@babel/parser": ^7.1.0 - "@babel/types": ^7.0.0 - "@types/babel__generator": "*" - "@types/babel__template": "*" - "@types/babel__traverse": "*" - checksum: 8c9fa87a1c2224cbec251683a58bebb0d74c497118034166aaa0491a4e2627998a6621fc71f8a60ffd27d9c0c52097defedf7637adc6618d0331c15adb302338 - languageName: node - linkType: hard - -"@types/babel__generator@npm:*": - version: 7.6.4 - resolution: "@types/babel__generator@npm:7.6.4" - dependencies: - "@babel/types": ^7.0.0 - checksum: 20effbbb5f8a3a0211e95959d06ae70c097fb6191011b73b38fe86deebefad8e09ee014605e0fd3cdaedc73d158be555866810e9166e1f09e4cfd880b874dcb0 - languageName: node - linkType: hard - -"@types/babel__template@npm:*": - version: 7.4.1 - resolution: "@types/babel__template@npm:7.4.1" - dependencies: - "@babel/parser": ^7.1.0 - "@babel/types": ^7.0.0 - checksum: 649fe8b42c2876be1fd28c6ed9b276f78152d5904ec290b6c861d9ef324206e0a5c242e8305c421ac52ecf6358fa7e32ab7a692f55370484825c1df29b1596ee - languageName: node - linkType: hard - -"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": - version: 7.14.2 - resolution: "@types/babel__traverse@npm:7.14.2" - dependencies: - "@babel/types": ^7.3.0 - checksum: a797ea09c72307569e3ee08aa3900ca744ce3091114084f2dc59b67a45ee7d01df7865252790dbfa787a7915ce892cdc820c9b920f3683292765fc656b08dc63 - languageName: node - linkType: hard - "@types/bcryptjs@npm:^2.4.2": version: 2.4.2 resolution: "@types/bcryptjs@npm:2.4.2" @@ -12797,6 +12531,22 @@ __metadata: languageName: node linkType: hard +"@types/chai-subset@npm:^1.3.3": + version: 1.3.3 + resolution: "@types/chai-subset@npm:1.3.3" + dependencies: + "@types/chai": "*" + checksum: 4481da7345022995f5a105e6683744f7203d2c3d19cfe88d8e17274d045722948abf55e0adfd97709e0f043dade37a4d4e98cd4c660e2e8a14f23e6ecf79418f + languageName: node + linkType: hard + +"@types/chai@npm:*, @types/chai@npm:^4.3.5": + version: 4.3.5 + resolution: "@types/chai@npm:4.3.5" + checksum: c8f26a88c6b5b53a3275c7f5ff8f107028e3cbb9ff26795fff5f3d9dea07106a54ce9e2dce5e40347f7c4cc35657900aaf0c83934a25a1ae12e61e0f5516e431 + languageName: node + linkType: hard + "@types/connect@npm:*, @types/connect@npm:^3.4.33": version: 3.4.35 resolution: "@types/connect@npm:3.4.35" @@ -12943,24 +12693,6 @@ __metadata: languageName: node linkType: hard -"@types/estree-jsx@npm:^0.0.1": - version: 0.0.1 - resolution: "@types/estree-jsx@npm:0.0.1" - dependencies: - "@types/estree": "*" - checksum: aed43dfcbcc9c881f571618847661cd69026680466657d1443c12f51d835c7a8ccf5109a800e56c8fc33ae093f096aa937e4656ce3eb71c4f914334c389cf7be - languageName: node - linkType: hard - -"@types/estree-jsx@npm:^1.0.0": - version: 1.0.0 - resolution: "@types/estree-jsx@npm:1.0.0" - dependencies: - "@types/estree": "*" - checksum: 851d7afb63a89fb9ce7822563930660433f29106d72db279ce9c99f791ec996ef21b05adc6f545325cd1745b3041cc86422f0ffa39a06734305b90cfbc871765 - languageName: node - linkType: hard - "@types/estree@npm:*, @types/estree@npm:^0.0.51": version: 0.0.51 resolution: "@types/estree@npm:0.0.51" @@ -12975,20 +12707,6 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:^0.0.46": - version: 0.0.46 - resolution: "@types/estree@npm:0.0.46" - checksum: 620f7549c8cf99fe1c91a943a42ae2684c18f6007dc1bd6a439a2bf3204022ab746ffb3be5244c70d43a822beeb3c948216be1a69cb25e79005daeca4ebe5722 - languageName: node - linkType: hard - -"@types/estree@npm:^0.0.50": - version: 0.0.50 - resolution: "@types/estree@npm:0.0.50" - checksum: 9a2b6a4a8c117f34d08fbda5e8f69b1dfb109f7d149b60b00fd7a9fb6ac545c078bc590aa4ec2f0a256d680cf72c88b3b28b60c326ee38a7bc8ee1ee95624922 - languageName: node - linkType: hard - "@types/estree@npm:^1.0.0": version: 1.0.1 resolution: "@types/estree@npm:1.0.1" @@ -13055,7 +12773,7 @@ __metadata: languageName: node linkType: hard -"@types/graceful-fs@npm:^4.1.2, @types/graceful-fs@npm:^4.1.3": +"@types/graceful-fs@npm:^4.1.2": version: 4.1.5 resolution: "@types/graceful-fs@npm:4.1.5" dependencies: @@ -13152,16 +12870,6 @@ __metadata: languageName: node linkType: hard -"@types/jest@npm:^28.1.7": - version: 28.1.7 - resolution: "@types/jest@npm:28.1.7" - dependencies: - expect: ^28.0.0 - pretty-format: ^28.0.0 - checksum: 17c9bf9667cd4f062eba5aa9eaea248927bf6479da1cc009e0b7d26d501f460f068dd23eddb67d2602264e915a30b63ae7c9cbb516723ffd18589f6f9d267d43 - languageName: node - linkType: hard - "@types/js-levenshtein@npm:^1.1.1": version: 1.1.1 resolution: "@types/js-levenshtein@npm:1.1.1" @@ -13169,17 +12877,6 @@ __metadata: languageName: node linkType: hard -"@types/jsdom@npm:^16.2.4": - version: 16.2.15 - resolution: "@types/jsdom@npm:16.2.15" - dependencies: - "@types/node": "*" - "@types/parse5": ^6.0.3 - "@types/tough-cookie": "*" - checksum: e038335321bef42ebf220aaf597e186e2eec8de6107ce7a70de1c046a84c1fbb42d454e195a20383a6870b18c7ef6fa6b73812a626f88a4a2ef1f711d2e2e13c - languageName: node - linkType: hard - "@types/jsforce@npm:^1.11.0": version: 1.11.0 resolution: "@types/jsforce@npm:1.11.0" @@ -13280,13 +12977,6 @@ __metadata: languageName: node linkType: hard -"@types/mdx@npm:^2.0.0": - version: 2.0.1 - resolution: "@types/mdx@npm:2.0.1" - checksum: 521ee9f976af45859a4dd60b159e1cc09109de49ec5570fe70f34cddb0a7b5472b87c1bea743429270398207a997821dec34fd7bd63cbc2a1e5f2f6c701dd1c2 - languageName: node - linkType: hard - "@types/memory-cache@npm:^0.2.2": version: 0.2.2 resolution: "@types/memory-cache@npm:0.2.2" @@ -13408,7 +13098,7 @@ __metadata: languageName: node linkType: hard -"@types/parse5@npm:^6.0.0, @types/parse5@npm:^6.0.3": +"@types/parse5@npm:^6.0.0": version: 6.0.3 resolution: "@types/parse5@npm:6.0.3" checksum: ddb59ee4144af5dfcc508a8dcf32f37879d11e12559561e65788756b95b33e6f03ea027d88e1f5408f9b7bfb656bf630ace31a2169edf44151daaf8dd58df1b7 @@ -13424,13 +13114,6 @@ __metadata: languageName: node linkType: hard -"@types/prettier@npm:^2.1.5": - version: 2.6.3 - resolution: "@types/prettier@npm:2.6.3" - checksum: e1836699ca189fff6d2a73dc22e028b6a6f693ed1180d5998ac29fa197caf8f85aa92cb38db642e4a370e616b451cb5722ad2395dab11c78e025a1455f37d1f0 - languageName: node - linkType: hard - "@types/pretty-hrtime@npm:^1.0.0": version: 1.0.1 resolution: "@types/pretty-hrtime@npm:1.0.1" @@ -13622,13 +13305,6 @@ __metadata: languageName: node linkType: hard -"@types/stack-utils@npm:^2.0.0": - version: 2.0.1 - resolution: "@types/stack-utils@npm:2.0.1" - checksum: 205fdbe3326b7046d7eaf5e494d8084f2659086a266f3f9cf00bccc549c8e36e407f88168ad4383c8b07099957ad669f75f2532ed4bc70be2b037330f7bae019 - languageName: node - linkType: hard - "@types/stripe@npm:^8.0.417": version: 8.0.417 resolution: "@types/stripe@npm:8.0.417" @@ -13652,13 +13328,6 @@ __metadata: languageName: node linkType: hard -"@types/tough-cookie@npm:*": - version: 4.0.2 - resolution: "@types/tough-cookie@npm:4.0.2" - checksum: e055556ffdaa39ad85ede0af192c93f93f986f4bd9e9426efdc2948e3e2632db3a4a584d4937dbf6d7620527419bc99e6182d3daf2b08685e710f2eda5291905 - languageName: node - linkType: hard - "@types/trusted-types@npm:*, @types/trusted-types@npm:^2.0.2": version: 2.0.2 resolution: "@types/trusted-types@npm:2.0.2" @@ -13703,13 +13372,6 @@ __metadata: languageName: node linkType: hard -"@types/warning@npm:^3.0.0": - version: 3.0.0 - resolution: "@types/warning@npm:3.0.0" - checksum: 120dcf90600d583c68a60872200061eab9318ae15ea898581f8e9a6dc71b7941095dd81d8324e36d2a6006e5e12b6fc1cf8eda00cc514ee12bb39a912cc4e040 - languageName: node - linkType: hard - "@types/webidl-conversions@npm:*": version: 6.1.1 resolution: "@types/webidl-conversions@npm:6.1.1" @@ -13793,15 +13455,6 @@ __metadata: languageName: node linkType: hard -"@types/yargs@npm:^17.0.8": - version: 17.0.10 - resolution: "@types/yargs@npm:17.0.10" - dependencies: - "@types/yargs-parser": "*" - checksum: f0673cbfc08e17239dc58952a88350d6c4db04a027a28a06fbad27d87b670e909f9cd9e66f9c64cebdd5071d1096261e33454a55868395f125297e5c50992ca8 - languageName: node - linkType: hard - "@types/yoga-layout@npm:1.9.2": version: 1.9.2 resolution: "@types/yoga-layout@npm:1.9.2" @@ -14155,6 +13808,60 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:0.31.1": + version: 0.31.1 + resolution: "@vitest/expect@npm:0.31.1" + dependencies: + "@vitest/spy": 0.31.1 + "@vitest/utils": 0.31.1 + chai: ^4.3.7 + checksum: 0d1e135ae753d913231eae830da00ee42afca53d354898fb43f97e82398dcf17298c02e9989dd6b19b9b2909989248ef76d203d63f6af6f9159dc96959ea654b + languageName: node + linkType: hard + +"@vitest/runner@npm:0.31.1": + version: 0.31.1 + resolution: "@vitest/runner@npm:0.31.1" + dependencies: + "@vitest/utils": 0.31.1 + concordance: ^5.0.4 + p-limit: ^4.0.0 + pathe: ^1.1.0 + checksum: cc8702e21b799d5e941409cb2afe6d0e576b4f3ac99df4a1393a8cd11b57f6b0b06e756cc24e2739812d095fbfd0824e22e861dbd6a71769ca387d485ade6fb5 + languageName: node + linkType: hard + +"@vitest/snapshot@npm:0.31.1": + version: 0.31.1 + resolution: "@vitest/snapshot@npm:0.31.1" + dependencies: + magic-string: ^0.30.0 + pathe: ^1.1.0 + pretty-format: ^27.5.1 + checksum: de05fa9136864f26f0804baf3ae8068f67de28015f29047329c84e67fb33be7305c9e52661b016e834d30f4081c136b3b6d8d4054c024a5d52b22a7f90fc4be0 + languageName: node + linkType: hard + +"@vitest/spy@npm:0.31.1": + version: 0.31.1 + resolution: "@vitest/spy@npm:0.31.1" + dependencies: + tinyspy: ^2.1.0 + checksum: 8b06cf25fcc028c16106ec82f4ceb84d6dfa04d06f651bca4738ce2b99796d1fc4e0c10319767240755eff8ede2bff9d31d5a901fe92828d319c65001581137b + languageName: node + linkType: hard + +"@vitest/utils@npm:0.31.1": + version: 0.31.1 + resolution: "@vitest/utils@npm:0.31.1" + dependencies: + concordance: ^5.0.4 + loupe: ^2.3.6 + pretty-format: ^27.5.1 + checksum: 58016c185455e3814632cb77e37368c846bde5e342f8b4a66fa229bde64f455ca39abebc9c12e2483696ee38bc17b3c4300379f7a3b18d1087f24f474448a8d8 + languageName: node + linkType: hard + "@wagmi/core@npm:^0.5.4": version: 0.5.4 resolution: "@wagmi/core@npm:0.5.4" @@ -14822,13 +14529,6 @@ __metadata: languageName: unknown linkType: soft -"abab@npm:^2.0.5": - version: 2.0.5 - resolution: "abab@npm:2.0.5" - checksum: 0ec951b46d5418c2c2f923021ec193eaebdb4e802ffd5506286781b454be722a13a8430f98085cd3e204918401d9130ec6cc8f5ae19be315b3a0e857d83196e1 - languageName: node - linkType: hard - "abab@npm:^2.0.6": version: 2.0.6 resolution: "abab@npm:2.0.6" @@ -14885,16 +14585,6 @@ __metadata: languageName: node linkType: hard -"acorn-globals@npm:^6.0.0": - version: 6.0.0 - resolution: "acorn-globals@npm:6.0.0" - dependencies: - acorn: ^7.1.1 - acorn-walk: ^7.1.1 - checksum: 72d95e5b5e585f9acd019b993ab8bbba68bb3cbc9d9b5c1ebb3c2f1fe5981f11deababfb4949f48e6262f9c57878837f5958c0cca396f81023814680ca878042 - languageName: node - linkType: hard - "acorn-import-assertions@npm:^1.7.6": version: 1.8.0 resolution: "acorn-import-assertions@npm:1.8.0" @@ -14904,7 +14594,7 @@ __metadata: languageName: node linkType: hard -"acorn-jsx@npm:^5.0.0, acorn-jsx@npm:^5.3.1, acorn-jsx@npm:^5.3.2": +"acorn-jsx@npm:^5.3.1, acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" peerDependencies: @@ -14924,14 +14614,14 @@ __metadata: languageName: node linkType: hard -"acorn-walk@npm:^7.0.0, acorn-walk@npm:^7.1.1, acorn-walk@npm:^7.2.0": +"acorn-walk@npm:^7.0.0, acorn-walk@npm:^7.2.0": version: 7.2.0 resolution: "acorn-walk@npm:7.2.0" checksum: 9252158a79b9d92f1bc0dd6acc0fcfb87a67339e84bcc301bb33d6078936d27e35d606b4d35626d2962cd43c256d6f27717e70cbe15c04fff999ab0b2260b21f languageName: node linkType: hard -"acorn-walk@npm:^8.0.0, acorn-walk@npm:^8.1.1": +"acorn-walk@npm:^8.0.0, acorn-walk@npm:^8.1.1, acorn-walk@npm:^8.2.0": version: 8.2.0 resolution: "acorn-walk@npm:8.2.0" checksum: 1715e76c01dd7b2d4ca472f9c58968516a4899378a63ad5b6c2d668bba8da21a71976c14ec5f5b75f887b6317c4ae0b897ab141c831d741dc76024d8745f1ad1 @@ -14947,7 +14637,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^7.0.0, acorn@npm:^7.1.1, acorn@npm:^7.4.1": +"acorn@npm:^7.0.0, acorn@npm:^7.4.1": version: 7.4.1 resolution: "acorn@npm:7.4.1" bin: @@ -14956,7 +14646,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.0.0, acorn@npm:^8.0.4, acorn@npm:^8.4.1": +"acorn@npm:^8.0.4, acorn@npm:^8.4.1": version: 8.7.0 resolution: "acorn@npm:8.7.0" bin: @@ -14983,6 +14673,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.8.2": + version: 8.8.2 + resolution: "acorn@npm:8.8.2" + bin: + acorn: bin/acorn + checksum: f790b99a1bf63ef160c967e23c46feea7787e531292bb827126334612c234ed489a0dc2c7ba33156416f0ffa8d25bf2b0fdb7f35c2ba60eb3e960572bece4001 + languageName: node + linkType: hard + "address@npm:^1.0.1": version: 1.2.0 resolution: "address@npm:1.2.0" @@ -15175,15 +14874,6 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^5.0.0": - version: 5.0.0 - resolution: "ansi-escapes@npm:5.0.0" - dependencies: - type-fest: ^1.0.2 - checksum: d4b5eb8207df38367945f5dd2ef41e08c28edc192dc766ef18af6b53736682f49d8bfcfa4e4d6ecbc2e2f97c258fda084fb29a9e43b69170b71090f771afccac - languageName: node - linkType: hard - "ansi-html-community@npm:0.0.8, ansi-html-community@npm:^0.0.8": version: 0.0.8 resolution: "ansi-html-community@npm:0.0.8" @@ -15228,7 +14918,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^3.1.0, ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1": +"ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" dependencies: @@ -15340,22 +15030,6 @@ __metadata: languageName: node linkType: hard -"arch@npm:^2.1.0": - version: 2.2.0 - resolution: "arch@npm:2.2.0" - checksum: e21b7635029fe8e9cdd5a026f9a6c659103e63fff423834323cdf836a1bb240a72d0c39ca8c470f84643385cf581bd8eda2cad8bf493e27e54bd9783abe9101f - languageName: node - linkType: hard - -"archive-type@npm:^4.0.0": - version: 4.0.0 - resolution: "archive-type@npm:4.0.0" - dependencies: - file-type: ^4.2.0 - checksum: 271f0d118294dd0305831f0700b635e8a9475f97693212d548eee48017f917e14349a25ad578f8e13486ba4b7cde1972d53e613d980e8738cfccea5fc626c76f - languageName: node - linkType: hard - "are-we-there-yet@npm:^2.0.0": version: 2.0.0 resolution: "are-we-there-yet@npm:2.0.0" @@ -15376,13 +15050,6 @@ __metadata: languageName: node linkType: hard -"arg@npm:1.0.0": - version: 1.0.0 - resolution: "arg@npm:1.0.0" - checksum: 0a35939e3cb59d4f18207884563e00b0f71647becccec932348e9c5bdecfe6ca41b2ed854bbb267a2e87f97f605ed3ca936f2f0b3313bf88be6d7cdbb2d0a4b1 - languageName: node - linkType: hard - "arg@npm:4.1.0": version: 4.1.0 resolution: "arg@npm:4.1.0" @@ -15730,6 +15397,13 @@ __metadata: languageName: node linkType: hard +"assertion-error@npm:^1.1.0": + version: 1.1.0 + resolution: "assertion-error@npm:1.1.0" + checksum: fd9429d3a3d4fd61782eb3962ae76b6d08aa7383123fca0596020013b3ebd6647891a85b05ce821c47d1471ed1271f00b0545cf6a4326cf2fc91efcc3b0fbecf + languageName: node + linkType: hard + "assign-symbols@npm:^1.0.0": version: 1.0.0 resolution: "assign-symbols@npm:1.0.0" @@ -15760,15 +15434,6 @@ __metadata: languageName: node linkType: hard -"astring@npm:^1.8.0": - version: 1.8.4 - resolution: "astring@npm:1.8.4" - bin: - astring: bin/astring - checksum: bc0b98087350c4a0c8a510d491d648cf8b299ec904629d5e0f5ae8d2ccc515cd27475327bb9729c7e92f4a4873adcd05cef15379d0f6f7293f1320319f0d24f0 - languageName: node - linkType: hard - "async-each@npm:^1.0.1": version: 1.0.3 resolution: "async-each@npm:1.0.3" @@ -15991,23 +15656,6 @@ __metadata: languageName: node linkType: hard -"babel-jest@npm:^28.1.0, babel-jest@npm:^28.1.3": - version: 28.1.3 - resolution: "babel-jest@npm:28.1.3" - dependencies: - "@jest/transform": ^28.1.3 - "@types/babel__core": ^7.1.14 - babel-plugin-istanbul: ^6.1.1 - babel-preset-jest: ^28.1.3 - chalk: ^4.0.0 - graceful-fs: ^4.2.9 - slash: ^3.0.0 - peerDependencies: - "@babel/core": ^7.8.0 - checksum: 57ccd2296e1839687b5df2fd138c3d00717e0369e385254b012ccd4ee70e75f5d5c8e6cfcdf92d155015b468cfebb847b38e69bb5805d8aaf730e20575127cc6 - languageName: node - linkType: hard - "babel-loader@npm:^8.0.0, babel-loader@npm:^8.2.5": version: 8.2.5 resolution: "babel-loader@npm:8.2.5" @@ -16060,7 +15708,7 @@ __metadata: languageName: node linkType: hard -"babel-plugin-istanbul@npm:^6.0.0, babel-plugin-istanbul@npm:^6.1.1": +"babel-plugin-istanbul@npm:^6.0.0": version: 6.1.1 resolution: "babel-plugin-istanbul@npm:6.1.1" dependencies: @@ -16073,18 +15721,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-jest-hoist@npm:^28.1.3": - version: 28.1.3 - resolution: "babel-plugin-jest-hoist@npm:28.1.3" - dependencies: - "@babel/template": ^7.3.3 - "@babel/types": ^7.3.3 - "@types/babel__core": ^7.1.14 - "@types/babel__traverse": ^7.0.6 - checksum: 648d89f9d80f6450ce7e50d0c32eb91b7f26269b47c3e37aaf2e0f2f66a980978345bd6b8c9b8c3aa6a8252ad2bc2c9fb50630e9895622c9a0972af5f70ed20e - languageName: node - linkType: hard - "babel-plugin-macros@npm:^2.6.1": version: 2.8.0 resolution: "babel-plugin-macros@npm:2.8.0" @@ -16209,40 +15845,6 @@ __metadata: languageName: node linkType: hard -"babel-preset-current-node-syntax@npm:^1.0.0": - version: 1.0.1 - resolution: "babel-preset-current-node-syntax@npm:1.0.1" - dependencies: - "@babel/plugin-syntax-async-generators": ^7.8.4 - "@babel/plugin-syntax-bigint": ^7.8.3 - "@babel/plugin-syntax-class-properties": ^7.8.3 - "@babel/plugin-syntax-import-meta": ^7.8.3 - "@babel/plugin-syntax-json-strings": ^7.8.3 - "@babel/plugin-syntax-logical-assignment-operators": ^7.8.3 - "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3 - "@babel/plugin-syntax-numeric-separator": ^7.8.3 - "@babel/plugin-syntax-object-rest-spread": ^7.8.3 - "@babel/plugin-syntax-optional-catch-binding": ^7.8.3 - "@babel/plugin-syntax-optional-chaining": ^7.8.3 - "@babel/plugin-syntax-top-level-await": ^7.8.3 - peerDependencies: - "@babel/core": ^7.0.0 - checksum: d118c2742498c5492c095bc8541f4076b253e705b5f1ad9a2e7d302d81a84866f0070346662355c8e25fc02caa28dc2da8d69bcd67794a0d60c4d6fab6913cc8 - languageName: node - linkType: hard - -"babel-preset-jest@npm:^28.1.3": - version: 28.1.3 - resolution: "babel-preset-jest@npm:28.1.3" - dependencies: - babel-plugin-jest-hoist: ^28.1.3 - babel-preset-current-node-syntax: ^1.0.0 - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 8248a4a5ca4242cc06ad13b10b9183ad2664da8fb0da060c352223dcf286f0ce9c708fa17901dc44ecabec25e6d309e5e5b9830a61dd777c3925f187a345a47d - languageName: node - linkType: hard - "bail@npm:^1.0.0": version: 1.0.5 resolution: "bail@npm:1.0.5" @@ -16427,16 +16029,6 @@ __metadata: languageName: node linkType: hard -"bl@npm:^1.0.0": - version: 1.2.3 - resolution: "bl@npm:1.2.3" - dependencies: - readable-stream: ^2.3.5 - safe-buffer: ^5.1.1 - checksum: 123f097989ce2fa9087ce761cd41176aaaec864e28f7dfe5c7dab8ae16d66d9844f849c3ad688eb357e3c5e4f49b573e3c0780bb8bc937206735a3b6f8569a5f - languageName: node - linkType: hard - "bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" @@ -16482,6 +16074,13 @@ __metadata: languageName: node linkType: hard +"blueimp-md5@npm:^2.10.0": + version: 2.19.0 + resolution: "blueimp-md5@npm:2.19.0" + checksum: 28095dcbd2c67152a2938006e8d7c74c3406ba6556071298f872505432feb2c13241b0476644160ee0a5220383ba94cb8ccdac0053b51f68d168728f9c382530 + languageName: node + linkType: hard + "bmp-js@npm:^0.1.0": version: 0.1.0 resolution: "bmp-js@npm:0.1.0" @@ -16688,13 +16287,6 @@ __metadata: languageName: node linkType: hard -"browser-process-hrtime@npm:^1.0.0": - version: 1.0.0 - resolution: "browser-process-hrtime@npm:1.0.0" - checksum: e30f868cdb770b1201afb714ad1575dd86366b6e861900884665fb627109b3cc757c40067d3bfee1ff2a29c835257ea30725a8018a9afd02ac1c24b408b1e45f - languageName: node - linkType: hard - "browserify-aes@npm:^1.0.0, browserify-aes@npm:^1.0.4, browserify-aes@npm:^1.2.0": version: 1.2.0 resolution: "browserify-aes@npm:1.2.0" @@ -16851,15 +16443,6 @@ __metadata: languageName: node linkType: hard -"bs-logger@npm:0.x": - version: 0.2.6 - resolution: "bs-logger@npm:0.2.6" - dependencies: - fast-json-stable-stringify: 2.x - checksum: d34bdaf68c64bd099ab97c3ea608c9ae7d3f5faa1178b3f3f345acd94e852e608b2d4f9103fb2e503f5e69780e98293df41691b84be909b41cf5045374d54606 - languageName: node - linkType: hard - "bs58@npm:^4.0.0, bs58@npm:^4.0.1": version: 4.0.1 resolution: "bs58@npm:4.0.1" @@ -16922,13 +16505,6 @@ __metadata: languageName: node linkType: hard -"buffer-crc32@npm:~0.2.3": - version: 0.2.13 - resolution: "buffer-crc32@npm:0.2.13" - checksum: 06252347ae6daca3453b94e4b2f1d3754a3b146a111d81c68924c22d91889a40623264e95e67955b1cb4a68cbedf317abeabb5140a9766ed248973096db5ce1c - languageName: node - linkType: hard - "buffer-equal-constant-time@npm:1.0.1": version: 1.0.1 resolution: "buffer-equal-constant-time@npm:1.0.1" @@ -17006,7 +16582,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.0.5, buffer@npm:^5.2.0, buffer@npm:^5.2.1, buffer@npm:^5.4.3, buffer@npm:^5.5.0, buffer@npm:^5.6.0": +"buffer@npm:^5.0.5, buffer@npm:^5.2.0, buffer@npm:^5.4.3, buffer@npm:^5.5.0, buffer@npm:^5.6.0": version: 5.7.1 resolution: "buffer@npm:5.7.1" dependencies: @@ -17071,6 +16647,28 @@ __metadata: languageName: node linkType: hard +"c8@npm:^7.13.0": + version: 7.13.0 + resolution: "c8@npm:7.13.0" + dependencies: + "@bcoe/v8-coverage": ^0.2.3 + "@istanbuljs/schema": ^0.1.3 + find-up: ^5.0.0 + foreground-child: ^2.0.0 + istanbul-lib-coverage: ^3.2.0 + istanbul-lib-report: ^3.0.0 + istanbul-reports: ^3.1.4 + rimraf: ^3.0.2 + test-exclude: ^6.0.0 + v8-to-istanbul: ^9.0.0 + yargs: ^16.2.0 + yargs-parser: ^20.2.9 + bin: + c8: bin/c8.js + checksum: 491abf4cf3097cdcfd24dbac49162f1383861c22c77fdd9280bcd38240e1e07d2c6a59da5d4df59a61a8204e2fc297d31fd526e495faf8d2f20dcc12a37b144c + languageName: node + linkType: hard + "c8@npm:^7.6.0": version: 7.11.3 resolution: "c8@npm:7.11.3" @@ -17093,6 +16691,13 @@ __metadata: languageName: node linkType: hard +"cac@npm:^6.7.14": + version: 6.7.14 + resolution: "cac@npm:6.7.14" + checksum: 45a2496a9443abbe7f52a49b22fbe51b1905eff46e03fd5e6c98e3f85077be3f8949685a1849b1a9cd2bc3e5567dfebcf64f01ce01847baf918f1b37c839791a + languageName: node + linkType: hard + "cacache@npm:^12.0.2": version: 12.0.4 resolution: "cacache@npm:12.0.4" @@ -17192,21 +16797,6 @@ __metadata: languageName: node linkType: hard -"cacheable-request@npm:^2.1.1": - version: 2.1.4 - resolution: "cacheable-request@npm:2.1.4" - dependencies: - clone-response: 1.0.2 - get-stream: 3.0.0 - http-cache-semantics: 3.8.1 - keyv: 3.0.0 - lowercase-keys: 1.0.0 - normalize-url: 2.0.1 - responselike: 1.0.2 - checksum: 69c684cb3645f75af094e3ef6e7959ca5edff33d70737498de1a068d2f719a12786efdd82fe1e2254a1f332bb88cce088273bd78fad3e57cdef5034f3ded9432 - languageName: node - linkType: hard - "cacheable-request@npm:^7.0.2": version: 7.0.2 resolution: "cacheable-request@npm:7.0.2" @@ -17231,23 +16821,21 @@ __metadata: "@playwright/test": ^1.31.2 "@snaplet/copycat": ^0.3.0 "@types/dompurify": ^2.4.0 - "@types/jest": ^28.1.7 + c8: ^7.13.0 city-timezones: ^1.2.1 dotenv-checker: ^1.1.5 eslint: ^8.34.0 - husky: ^8.0.1 - jest: ^28.1.0 - jest-environment-jsdom: ^28.1.3 - jest-mock-extended: ^2.0.7 - jest-watch-select-projects: ^2.0.0 - jest-watch-typeahead: ^2.0.0 + husky: ^8.0.0 + jest-diff: ^29.5.0 + jsdom: ^22.0.0 lint-staged: ^12.5.0 lucide-react: ^0.171.0 prettier: ^2.8.6 - ts-jest: ^28.0.8 tsc-absolute: ^1.0.0 turbo: ^1.8.3 typescript: ^4.9.4 + vitest: ^0.31.1 + vitest-mock-extended: ^1.1.3 languageName: unknown linkType: soft @@ -17423,18 +17011,22 @@ __metadata: languageName: node linkType: hard -"chalk@npm:2.3.0": - version: 2.3.0 - resolution: "chalk@npm:2.3.0" +"chai@npm:^4.3.7": + version: 4.3.7 + resolution: "chai@npm:4.3.7" dependencies: - ansi-styles: ^3.1.0 - escape-string-regexp: ^1.0.5 - supports-color: ^4.0.0 - checksum: d348fc0f4f8d27c068a6ac492e708fa35a75e273d5f0004da61ea694e958981658c96693790f4d23e7b3712f9e3e4ca0988136cb0403876de5459a4c0d13078f + assertion-error: ^1.1.0 + check-error: ^1.0.2 + deep-eql: ^4.1.2 + get-func-name: ^2.0.0 + loupe: ^2.3.1 + pathval: ^1.1.1 + type-detect: ^4.0.5 + checksum: 0bba7d267848015246a66995f044ce3f0ebc35e530da3cbdf171db744e14cbe301ab913a8d07caf7952b430257ccbb1a4a983c570a7c5748dc537897e5131f7c languageName: node linkType: hard -"chalk@npm:3.0.0, chalk@npm:^3.0.0": +"chalk@npm:3.0.0": version: 3.0.0 resolution: "chalk@npm:3.0.0" dependencies: @@ -17512,20 +17104,6 @@ __metadata: languageName: node linkType: hard -"char-regex@npm:^1.0.2": - version: 1.0.2 - resolution: "char-regex@npm:1.0.2" - checksum: b563e4b6039b15213114626621e7a3d12f31008bdce20f9c741d69987f62aeaace7ec30f6018890ad77b2e9b4d95324c9f5acfca58a9441e3b1dcdd1e2525d17 - languageName: node - linkType: hard - -"char-regex@npm:^2.0.0": - version: 2.0.1 - resolution: "char-regex@npm:2.0.1" - checksum: 8524c03fd7e58381dccf33babe885fe62731ae20755528b19c39945b8203479184f35247210dc9eeeef279cdbdd6511cd3182e0e1db8e4549bf2586470b7c204 - languageName: node - linkType: hard - "character-entities-html4@npm:^2.0.0": version: 2.1.0 resolution: "character-entities-html4@npm:2.1.0" @@ -17568,13 +17146,6 @@ __metadata: languageName: node linkType: hard -"character-reference-invalid@npm:^2.0.0": - version: 2.0.1 - resolution: "character-reference-invalid@npm:2.0.1" - checksum: 98d3b1a52ae510b7329e6ee7f6210df14f1e318c5415975d4c9e7ee0ef4c07875d47c6e74230c64551f12f556b4a8ccc24d9f3691a2aa197019e72a95e9297ee - languageName: node - linkType: hard - "chardet@npm:^0.7.0": version: 0.7.0 resolution: "chardet@npm:0.7.0" @@ -17596,6 +17167,13 @@ __metadata: languageName: node linkType: hard +"check-error@npm:^1.0.2": + version: 1.0.2 + resolution: "check-error@npm:1.0.2" + checksum: d9d106504404b8addd1ee3f63f8c0eaa7cd962a1a28eb9c519b1c4a1dc7098be38007fc0060f045ee00f075fbb7a2a4f42abcf61d68323677e11ab98dc16042e + languageName: node + linkType: hard + "check-more-types@npm:2.24.0": version: 2.24.0 resolution: "check-more-types@npm:2.24.0" @@ -17719,13 +17297,6 @@ __metadata: languageName: node linkType: hard -"cjs-module-lexer@npm:^1.0.0": - version: 1.2.2 - resolution: "cjs-module-lexer@npm:1.2.2" - checksum: 977f3f042bd4f08e368c890d91eecfbc4f91da0bc009a3c557bc4dfbf32022ad1141244ac1178d44de70fc9f3dea7add7cd9a658a34b9fae98a55d8f92331ce5 - languageName: node - linkType: hard - "class-is@npm:^1.1.0": version: 1.1.0 resolution: "class-is@npm:1.1.0" @@ -17764,7 +17335,7 @@ __metadata: languageName: node linkType: hard -"classnames@npm:^2.2.6, classnames@npm:^2.3.1": +"classnames@npm:^2.3.1": version: 2.3.1 resolution: "classnames@npm:2.3.1" checksum: 14db8889d56c267a591f08b0834989fe542d47fac659af5a539e110cc4266694e8de86e4e3bbd271157dbd831361310a8293e0167141e80b0f03a0f175c80960 @@ -17892,16 +17463,6 @@ __metadata: languageName: node linkType: hard -"clipboardy@npm:1.2.2": - version: 1.2.2 - resolution: "clipboardy@npm:1.2.2" - dependencies: - arch: ^2.1.0 - execa: ^0.8.0 - checksum: 7744a95e014e5945f17554b426fe78c6e1329ead85a61fa589dd4c3f4a596a037d45cad0bfaa3e60bdf77717f0dfda67c35454216f4dbb7832ec0eba42ba8a06 - languageName: node - linkType: hard - "cliui@npm:^5.0.0": version: 5.0.0 resolution: "cliui@npm:5.0.0" @@ -17957,7 +17518,7 @@ __metadata: languageName: node linkType: hard -"clone-response@npm:1.0.2, clone-response@npm:^1.0.2": +"clone-response@npm:^1.0.2": version: 1.0.2 resolution: "clone-response@npm:1.0.2" dependencies: @@ -18024,13 +17585,6 @@ __metadata: languageName: node linkType: hard -"co@npm:^4.6.0": - version: 4.6.0 - resolution: "co@npm:4.6.0" - checksum: 5210d9223010eb95b29df06a91116f2cf7c8e0748a9013ed853b53f362ea0e822f1e5bb054fb3cefc645239a4cf966af1f6133a3b43f40d591f3b68ed6cf0510 - languageName: node - linkType: hard - "cobe@npm:^0.4.1": version: 0.4.2 resolution: "cobe@npm:0.4.2" @@ -18075,13 +17629,6 @@ __metadata: languageName: node linkType: hard -"collect-v8-coverage@npm:^1.0.0": - version: 1.0.1 - resolution: "collect-v8-coverage@npm:1.0.1" - checksum: 4efe0a1fccd517b65478a2364b33dadd0a43fc92a56f59aaece9b6186fe5177b2de471253587de7c91516f07c7268c2f6770b6cbcffc0e0ece353b766ec87e55 - languageName: node - linkType: hard - "collection-visit@npm:^1.0.0": version: 1.0.0 resolution: "collection-visit@npm:1.0.0" @@ -18191,7 +17738,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^2.19.0, commander@npm:^2.20.0, commander@npm:^2.20.3, commander@npm:^2.7.1, commander@npm:^2.8.1, commander@npm:^2.9.0": +"commander@npm:^2.19.0, commander@npm:^2.20.0, commander@npm:^2.20.3, commander@npm:^2.7.1, commander@npm:^2.9.0": version: 2.20.3 resolution: "commander@npm:2.20.3" checksum: ab8c07884e42c3a8dbc5dd9592c606176c7eb5c1ca5ff274bcf907039b2c41de3626f684ea75ccf4d361ba004bbaff1f577d5384c155f3871e456bdf27becf9e @@ -18328,6 +17875,22 @@ __metadata: languageName: node linkType: hard +"concordance@npm:^5.0.4": + version: 5.0.4 + resolution: "concordance@npm:5.0.4" + dependencies: + date-time: ^3.1.0 + esutils: ^2.0.3 + fast-diff: ^1.2.0 + js-string-escape: ^1.0.1 + lodash: ^4.17.15 + md5-hex: ^3.0.1 + semver: ^7.3.2 + well-known-symbols: ^2.0.0 + checksum: 749153ba711492feb7c3d2f5bb04c107157440b3e39509bd5dd19ee7b3ac751d1e4cd75796d9f702e0a713312dbc661421c68aa4a2c34d5f6d91f47e3a1c64a6 + languageName: node + linkType: hard + "concurrently@npm:^7.6.0": version: 7.6.0 resolution: "concurrently@npm:7.6.0" @@ -18379,7 +17942,7 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:0.5.4, content-disposition@npm:^0.5.2, content-disposition@npm:^0.5.3": +"content-disposition@npm:0.5.4, content-disposition@npm:^0.5.3": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" dependencies: @@ -18736,7 +18299,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^5.0.1, cross-spawn@npm:^5.1.0": +"cross-spawn@npm:^5.1.0": version: 5.1.0 resolution: "cross-spawn@npm:5.1.0" dependencies: @@ -18934,26 +18497,12 @@ __metadata: languageName: node linkType: hard -"cssom@npm:^0.5.0": - version: 0.5.0 - resolution: "cssom@npm:0.5.0" - checksum: 823471aa30091c59e0a305927c30e7768939b6af70405808f8d2ce1ca778cddcb24722717392438329d1691f9a87cb0183b64b8d779b56a961546d54854fde01 - languageName: node - linkType: hard - -"cssom@npm:~0.3.6": - version: 0.3.8 - resolution: "cssom@npm:0.3.8" - checksum: 24beb3087c76c0d52dd458be9ee1fbc80ac771478a9baef35dd258cdeb527c68eb43204dd439692bb2b1ae5272fa5f2946d10946edab0d04f1078f85e06bc7f6 - languageName: node - linkType: hard - -"cssstyle@npm:^2.3.0": - version: 2.3.0 - resolution: "cssstyle@npm:2.3.0" +"cssstyle@npm:^3.0.0": + version: 3.0.0 + resolution: "cssstyle@npm:3.0.0" dependencies: - cssom: ~0.3.6 - checksum: 5f05e6fd2e3df0b44695c2f08b9ef38b011862b274e320665176467c0725e44a53e341bc4959a41176e83b66064ab786262e7380fd1cabeae6efee0d255bb4e3 + rrweb-cssom: ^0.6.0 + checksum: 31f694dfed9998ed93570fe539610837b878193dd8487c33cb12db8004333c53c2a3904166288bbec68388c72fb01014d46d3243ddfb02fe845989d852c06f27 languageName: node linkType: hard @@ -19141,14 +18690,14 @@ __metadata: languageName: node linkType: hard -"data-urls@npm:^3.0.1": - version: 3.0.2 - resolution: "data-urls@npm:3.0.2" +"data-urls@npm:^4.0.0": + version: 4.0.0 + resolution: "data-urls@npm:4.0.0" dependencies: abab: ^2.0.6 whatwg-mimetype: ^3.0.0 - whatwg-url: ^11.0.0 - checksum: 033fc3dd0fba6d24bc9a024ddcf9923691dd24f90a3d26f6545d6a2f71ec6956f93462f2cdf2183cc46f10dc01ed3bcb36731a8208456eb1a08147e571fe2a76 + whatwg-url: ^12.0.0 + checksum: 006e869b5bf079647949a3e9b1dd69d84b2d5d26e6b01c265485699bc96e83817d4b5aae758b2910a4c58c0601913f3a0034121c1ca2da268e9a244c57515b15 languageName: node linkType: hard @@ -19168,6 +18717,15 @@ __metadata: languageName: node linkType: hard +"date-time@npm:^3.1.0": + version: 3.1.0 + resolution: "date-time@npm:3.1.0" + dependencies: + time-zone: ^1.0.0 + checksum: f9cfcd1b15dfeabab15c0b9d18eb9e4e2d9d4371713564178d46a8f91ad577a290b5178b80050718d02d9c0cf646f8a875011e12d1ed05871e9f72c72c8a8fe6 + languageName: node + linkType: hard + "datocms-listen@npm:^0.1.9": version: 0.1.15 resolution: "datocms-listen@npm:0.1.15" @@ -19289,10 +18847,10 @@ __metadata: languageName: node linkType: hard -"decimal.js@npm:^10.3.1": - version: 10.4.0 - resolution: "decimal.js@npm:10.4.0" - checksum: 98702d9d817a9e5b3767ea6580e7f3b35544b9454e463a5dd5d3232131470f39067d02864c45cab009eb1200bc162cd26a33d34c622cd79e4657a3e25e95fb4e +"decimal.js@npm:^10.4.3": + version: 10.4.3 + resolution: "decimal.js@npm:10.4.3" + checksum: 796404dcfa9d1dbfdc48870229d57f788b48c21c603c3f6554a1c17c10195fc1024de338b0cf9e1efe0c7c167eeb18f04548979bcc5fdfabebb7cc0ae3287bae languageName: node linkType: hard @@ -19330,69 +18888,6 @@ __metadata: languageName: node linkType: hard -"decompress-tar@npm:^4.0.0, decompress-tar@npm:^4.1.0, decompress-tar@npm:^4.1.1": - version: 4.1.1 - resolution: "decompress-tar@npm:4.1.1" - dependencies: - file-type: ^5.2.0 - is-stream: ^1.1.0 - tar-stream: ^1.5.2 - checksum: 42d5360b558a28dd884e1bf809e3fea92b9910fda5151add004d4a64cc76ac124e8b3e9117e805f2349af9e49c331d873e6fc5ad86a00e575703fee632b0a225 - languageName: node - linkType: hard - -"decompress-tarbz2@npm:^4.0.0": - version: 4.1.1 - resolution: "decompress-tarbz2@npm:4.1.1" - dependencies: - decompress-tar: ^4.1.0 - file-type: ^6.1.0 - is-stream: ^1.1.0 - seek-bzip: ^1.0.5 - unbzip2-stream: ^1.0.9 - checksum: 519c81337730159a1f2d7072a6ee8523ffd76df48d34f14c27cb0a27f89b4e2acf75dad2f761838e5bc63230cea1ac154b092ecb7504be4e93f7d0e32ddd6aff - languageName: node - linkType: hard - -"decompress-targz@npm:^4.0.0": - version: 4.1.1 - resolution: "decompress-targz@npm:4.1.1" - dependencies: - decompress-tar: ^4.1.1 - file-type: ^5.2.0 - is-stream: ^1.1.0 - checksum: 22738f58eb034568dc50d370c03b346c428bfe8292fe56165847376b5af17d3c028fefca82db642d79cb094df4c0a599d40a8f294b02aad1d3ddec82f3fd45d4 - languageName: node - linkType: hard - -"decompress-unzip@npm:^4.0.1": - version: 4.0.1 - resolution: "decompress-unzip@npm:4.0.1" - dependencies: - file-type: ^3.8.0 - get-stream: ^2.2.0 - pify: ^2.3.0 - yauzl: ^2.4.2 - checksum: ba9f3204ab2415bedb18d796244928a18148ef40dbb15174d0d01e5991b39536b03d02800a8a389515a1523f8fb13efc7cd44697df758cd06c674879caefd62b - languageName: node - linkType: hard - -"decompress@npm:^4.2.1": - version: 4.2.1 - resolution: "decompress@npm:4.2.1" - dependencies: - decompress-tar: ^4.0.0 - decompress-tarbz2: ^4.0.0 - decompress-targz: ^4.0.0 - decompress-unzip: ^4.0.1 - graceful-fs: ^4.1.10 - make-dir: ^1.0.0 - pify: ^2.3.0 - strip-dirs: ^2.0.0 - checksum: 8247a31c6db7178413715fdfb35a482f019c81dfcd6e8e623d9f0382c9889ce797ce0144de016b256ed03298907a620ce81387cca0e69067a933470081436cb8 - languageName: node - linkType: hard - "dedent@npm:^0.7.0": version: 0.7.0 resolution: "dedent@npm:0.7.0" @@ -19400,6 +18895,15 @@ __metadata: languageName: node linkType: hard +"deep-eql@npm:^4.1.2": + version: 4.1.3 + resolution: "deep-eql@npm:4.1.3" + dependencies: + type-detect: ^4.0.0 + checksum: 7f6d30cb41c713973dc07eaadded848b2ab0b835e518a88b91bea72f34e08c4c71d167a722a6f302d3a6108f05afd8e6d7650689a84d5d29ec7fe6220420397f + languageName: node + linkType: hard + "deep-extend@npm:0.6.0": version: 0.6.0 resolution: "deep-extend@npm:0.6.0" @@ -19644,13 +19148,6 @@ __metadata: languageName: node linkType: hard -"detect-newline@npm:^3.0.0": - version: 3.1.0 - resolution: "detect-newline@npm:3.1.0" - checksum: ae6cd429c41ad01b164c59ea36f264a2c479598e61cba7c99da24175a7ab80ddf066420f2bec9a1c57a6bead411b4655ff15ad7d281c000a89791f48cbe939e7 - languageName: node - linkType: hard - "detect-node-es@npm:^1.1.0": version: 1.1.0 resolution: "detect-node-es@npm:1.1.0" @@ -19700,10 +19197,10 @@ __metadata: languageName: node linkType: hard -"diff-sequences@npm:^28.1.1": - version: 28.1.1 - resolution: "diff-sequences@npm:28.1.1" - checksum: e2529036505567c7ca5a2dea86b6bcd1ca0e3ae63bf8ebf529b8a99cfa915bbf194b7021dc1c57361a4017a6d95578d4ceb29fabc3232a4f4cb866a2726c7690 +"diff-sequences@npm:^29.4.3": + version: 29.4.3 + resolution: "diff-sequences@npm:29.4.3" + checksum: 28b265e04fdddcf7f9f814effe102cc95a9dec0564a579b5aed140edb24fc345c611ca52d76d725a3cab55d3888b915b5e8a4702e0f6058968a90fa5f41fcde7 languageName: node linkType: hard @@ -20030,25 +19527,6 @@ __metadata: languageName: node linkType: hard -"download@npm:^8.0.0": - version: 8.0.0 - resolution: "download@npm:8.0.0" - dependencies: - archive-type: ^4.0.0 - content-disposition: ^0.5.2 - decompress: ^4.2.1 - ext-name: ^5.0.0 - file-type: ^11.1.0 - filenamify: ^3.0.0 - get-stream: ^4.1.0 - got: ^8.3.1 - make-dir: ^2.1.0 - p-event: ^2.1.0 - pify: ^4.0.1 - checksum: 8a26b21eee8d23352265729dba8eea9f18cba0ebfa3e064041afffeefdfe508fc31e54a08bd0606ff8b0d548466bdb2e2e32b571a8f95227efa5b7c09c261a2f - languageName: node - linkType: hard - "downshift@npm:^6.1.9": version: 6.1.9 resolution: "downshift@npm:6.1.9" @@ -20189,13 +19667,6 @@ __metadata: languageName: node linkType: hard -"emittery@npm:^0.10.2": - version: 0.10.2 - resolution: "emittery@npm:0.10.2" - checksum: ee3e21788b043b90885b18ea756ec3105c1cedc50b29709c92b01e239c7e55345d4bb6d3aef4ddbaf528eef448a40b3bb831bad9ee0fc9c25cbf1367ab1ab5ac - languageName: node - linkType: hard - "emoji-regex@npm:^10.2.1": version: 10.2.1 resolution: "emoji-regex@npm:10.2.1" @@ -21125,6 +20596,83 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.17.5": + version: 0.17.19 + resolution: "esbuild@npm:0.17.19" + dependencies: + "@esbuild/android-arm": 0.17.19 + "@esbuild/android-arm64": 0.17.19 + "@esbuild/android-x64": 0.17.19 + "@esbuild/darwin-arm64": 0.17.19 + "@esbuild/darwin-x64": 0.17.19 + "@esbuild/freebsd-arm64": 0.17.19 + "@esbuild/freebsd-x64": 0.17.19 + "@esbuild/linux-arm": 0.17.19 + "@esbuild/linux-arm64": 0.17.19 + "@esbuild/linux-ia32": 0.17.19 + "@esbuild/linux-loong64": 0.17.19 + "@esbuild/linux-mips64el": 0.17.19 + "@esbuild/linux-ppc64": 0.17.19 + "@esbuild/linux-riscv64": 0.17.19 + "@esbuild/linux-s390x": 0.17.19 + "@esbuild/linux-x64": 0.17.19 + "@esbuild/netbsd-x64": 0.17.19 + "@esbuild/openbsd-x64": 0.17.19 + "@esbuild/sunos-x64": 0.17.19 + "@esbuild/win32-arm64": 0.17.19 + "@esbuild/win32-ia32": 0.17.19 + "@esbuild/win32-x64": 0.17.19 + dependenciesMeta: + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: ac11b1a5a6008e4e37ccffbd6c2c054746fc58d0ed4a2f9ee643bd030cfcea9a33a235087bc777def8420f2eaafb3486e76adb7bdb7241a9143b43a69a10afd8 + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.1.1 resolution: "escalade@npm:3.1.1" @@ -21202,29 +20750,6 @@ __metadata: languageName: node linkType: hard -"eslint-config-next@npm:~13.2.1": - version: 13.2.4 - resolution: "eslint-config-next@npm:13.2.4" - dependencies: - "@next/eslint-plugin-next": 13.2.4 - "@rushstack/eslint-patch": ^1.1.3 - "@typescript-eslint/parser": ^5.42.0 - eslint-import-resolver-node: ^0.3.6 - eslint-import-resolver-typescript: ^3.5.2 - eslint-plugin-import: ^2.26.0 - eslint-plugin-jsx-a11y: ^6.5.1 - eslint-plugin-react: ^7.31.7 - eslint-plugin-react-hooks: ^4.5.0 - peerDependencies: - eslint: ^7.23.0 || ^8.0.0 - typescript: ">=3.3.1" - peerDependenciesMeta: - typescript: - optional: true - checksum: 84c202db75ec17c8a7fcebb3364f5b66fc9e81199442cb85d9531f284be5e102935f17aec5274da9ea39752ae9fc8a6afaf5a1e06fe4c8cb8695cf9bee8ea24f - languageName: node - linkType: hard - "eslint-config-prettier@npm:^8.6.0": version: 8.6.0 resolution: "eslint-config-prettier@npm:8.6.0" @@ -21714,54 +21239,6 @@ __metadata: languageName: node linkType: hard -"estree-util-attach-comments@npm:^2.0.0": - version: 2.0.0 - resolution: "estree-util-attach-comments@npm:2.0.0" - dependencies: - "@types/estree": ^0.0.46 - checksum: 64048f336bd18dea83e9945c3f70efc51e363c5befa6bb32a417e27fc7d7303f72f63f6910a9886c2e9e369168f5c6d023755420bd76edbce07ff0b98fdc998f - languageName: node - linkType: hard - -"estree-util-build-jsx@npm:^2.0.0": - version: 2.0.0 - resolution: "estree-util-build-jsx@npm:2.0.0" - dependencies: - "@types/estree-jsx": ^0.0.1 - estree-util-is-identifier-name: ^2.0.0 - estree-walker: ^3.0.0 - checksum: 840a9d6a8d05db431da315f34c2bde9b7c7e1511f988d71d5fedbe43077ca55b4f35322256e608659adadbe944092fde9610b4c3ee207fbbbb8aa9f247cd981d - languageName: node - linkType: hard - -"estree-util-is-identifier-name@npm:^2.0.0": - version: 2.0.0 - resolution: "estree-util-is-identifier-name@npm:2.0.0" - checksum: 09305036c4e22e388686826c4f90813b25a907b7269557dd934664bac98bf2fa814c40a0e93ce9815fd7d64ff04d2a4d48ed405cb90762025cd5f80dcfd9b58e - languageName: node - linkType: hard - -"estree-util-to-js@npm:^1.1.0": - version: 1.1.0 - resolution: "estree-util-to-js@npm:1.1.0" - dependencies: - "@types/estree-jsx": ^1.0.0 - astring: ^1.8.0 - source-map: ^0.7.0 - checksum: 3ce2ef2fd78497fa7a0e5250be0f217af9060c819f7ed4f4739285e4ade4ed244536cb88e8ba1e38986af98d3a9064165122bb1622f2c6d57fe7b241b884fc47 - languageName: node - linkType: hard - -"estree-util-visit@npm:^1.0.0": - version: 1.1.0 - resolution: "estree-util-visit@npm:1.1.0" - dependencies: - "@types/estree-jsx": ^0.0.1 - "@types/unist": ^2.0.0 - checksum: 4facaa960d405f2f61978794282b3852b1c419bdf110b7f74f2b3f61c5c4ac1c08527ee30c29533b429dd246d7a0730ec398fdd8b34bbc7dd30971225bab3eca - languageName: node - linkType: hard - "estree-walker@npm:^1.0.1": version: 1.0.1 resolution: "estree-walker@npm:1.0.1" @@ -21776,14 +21253,7 @@ __metadata: languageName: node linkType: hard -"estree-walker@npm:^3.0.0": - version: 3.0.1 - resolution: "estree-walker@npm:3.0.1" - checksum: 674096950819041f1ee471e63f7aa987f2ed3a3a441cc41a5176e9ed01ea5cfd6487822c3b9c2cddd0e2c8f9d7ef52d32d06147a19b5a9ca9f8ab0c094bd43b9 - languageName: node - linkType: hard - -"esutils@npm:^2.0.2": +"esutils@npm:^2.0.2, esutils@npm:^2.0.3": version: 2.0.3 resolution: "esutils@npm:2.0.3" checksum: 22b5b08f74737379a840b8ed2036a5fb35826c709ab000683b092d9054e5c2a82c27818f12604bfc2a9a76b90b6834ef081edbc1c7ae30d1627012e067c6ec87 @@ -22165,22 +21635,7 @@ __metadata: languageName: node linkType: hard -"execa@npm:^0.8.0": - version: 0.8.0 - resolution: "execa@npm:0.8.0" - dependencies: - cross-spawn: ^5.0.1 - get-stream: ^3.0.0 - is-stream: ^1.1.0 - npm-run-path: ^2.0.0 - p-finally: ^1.0.0 - signal-exit: ^3.0.0 - strip-eof: ^1.0.0 - checksum: c2a4bf6e051737e46bee61a93ec286cb71a05f16650a1918c8d6262ba9f0bac031472252411baa8c78b7f432f10cb4c601349403774d69be2ebd864e9b1eca60 - languageName: node - linkType: hard - -"execa@npm:^5.0.0, execa@npm:^5.1.1": +"execa@npm:^5.1.1": version: 5.1.1 resolution: "execa@npm:5.1.1" dependencies: @@ -22211,13 +21666,6 @@ __metadata: languageName: node linkType: hard -"exit@npm:^0.1.2": - version: 0.1.2 - resolution: "exit@npm:0.1.2" - checksum: abc407f07a875c3961e4781dfcb743b58d6c93de9ab263f4f8c9d23bb6da5f9b7764fc773f86b43dd88030444d5ab8abcb611cb680fba8ca075362b77114bba3 - languageName: node - linkType: hard - "expand-brackets@npm:^2.1.4": version: 2.1.4 resolution: "expand-brackets@npm:2.1.4" @@ -22233,19 +21681,6 @@ __metadata: languageName: node linkType: hard -"expect@npm:^28.0.0, expect@npm:^28.1.3": - version: 28.1.3 - resolution: "expect@npm:28.1.3" - dependencies: - "@jest/expect-utils": ^28.1.3 - jest-get-type: ^28.0.2 - jest-matcher-utils: ^28.1.3 - jest-message-util: ^28.1.3 - jest-util: ^28.1.3 - checksum: 101e0090de300bcafedb7dbfd19223368a2251ce5fe0105bbb6de5720100b89fb6b64290ebfb42febc048324c76d6a4979cdc4b61eb77747857daf7a5de9b03d - languageName: node - linkType: hard - "express@npm:^4.14.0": version: 4.17.3 resolution: "express@npm:4.17.3" @@ -22323,25 +21758,6 @@ __metadata: languageName: node linkType: hard -"ext-list@npm:^2.0.0": - version: 2.2.2 - resolution: "ext-list@npm:2.2.2" - dependencies: - mime-db: ^1.28.0 - checksum: 9b2426bea312e674eeced62c5f18407ab9a8653bbdfbde36492331c7973dab7fbf9e11d6c38605786168b42da333910314988097ca06eee61f1b9b57efae3f18 - languageName: node - linkType: hard - -"ext-name@npm:^5.0.0": - version: 5.0.0 - resolution: "ext-name@npm:5.0.0" - dependencies: - ext-list: ^2.0.0 - sort-keys-length: ^1.0.0 - checksum: f598269bd5de4295540ea7d6f8f6a01d82a7508f148b7700a05628ef6121648d26e6e5e942049e953b3051863df6b54bd8fe951e7877f185e34ace5d44370b33 - languageName: node - linkType: hard - "ext@npm:^1.1.2": version: 1.6.0 resolution: "ext@npm:1.6.0" @@ -22446,6 +21862,13 @@ __metadata: languageName: node linkType: hard +"fast-diff@npm:^1.2.0": + version: 1.3.0 + resolution: "fast-diff@npm:1.3.0" + checksum: d22d371b994fdc8cce9ff510d7b8dc4da70ac327bcba20df607dd5b9cae9f908f4d1028f5fe467650f058d1e7270235ae0b8230809a262b4df587a3b3aa216c3 + languageName: node + linkType: hard + "fast-equals@npm:^1.6.3": version: 1.6.3 resolution: "fast-equals@npm:1.6.3" @@ -22521,7 +21944,7 @@ __metadata: languageName: node linkType: hard -"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": +"fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: b191531e36c607977e5b1c47811158733c34ccb3bfde92c44798929e9b4154884378536d26ad90dfecd32e1ffc09c545d23535ad91b3161a27ddbb8ebe0cbecb @@ -22631,15 +22054,6 @@ __metadata: languageName: node linkType: hard -"fd-slicer@npm:~1.1.0": - version: 1.1.0 - resolution: "fd-slicer@npm:1.1.0" - dependencies: - pend: ~1.2.0 - checksum: c8585fd5713f4476eb8261150900d2cb7f6ff2d87f8feb306ccc8a1122efd152f1783bdb2b8dc891395744583436bfd8081d8e63ece0ec8687eeefea394d4ff2 - languageName: node - linkType: hard - "fetch-retry@npm:^5.0.2": version: 5.0.3 resolution: "fetch-retry@npm:5.0.3" @@ -22722,41 +22136,6 @@ __metadata: languageName: node linkType: hard -"file-type@npm:^11.1.0": - version: 11.1.0 - resolution: "file-type@npm:11.1.0" - checksum: 66c2086867291fda760a245534bec1fbf12817dc6fd3426c2b41f29a37c71bb61f1091505c98f03a446703321cc1d4a8e873ce631f5763fc53178645d9eb3f85 - languageName: node - linkType: hard - -"file-type@npm:^3.8.0": - version: 3.9.0 - resolution: "file-type@npm:3.9.0" - checksum: 1db70b2485ac77c4edb4b8753c1874ee6194123533f43c2651820f96b518f505fa570b093fedd6672eb105ba9fb89c62f84b6492e46788e39c3447aed37afa2d - languageName: node - linkType: hard - -"file-type@npm:^4.2.0": - version: 4.4.0 - resolution: "file-type@npm:4.4.0" - checksum: f3e0b38bef643a330b3d98e3aa9d6f0f32d2d80cb9341f5612187bd53ac84489a4dc66b354bd0cff6b60bff053c7ef21eb8923d62e9f1196ac627b63bd7875ef - languageName: node - linkType: hard - -"file-type@npm:^5.2.0": - version: 5.2.0 - resolution: "file-type@npm:5.2.0" - checksum: b2b21c7fc3cfb3c6a3a18b0d5d7233b74d8c17d82757655766573951daf42962a5c809e5fc3637675b237c558ebc67e4958fb2cc5a4ad407bc545aaa40001c74 - languageName: node - linkType: hard - -"file-type@npm:^6.1.0": - version: 6.2.0 - resolution: "file-type@npm:6.2.0" - checksum: 749540cefcd4959121eb83e373ed84e49b2e5a510aa5d598b725bd772dd306ae41fd00d3162ae3f6563b4db5cfafbbd0df321de3f20c17e20a8c56431ae55e58 - languageName: node - linkType: hard - "file-type@npm:^9.0.0": version: 9.0.0 resolution: "file-type@npm:9.0.0" @@ -22771,24 +22150,6 @@ __metadata: languageName: node linkType: hard -"filename-reserved-regex@npm:^2.0.0": - version: 2.0.0 - resolution: "filename-reserved-regex@npm:2.0.0" - checksum: 323a0020fd7f243238ffccab9d728cbc5f3a13c84b2c10e01efb09b8324561d7a51776be76f36603c734d4f69145c39a5d12492bf6142a28b50d7f90bd6190bc - languageName: node - linkType: hard - -"filenamify@npm:^3.0.0": - version: 3.0.0 - resolution: "filenamify@npm:3.0.0" - dependencies: - filename-reserved-regex: ^2.0.0 - strip-outer: ^1.0.0 - trim-repeated: ^1.0.0 - checksum: d419eaa1b8c331ab8616e1fffe33e4af135c60b5364320bbe015bc93ded89c6c301363f69593991de18a8f9dd278324c0a0d89fd554c30250306f4c16c956673 - languageName: node - linkType: hard - "fill-range@npm:^4.0.0": version: 4.0.0 resolution: "fill-range@npm:4.0.0" @@ -22963,13 +22324,6 @@ __metadata: languageName: node linkType: hard -"focus-visible@npm:^5.1.0": - version: 5.2.0 - resolution: "focus-visible@npm:5.2.0" - checksum: 876f646ef453680d3d34e9f9b23961527ffd5ccaed8690f423d4fbfa37ff023d98a490972bc1387850e37ec2e44958c81f6096ef95b67462e5c4b5404cf1dbb9 - languageName: node - linkType: hard - "follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.7, follow-redirects@npm:^1.14.8": version: 1.14.9 resolution: "follow-redirects@npm:1.14.9" @@ -23218,7 +22572,7 @@ __metadata: languageName: node linkType: hard -"from2@npm:^2.1.0, from2@npm:^2.1.1": +"from2@npm:^2.1.0": version: 2.3.0 resolution: "from2@npm:2.3.0" dependencies: @@ -23235,13 +22589,6 @@ __metadata: languageName: node linkType: hard -"fs-constants@npm:^1.0.0": - version: 1.0.0 - resolution: "fs-constants@npm:1.0.0" - checksum: 18f5b718371816155849475ac36c7d0b24d39a11d91348cfcb308b4494824413e03572c403c86d3a260e049465518c4f0d5bd00f0371cdfcad6d4f30a85b350d - languageName: node - linkType: hard - "fs-extra@npm:^10.1.0": version: 10.1.0 resolution: "fs-extra@npm:10.1.0" @@ -23368,7 +22715,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:2.3.2, fsevents@npm:^2.1.2, fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": +"fsevents@npm:2.3.2, fsevents@npm:^2.1.2, fsevents@npm:~2.3.2": version: 2.3.2 resolution: "fsevents@npm:2.3.2" dependencies: @@ -23389,7 +22736,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@2.3.2#~builtin, fsevents@patch:fsevents@^2.1.2#~builtin, fsevents@patch:fsevents@^2.3.2#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin": +"fsevents@patch:fsevents@2.3.2#~builtin, fsevents@patch:fsevents@^2.1.2#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin": version: 2.3.2 resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=df0bf1" dependencies: @@ -23536,6 +22883,13 @@ __metadata: languageName: node linkType: hard +"get-func-name@npm:^2.0.0": + version: 2.0.0 + resolution: "get-func-name@npm:2.0.0" + checksum: 8d82e69f3e7fab9e27c547945dfe5cc0c57fc0adf08ce135dddb01081d75684a03e7a0487466f478872b341d52ac763ae49e660d01ab83741f74932085f693c3 + languageName: node + linkType: hard + "get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1": version: 1.1.1 resolution: "get-intrinsic@npm:1.1.1" @@ -23586,24 +22940,14 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:3.0.0, get-stream@npm:^3.0.0": +"get-stream@npm:^3.0.0": version: 3.0.0 resolution: "get-stream@npm:3.0.0" checksum: 36142f46005ed74ce3a45c55545ec4e7da8e243554179e345a786baf144e5c4a35fb7bdc49fadfa9f18bd08000589b6fe364abdadfc4e1eb0e1b9914a6bb9c56 languageName: node linkType: hard -"get-stream@npm:^2.2.0": - version: 2.3.1 - resolution: "get-stream@npm:2.3.1" - dependencies: - object-assign: ^4.0.1 - pinkie-promise: ^2.0.0 - checksum: d82c86556e131ba7bef00233aa0aa7a51230e6deac11a971ce0f47cd43e2a5e968a3e3914cd082f07cd0d69425653b2f96735b0a7d5c5c03fef3ab857a531367 - languageName: node - linkType: hard - -"get-stream@npm:^4.0.0, get-stream@npm:^4.1.0": +"get-stream@npm:^4.0.0": version: 4.1.0 resolution: "get-stream@npm:4.1.0" dependencies: @@ -23687,7 +23031,7 @@ __metadata: languageName: node linkType: hard -"github-slugger@npm:^1.0.0, github-slugger@npm:^1.3.0": +"github-slugger@npm:^1.0.0": version: 1.4.0 resolution: "github-slugger@npm:1.4.0" checksum: 4f52e7a21f5c6a4c5328f01fe4fe13ae8881fea78bfe31f9e72c4038f97e3e70d52fb85aa7633a52c501dc2486874474d9abd22aa61cbe9b113099a495551c6b @@ -24067,38 +23411,6 @@ __metadata: languageName: node linkType: hard -"got@npm:^8.3.1": - version: 8.3.2 - resolution: "got@npm:8.3.2" - dependencies: - "@sindresorhus/is": ^0.7.0 - cacheable-request: ^2.1.1 - decompress-response: ^3.3.0 - duplexer3: ^0.1.4 - get-stream: ^3.0.0 - into-stream: ^3.1.0 - is-retry-allowed: ^1.1.0 - isurl: ^1.0.0-alpha5 - lowercase-keys: ^1.0.0 - mimic-response: ^1.0.0 - p-cancelable: ^0.4.0 - p-timeout: ^2.0.1 - pify: ^3.0.0 - safe-buffer: ^5.1.1 - timed-out: ^4.0.1 - url-parse-lax: ^3.0.0 - url-to-options: ^1.0.1 - checksum: ab05bfcb6de86dc0c3fba8d25cc51cb2b09851ff3f6f899c86cde8c63b30269f8823d69dbbc6d03f7c58bb069f55a3c5f60aba74aad6721938652d8f35fd3165 - languageName: node - linkType: hard - -"graceful-fs@npm:^4.1.10, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": - version: 4.2.9 - resolution: "graceful-fs@npm:4.2.9" - checksum: 68ea4e07ff2c041ada184f9278b830375f8e0b75154e3f080af6b70f66172fabb4108d19b3863a96b53fc068a310b9b6493d86d1291acc5f3861eb4b79d26ad6 - languageName: node - linkType: hard - "graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.2.0": version: 4.2.10 resolution: "graceful-fs@npm:4.2.10" @@ -24106,6 +23418,13 @@ __metadata: languageName: node linkType: hard +"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": + version: 4.2.9 + resolution: "graceful-fs@npm:4.2.9" + checksum: 68ea4e07ff2c041ada184f9278b830375f8e0b75154e3f080af6b70f66172fabb4108d19b3863a96b53fc068a310b9b6493d86d1291acc5f3861eb4b79d26ad6 + languageName: node + linkType: hard + "graceful-fs@npm:^4.1.5": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" @@ -24241,13 +23560,6 @@ __metadata: languageName: node linkType: hard -"has-flag@npm:^2.0.0": - version: 2.0.0 - resolution: "has-flag@npm:2.0.0" - checksum: 7d060d142ef6740c79991cb99afe5962b267e6e95538bf8b607026b9b1e7451288927bc8e7b4a9484a8b99935c0af023070f91ee49faef791ecd401dc58b2e8d - languageName: node - linkType: hard - "has-flag@npm:^3.0.0": version: 3.0.0 resolution: "has-flag@npm:3.0.0" @@ -24494,28 +23806,6 @@ __metadata: languageName: node linkType: hard -"hast-util-to-estree@npm:^2.0.0": - version: 2.0.2 - resolution: "hast-util-to-estree@npm:2.0.2" - dependencies: - "@types/estree-jsx": ^0.0.1 - "@types/hast": ^2.0.0 - "@types/unist": ^2.0.0 - comma-separated-tokens: ^2.0.0 - estree-util-attach-comments: ^2.0.0 - estree-util-is-identifier-name: ^2.0.0 - hast-util-whitespace: ^2.0.0 - mdast-util-mdx-expression: ^1.0.0 - mdast-util-mdxjs-esm: ^1.0.0 - property-information: ^6.0.0 - space-separated-tokens: ^2.0.0 - style-to-object: ^0.3.0 - unist-util-position: ^4.0.0 - zwitch: ^2.0.0 - checksum: 7ac9884c35c0a1dc96c6237f841e0bdd4410746d0a9ab7ddfaaed9cf36adb3186633782b50e45c57bc8825640c5e1db070fd5eeca5061f76fc19bf991df8a520 - languageName: node - linkType: hard - "hast-util-to-html@npm:^8.0.0": version: 8.0.4 resolution: "hast-util-to-html@npm:8.0.4" @@ -24818,13 +24108,6 @@ __metadata: languageName: node linkType: hard -"http-cache-semantics@npm:3.8.1": - version: 3.8.1 - resolution: "http-cache-semantics@npm:3.8.1" - checksum: b1108d37be478fa9b03890d4185217aac2256e9d2247ce6c6bd90bc5432687d68dc7710ba908cea6166fb983a849d902195241626cf175a3c62817a494c0f7f6 - languageName: node - linkType: hard - "http-cache-semantics@npm:^4.0.0": version: 4.1.0 resolution: "http-cache-semantics@npm:4.1.0" @@ -24952,6 +24235,16 @@ __metadata: languageName: node linkType: hard +"https-proxy-agent@npm:^5.0.1": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: 6 + debug: 4 + checksum: 571fccdf38184f05943e12d37d6ce38197becdd69e58d03f43637f7fa1269cf303a7d228aa27e5b27bbd3af8f09fd938e1c91dcfefff2df7ba77c20ed8dfc765 + languageName: node + linkType: hard + "httpsnippet@npm:^2.0.0": version: 2.0.0 resolution: "httpsnippet@npm:2.0.0" @@ -24994,12 +24287,12 @@ __metadata: languageName: node linkType: hard -"husky@npm:^8.0.1": - version: 8.0.1 - resolution: "husky@npm:8.0.1" +"husky@npm:^8.0.0": + version: 8.0.3 + resolution: "husky@npm:8.0.3" bin: husky: lib/bin.js - checksum: 943a73a13d0201318fd30e83d299bb81d866bd245b69e6277804c3b462638dc1921694cb94c2b8c920a4a187060f7d6058d3365152865406352e934c5fff70dc + checksum: 837bc7e4413e58c1f2946d38fb050f5d7324c6f16b0fd66411ffce5703b294bd21429e8ba58711cd331951ee86ed529c5be4f76805959ff668a337dbfa82a1b0 languageName: node linkType: hard @@ -25188,18 +24481,6 @@ __metadata: languageName: node linkType: hard -"import-local@npm:^3.0.2": - version: 3.1.0 - resolution: "import-local@npm:3.1.0" - dependencies: - pkg-dir: ^4.2.0 - resolve-cwd: ^3.0.0 - bin: - import-local-fixture: fixtures/cli.js - checksum: bfcdb63b5e3c0e245e347f3107564035b128a414c4da1172a20dc67db2504e05ede4ac2eee1252359f78b0bfd7b19ef180aec427c2fce6493ae782d73a04cddd - languageName: node - linkType: hard - "imurmurhash@npm:^0.1.4": version: 0.1.4 resolution: "imurmurhash@npm:0.1.4" @@ -25418,13 +24699,6 @@ __metadata: languageName: node linkType: hard -"intersection-observer@npm:^0.12.0": - version: 0.12.0 - resolution: "intersection-observer@npm:0.12.0" - checksum: 96d2f3ea4995b7b429b79646467c975230cfbe3ede7fb08d0c022cfe6ee0c89dfa98aa4377e8b5f7bee880322eb118e6c0275607a53043f9c50921da8363c1e8 - languageName: node - linkType: hard - "intl-messageformat@npm:9.13.0": version: 9.13.0 resolution: "intl-messageformat@npm:9.13.0" @@ -25437,16 +24711,6 @@ __metadata: languageName: node linkType: hard -"into-stream@npm:^3.1.0": - version: 3.1.0 - resolution: "into-stream@npm:3.1.0" - dependencies: - from2: ^2.1.1 - p-is-promise: ^1.1.0 - checksum: e6e1a202227b20c446c251ef95348b3e8503cdc75aa2a09076f8821fc42c1b7fd43fabaeb8ed3cf9eb875942cfa4510b66949c5317997aa640921cc9bbadcd17 - languageName: node - linkType: hard - "invariant@npm:^2.2.2, invariant@npm:^2.2.4": version: 2.2.4 resolution: "invariant@npm:2.2.4" @@ -25502,13 +24766,6 @@ __metadata: languageName: node linkType: hard -"is-alphabetical@npm:^2.0.0": - version: 2.0.1 - resolution: "is-alphabetical@npm:2.0.1" - checksum: 56207db8d9de0850f0cd30f4966bf731eb82cedfe496cbc2e97e7c3bacaf66fc54a972d2d08c0d93bb679cb84976a05d24c5ad63de56fabbfc60aadae312edaa - languageName: node - linkType: hard - "is-alphanumerical@npm:^1.0.0": version: 1.0.4 resolution: "is-alphanumerical@npm:1.0.4" @@ -25519,16 +24776,6 @@ __metadata: languageName: node linkType: hard -"is-alphanumerical@npm:^2.0.0": - version: 2.0.1 - resolution: "is-alphanumerical@npm:2.0.1" - dependencies: - is-alphabetical: ^2.0.0 - is-decimal: ^2.0.0 - checksum: 87acc068008d4c9c4e9f5bd5e251041d42e7a50995c77b1499cf6ed248f971aadeddb11f239cabf09f7975ee58cac7a48ffc170b7890076d8d227b24a68663c9 - languageName: node - linkType: hard - "is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.0": version: 1.1.1 resolution: "is-arguments@npm:1.1.1" @@ -25703,13 +24950,6 @@ __metadata: languageName: node linkType: hard -"is-decimal@npm:^2.0.0": - version: 2.0.1 - resolution: "is-decimal@npm:2.0.1" - checksum: 97132de7acdce77caa7b797632970a2ecd649a88e715db0e4dbc00ab0708b5e7574ba5903962c860cd4894a14fd12b100c0c4ac8aed445cf6f55c6cf747a4158 - languageName: node - linkType: hard - "is-descriptor@npm:^0.1.0": version: 0.1.6 resolution: "is-descriptor@npm:0.1.6" @@ -25809,13 +25049,6 @@ __metadata: languageName: node linkType: hard -"is-generator-fn@npm:^2.0.0": - version: 2.1.0 - resolution: "is-generator-fn@npm:2.1.0" - checksum: a6ad5492cf9d1746f73b6744e0c43c0020510b59d56ddcb78a91cbc173f09b5e6beff53d75c9c5a29feb618bfef2bf458e025ecf3a57ad2268e2fb2569f56215 - languageName: node - linkType: hard - "is-generator-function@npm:^1.0.7": version: 1.0.10 resolution: "is-generator-function@npm:1.0.10" @@ -25857,13 +25090,6 @@ __metadata: languageName: node linkType: hard -"is-hexadecimal@npm:^2.0.0": - version: 2.0.1 - resolution: "is-hexadecimal@npm:2.0.1" - checksum: 66a2ea85994c622858f063f23eda506db29d92b52580709eb6f4c19550552d4dcf3fb81952e52f7cf972097237959e00adc7bb8c9400cd12886e15bf06145321 - languageName: node - linkType: hard - "is-interactive@npm:^1.0.0": version: 1.0.0 resolution: "is-interactive@npm:1.0.0" @@ -25911,13 +25137,6 @@ __metadata: languageName: node linkType: hard -"is-natural-number@npm:^4.0.1": - version: 4.0.1 - resolution: "is-natural-number@npm:4.0.1" - checksum: 3e5e3d52e0dfa4fea923b5d2b8a5cdbd9bf110c4598d30304b98528b02f40c9058a2abf1bae10bcbaf2bac18ace41cff7bc9673aff339f8c8297fae74ae0e75d - languageName: node - linkType: hard - "is-negative-zero@npm:^2.0.2": version: 2.0.2 resolution: "is-negative-zero@npm:2.0.2" @@ -25985,7 +25204,7 @@ __metadata: languageName: node linkType: hard -"is-plain-obj@npm:^1.0.0, is-plain-obj@npm:^1.1.0": +"is-plain-obj@npm:^1.1.0": version: 1.1.0 resolution: "is-plain-obj@npm:1.1.0" checksum: 0ee04807797aad50859652a7467481816cbb57e5cc97d813a7dcd8915da8195dc68c436010bf39d195226cde6a2d352f4b815f16f26b7bf486a5754290629931 @@ -26045,15 +25264,6 @@ __metadata: languageName: node linkType: hard -"is-reference@npm:^3.0.0": - version: 3.0.0 - resolution: "is-reference@npm:3.0.0" - dependencies: - "@types/estree": "*" - checksum: 408bb3442ff5f90a9740bf578e8fa2863f68bc07ee99b92079a358a34af58341dc7014b054e8cc51a3da5d1ab83f635b6ee1ce2982db7899a128d7a05173898f - languageName: node - linkType: hard - "is-regex@npm:^1.1.2, is-regex@npm:^1.1.4": version: 1.1.4 resolution: "is-regex@npm:1.1.4" @@ -26071,7 +25281,7 @@ __metadata: languageName: node linkType: hard -"is-retry-allowed@npm:^1.0.0, is-retry-allowed@npm:^1.1.0": +"is-retry-allowed@npm:^1.0.0": version: 1.2.0 resolution: "is-retry-allowed@npm:1.2.0" checksum: 50d700a89ae31926b1c91b3eb0104dbceeac8790d8b80d02f5c76d9a75c2056f1bb24b5268a8a018dead606bddf116b2262e5ac07401eb8b8783b266ed22558d @@ -26356,19 +25566,6 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-instrument@npm:^5.1.0": - version: 5.2.0 - resolution: "istanbul-lib-instrument@npm:5.2.0" - dependencies: - "@babel/core": ^7.12.3 - "@babel/parser": ^7.14.7 - "@istanbuljs/schema": ^0.1.2 - istanbul-lib-coverage: ^3.2.0 - semver: ^6.3.0 - checksum: 7c242ed782b6bf7b655656576afae8b6bd23dcc020e5fdc1472cca3dfb6ddb196a478385206d0df5219b9babf46ac4f21fea5d8ea9a431848b6cca6007012353 - languageName: node - linkType: hard - "istanbul-lib-report@npm:^3.0.0": version: 3.0.0 resolution: "istanbul-lib-report@npm:3.0.0" @@ -26380,27 +25577,6 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-source-maps@npm:^4.0.0": - version: 4.0.1 - resolution: "istanbul-lib-source-maps@npm:4.0.1" - dependencies: - debug: ^4.1.1 - istanbul-lib-coverage: ^3.0.0 - source-map: ^0.6.1 - checksum: 21ad3df45db4b81852b662b8d4161f6446cd250c1ddc70ef96a585e2e85c26ed7cd9c2a396a71533cfb981d1a645508bc9618cae431e55d01a0628e7dec62ef2 - languageName: node - linkType: hard - -"istanbul-reports@npm:^3.1.3": - version: 3.1.5 - resolution: "istanbul-reports@npm:3.1.5" - dependencies: - html-escaper: ^2.0.0 - istanbul-lib-report: ^3.0.0 - checksum: 7867228f83ed39477b188ea07e7ccb9b4f5320b6f73d1db93a0981b7414fa4ef72d3f80c4692c442f90fc250d9406e71d8d7ab65bb615cb334e6292b73192b89 - languageName: node - linkType: hard - "istanbul-reports@npm:^3.1.4": version: 3.1.4 resolution: "istanbul-reports@npm:3.1.4" @@ -26468,176 +25644,22 @@ __metadata: languageName: node linkType: hard -"jest-changed-files@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-changed-files@npm:28.1.3" - dependencies: - execa: ^5.0.0 - p-limit: ^3.1.0 - checksum: c78af14a68b9b19101623ae7fde15a2488f9b3dbe8cca12a05c4a223bc9bfd3bf41ee06830f20fb560c52434435d6153c9cc6cf450b1f7b03e5e7f96a953a6a6 - languageName: node - linkType: hard - -"jest-circus@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-circus@npm:28.1.3" - dependencies: - "@jest/environment": ^28.1.3 - "@jest/expect": ^28.1.3 - "@jest/test-result": ^28.1.3 - "@jest/types": ^28.1.3 - "@types/node": "*" - chalk: ^4.0.0 - co: ^4.6.0 - dedent: ^0.7.0 - is-generator-fn: ^2.0.0 - jest-each: ^28.1.3 - jest-matcher-utils: ^28.1.3 - jest-message-util: ^28.1.3 - jest-runtime: ^28.1.3 - jest-snapshot: ^28.1.3 - jest-util: ^28.1.3 - p-limit: ^3.1.0 - pretty-format: ^28.1.3 - slash: ^3.0.0 - stack-utils: ^2.0.3 - checksum: b635e60a9c92adaefc3f24def8eba691e7c2fdcf6c9fa640cddf2eb8c8b26ee62eab73ebb88798fd7c52a74c1495a984e39b748429b610426f02e9d3d56e09b2 - languageName: node - linkType: hard - -"jest-cli@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-cli@npm:28.1.3" - dependencies: - "@jest/core": ^28.1.3 - "@jest/test-result": ^28.1.3 - "@jest/types": ^28.1.3 - chalk: ^4.0.0 - exit: ^0.1.2 - graceful-fs: ^4.2.9 - import-local: ^3.0.2 - jest-config: ^28.1.3 - jest-util: ^28.1.3 - jest-validate: ^28.1.3 - prompts: ^2.0.1 - yargs: ^17.3.1 - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - bin: - jest: bin/jest.js - checksum: fb424576bf38346318daddee3fcc597cd78cb8dda1759d09c529d8ba1a748f2765c17b00671072a838826e59465a810ff8a232bc6ba2395c131bf3504425a363 - languageName: node - linkType: hard - -"jest-config@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-config@npm:28.1.3" - dependencies: - "@babel/core": ^7.11.6 - "@jest/test-sequencer": ^28.1.3 - "@jest/types": ^28.1.3 - babel-jest: ^28.1.3 - chalk: ^4.0.0 - ci-info: ^3.2.0 - deepmerge: ^4.2.2 - glob: ^7.1.3 - graceful-fs: ^4.2.9 - jest-circus: ^28.1.3 - jest-environment-node: ^28.1.3 - jest-get-type: ^28.0.2 - jest-regex-util: ^28.0.2 - jest-resolve: ^28.1.3 - jest-runner: ^28.1.3 - jest-util: ^28.1.3 - jest-validate: ^28.1.3 - micromatch: ^4.0.4 - parse-json: ^5.2.0 - pretty-format: ^28.1.3 - slash: ^3.0.0 - strip-json-comments: ^3.1.1 - peerDependencies: - "@types/node": "*" - ts-node: ">=9.0.0" - peerDependenciesMeta: - "@types/node": - optional: true - ts-node: - optional: true - checksum: ddabffd3a3a8cb6c2f58f06cdf3535157dbf8c70bcde3e5c3de7bee6a8d617840ffc8cffb0083e38c6814f2a08c225ca19f58898efaf4f351af94679f22ce6bc - languageName: node - linkType: hard - -"jest-diff@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-diff@npm:28.1.3" +"jest-diff@npm:^29.5.0": + version: 29.5.0 + resolution: "jest-diff@npm:29.5.0" dependencies: chalk: ^4.0.0 - diff-sequences: ^28.1.1 - jest-get-type: ^28.0.2 - pretty-format: ^28.1.3 - checksum: fa8583e0ccbe775714ce850b009be1b0f6b17a4b6759f33ff47adef27942ebc610dbbcc8a5f7cfb7f12b3b3b05afc9fb41d5f766674616025032ff1e4f9866e0 + diff-sequences: ^29.4.3 + jest-get-type: ^29.4.3 + pretty-format: ^29.5.0 + checksum: dfd0f4a299b5d127779c76b40106c37854c89c3e0785098c717d52822d6620d227f6234c3a9291df204d619e799e3654159213bf93220f79c8e92a55475a3d39 languageName: node linkType: hard -"jest-docblock@npm:^28.1.1": - version: 28.1.1 - resolution: "jest-docblock@npm:28.1.1" - dependencies: - detect-newline: ^3.0.0 - checksum: 22fca68d988ecb2933bc65f448facdca85fc71b4bd0a188ea09a5ae1b0cc3a049a2a6ec7e7eaa2542c1d5cb5e5145e420a3df4fa280f5070f486c44da1d36151 - languageName: node - linkType: hard - -"jest-each@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-each@npm:28.1.3" - dependencies: - "@jest/types": ^28.1.3 - chalk: ^4.0.0 - jest-get-type: ^28.0.2 - jest-util: ^28.1.3 - pretty-format: ^28.1.3 - checksum: 5c5b8ccb1484e58b027bea682cfa020a45e5bf5379cc7c23bdec972576c1dc3c3bf03df2b78416cefc1a58859dd33b7cf5fff54c370bc3c0f14a3e509eb87282 - languageName: node - linkType: hard - -"jest-environment-jsdom@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-environment-jsdom@npm:28.1.3" - dependencies: - "@jest/environment": ^28.1.3 - "@jest/fake-timers": ^28.1.3 - "@jest/types": ^28.1.3 - "@types/jsdom": ^16.2.4 - "@types/node": "*" - jest-mock: ^28.1.3 - jest-util: ^28.1.3 - jsdom: ^19.0.0 - checksum: 32758f9b9a1fd04ec3ebaaa608d740a36b960d37d00bd3d4d83fdc4b527afc474c14f04fa860817e1fa22923e2dc3cd2b497db41af6a5d73e91327951612025e - languageName: node - linkType: hard - -"jest-environment-node@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-environment-node@npm:28.1.3" - dependencies: - "@jest/environment": ^28.1.3 - "@jest/fake-timers": ^28.1.3 - "@jest/types": ^28.1.3 - "@types/node": "*" - jest-mock: ^28.1.3 - jest-util: ^28.1.3 - checksum: 1048fe306a6a8b0880a4c66278ebb57479f29c12cff89aab3aa79ab77a8859cf17ab8aa9919fd21c329a7db90e35581b43664e694ad453d5b04e00f3c6420469 - languageName: node - linkType: hard - -"jest-get-type@npm:^28.0.2": - version: 28.0.2 - resolution: "jest-get-type@npm:28.0.2" - checksum: 5281d7c89bc8156605f6d15784f45074f4548501195c26e9b188742768f72d40948252d13230ea905b5349038865a1a8eeff0e614cc530ff289dfc41fe843abd +"jest-get-type@npm:^29.4.3": + version: 29.4.3 + resolution: "jest-get-type@npm:29.4.3" + checksum: 6ac7f2dde1c65e292e4355b6c63b3a4897d7e92cb4c8afcf6d397f2682f8080e094c8b0b68205a74d269882ec06bf696a9de6cd3e1b7333531e5ed7b112605ce languageName: node linkType: hard @@ -26666,80 +25688,6 @@ __metadata: languageName: node linkType: hard -"jest-haste-map@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-haste-map@npm:28.1.3" - dependencies: - "@jest/types": ^28.1.3 - "@types/graceful-fs": ^4.1.3 - "@types/node": "*" - anymatch: ^3.0.3 - fb-watchman: ^2.0.0 - fsevents: ^2.3.2 - graceful-fs: ^4.2.9 - jest-regex-util: ^28.0.2 - jest-util: ^28.1.3 - jest-worker: ^28.1.3 - micromatch: ^4.0.4 - walker: ^1.0.8 - dependenciesMeta: - fsevents: - optional: true - checksum: d05fdc108645fc2b39fcd4001952cc7a8cb550e93494e98c1e9ab1fc542686f6ac67177c132e564cf94fe8f81503f3f8db8b825b9b713dc8c5748aec63ba4688 - languageName: node - linkType: hard - -"jest-leak-detector@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-leak-detector@npm:28.1.3" - dependencies: - jest-get-type: ^28.0.2 - pretty-format: ^28.1.3 - checksum: 2e976a4880cf9af11f53a19f6a3820e0f90b635a900737a5427fc42e337d5628ba446dcd7c020ecea3806cf92bc0bbf6982ed62a9cd84e5a13d8751aa30fbbb7 - languageName: node - linkType: hard - -"jest-matcher-utils@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-matcher-utils@npm:28.1.3" - dependencies: - chalk: ^4.0.0 - jest-diff: ^28.1.3 - jest-get-type: ^28.0.2 - pretty-format: ^28.1.3 - checksum: 6b34f0cf66f6781e92e3bec97bf27796bd2ba31121e5c5997218d9adba6deea38a30df5203937d6785b68023ed95cbad73663cc9aad6fb0cb59aeb5813a58daf - languageName: node - linkType: hard - -"jest-message-util@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-message-util@npm:28.1.3" - dependencies: - "@babel/code-frame": ^7.12.13 - "@jest/types": ^28.1.3 - "@types/stack-utils": ^2.0.0 - chalk: ^4.0.0 - graceful-fs: ^4.2.9 - micromatch: ^4.0.4 - pretty-format: ^28.1.3 - slash: ^3.0.0 - stack-utils: ^2.0.3 - checksum: 1f266854166dcc6900d75a88b54a25225a2f3710d463063ff1c99021569045c35c7d58557b25447a17eb3a65ce763b2f9b25550248b468a9d4657db365f39e96 - languageName: node - linkType: hard - -"jest-mock-extended@npm:^2.0.7": - version: 2.0.7 - resolution: "jest-mock-extended@npm:2.0.7" - dependencies: - ts-essentials: ^7.0.3 - peerDependencies: - jest: ^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0 || ^28.0.0 - typescript: ^3.0.0 || ^4.0.0 - checksum: e9928f1c1e4c9d1bb9746e0f0791e7b0c5b91474d10ed07c6f59e80294830b46b9f6f99b418e4ebcb3641db7957f2014317ab4267e56b676789f7013db72aa8e - languageName: node - linkType: hard - "jest-mock@npm:^27.0.6": version: 27.5.1 resolution: "jest-mock@npm:27.5.1" @@ -26750,28 +25698,6 @@ __metadata: languageName: node linkType: hard -"jest-mock@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-mock@npm:28.1.3" - dependencies: - "@jest/types": ^28.1.3 - "@types/node": "*" - checksum: a573bf8e5f12f4c29c661266c31b5c6b69a28d3195b83049983bce025b2b1a0152351567e89e63b102ef817034c2a3aa97eda4e776f3bae2aee54c5765573aa7 - languageName: node - linkType: hard - -"jest-pnp-resolver@npm:^1.2.2": - version: 1.2.2 - resolution: "jest-pnp-resolver@npm:1.2.2" - peerDependencies: - jest-resolve: "*" - peerDependenciesMeta: - jest-resolve: - optional: true - checksum: bd85dcc0e76e0eb0c3d56382ec140f08d25ff4068cda9d0e360bb78fb176cb726d0beab82dc0e8694cafd09f55fee7622b8bcb240afa5fad301f4ed3eebb4f47 - languageName: node - linkType: hard - "jest-regex-util@npm:^26.0.0": version: 26.0.0 resolution: "jest-regex-util@npm:26.0.0" @@ -26779,99 +25705,6 @@ __metadata: languageName: node linkType: hard -"jest-regex-util@npm:^28.0.0, jest-regex-util@npm:^28.0.2": - version: 28.0.2 - resolution: "jest-regex-util@npm:28.0.2" - checksum: 0ea8c5c82ec88bc85e273c0ec82e0c0f35f7a1e2d055070e50f0cc2a2177f848eec55f73e37ae0d045c3db5014c42b2f90ac62c1ab3fdb354d2abd66a9e08add - languageName: node - linkType: hard - -"jest-resolve-dependencies@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-resolve-dependencies@npm:28.1.3" - dependencies: - jest-regex-util: ^28.0.2 - jest-snapshot: ^28.1.3 - checksum: 4eea9ec33aefc1c71dc5956391efbcc7be76bda986b366ab3931d99c5f7ed01c9ebd7520e405ea2c76e1bb2c7ce504be6eca2b9831df16564d1e625500f3bfe7 - languageName: node - linkType: hard - -"jest-resolve@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-resolve@npm:28.1.3" - dependencies: - chalk: ^4.0.0 - graceful-fs: ^4.2.9 - jest-haste-map: ^28.1.3 - jest-pnp-resolver: ^1.2.2 - jest-util: ^28.1.3 - jest-validate: ^28.1.3 - resolve: ^1.20.0 - resolve.exports: ^1.1.0 - slash: ^3.0.0 - checksum: df61a490c93f4f4cf52135e43d6a4fcacb07b0b7d4acc6319e9289529c1d14f2d8e1638e095dbf96f156834802755e38db68caca69dba21a3261ee711d4426b6 - languageName: node - linkType: hard - -"jest-runner@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-runner@npm:28.1.3" - dependencies: - "@jest/console": ^28.1.3 - "@jest/environment": ^28.1.3 - "@jest/test-result": ^28.1.3 - "@jest/transform": ^28.1.3 - "@jest/types": ^28.1.3 - "@types/node": "*" - chalk: ^4.0.0 - emittery: ^0.10.2 - graceful-fs: ^4.2.9 - jest-docblock: ^28.1.1 - jest-environment-node: ^28.1.3 - jest-haste-map: ^28.1.3 - jest-leak-detector: ^28.1.3 - jest-message-util: ^28.1.3 - jest-resolve: ^28.1.3 - jest-runtime: ^28.1.3 - jest-util: ^28.1.3 - jest-watcher: ^28.1.3 - jest-worker: ^28.1.3 - p-limit: ^3.1.0 - source-map-support: 0.5.13 - checksum: 32405cd970fa6b11e039192dae699fd1bcc6f61f67d50605af81d193f24dd4373b25f5fcc1c571a028ec1b02174e8a4b6d0d608772063fb06f08a5105693533b - languageName: node - linkType: hard - -"jest-runtime@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-runtime@npm:28.1.3" - dependencies: - "@jest/environment": ^28.1.3 - "@jest/fake-timers": ^28.1.3 - "@jest/globals": ^28.1.3 - "@jest/source-map": ^28.1.2 - "@jest/test-result": ^28.1.3 - "@jest/transform": ^28.1.3 - "@jest/types": ^28.1.3 - chalk: ^4.0.0 - cjs-module-lexer: ^1.0.0 - collect-v8-coverage: ^1.0.0 - execa: ^5.0.0 - glob: ^7.1.3 - graceful-fs: ^4.2.9 - jest-haste-map: ^28.1.3 - jest-message-util: ^28.1.3 - jest-mock: ^28.1.3 - jest-regex-util: ^28.0.2 - jest-resolve: ^28.1.3 - jest-snapshot: ^28.1.3 - jest-util: ^28.1.3 - slash: ^3.0.0 - strip-bom: ^4.0.0 - checksum: b17c40af858e74dafa4f515ef3711c1e9ef3d4ad7d74534ee0745422534bc04fd166d4eceb62a3aa7dc951505d6f6d2a81d16e90bebb032be409ec0500974a36 - languageName: node - linkType: hard - "jest-serializer@npm:^26.6.2": version: 26.6.2 resolution: "jest-serializer@npm:26.6.2" @@ -26882,37 +25715,6 @@ __metadata: languageName: node linkType: hard -"jest-snapshot@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-snapshot@npm:28.1.3" - dependencies: - "@babel/core": ^7.11.6 - "@babel/generator": ^7.7.2 - "@babel/plugin-syntax-typescript": ^7.7.2 - "@babel/traverse": ^7.7.2 - "@babel/types": ^7.3.3 - "@jest/expect-utils": ^28.1.3 - "@jest/transform": ^28.1.3 - "@jest/types": ^28.1.3 - "@types/babel__traverse": ^7.0.6 - "@types/prettier": ^2.1.5 - babel-preset-current-node-syntax: ^1.0.0 - chalk: ^4.0.0 - expect: ^28.1.3 - graceful-fs: ^4.2.9 - jest-diff: ^28.1.3 - jest-get-type: ^28.0.2 - jest-haste-map: ^28.1.3 - jest-matcher-utils: ^28.1.3 - jest-message-util: ^28.1.3 - jest-util: ^28.1.3 - natural-compare: ^1.4.0 - pretty-format: ^28.1.3 - semver: ^7.3.5 - checksum: 2a46a5493f1fb50b0a236a21f25045e7f46a244f9f3ae37ef4fbcd40249d0d68bb20c950ce77439e4e2cac985b05c3061c90b34739bf6069913a1199c8c716e1 - languageName: node - linkType: hard - "jest-util@npm:^26.6.2": version: 26.6.2 resolution: "jest-util@npm:26.6.2" @@ -26927,78 +25729,6 @@ __metadata: languageName: node linkType: hard -"jest-util@npm:^28.0.0, jest-util@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-util@npm:28.1.3" - dependencies: - "@jest/types": ^28.1.3 - "@types/node": "*" - chalk: ^4.0.0 - ci-info: ^3.2.0 - graceful-fs: ^4.2.9 - picomatch: ^2.2.3 - checksum: fd6459742c941f070223f25e38a2ac0719aad92561591e9fb2a50d602a5d19d754750b79b4074327a42b00055662b95da3b006542ceb8b54309da44d4a62e721 - languageName: node - linkType: hard - -"jest-validate@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-validate@npm:28.1.3" - dependencies: - "@jest/types": ^28.1.3 - camelcase: ^6.2.0 - chalk: ^4.0.0 - jest-get-type: ^28.0.2 - leven: ^3.1.0 - pretty-format: ^28.1.3 - checksum: 95e0513b3803c3372a145cda86edbdb33d9dfeaa18818176f2d581e821548ceac9a179f065b6d4671a941de211354efd67f1fff8789a4fb89962565c85f646db - languageName: node - linkType: hard - -"jest-watch-select-projects@npm:^2.0.0": - version: 2.0.0 - resolution: "jest-watch-select-projects@npm:2.0.0" - dependencies: - ansi-escapes: ^4.3.0 - chalk: ^3.0.0 - prompts: ^2.2.1 - checksum: 67b7a08d8e7b5ecfba67d86f02be29e4917c4416c9f169246f10cc40792b1c5fa38fcfeb25195643db080ace1f4fdf2f827bd244e7cdff7512d1ddfbc94270f0 - languageName: node - linkType: hard - -"jest-watch-typeahead@npm:^2.0.0": - version: 2.0.0 - resolution: "jest-watch-typeahead@npm:2.0.0" - dependencies: - ansi-escapes: ^5.0.0 - chalk: ^4.0.0 - jest-regex-util: ^28.0.0 - jest-watcher: ^28.0.0 - slash: ^4.0.0 - string-length: ^5.0.1 - strip-ansi: ^7.0.1 - peerDependencies: - jest: ^27.0.0 || ^28.0.0 - checksum: c25fa793e56e0a316642a714c960b29359ddc1ce3f621ffb8a42a3ccfffb67c57223597e2fb387d5f49e2b93a22fc1f4b54e900e4fa80eca6602f70a30c22ffb - languageName: node - linkType: hard - -"jest-watcher@npm:^28.0.0, jest-watcher@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-watcher@npm:28.1.3" - dependencies: - "@jest/test-result": ^28.1.3 - "@jest/types": ^28.1.3 - "@types/node": "*" - ansi-escapes: ^4.2.1 - chalk: ^4.0.0 - emittery: ^0.10.2 - jest-util: ^28.1.3 - string-length: ^4.0.1 - checksum: 8f6d674a4865e7df251f71544f1b51f06fd36b5a3a61f2ac81aeb81fa2a196be354fba51d0f97911c88f67cd254583b3a22ee124bf2c5b6ee2fadec27356c207 - languageName: node - linkType: hard - "jest-worker@npm:^26.5.0, jest-worker@npm:^26.6.2": version: 26.6.2 resolution: "jest-worker@npm:26.6.2" @@ -27021,36 +25751,6 @@ __metadata: languageName: node linkType: hard -"jest-worker@npm:^28.1.3": - version: 28.1.3 - resolution: "jest-worker@npm:28.1.3" - dependencies: - "@types/node": "*" - merge-stream: ^2.0.0 - supports-color: ^8.0.0 - checksum: e921c9a1b8f0909da9ea07dbf3592f95b653aef3a8bb0cbcd20fc7f9a795a1304adecac31eecb308992c167e8d7e75c522061fec38a5928ace0f9571c90169ca - languageName: node - linkType: hard - -"jest@npm:^28.1.0": - version: 28.1.3 - resolution: "jest@npm:28.1.3" - dependencies: - "@jest/core": ^28.1.3 - "@jest/types": ^28.1.3 - import-local: ^3.0.2 - jest-cli: ^28.1.3 - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - bin: - jest: bin/jest.js - checksum: b9dcb542eb7c16261c281cdc2bf37155dbb3f1205bae0b567f05051db362c85ddd4b765f126591efb88f6d298eb10336d0aa6c7d5373b4d53f918137a9a70182 - languageName: node - linkType: hard - "jimp@npm:^0.16.1": version: 0.16.1 resolution: "jimp@npm:0.16.1" @@ -27221,43 +25921,39 @@ __metadata: languageName: node linkType: hard -"jsdom@npm:^19.0.0": - version: 19.0.0 - resolution: "jsdom@npm:19.0.0" +"jsdom@npm:^22.0.0": + version: 22.0.0 + resolution: "jsdom@npm:22.0.0" dependencies: - abab: ^2.0.5 - acorn: ^8.5.0 - acorn-globals: ^6.0.0 - cssom: ^0.5.0 - cssstyle: ^2.3.0 - data-urls: ^3.0.1 - decimal.js: ^10.3.1 + abab: ^2.0.6 + cssstyle: ^3.0.0 + data-urls: ^4.0.0 + decimal.js: ^10.4.3 domexception: ^4.0.0 - escodegen: ^2.0.0 form-data: ^4.0.0 html-encoding-sniffer: ^3.0.0 http-proxy-agent: ^5.0.0 - https-proxy-agent: ^5.0.0 + https-proxy-agent: ^5.0.1 is-potential-custom-element-name: ^1.0.1 - nwsapi: ^2.2.0 - parse5: 6.0.1 - saxes: ^5.0.1 + nwsapi: ^2.2.4 + parse5: ^7.1.2 + rrweb-cssom: ^0.6.0 + saxes: ^6.0.0 symbol-tree: ^3.2.4 - tough-cookie: ^4.0.0 - w3c-hr-time: ^1.0.2 - w3c-xmlserializer: ^3.0.0 + tough-cookie: ^4.1.2 + w3c-xmlserializer: ^4.0.0 webidl-conversions: ^7.0.0 whatwg-encoding: ^2.0.0 whatwg-mimetype: ^3.0.0 - whatwg-url: ^10.0.0 - ws: ^8.2.3 + whatwg-url: ^12.0.1 + ws: ^8.13.0 xml-name-validator: ^4.0.0 peerDependencies: canvas: ^2.5.0 peerDependenciesMeta: canvas: optional: true - checksum: 94b693bf4a394097dd96705550bb7b6cd3c8db3c5414e6e9c92a0995ed8b61067597da2f37fca6bed4b5a2f1ef33960ee759522156dccd0b306311988ea87cfb + checksum: 5d554ccb1637035d1c3baa832ce6f6c66549b6fa43cc7c39295250092e74a10c2e6c674cb40a208ae850d80436dd109862f95d293528345ab5d6cc5213847f8e languageName: node linkType: hard @@ -27313,13 +26009,6 @@ __metadata: languageName: node linkType: hard -"json-buffer@npm:3.0.0": - version: 3.0.0 - resolution: "json-buffer@npm:3.0.0" - checksum: 0cecacb8025370686a916069a2ff81f7d55167421b6aa7270ee74e244012650dd6bce22b0852202ea7ff8624fce50ff0ec1bdf95914ccb4553426e290d5a63fa - languageName: node - linkType: hard - "json-buffer@npm:3.0.1, json-buffer@npm:~3.0.1": version: 3.0.1 resolution: "json-buffer@npm:3.0.1" @@ -27457,6 +26146,13 @@ __metadata: languageName: node linkType: hard +"jsonc-parser@npm:^3.2.0": + version: 3.2.0 + resolution: "jsonc-parser@npm:3.2.0" + checksum: 946dd9a5f326b745aa326d48a7257e3f4a4b62c5e98ec8e49fa2bdd8d96cef7e6febf1399f5c7016114fd1f68a1c62c6138826d5d90bc650448e3cf0951c53c7 + languageName: node + linkType: hard + "jsonfile@npm:^4.0.0": version: 4.0.0 resolution: "jsonfile@npm:4.0.0" @@ -27665,15 +26361,6 @@ __metadata: languageName: node linkType: hard -"keyv@npm:3.0.0": - version: 3.0.0 - resolution: "keyv@npm:3.0.0" - dependencies: - json-buffer: 3.0.0 - checksum: 5182775e546cdbb88dc583825bc0e990164709f31904a219e3321b3bf564a301ac4e5255ba95f7fba466548eba793b356a04a0242110173b199a37192b3b565f - languageName: node - linkType: hard - "keyv@npm:^4.0.0": version: 4.3.3 resolution: "keyv@npm:4.3.3" @@ -27890,13 +26577,6 @@ __metadata: languageName: node linkType: hard -"leven@npm:^3.1.0": - version: 3.1.0 - resolution: "leven@npm:3.1.0" - checksum: 638401d534585261b6003db9d99afd244dfe82d75ddb6db5c0df412842d5ab30b2ef18de471aaec70fe69a46f17b4ae3c7f01d8a4e6580ef7adb9f4273ad1e55 - languageName: node - linkType: hard - "levn@npm:^0.4.1": version: 0.4.1 resolution: "levn@npm:0.4.1" @@ -28158,6 +26838,13 @@ __metadata: languageName: node linkType: hard +"local-pkg@npm:^0.4.3": + version: 0.4.3 + resolution: "local-pkg@npm:0.4.3" + checksum: 7825aca531dd6afa3a3712a0208697aa4a5cd009065f32e3fb732aafcc42ed11f277b5ac67229222e96f4def55197171cdf3d5522d0381b489d2e5547b407d55 + languageName: node + linkType: hard + "localforage@npm:^1.8.1": version: 1.10.0 resolution: "localforage@npm:1.10.0" @@ -28289,13 +26976,6 @@ __metadata: languageName: node linkType: hard -"lodash.memoize@npm:4.x": - version: 4.1.2 - resolution: "lodash.memoize@npm:4.1.2" - checksum: 9ff3942feeccffa4f1fafa88d32f0d24fdc62fd15ded5a74a5f950ff5f0c6f61916157246744c620173dddf38d37095a92327d5fd3861e2063e736a5c207d089 - languageName: node - linkType: hard - "lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" @@ -28374,13 +27054,6 @@ __metadata: languageName: node linkType: hard -"longest-streak@npm:^2.0.0": - version: 2.0.4 - resolution: "longest-streak@npm:2.0.4" - checksum: 28b8234a14963002c5c71035dee13a0a11e9e9d18ffa320fdc8796ed7437399204495702ed69cd2a7087b0af041a2a8b562829b7c1e2042e73a3374d1ecf6580 - languageName: node - linkType: hard - "longest-streak@npm:^3.0.0": version: 3.0.1 resolution: "longest-streak@npm:3.0.1" @@ -28428,6 +27101,15 @@ __metadata: languageName: node linkType: hard +"loupe@npm:^2.3.1, loupe@npm:^2.3.6": + version: 2.3.6 + resolution: "loupe@npm:2.3.6" + dependencies: + get-func-name: ^2.0.0 + checksum: cc83f1b124a1df7384601d72d8d1f5fe95fd7a8185469fec48bb2e4027e45243949e7a013e8d91051a138451ff0552310c32aa9786e60b6a30d1e801bdc2163f + languageName: node + linkType: hard + "lower-case-first@npm:^1.0.0": version: 1.0.2 resolution: "lower-case-first@npm:1.0.2" @@ -28453,13 +27135,6 @@ __metadata: languageName: node linkType: hard -"lowercase-keys@npm:1.0.0": - version: 1.0.0 - resolution: "lowercase-keys@npm:1.0.0" - checksum: 2370110c149967038fd5eb278f9b2d889eb427487c0e7fb417ab2ef4d93bacba1c8f226cf2ef1c2848b3191f37d84167d4342fbee72a1a122086680adecf362b - languageName: node - linkType: hard - "lowercase-keys@npm:^1.0.0": version: 1.0.1 resolution: "lowercase-keys@npm:1.0.1" @@ -28644,12 +27319,12 @@ __metadata: languageName: node linkType: hard -"make-dir@npm:^1.0.0": - version: 1.3.0 - resolution: "make-dir@npm:1.3.0" +"magic-string@npm:^0.30.0": + version: 0.30.0 + resolution: "magic-string@npm:0.30.0" dependencies: - pify: ^3.0.0 - checksum: c564f6e7bb5ace1c02ad56b3a5f5e07d074af0c0b693c55c7b2c2b148882827c8c2afc7b57e43338a9f90c125b58d604e8cf3e6990a48bf949dfea8c79668c0b + "@jridgewell/sourcemap-codec": ^1.4.13 + checksum: 7bdf22e27334d8a393858a16f5f840af63a7c05848c000fd714da5aa5eefa09a1bc01d8469362f25cc5c4a14ec01b46557b7fff8751365522acddf21e57c488d languageName: node linkType: hard @@ -28672,7 +27347,7 @@ __metadata: languageName: node linkType: hard -"make-error@npm:1.x, make-error@npm:^1.1.1": +"make-error@npm:^1.1.1": version: 1.3.6 resolution: "make-error@npm:1.3.6" checksum: b86e5e0e25f7f777b77fabd8e2cbf15737972869d852a22b7e73c17623928fccb826d8e46b9951501d3f20e51ad74ba8c59ed584f610526a48f8ccf88aaec402 @@ -28779,13 +27454,6 @@ __metadata: languageName: node linkType: hard -"markdown-extensions@npm:^1.0.0": - version: 1.1.1 - resolution: "markdown-extensions@npm:1.1.1" - checksum: 8a6dd128be1c524049ea6a41a9193715c2835d3d706af4b8b714ff2043a82786dbcd4a8f1fa9ddd28facbc444426c97515aef2d1f3dd11d5e2d63749ba577b1e - languageName: node - linkType: hard - "markdown-it@npm:^13.0.1": version: 13.0.1 resolution: "markdown-it@npm:13.0.1" @@ -28801,15 +27469,6 @@ __metadata: languageName: node linkType: hard -"markdown-table@npm:^2.0.0": - version: 2.0.0 - resolution: "markdown-table@npm:2.0.0" - dependencies: - repeat-string: ^1.0.0 - checksum: 9bb634a9300016cbb41216c1eab44c74b6b7083ac07872e296f900a29449cf0e260ece03fa10c3e9784ab94c61664d1d147da0315f95e1336e2bdcc025615c90 - languageName: node - linkType: hard - "marked@npm:4.2.12": version: 4.2.12 resolution: "marked@npm:4.2.12" @@ -28819,13 +27478,12 @@ __metadata: languageName: node linkType: hard -"match-sorter@npm:^4.2.0": - version: 4.2.1 - resolution: "match-sorter@npm:4.2.1" +"md5-hex@npm:^3.0.1": + version: 3.0.1 + resolution: "md5-hex@npm:3.0.1" dependencies: - "@babel/runtime": ^7.10.5 - remove-accents: 0.4.2 - checksum: 7f3cd8f84cdb4567b7a81f66a095e418044f318f41b6c8a1640730c99e370af9e5054f5a1ed2dfa1287a23101d75a105da63e4d95e8ac2f7061a8f8b32367c7d + blueimp-md5: ^2.10.0 + checksum: 6799a19e8bdd3e0c2861b94c1d4d858a89220488d7885c1fa236797e367d0c2e5f2b789e05309307083503f85be3603a9686a5915568a473137d6b4117419cc2 languageName: node linkType: hard @@ -28880,30 +27538,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-find-and-replace@npm:^1.1.0": - version: 1.1.1 - resolution: "mdast-util-find-and-replace@npm:1.1.1" - dependencies: - escape-string-regexp: ^4.0.0 - unist-util-is: ^4.0.0 - unist-util-visit-parents: ^3.0.0 - checksum: e4c9e50d9bce5ae4c728a925bd60080b94d16aaa312c27e2b70b16ddc29a5d0a0844d6e18efaef08aeb22c68303ec528f20183d1b0420504a0c2c1710cebd76f - languageName: node - linkType: hard - -"mdast-util-from-markdown@npm:^0.8.0": - version: 0.8.5 - resolution: "mdast-util-from-markdown@npm:0.8.5" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-string: ^2.0.0 - micromark: ~2.11.0 - parse-entities: ^2.0.0 - unist-util-stringify-position: ^2.0.0 - checksum: 5a9d0d753a42db763761e874c22365d0c7c9934a5a18b5ff76a0643610108a208a041ffdb2f3d3dd1863d3d915225a4020a0aade282af0facfd0df110601eee6 - languageName: node - linkType: hard - "mdast-util-from-markdown@npm:^1.0.0": version: 1.2.0 resolution: "mdast-util-from-markdown@npm:1.2.0" @@ -28924,113 +27558,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-gfm-autolink-literal@npm:^0.1.0": - version: 0.1.3 - resolution: "mdast-util-gfm-autolink-literal@npm:0.1.3" - dependencies: - ccount: ^1.0.0 - mdast-util-find-and-replace: ^1.1.0 - micromark: ^2.11.3 - checksum: 9f7b888678631fd8c0a522b0689a750aead2b05d57361dbdf02c10381557f1ce874f746226141f3ace1e0e7952495e8d5ce8f9af423a7a66bb300d4635a918eb - languageName: node - linkType: hard - -"mdast-util-gfm-strikethrough@npm:^0.2.0": - version: 0.2.3 - resolution: "mdast-util-gfm-strikethrough@npm:0.2.3" - dependencies: - mdast-util-to-markdown: ^0.6.0 - checksum: 51aa11ca8f1a5745f1eb9ccddb0eca797b3ede6f0c7bf355d594ad57c02c98d95260f00b1c4b07504018e0b22708531eabb76037841f09ce8465444706a06522 - languageName: node - linkType: hard - -"mdast-util-gfm-table@npm:^0.1.0": - version: 0.1.6 - resolution: "mdast-util-gfm-table@npm:0.1.6" - dependencies: - markdown-table: ^2.0.0 - mdast-util-to-markdown: ~0.6.0 - checksum: eeb43faf833753315b4ccf8d7bc8a6845b31562b2d2dd12a92aa40f9cee1b1954643c7515399a98f9b2e143c95cf6b5c0aac5941a4f609d6a57335587cee99ac - languageName: node - linkType: hard - -"mdast-util-gfm-task-list-item@npm:^0.1.0": - version: 0.1.6 - resolution: "mdast-util-gfm-task-list-item@npm:0.1.6" - dependencies: - mdast-util-to-markdown: ~0.6.0 - checksum: c10480c0ae86547980b38b49fba2ecd36a50bf1f3478d3f12810a0d8e8f821585c2bd7d805dd735518e84493b5eef314afdb8d59807021e2d9aa22d077eb7588 - languageName: node - linkType: hard - -"mdast-util-gfm@npm:^0.1.0": - version: 0.1.2 - resolution: "mdast-util-gfm@npm:0.1.2" - dependencies: - mdast-util-gfm-autolink-literal: ^0.1.0 - mdast-util-gfm-strikethrough: ^0.2.0 - mdast-util-gfm-table: ^0.1.0 - mdast-util-gfm-task-list-item: ^0.1.0 - mdast-util-to-markdown: ^0.6.1 - checksum: 368ed535b2c2e0f33d0225a9e9c985468bf4825a06896815369aea585f6defaccb555ac40ba911e02c8e8c47e79f7efb4348de532de50bca2638a1e568f2d3c9 - languageName: node - linkType: hard - -"mdast-util-mdx-expression@npm:^1.0.0": - version: 1.2.0 - resolution: "mdast-util-mdx-expression@npm:1.2.0" - dependencies: - "@types/estree-jsx": ^0.0.1 - "@types/hast": ^2.0.0 - "@types/mdast": ^3.0.0 - mdast-util-from-markdown: ^1.0.0 - mdast-util-to-markdown: ^1.0.0 - checksum: 375342525db8c4e3060cf9a2ddcc2f50ac2c0878da63557695165f593b7a2e4b2d6d398f05f638a6f7ef36c2c3bdbd4c64aab85ef35d990dae17303d55635cfd - languageName: node - linkType: hard - -"mdast-util-mdx-jsx@npm:^2.0.0": - version: 2.0.1 - resolution: "mdast-util-mdx-jsx@npm:2.0.1" - dependencies: - "@types/estree-jsx": ^0.0.1 - "@types/hast": ^2.0.0 - "@types/mdast": ^3.0.0 - ccount: ^2.0.0 - mdast-util-to-markdown: ^1.3.0 - parse-entities: ^4.0.0 - stringify-entities: ^4.0.0 - unist-util-remove-position: ^4.0.0 - unist-util-stringify-position: ^3.0.0 - vfile-message: ^3.0.0 - checksum: 19825928595153eb7081275d15b40965960a2b52077913dc37469e891d91a8166a6c837646ce5b90ec1903fbfd51b276d570d1e2c89f00f18c0bfe286f1da681 - languageName: node - linkType: hard - -"mdast-util-mdx@npm:^2.0.0": - version: 2.0.0 - resolution: "mdast-util-mdx@npm:2.0.0" - dependencies: - mdast-util-mdx-expression: ^1.0.0 - mdast-util-mdx-jsx: ^2.0.0 - mdast-util-mdxjs-esm: ^1.0.0 - checksum: 4744bfbbd337c2a99a3ef339673c549a670d6496e0d3a6d747d2451e112d6fef7d27613549b0bd62a5f92ea7919e3bacd78c731e8a3d80552a09b80896554cf6 - languageName: node - linkType: hard - -"mdast-util-mdxjs-esm@npm:^1.0.0": - version: 1.2.0 - resolution: "mdast-util-mdxjs-esm@npm:1.2.0" - dependencies: - "@types/estree-jsx": ^0.0.1 - "@types/hast": ^2.0.0 - "@types/mdast": ^3.0.0 - mdast-util-from-markdown: ^1.0.0 - mdast-util-to-markdown: ^1.0.0 - checksum: be8a9bc77bc66b2a7dd9057974b30e7e8e0a37caee467ee7f22451da25e4aeb58eec78b93132672449f8714bfb5c60dc58b3822956bdc8fffbc2ff476a1e4450 - languageName: node - linkType: hard - "mdast-util-to-hast@npm:10.0.1": version: 10.0.1 resolution: "mdast-util-to-hast@npm:10.0.1" @@ -29064,39 +27591,7 @@ __metadata: languageName: node linkType: hard -"mdast-util-to-hast@npm:^12.1.0": - version: 12.1.1 - resolution: "mdast-util-to-hast@npm:12.1.1" - dependencies: - "@types/hast": ^2.0.0 - "@types/mdast": ^3.0.0 - "@types/mdurl": ^1.0.0 - mdast-util-definitions: ^5.0.0 - mdurl: ^1.0.0 - micromark-util-sanitize-uri: ^1.0.0 - unist-builder: ^3.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 4c5a73e0463493d5ab2c033d42f8daead24d0808969bf21c3a720786784a347cedc1d3ae26b37737dbe3ea0839bc130bf7e20f3bc02e2387e6e7b0a5f94bafe4 - languageName: node - linkType: hard - -"mdast-util-to-markdown@npm:^0.6.0, mdast-util-to-markdown@npm:^0.6.1, mdast-util-to-markdown@npm:~0.6.0": - version: 0.6.5 - resolution: "mdast-util-to-markdown@npm:0.6.5" - dependencies: - "@types/unist": ^2.0.0 - longest-streak: ^2.0.0 - mdast-util-to-string: ^2.0.0 - parse-entities: ^2.0.0 - repeat-string: ^1.0.0 - zwitch: ^1.0.0 - checksum: 7ebc47533bff6e8669f85ae124dc521ea570e9df41c0d9e4f0f43c19ef4a8c9928d741f3e4afa62fcca1927479b714582ff5fd684ef240d84ee5b75ab9d863cf - languageName: node - linkType: hard - -"mdast-util-to-markdown@npm:^1.0.0, mdast-util-to-markdown@npm:^1.3.0": +"mdast-util-to-markdown@npm:^1.0.0": version: 1.3.0 resolution: "mdast-util-to-markdown@npm:1.3.0" dependencies: @@ -29118,13 +27613,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-to-string@npm:^2.0.0": - version: 2.0.0 - resolution: "mdast-util-to-string@npm:2.0.0" - checksum: 0b2113ada10e002fbccb014170506dabe2f2ddacaacbe4bc1045c33f986652c5a162732a2c057c5335cdb58419e2ad23e368e5be226855d4d4e280b81c4e9ec2 - languageName: node - linkType: hard - "mdast-util-to-string@npm:^3.0.0, mdast-util-to-string@npm:^3.1.0": version: 3.1.0 resolution: "mdast-util-to-string@npm:3.1.0" @@ -29350,7 +27838,7 @@ __metadata: languageName: node linkType: hard -"micromark-core-commonmark@npm:^1.0.0, micromark-core-commonmark@npm:^1.0.1": +"micromark-core-commonmark@npm:^1.0.1": version: 1.0.6 resolution: "micromark-core-commonmark@npm:1.0.6" dependencies: @@ -29374,136 +27862,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-gfm-autolink-literal@npm:~0.5.0": - version: 0.5.7 - resolution: "micromark-extension-gfm-autolink-literal@npm:0.5.7" - dependencies: - micromark: ~2.11.3 - checksum: 319ec793c2e374e4cc0cbbb07326c1affb78819e507c7c1577f9d14b972852a6bb55e664332ec51f7cca24bdddd43429c5dd55f11e9200b1a00bab1bf494fb2d - languageName: node - linkType: hard - -"micromark-extension-gfm-strikethrough@npm:~0.6.5": - version: 0.6.5 - resolution: "micromark-extension-gfm-strikethrough@npm:0.6.5" - dependencies: - micromark: ~2.11.0 - checksum: 67711633590d3e688759a46aaed9f9d04bcaf29b6615eec17af082eabe1059fbca4beb41ba13db418ae7be3ac90198742fbabe519a70f9b6bb615598c5d6ef1a - languageName: node - linkType: hard - -"micromark-extension-gfm-table@npm:~0.4.0": - version: 0.4.3 - resolution: "micromark-extension-gfm-table@npm:0.4.3" - dependencies: - micromark: ~2.11.0 - checksum: 12c78de985944dd66aae409871c45d801cc65704f55ea5cc8afac422042c6d3b5e777b154c079ae81298b30b83434b257b54981bda51c220a102042dd2524a63 - languageName: node - linkType: hard - -"micromark-extension-gfm-tagfilter@npm:~0.3.0": - version: 0.3.0 - resolution: "micromark-extension-gfm-tagfilter@npm:0.3.0" - checksum: 9369736a203836b2933dfdeacab863e7a4976139b9dd46fa5bd6c2feeef50c7dbbcdd641ae95f0481f577d8aa22396bfa7ed9c38515647d4cf3f2c727cc094a3 - languageName: node - linkType: hard - -"micromark-extension-gfm-task-list-item@npm:~0.3.0": - version: 0.3.3 - resolution: "micromark-extension-gfm-task-list-item@npm:0.3.3" - dependencies: - micromark: ~2.11.0 - checksum: e4ccbe6b440234c8ee05d89315e1204c78773724241af31ac328194470a8a61bc6606eab3ce2d9a83da4401b06e07936038654493da715d40522133d1556dda4 - languageName: node - linkType: hard - -"micromark-extension-gfm@npm:^0.3.0": - version: 0.3.3 - resolution: "micromark-extension-gfm@npm:0.3.3" - dependencies: - micromark: ~2.11.0 - micromark-extension-gfm-autolink-literal: ~0.5.0 - micromark-extension-gfm-strikethrough: ~0.6.5 - micromark-extension-gfm-table: ~0.4.0 - micromark-extension-gfm-tagfilter: ~0.3.0 - micromark-extension-gfm-task-list-item: ~0.3.0 - checksum: 7957a1afd8c92daa0fc165342902729334b22d59feacd85b20a0d9cc453c90bbdd5b5ba85a3d177c01802060aeb3326daf05d3e6d95932fcbc8371827c98336e - languageName: node - linkType: hard - -"micromark-extension-mdx-expression@npm:^1.0.0": - version: 1.0.3 - resolution: "micromark-extension-mdx-expression@npm:1.0.3" - dependencies: - micromark-factory-mdx-expression: ^1.0.0 - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-events-to-acorn: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: ef4b4137894624a6754b951d3cb7abb20951ca7b37f9ad8a50d2e2b95d0cf880258d71296bfac6be4ff83a8d137b6b657ae852bb6f11f4ca11e5e6d62f1b025d - languageName: node - linkType: hard - -"micromark-extension-mdx-jsx@npm:^1.0.0": - version: 1.0.3 - resolution: "micromark-extension-mdx-jsx@npm:1.0.3" - dependencies: - "@types/acorn": ^4.0.0 - estree-util-is-identifier-name: ^2.0.0 - micromark-factory-mdx-expression: ^1.0.0 - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - vfile-message: ^3.0.0 - checksum: 1a5566890aabc52fe96b78e3a3a507dee03a2232e44b9360b00617734e156f934e85bc6a477fbb856c793fe33c9fb7d2207a4f50e680168c0d04ba9c9336d960 - languageName: node - linkType: hard - -"micromark-extension-mdx-md@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-extension-mdx-md@npm:1.0.0" - dependencies: - micromark-util-types: ^1.0.0 - checksum: b4f205e1d5f0946b4755541ef44ffd0b3be8c7ecfc08d8b139b6a21fbd3ff62d8fdb6b7e6d17bd9a3b610450267f43a41703dc48b341da9addd743a28cdefa64 - languageName: node - linkType: hard - -"micromark-extension-mdxjs-esm@npm:^1.0.0": - version: 1.0.2 - resolution: "micromark-extension-mdxjs-esm@npm:1.0.2" - dependencies: - micromark-core-commonmark: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-events-to-acorn: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - unist-util-position-from-estree: ^1.1.0 - uvu: ^0.5.0 - vfile-message: ^3.0.0 - checksum: 725b4e420f502fcc7b351781f9897a1077959006b175e0d55e11054df44ce4b1df074e9e9a661c7c092f33acf3dd91f71d4cbb0626a387b9af28594c18c57bcb - languageName: node - linkType: hard - -"micromark-extension-mdxjs@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-extension-mdxjs@npm:1.0.0" - dependencies: - acorn: ^8.0.0 - acorn-jsx: ^5.0.0 - micromark-extension-mdx-expression: ^1.0.0 - micromark-extension-mdx-jsx: ^1.0.0 - micromark-extension-mdx-md: ^1.0.0 - micromark-extension-mdxjs-esm: ^1.0.0 - micromark-util-combine-extensions: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: ba836c6d2dfc67597886e88f533ffa02f2029dbe216a0651f1066e70f8529a700bcc7fa2bc4201ee12fd3d1cd7da7093d5a442442daeb84b27df96aaffb7699c - languageName: node - linkType: hard - "micromark-factory-destination@npm:^1.0.0": version: 1.0.0 resolution: "micromark-factory-destination@npm:1.0.0" @@ -29527,22 +27885,6 @@ __metadata: languageName: node linkType: hard -"micromark-factory-mdx-expression@npm:^1.0.0": - version: 1.0.6 - resolution: "micromark-factory-mdx-expression@npm:1.0.6" - dependencies: - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-events-to-acorn: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - unist-util-position-from-estree: ^1.0.0 - uvu: ^0.5.0 - vfile-message: ^3.0.0 - checksum: 7b69f0e77664e9820639cf23c4f01d43aa0e7abd88021c3db428435e3a5a1f9446f8dc5c2a6ed4ac16c6495ca51937609a5c98ff59a62c54be382c2725500b39 - languageName: node - linkType: hard - "micromark-factory-space@npm:^1.0.0": version: 1.0.0 resolution: "micromark-factory-space@npm:1.0.0" @@ -29646,20 +27988,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-events-to-acorn@npm:^1.0.0": - version: 1.0.4 - resolution: "micromark-util-events-to-acorn@npm:1.0.4" - dependencies: - "@types/acorn": ^4.0.0 - "@types/estree": ^0.0.50 - estree-util-visit: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - vfile-message: ^3.0.0 - checksum: e1e254b00474416c98b432a5f01c147e745a01a4b7886b97ada4997ac5ea88085255747d86492a3e16c3d66dbc3fc2a613fb3128fc3f3ad3987424104ffd8abe - languageName: node - linkType: hard - "micromark-util-html-tag-name@npm:^1.0.0": version: 1.0.0 resolution: "micromark-util-html-tag-name@npm:1.0.0" @@ -29722,16 +28050,6 @@ __metadata: languageName: node linkType: hard -"micromark@npm:^2.11.3, micromark@npm:~2.11.0, micromark@npm:~2.11.3": - version: 2.11.4 - resolution: "micromark@npm:2.11.4" - dependencies: - debug: ^4.0.0 - parse-entities: ^2.0.0 - checksum: f8a5477d394908a5d770227aea71657a76423d420227c67ea0699e659a5f62eb39d504c1f7d69ec525a6af5aaeb6a7bffcdba95614968c03d41d3851edecb0d6 - languageName: node - linkType: hard - "micromark@npm:^3.0.0": version: 3.0.10 resolution: "micromark@npm:3.0.10" @@ -29800,7 +28118,7 @@ __metadata: languageName: node linkType: hard -"mime-db@npm:1.52.0, mime-db@npm:>= 1.43.0 < 2, mime-db@npm:^1.28.0": +"mime-db@npm:1.52.0, mime-db@npm:>= 1.43.0 < 2": version: 1.52.0 resolution: "mime-db@npm:1.52.0" checksum: 0d99a03585f8b39d68182803b12ac601d9c01abfa28ec56204fa330bc9f3d1c5e14beb049bafadb3dbdf646dfb94b87e24d4ec7b31b7279ef906a8ea9b6a513f @@ -30140,6 +28458,18 @@ __metadata: languageName: node linkType: hard +"mlly@npm:^1.2.0": + version: 1.2.1 + resolution: "mlly@npm:1.2.1" + dependencies: + acorn: ^8.8.2 + pathe: ^1.1.0 + pkg-types: ^1.0.3 + ufo: ^1.1.2 + checksum: 82939436ec0e1c53e400d86fd571bd005090383b3fa49d5d8b9df4b388b24bae2b7b2f0369ef0c905d29fb35377cd4724d2bf5e5cd130d922849f74948bb81f0 + languageName: node + linkType: hard + "mock-fs@npm:^4.1.0": version: 4.14.0 resolution: "mock-fs@npm:4.14.0" @@ -30464,6 +28794,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.6": + version: 3.3.6 + resolution: "nanoid@npm:3.3.6" + bin: + nanoid: bin/nanoid.cjs + checksum: 7d0eda657002738aa5206107bd0580aead6c95c460ef1bdd0b1a87a9c7ae6277ac2e9b945306aaa5b32c6dcb7feaf462d0f552e7f8b5718abfc6ead5c94a71b3 + languageName: node + linkType: hard + "nanomatch@npm:^1.2.9": version: 1.2.13 resolution: "nanomatch@npm:1.2.13" @@ -30629,17 +28968,6 @@ __metadata: languageName: node linkType: hard -"next-themes@npm:^0.0.8": - version: 0.0.8 - resolution: "next-themes@npm:0.0.8" - peerDependencies: - next: ^9.5.5 - react: ^16.13.1 - react-dom: ^16.13.1 - checksum: 0764d2594f81019fa7d121fa0cea47fcd4e027efe8b341c8a54127bde80795f712dab4058ee45be64e5d070725920f91b64ece0f63680130c5e5555efe6eb9bf - languageName: node - linkType: hard - "next-themes@npm:^0.2.0": version: 0.2.0 resolution: "next-themes@npm:0.2.0" @@ -30819,50 +29147,6 @@ __metadata: languageName: node linkType: hard -"nextra-theme-docs@npm:^1.2.2": - version: 1.2.6 - resolution: "nextra-theme-docs@npm:1.2.6" - dependencies: - "@mdx-js/react": ^1.6.16 - "@reach/skip-nav": ^0.11.2 - classnames: ^2.2.6 - focus-visible: ^5.1.0 - github-slugger: ^1.3.0 - grapheme-splitter: ^1.0.4 - intersection-observer: ^0.12.0 - match-sorter: ^4.2.0 - next-themes: ^0.0.8 - parse-git-url: ^1.0.1 - prism-react-renderer: ^1.1.1 - react-innertext: ^1.1.5 - title: ^3.4.2 - peerDependencies: - next: ">=9.5.3" - react: ">=16.13.1" - react-dom: ">=16.13.1" - checksum: 545f5690cc486686e86022ebc86583b3e92602f1d7532c22479a6350b7422974631f62695913cc6f3f19cadbf76688037306142fe33aa77c59e9fd0720997cb3 - languageName: node - linkType: hard - -"nextra@npm:^1.1.0": - version: 1.1.0 - resolution: "nextra@npm:1.1.0" - dependencies: - "@mdx-js/loader": ^2.0.0-next.9 - download: ^8.0.0 - graceful-fs: ^4.2.6 - gray-matter: ^4.0.3 - loader-utils: ^2.0.0 - remark: ^13.0.0 - remark-gfm: ^1.0.0 - slash: ^3.0.0 - strip-markdown: ^4.0.0 - peerDependencies: - react: ">=16.13.1" - checksum: 59afdb888068595793a2637dd071795a3a41bd43b56efa7f44f2beaf9420c6253e688289171ffad2c650189e13c984ecfa88cdf432b8d52e3ede17ad6280ce7c - languageName: node - linkType: hard - "nice-try@npm:^1.0.4": version: 1.0.5 resolution: "nice-try@npm:1.0.5" @@ -31110,17 +29394,6 @@ __metadata: languageName: node linkType: hard -"normalize-url@npm:2.0.1": - version: 2.0.1 - resolution: "normalize-url@npm:2.0.1" - dependencies: - prepend-http: ^2.0.0 - query-string: ^5.0.1 - sort-keys: ^2.0.0 - checksum: 30e337ee03fc7f360c7d2b966438657fabd2628925cc58bffc893982fe4d2c59b397ae664fa2c319cd83565af73eee88906e80bc5eec91bc32b601920e770d75 - languageName: node - linkType: hard - "normalize-url@npm:^6.0.1": version: 6.1.0 resolution: "normalize-url@npm:6.1.0" @@ -31224,10 +29497,10 @@ __metadata: languageName: node linkType: hard -"nwsapi@npm:^2.2.0": - version: 2.2.0 - resolution: "nwsapi@npm:2.2.0" - checksum: 5ef4a9bc0c1a5b7f2e014aa6a4b359a257503b796618ed1ef0eb852098f77e772305bb0e92856e4bbfa3e6c75da48c0113505c76f144555ff38867229c2400a7 +"nwsapi@npm:^2.2.4": + version: 2.2.4 + resolution: "nwsapi@npm:2.2.4" + checksum: a5eb9467158bdf255d27e9c4555e9ca02e4ba84ddce9b683856ed49de23eb1bb28ae3b8e791b7a93d156ad62b324a56f4d44cad827c2ca288c107ed6bdaff8a8 languageName: node linkType: hard @@ -31735,13 +30008,6 @@ __metadata: languageName: node linkType: hard -"p-cancelable@npm:^0.4.0": - version: 0.4.1 - resolution: "p-cancelable@npm:0.4.1" - checksum: d11144d72ee3a99f62fe595cb0e13b8585ea73c3807b4a9671744f1bf5d3ccddb049247a4ec3ceff05ca4adba9d0bb0f1862829daf20795bf528c86fa088509c - languageName: node - linkType: hard - "p-cancelable@npm:^3.0.0": version: 3.0.0 resolution: "p-cancelable@npm:3.0.0" @@ -31756,15 +30022,6 @@ __metadata: languageName: node linkType: hard -"p-event@npm:^2.1.0": - version: 2.3.1 - resolution: "p-event@npm:2.3.1" - dependencies: - p-timeout: ^2.0.1 - checksum: 7f973c4c001045bcd561202fc1b2bdf9e148182bb28a7bafa8e7b2ebfaf71a4f9ba91554222040d364290e707e3ebbb049122b8eda9d2aac413b4cf8de0b79ff - languageName: node - linkType: hard - "p-event@npm:^4.1.0": version: 4.2.0 resolution: "p-event@npm:4.2.0" @@ -31790,13 +30047,6 @@ __metadata: languageName: node linkType: hard -"p-is-promise@npm:^1.1.0": - version: 1.1.0 - resolution: "p-is-promise@npm:1.1.0" - checksum: 64d7c6cda18af2c91c04209e5856c54d1a9818662d2320b34153d446645f431307e04406969a1be00cad680288e86dcf97b9eb39edd5dc4d0b1bd714ee85e13b - languageName: node - linkType: hard - "p-limit@npm:^1.1.0": version: 1.3.0 resolution: "p-limit@npm:1.3.0" @@ -31815,7 +30065,7 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": +"p-limit@npm:^3.0.2": version: 3.1.0 resolution: "p-limit@npm:3.1.0" dependencies: @@ -31824,6 +30074,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:^4.0.0": + version: 4.0.0 + resolution: "p-limit@npm:4.0.0" + dependencies: + yocto-queue: ^1.0.0 + checksum: 01d9d70695187788f984226e16c903475ec6a947ee7b21948d6f597bed788e3112cc7ec2e171c1d37125057a5f45f3da21d8653e04a3a793589e12e9e80e756b + languageName: node + linkType: hard + "p-locate@npm:^2.0.0": version: 2.0.0 resolution: "p-locate@npm:2.0.0" @@ -31894,15 +30153,6 @@ __metadata: languageName: node linkType: hard -"p-timeout@npm:^2.0.1": - version: 2.0.1 - resolution: "p-timeout@npm:2.0.1" - dependencies: - p-finally: ^1.0.0 - checksum: 9205a661173f03adbeabda8e02826de876376b09c99768bdc33e5b25ae73230e3ac00e520acedbe3cf05fbd3352fb02efbd3811a9a021b148fb15eb07e7accac - languageName: node - linkType: hard - "p-timeout@npm:^3.1.0": version: 3.2.0 resolution: "p-timeout@npm:3.2.0" @@ -32044,29 +30294,6 @@ __metadata: languageName: node linkType: hard -"parse-entities@npm:^4.0.0": - version: 4.0.0 - resolution: "parse-entities@npm:4.0.0" - dependencies: - "@types/unist": ^2.0.0 - character-entities: ^2.0.0 - character-entities-legacy: ^3.0.0 - character-reference-invalid: ^2.0.0 - decode-named-character-reference: ^1.0.0 - is-alphanumerical: ^2.0.0 - is-decimal: ^2.0.0 - is-hexadecimal: ^2.0.0 - checksum: cd9fa53bc056ad8cf8a45494bfd7ce65e8bf6f1b12dcc9a6343376fa529c2012041303c5d0f86babf70afbd13b71c2f219fc3a76fb97d9d559b66578e19cdaf0 - languageName: node - linkType: hard - -"parse-git-url@npm:^1.0.1": - version: 1.0.1 - resolution: "parse-git-url@npm:1.0.1" - checksum: 764d15c50d17308dd90883337d34f6dfc68a6a1e3441d714d9434cf2b12607ee74dd51754b01d07e2fb19592357e59242a1ebdfe2d8025ac6c0998205f66d6dd - languageName: node - linkType: hard - "parse-headers@npm:^2.0.0": version: 2.0.5 resolution: "parse-headers@npm:2.0.5" @@ -32093,7 +30320,7 @@ __metadata: languageName: node linkType: hard -"parse-json@npm:^5.0.0, parse-json@npm:^5.2.0": +"parse-json@npm:^5.0.0": version: 5.2.0 resolution: "parse-json@npm:5.2.0" dependencies: @@ -32121,17 +30348,26 @@ __metadata: languageName: node linkType: hard -"parse5@npm:6.0.1, parse5@npm:^6.0.0, parse5@npm:^6.0.1": +"parse5@npm:^5.1.1": + version: 5.1.1 + resolution: "parse5@npm:5.1.1" + checksum: 613a714af4c1101d1cb9f7cece2558e35b9ae8a0c03518223a4a1e35494624d9a9ad5fad4c13eab66a0e0adccd9aa3d522fc8f5f9cc19789e0579f3fa0bdfc65 + languageName: node + linkType: hard + +"parse5@npm:^6.0.0, parse5@npm:^6.0.1": version: 6.0.1 resolution: "parse5@npm:6.0.1" checksum: 7d569a176c5460897f7c8f3377eff640d54132b9be51ae8a8fa4979af940830b2b0c296ce75e5bd8f4041520aadde13170dbdec44889975f906098ea0002f4bd languageName: node linkType: hard -"parse5@npm:^5.1.1": - version: 5.1.1 - resolution: "parse5@npm:5.1.1" - checksum: 613a714af4c1101d1cb9f7cece2558e35b9ae8a0c03518223a4a1e35494624d9a9ad5fad4c13eab66a0e0adccd9aa3d522fc8f5f9cc19789e0579f3fa0bdfc65 +"parse5@npm:^7.1.2": + version: 7.1.2 + resolution: "parse5@npm:7.1.2" + dependencies: + entities: ^4.4.0 + checksum: 59465dd05eb4c5ec87b76173d1c596e152a10e290b7abcda1aecf0f33be49646ea74840c69af975d7887543ea45564801736356c568d6b5e71792fd0f4055713 languageName: node linkType: hard @@ -32298,6 +30534,20 @@ __metadata: languageName: node linkType: hard +"pathe@npm:^1.1.0": + version: 1.1.0 + resolution: "pathe@npm:1.1.0" + checksum: 6b9be9968ea08a90c0824934799707a1c6a1ad22ac1f22080f377e3f75856d5e53a331b01d327329bfce538a14590587cfb250e8e7947f64408797c84c252056 + languageName: node + linkType: hard + +"pathval@npm:^1.1.1": + version: 1.1.1 + resolution: "pathval@npm:1.1.1" + checksum: 090e3147716647fb7fb5b4b8c8e5b55e5d0a6086d085b6cd23f3d3c01fcf0ff56fd3cc22f2f4a033bd2e46ed55d61ed8379e123b42afe7d531a2a5fc8bb556d6 + languageName: node + linkType: hard + "pause-stream@npm:0.0.11": version: 0.0.11 resolution: "pause-stream@npm:0.0.11" @@ -32320,13 +30570,6 @@ __metadata: languageName: node linkType: hard -"pend@npm:~1.2.0": - version: 1.2.0 - resolution: "pend@npm:1.2.0" - checksum: 6c72f5243303d9c60bd98e6446ba7d30ae29e3d56fdb6fae8767e8ba6386f33ee284c97efe3230a0d0217e2b1723b8ab490b1bbf34fcbb2180dbc8a9de47850d - languageName: node - linkType: hard - "performance-now@npm:^2.1.0": version: 2.1.0 resolution: "performance-now@npm:2.1.0" @@ -32334,16 +30577,6 @@ __metadata: languageName: node linkType: hard -"periscopic@npm:^3.0.0": - version: 3.0.4 - resolution: "periscopic@npm:3.0.4" - dependencies: - estree-walker: ^3.0.0 - is-reference: ^3.0.0 - checksum: 0920ea1b0294c2463b7df858d7f895d0a69f15ec5c7b93d63749e7a8f6d9c065853ebea701305f1756f70310633832cf5c90e43e9363cce51abec44cc2f5c188 - languageName: node - linkType: hard - "pg-connection-string@npm:^2.5.0": version: 2.5.0 resolution: "pg-connection-string@npm:2.5.0" @@ -32444,7 +30677,7 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.2, picomatch@npm:^2.2.3, picomatch@npm:^2.3.0, picomatch@npm:^2.3.1": +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.2, picomatch@npm:^2.3.0, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" checksum: 050c865ce81119c4822c45d3c84f1ced46f93a0126febae20737bd05ca20589c564d6e9226977df859ed5e03dc73f02584a2b0faad36e896936238238b0446cf @@ -32529,7 +30762,7 @@ __metadata: languageName: node linkType: hard -"pirates@npm:^4.0.1, pirates@npm:^4.0.4, pirates@npm:^4.0.5": +"pirates@npm:^4.0.1, pirates@npm:^4.0.5": version: 4.0.5 resolution: "pirates@npm:4.0.5" checksum: c9994e61b85260bec6c4fc0307016340d9b0c4f4b6550a957afaaff0c9b1ad58fbbea5cfcf083860a25cb27a375442e2b0edf52e2e1e40e69934e08dcc52d227 @@ -32574,6 +30807,17 @@ __metadata: languageName: node linkType: hard +"pkg-types@npm:^1.0.3": + version: 1.0.3 + resolution: "pkg-types@npm:1.0.3" + dependencies: + jsonc-parser: ^3.2.0 + mlly: ^1.2.0 + pathe: ^1.1.0 + checksum: 4b305c834b912ddcc8a0fe77530c0b0321fe340396f84cbb87aecdbc126606f47f2178f23b8639e71a4870f9631c7217aef52ffed0ae17ea2dbbe7e43d116a6e + languageName: node + linkType: hard + "playwright-core@npm:1.31.2": version: 1.31.2 resolution: "playwright-core@npm:1.31.2" @@ -32910,6 +31154,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.4.23": + version: 8.4.23 + resolution: "postcss@npm:8.4.23" + dependencies: + nanoid: ^3.3.6 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: 8bb9d1b2ea6e694f8987d4f18c94617971b2b8d141602725fedcc2222fdc413b776a6e1b969a25d627d7b2681ca5aabb56f59e727ef94072e1b6ac8412105a2f + languageName: node + linkType: hard + "postgres-array@npm:~2.0.0": version: 2.0.0 resolution: "postgres-array@npm:2.0.0" @@ -33016,13 +31271,6 @@ __metadata: languageName: node linkType: hard -"prepend-http@npm:^2.0.0": - version: 2.0.0 - resolution: "prepend-http@npm:2.0.0" - checksum: 7694a9525405447662c1ffd352fcb41b6410c705b739b6f4e3a3e21cf5fdede8377890088e8934436b8b17ba55365a615f153960f30877bf0d0392f9e93503ea - languageName: node - linkType: hard - "prettier-linter-helpers@npm:^1.0.0": version: 1.0.0 resolution: "prettier-linter-helpers@npm:1.0.0" @@ -33134,7 +31382,7 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:^27.0.2": +"pretty-format@npm:^27.0.2, pretty-format@npm:^27.5.1": version: 27.5.1 resolution: "pretty-format@npm:27.5.1" dependencies: @@ -33145,15 +31393,14 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:^28.0.0, pretty-format@npm:^28.1.3": - version: 28.1.3 - resolution: "pretty-format@npm:28.1.3" +"pretty-format@npm:^29.5.0": + version: 29.5.0 + resolution: "pretty-format@npm:29.5.0" dependencies: - "@jest/schemas": ^28.1.3 - ansi-regex: ^5.0.1 + "@jest/schemas": ^29.4.3 ansi-styles: ^5.0.0 react-is: ^18.0.0 - checksum: e69f857358a3e03d271252d7524bec758c35e44680287f36c1cb905187fbc82da9981a6eb07edfd8a03bc3cbeebfa6f5234c13a3d5b59f2bbdf9b4c4053e0a7f + checksum: 4065356b558e6db25b4d41a01efb386935a6c06a0c9c104ef5ce59f2f476b8210edb8b3949b386e60ada0a6dc5ebcb2e6ccddc8c64dfd1a9943c3c3a9e7eaf89 languageName: node linkType: hard @@ -33180,15 +31427,6 @@ __metadata: languageName: node linkType: hard -"prism-react-renderer@npm:^1.1.1": - version: 1.3.1 - resolution: "prism-react-renderer@npm:1.3.1" - peerDependencies: - react: ">=0.14.9" - checksum: 883693ea59dd6c7d821930ff232f1b28cd3f5d4a111fdb0a730f2ae34ffc7ca805ad3522c86eecbc79f9bee30279371e1fa6b33b4f72a6d3f17921b733b303b6 - languageName: node - linkType: hard - "prism-react-renderer@npm:^1.3.5": version: 1.3.5 resolution: "prism-react-renderer@npm:1.3.5" @@ -33328,7 +31566,7 @@ __metadata: languageName: node linkType: hard -"prompts@npm:^2.0.1, prompts@npm:^2.2.1, prompts@npm:^2.4.0": +"prompts@npm:^2.4.0": version: 2.4.2 resolution: "prompts@npm:2.4.2" dependencies: @@ -33490,6 +31728,13 @@ __metadata: languageName: node linkType: hard +"punycode@npm:^2.3.0": + version: 2.3.0 + resolution: "punycode@npm:2.3.0" + checksum: 39f760e09a2a3bbfe8f5287cf733ecdad69d6af2fe6f97ca95f24b8921858b91e9ea3c9eeec6e08cede96181b3bb33f95c6ffd8c77e63986508aa2e8159fa200 + languageName: node + linkType: hard + "q@npm:2.0.x": version: 2.0.3 resolution: "q@npm:2.0.3" @@ -34161,16 +32406,6 @@ __metadata: languageName: node linkType: hard -"react-innertext@npm:^1.1.5": - version: 1.1.5 - resolution: "react-innertext@npm:1.1.5" - peerDependencies: - "@types/react": ">=0.0.0 <=99" - react: ">=0.0.0 <=99" - checksum: 01c9c8a5a471f03965b9d69c8bee02d420fd3a607b678004f0bdee098c9bccb3e2eae8460c90b1d180d2a8d544a3ddb09b8c2a11e5aa8b3a077733e2d9c25c2c - languageName: node - linkType: hard - "react-inspector@npm:^5.1.0, react-inspector@npm:^5.1.1": version: 5.1.1 resolution: "react-inspector@npm:5.1.1" @@ -34773,7 +33008,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:1 || 2, readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.1.0, readable-stream@npm:^2.1.5, readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.0, readable-stream@npm:^2.3.3, readable-stream@npm:^2.3.5, readable-stream@npm:^2.3.6, readable-stream@npm:~2.3.6": +"readable-stream@npm:1 || 2, readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.1.0, readable-stream@npm:^2.1.5, readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.3, readable-stream@npm:^2.3.6, readable-stream@npm:~2.3.6": version: 2.3.7 resolution: "readable-stream@npm:2.3.7" dependencies: @@ -35105,16 +33340,6 @@ __metadata: languageName: node linkType: hard -"remark-gfm@npm:^1.0.0": - version: 1.0.0 - resolution: "remark-gfm@npm:1.0.0" - dependencies: - mdast-util-gfm: ^0.1.0 - micromark-extension-gfm: ^0.3.0 - checksum: 877b0f6472a90a490b5d5a1393f46d22c4ab7451b1e83ebd7362e5be9c661b6ed03e76c28f76894f460bedf23345c589d3f412c273ce0d4d442c6a4d65b0eae4 - languageName: node - linkType: hard - "remark-html@npm:^14.0.1": version: 14.0.1 resolution: "remark-html@npm:14.0.1" @@ -35144,16 +33369,6 @@ __metadata: languageName: node linkType: hard -"remark-mdx@npm:^2.0.0": - version: 2.1.1 - resolution: "remark-mdx@npm:2.1.1" - dependencies: - mdast-util-mdx: ^2.0.0 - micromark-extension-mdxjs: ^1.0.0 - checksum: cd67006499d85b73fd69ff79e3a9202ffa831bfd243eeafd53a2d335d860e7cb4956801461d752e8791a4cface9663f787ad5c828ed028cbc0f11b12846633aa - languageName: node - linkType: hard - "remark-parse@npm:8.0.3": version: 8.0.3 resolution: "remark-parse@npm:8.0.3" @@ -35189,27 +33404,6 @@ __metadata: languageName: node linkType: hard -"remark-parse@npm:^9.0.0": - version: 9.0.0 - resolution: "remark-parse@npm:9.0.0" - dependencies: - mdast-util-from-markdown: ^0.8.0 - checksum: 50104880549639b7dd7ae6f1e23c214915fe9c054f02f3328abdaee3f6de6d7282bf4357c3c5b106958fe75e644a3c248c2197755df34f9955e8e028fc74868f - languageName: node - linkType: hard - -"remark-rehype@npm:^10.0.0": - version: 10.1.0 - resolution: "remark-rehype@npm:10.1.0" - dependencies: - "@types/hast": ^2.0.0 - "@types/mdast": ^3.0.0 - mdast-util-to-hast: ^12.1.0 - unified: ^10.0.0 - checksum: b9ac8acff3383b204dfdc2599d0bdf86e6ca7e837033209584af2e6aaa6a9013e519a379afa3201299798cab7298c8f4b388de118c312c67234c133318aec084 - languageName: node - linkType: hard - "remark-slug@npm:^6.0.0": version: 6.1.0 resolution: "remark-slug@npm:6.1.0" @@ -35241,26 +33435,6 @@ __metadata: languageName: node linkType: hard -"remark-stringify@npm:^9.0.0": - version: 9.0.1 - resolution: "remark-stringify@npm:9.0.1" - dependencies: - mdast-util-to-markdown: ^0.6.0 - checksum: 93f46076f4d96ab1946d13e7dd43e83088480ac6b1dfe05a65e2c2f0e33d1f52a50175199b464a81803fc0f5b3bf182037665f89720b30515eba37bec4d63d56 - languageName: node - linkType: hard - -"remark@npm:^13.0.0": - version: 13.0.0 - resolution: "remark@npm:13.0.0" - dependencies: - remark-parse: ^9.0.0 - remark-stringify: ^9.0.0 - unified: ^9.1.0 - checksum: e3432bfa1b0029680302e99a6356c08789b3e908457a71eca37ada6a58497e302f08bd5f62fbad840082a8348c181b7f6f981aaf3cd3112207583ddf793a2429 - languageName: node - linkType: hard - "remark@npm:^14.0.2": version: 14.0.3 resolution: "remark@npm:14.0.3" @@ -35285,13 +33459,6 @@ __metadata: languageName: node linkType: hard -"remove-accents@npm:0.4.2": - version: 0.4.2 - resolution: "remove-accents@npm:0.4.2" - checksum: 84a6988555dea24115e2d1954db99509588d43fe55a1590f0b5894802776f7b488b3151c37ceb9e4f4b646f26b80b7325dcea2fae58bc3865df146e1fa606711 - languageName: node - linkType: hard - "remove-markdown@npm:^0.5.0": version: 0.5.0 resolution: "remove-markdown@npm:0.5.0" @@ -35339,7 +33506,7 @@ __metadata: languageName: node linkType: hard -"repeat-string@npm:^1.0.0, repeat-string@npm:^1.5.2, repeat-string@npm:^1.5.4, repeat-string@npm:^1.6.1": +"repeat-string@npm:^1.5.2, repeat-string@npm:^1.5.4, repeat-string@npm:^1.6.1": version: 1.6.1 resolution: "repeat-string@npm:1.6.1" checksum: 1b809fc6db97decdc68f5b12c4d1a671c8e3f65ec4a40c238bc5200e44e85bcc52a54f78268ab9c29fcf5fe4f1343e805420056d1f30fa9a9ee4c2d93e3cc6c0 @@ -35425,15 +33592,6 @@ __metadata: languageName: node linkType: hard -"resolve-cwd@npm:^3.0.0": - version: 3.0.0 - resolution: "resolve-cwd@npm:3.0.0" - dependencies: - resolve-from: ^5.0.0 - checksum: 546e0816012d65778e580ad62b29e975a642989108d9a3c5beabfb2304192fa3c9f9146fbdfe213563c6ff51975ae41bac1d3c6e047dd9572c94863a057b4d81 - languageName: node - linkType: hard - "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -35468,13 +33626,6 @@ __metadata: languageName: node linkType: hard -"resolve.exports@npm:^1.1.0": - version: 1.1.0 - resolution: "resolve.exports@npm:1.1.0" - checksum: 52865af8edb088f6c7759a328584a5de6b226754f004b742523adcfe398cfbc4559515104bc2ae87b8e78b1e4de46c9baec400b3fb1f7d517b86d2d48a098a2d - languageName: node - linkType: hard - "resolve@npm:^1.1.7, resolve@npm:^1.14.2, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.22.1, resolve@npm:^1.3.2": version: 1.22.1 resolution: "resolve@npm:1.22.1" @@ -35547,15 +33698,6 @@ __metadata: languageName: node linkType: hard -"responselike@npm:1.0.2": - version: 1.0.2 - resolution: "responselike@npm:1.0.2" - dependencies: - lowercase-keys: ^1.0.0 - checksum: 2e9e70f1dcca3da621a80ce71f2f9a9cad12c047145c6ece20df22f0743f051cf7c73505e109814915f23f9e34fb0d358e22827723ee3d56b623533cab8eafcd - languageName: node - linkType: hard - "responselike@npm:^2.0.0": version: 2.0.1 resolution: "responselike@npm:2.0.1" @@ -35645,7 +33787,7 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": +"rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" dependencies: @@ -35756,6 +33898,20 @@ __metadata: languageName: node linkType: hard +"rollup@npm:^3.21.0": + version: 3.22.0 + resolution: "rollup@npm:3.22.0" + dependencies: + fsevents: ~2.3.2 + dependenciesMeta: + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 6da2bfe1eb16714ccd4bf56ca92430309dc71b4492a3c38502f68f645433dcc789fb79f3761fc2269aa0cf20ece7821c4d7c95b860b7343c2ed52ed1c36c9a90 + languageName: node + linkType: hard + "rootpath@npm:^0.1.2": version: 0.1.2 resolution: "rootpath@npm:0.1.2" @@ -35791,6 +33947,13 @@ __metadata: languageName: node linkType: hard +"rrweb-cssom@npm:^0.6.0": + version: 0.6.0 + resolution: "rrweb-cssom@npm:0.6.0" + checksum: 182312f6e4f41d18230ccc34f14263bc8e8a6b9d30ee3ec0d2d8e643c6f27964cd7a8d638d4a00e988d93e8dc55369f4ab5a473ccfeff7a8bab95b36d2b5499c + languageName: node + linkType: hard + "rsvp@npm:^4.8.4": version: 4.8.5 resolution: "rsvp@npm:4.8.5" @@ -36035,12 +34198,12 @@ __metadata: languageName: node linkType: hard -"saxes@npm:^5.0.1": - version: 5.0.1 - resolution: "saxes@npm:5.0.1" +"saxes@npm:^6.0.0": + version: 6.0.0 + resolution: "saxes@npm:6.0.0" dependencies: xmlchars: ^2.2.0 - checksum: 5636b55cf15f7cf0baa73f2797bf992bdcf75d1b39d82c0aa4608555c774368f6ac321cb641fd5f3d3ceb87805122cd47540da6a7b5960fe0dbdb8f8c263f000 + checksum: d3fa3e2aaf6c65ed52ee993aff1891fc47d5e47d515164b5449cbf5da2cbdc396137e55590472e64c5c436c14ae64a8a03c29b9e7389fc6f14035cf4e982ef3b languageName: node linkType: hard @@ -36164,18 +34327,6 @@ __metadata: languageName: node linkType: hard -"seek-bzip@npm:^1.0.5": - version: 1.0.6 - resolution: "seek-bzip@npm:1.0.6" - dependencies: - commander: ^2.8.1 - bin: - seek-bunzip: bin/seek-bunzip - seek-table: bin/seek-bzip-table - checksum: c2ab3291e7085558499efd4e99d1466ee6782f6c4a4e4c417aa859e1cd2f5117fb3b5444f3d27c38ec5908c0f0312e2a0bc69dff087751f97b3921b5bde4f9ed - languageName: node - linkType: hard - "semver@npm:2 || 3 || 4 || 5, semver@npm:^5.4.1, semver@npm:^5.5.0, semver@npm:^5.6.0": version: 5.7.1 resolution: "semver@npm:5.7.1" @@ -36194,17 +34345,6 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.x, semver@npm:^7.3.2": - version: 7.3.5 - resolution: "semver@npm:7.3.5" - dependencies: - lru-cache: ^6.0.0 - bin: - semver: bin/semver.js - checksum: 5eafe6102bea2a7439897c1856362e31cc348ccf96efd455c8b5bc2c61e6f7e7b8250dc26b8828c1d76a56f818a7ee907a36ae9fb37a599d3d24609207001d60 - languageName: node - linkType: hard - "semver@npm:^6.0.0, semver@npm:^6.1.1, semver@npm:^6.1.2, semver@npm:^6.3.0": version: 6.3.0 resolution: "semver@npm:6.3.0" @@ -36225,6 +34365,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.3.2": + version: 7.3.5 + resolution: "semver@npm:7.3.5" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 5eafe6102bea2a7439897c1856362e31cc348ccf96efd455c8b5bc2c61e6f7e7b8250dc26b8828c1d76a56f818a7ee907a36ae9fb37a599d3d24609207001d60 + languageName: node + linkType: hard + "semver@npm:~2.3.1": version: 2.3.2 resolution: "semver@npm:2.3.2" @@ -36520,6 +34671,13 @@ __metadata: languageName: node linkType: hard +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 8aa5a98640ca09fe00d74416eca97551b3e42991614a3d1b824b115fc1401543650914f651ab1311518177e4d297e80b953f4cd4cd7ea1eabe824e8f2091de01 + languageName: node + linkType: hard + "signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -36705,33 +34863,6 @@ __metadata: languageName: node linkType: hard -"sort-keys-length@npm:^1.0.0": - version: 1.0.1 - resolution: "sort-keys-length@npm:1.0.1" - dependencies: - sort-keys: ^1.0.0 - checksum: f9acac5fb31580a9e3d43b419dc86a1b75e85b79036a084d95dd4d1062b621c9589906588ac31e370a0dd381be46d8dbe900efa306d087ca9c912d7a59b5a590 - languageName: node - linkType: hard - -"sort-keys@npm:^1.0.0": - version: 1.1.2 - resolution: "sort-keys@npm:1.1.2" - dependencies: - is-plain-obj: ^1.0.0 - checksum: 5963fd191a2a185a5ec86f06e47721e8e04713eda43bb04ae60d2a8afb21241553dd5bc9d863ed2bd7c3d541b609b0c8d0e58836b1a3eb6764c09c094bcc8b00 - languageName: node - linkType: hard - -"sort-keys@npm:^2.0.0": - version: 2.0.0 - resolution: "sort-keys@npm:2.0.0" - dependencies: - is-plain-obj: ^1.0.0 - checksum: f0fd827fa9f8f866e98588d2a38c35209afbf1e9a05bb0e4ceeeb8bbf31d923c8902b0a7e0f561590ddb65e58eba6a74f74b991c85360bcc52e83a3f0d1cffd7 - languageName: node - linkType: hard - "sorted-array-functions@npm:^1.3.0": version: 1.3.0 resolution: "sorted-array-functions@npm:1.3.0" @@ -36766,16 +34897,6 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:0.5.13": - version: 0.5.13 - resolution: "source-map-support@npm:0.5.13" - dependencies: - buffer-from: ^1.0.0 - source-map: ^0.6.0 - checksum: 933550047b6c1a2328599a21d8b7666507427c0f5ef5eaadd56b5da0fd9505e239053c66fe181bf1df469a3b7af9d775778eee283cbb7ae16b902ddc09e93a97 - languageName: node - linkType: hard - "source-map-support@npm:^0.5.16, source-map-support@npm:^0.5.21, source-map-support@npm:~0.5.12, source-map-support@npm:~0.5.20": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" @@ -36807,7 +34928,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.7.0, source-map@npm:^0.7.3": +"source-map@npm:^0.7.3": version: 0.7.3 resolution: "source-map@npm:0.7.3" checksum: cd24efb3b8fa69b64bf28e3c1b1a500de77e84260c5b7f2b873f88284df17974157cc88d386ee9b6d081f08fdd8242f3fc05c953685a6ad81aad94c7393dedea @@ -37017,7 +35138,7 @@ __metadata: languageName: node linkType: hard -"stack-utils@npm:^2.0.2, stack-utils@npm:^2.0.3": +"stack-utils@npm:^2.0.2": version: 2.0.5 resolution: "stack-utils@npm:2.0.5" dependencies: @@ -37026,6 +35147,13 @@ __metadata: languageName: node linkType: hard +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 2d4dc4e64e2db796de4a3c856d5943daccdfa3dd092e452a1ce059c81e9a9c29e0b9badba91b43ef0d5ff5c04ee62feb3bcc559a804e16faf447bac2d883aa99 + languageName: node + linkType: hard + "stackframe@npm:^1.3.4": version: 1.3.4 resolution: "stackframe@npm:1.3.4" @@ -37073,6 +35201,13 @@ __metadata: languageName: node linkType: hard +"std-env@npm:^3.3.2": + version: 3.3.3 + resolution: "std-env@npm:3.3.3" + checksum: 6665f6d8bd63aae432d3eb9abbd7322847ad0d902603e6dce1e8051b4f42ceeb4f7f96a4faf70bb05ce65ceee2dc982502b701575c8a58b1bfad29f3dbb19f81 + languageName: node + linkType: hard + "stoppable@npm:^1.1.0": version: 1.1.0 resolution: "stoppable@npm:1.1.0" @@ -37290,26 +35425,6 @@ __metadata: languageName: node linkType: hard -"string-length@npm:^4.0.1": - version: 4.0.2 - resolution: "string-length@npm:4.0.2" - dependencies: - char-regex: ^1.0.2 - strip-ansi: ^6.0.0 - checksum: ce85533ef5113fcb7e522bcf9e62cb33871aa99b3729cec5595f4447f660b0cefd542ca6df4150c97a677d58b0cb727a3fe09ac1de94071d05526c73579bf505 - languageName: node - linkType: hard - -"string-length@npm:^5.0.1": - version: 5.0.1 - resolution: "string-length@npm:5.0.1" - dependencies: - char-regex: ^2.0.0 - strip-ansi: ^7.0.1 - checksum: 71f73b8c8a743e01dcd001bcf1b197db78d5e5e53b12bd898cddaf0961be09f947dfd8c429783db3694b55b05cb5a51de6406c5085ff1aaa10c4771440c8396d - languageName: node - linkType: hard - "string-range@npm:~1.2, string-range@npm:~1.2.1": version: 1.2.2 resolution: "string-range@npm:1.2.2" @@ -37558,22 +35673,6 @@ __metadata: languageName: node linkType: hard -"strip-bom@npm:^4.0.0": - version: 4.0.0 - resolution: "strip-bom@npm:4.0.0" - checksum: 9dbcfbaf503c57c06af15fe2c8176fb1bf3af5ff65003851a102749f875a6dbe0ab3b30115eccf6e805e9d756830d3e40ec508b62b3f1ddf3761a20ebe29d3f3 - languageName: node - linkType: hard - -"strip-dirs@npm:^2.0.0": - version: 2.1.0 - resolution: "strip-dirs@npm:2.1.0" - dependencies: - is-natural-number: ^4.0.1 - checksum: 9465547d71d8819daa7a5c9d4d783289ed8eac72eb06bd687bed382ce62af8ab8e6ffbda229805f5d2e71acce2ca4915e781c94190d284994cbc0b7cdc8303cc - languageName: node - linkType: hard - "strip-eof@npm:^1.0.0": version: 1.0.0 resolution: "strip-eof@npm:1.0.0" @@ -37624,19 +35723,12 @@ __metadata: languageName: node linkType: hard -"strip-markdown@npm:^4.0.0": - version: 4.2.0 - resolution: "strip-markdown@npm:4.2.0" - checksum: 85bf852685b9353791073774fa91730beb26f8e122307241f812e5731227eb01829a3979e7606b922c3a83a8a69a1e4c0218cb02ab6025e55c37a75a14472ce6 - languageName: node - linkType: hard - -"strip-outer@npm:^1.0.0": +"strip-literal@npm:^1.0.1": version: 1.0.1 - resolution: "strip-outer@npm:1.0.1" + resolution: "strip-literal@npm:1.0.1" dependencies: - escape-string-regexp: ^1.0.2 - checksum: f8d65d33ca2b49aabc66bb41d689dda7b8b9959d320e3a40a2ef4d7079ff2f67ffb72db43f179f48dbf9495c2e33742863feab7a584d180fa62505439162c191 + acorn: ^8.8.2 + checksum: ab40496820f02220390d95cdd620a997168efb69d5bd7d180bc4ef83ca562a95447843d8c7c88b8284879a29cf4eedc89d8001d1e098c1a1e23d12a9c755dff4 languageName: node linkType: hard @@ -37789,15 +35881,6 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^4.0.0": - version: 4.5.0 - resolution: "supports-color@npm:4.5.0" - dependencies: - has-flag: ^2.0.0 - checksum: 6da4f498d5c71e8619f06e4a11d16f044105faf7590b5b005fc84933fbefdf72c2b4e5b7174c66da6ddc68e7f6ef56cc960a5ebd6f2d542d910e259e61b02335 - languageName: node - linkType: hard - "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -37832,16 +35915,6 @@ __metadata: languageName: node linkType: hard -"supports-hyperlinks@npm:^2.0.0": - version: 2.2.0 - resolution: "supports-hyperlinks@npm:2.2.0" - dependencies: - has-flag: ^4.0.0 - supports-color: ^7.0.0 - checksum: aef04fb41f4a67f1bc128f7c3e88a81b6cf2794c800fccf137006efe5bafde281da3e42e72bf9206c2fcf42e6438f37e3a820a389214d0a88613ca1f2d36076a - languageName: node - linkType: hard - "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -38171,21 +36244,6 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^1.5.2": - version: 1.6.2 - resolution: "tar-stream@npm:1.6.2" - dependencies: - bl: ^1.0.0 - buffer-alloc: ^1.2.0 - end-of-stream: ^1.0.0 - fs-constants: ^1.0.0 - readable-stream: ^2.3.0 - to-buffer: ^1.1.1 - xtend: ^4.0.0 - checksum: a5d49e232d3e33321bbd150381b6a4e5046bf12b1c2618acb95435b7871efde4d98bd1891eb2200478a7142ef7e304e033eb29bbcbc90451a2cdfa1890e05245 - languageName: node - linkType: hard - "tar@npm:^4.0.2": version: 4.4.19 resolution: "tar@npm:4.4.19" @@ -38286,16 +36344,6 @@ __metadata: languageName: node linkType: hard -"terminal-link@npm:^2.0.0": - version: 2.1.1 - resolution: "terminal-link@npm:2.1.1" - dependencies: - ansi-escapes: ^4.2.1 - supports-hyperlinks: ^2.0.0 - checksum: ce3d2cd3a438c4a9453947aa664581519173ea40e77e2534d08c088ee6dda449eabdbe0a76d2a516b8b73c33262fedd10d5270ccf7576ae316e3db170ce6562f - languageName: node - linkType: hard - "terser-webpack-plugin@npm:^1.4.3": version: 1.4.5 resolution: "terser-webpack-plugin@npm:1.4.5" @@ -38486,6 +36534,13 @@ __metadata: languageName: node linkType: hard +"time-zone@npm:^1.0.0": + version: 1.0.0 + resolution: "time-zone@npm:1.0.0" + checksum: e46f5a69b8c236dcd8e91e29d40d4e7a3495ed4f59888c3f84ce1d9678e20461421a6ba41233509d47dd94bc18f1a4377764838b21b584663f942b3426dcbce8 + languageName: node + linkType: hard + "timed-out@npm:^4.0.0, timed-out@npm:^4.0.1": version: 4.0.1 resolution: "timed-out@npm:4.0.1" @@ -38547,6 +36602,13 @@ __metadata: languageName: node linkType: hard +"tinybench@npm:^2.5.0": + version: 2.5.0 + resolution: "tinybench@npm:2.5.0" + checksum: 284bb9428f197ec8b869c543181315e65e41ccfdad3c4b6c916bb1fdae1b5c6785661b0d90cf135b48d833b03cb84dc5357b2d33ec65a1f5971fae0ab2023821 + languageName: node + linkType: hard + "tinycolor2@npm:^1.0.0, tinycolor2@npm:^1.4.1": version: 1.4.2 resolution: "tinycolor2@npm:1.4.2" @@ -38564,6 +36626,20 @@ __metadata: languageName: node linkType: hard +"tinypool@npm:^0.5.0": + version: 0.5.0 + resolution: "tinypool@npm:0.5.0" + checksum: 4e0dfd8f28666d541c1d92304222edc4613f05d74fe2243c8520d466a2cc6596011a7072c1c41a7de7522351b82fda07e8038198e8f43673d8d69401c5903f8c + languageName: node + linkType: hard + +"tinyspy@npm:^2.1.0": + version: 2.1.0 + resolution: "tinyspy@npm:2.1.0" + checksum: cb83c1f74a79dd5934018bad94f60a304a29d98a2d909ea45fc367f7b80b21b0a7d8135a2ce588deb2b3ba56c7c607258b2a03e6001d89e4d564f9a95cc6a81f + languageName: node + linkType: hard + "title-case@npm:^1.1.0": version: 1.1.2 resolution: "title-case@npm:1.1.2" @@ -38574,27 +36650,6 @@ __metadata: languageName: node linkType: hard -"title@npm:^3.4.2": - version: 3.4.4 - resolution: "title@npm:3.4.4" - dependencies: - arg: 1.0.0 - chalk: 2.3.0 - clipboardy: 1.2.2 - titleize: 1.0.0 - bin: - title: bin/title.js - checksum: bc2a8728f1fff4fa29062c6fb9652ee37e46d13728661e1fd5197d74ebb2ebe122bf4c142828c2c86d95f135e653d3374c1918df35120b1eb68f357ad7669b5f - languageName: node - linkType: hard - -"titleize@npm:1.0.0": - version: 1.0.0 - resolution: "titleize@npm:1.0.0" - checksum: a8234b7860077f7d391d06e13c645fe2bf60431012d78ca196d4d73e4768e33cfc3a0dd881bf2f9974c8bd8beeb9d85e7842ec35d267b7c2a06f287de1c4ea7e - languageName: node - linkType: hard - "tmp@npm:^0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -38618,13 +36673,6 @@ __metadata: languageName: node linkType: hard -"to-buffer@npm:^1.1.1": - version: 1.1.1 - resolution: "to-buffer@npm:1.1.1" - checksum: 6c897f58c2bdd8b8b1645ea515297732fec6dafb089bf36d12370c102ff5d64abf2be9410e0b1b7cfc707bada22d9a4084558010bfc78dd7023748dc5dd9a1ce - languageName: node - linkType: hard - "to-fast-properties@npm:^2.0.0": version: 2.0.0 resolution: "to-fast-properties@npm:2.0.0" @@ -38707,7 +36755,7 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:*": +"tough-cookie@npm:*, tough-cookie@npm:^4.1.2": version: 4.1.2 resolution: "tough-cookie@npm:4.1.2" dependencies: @@ -38719,17 +36767,6 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:^4.0.0": - version: 4.0.0 - resolution: "tough-cookie@npm:4.0.0" - dependencies: - psl: ^1.1.33 - punycode: ^2.1.1 - universalify: ^0.1.2 - checksum: 0891b37eb7d17faa3479d47f0dce2e3007f2583094ad272f2670d120fbcc3df3b0b0a631ba96ecad49f9e2297d93ff8995ce0d3292d08dd7eabe162f5b224d69 - languageName: node - linkType: hard - "tough-cookie@npm:~2.5.0": version: 2.5.0 resolution: "tough-cookie@npm:2.5.0" @@ -38749,6 +36786,15 @@ __metadata: languageName: node linkType: hard +"tr46@npm:^4.1.1": + version: 4.1.1 + resolution: "tr46@npm:4.1.1" + dependencies: + punycode: ^2.3.0 + checksum: aeeb821ac2cd792e63ec84888b4fd6598ac6ed75d861579e21a5cf9d4ee78b2c6b94e7d45036f2ca2088bc85b9b46560ad23c4482979421063b24137349dbd96 + languageName: node + linkType: hard + "tr46@npm:~0.0.3": version: 0.0.3 resolution: "tr46@npm:0.0.3" @@ -38786,15 +36832,6 @@ __metadata: languageName: node linkType: hard -"trim-repeated@npm:^1.0.0": - version: 1.0.0 - resolution: "trim-repeated@npm:1.0.0" - dependencies: - escape-string-regexp: ^1.0.2 - checksum: e25c235305b82c43f1d64a67a71226c406b00281755e4c2c4f3b1d0b09c687a535dd3c4483327f949f28bb89dc400a0bc5e5b749054f4b99f49ebfe48ba36496 - languageName: node - linkType: hard - "trim-trailing-lines@npm:^1.0.0": version: 1.1.4 resolution: "trim-trailing-lines@npm:1.1.4" @@ -38830,12 +36867,12 @@ __metadata: languageName: node linkType: hard -"ts-essentials@npm:^7.0.3": - version: 7.0.3 - resolution: "ts-essentials@npm:7.0.3" +"ts-essentials@npm:^9.3.1": + version: 9.3.2 + resolution: "ts-essentials@npm:9.3.2" peerDependencies: - typescript: ">=3.7.0" - checksum: 74d75868acf7f8b95e447d8b3b7442ca21738c6894e576df9917a352423fde5eb43c5651da5f78997da6061458160ae1f6b279150b42f47ccc58b73e55acaa2f + typescript: ">=4.1.0" + checksum: 88726b3f9bdc8f59f9a998bb925c989268d7e2f24d29ac13545e457d3ac8ee5472c78990eaa415b4c58244d55b989985a999345d6845055f5a58bb0f098c6f44 languageName: node linkType: hard @@ -38846,39 +36883,6 @@ __metadata: languageName: node linkType: hard -"ts-jest@npm:^28.0.8": - version: 28.0.8 - resolution: "ts-jest@npm:28.0.8" - dependencies: - bs-logger: 0.x - fast-json-stable-stringify: 2.x - jest-util: ^28.0.0 - json5: ^2.2.1 - lodash.memoize: 4.x - make-error: 1.x - semver: 7.x - yargs-parser: ^21.0.1 - peerDependencies: - "@babel/core": ">=7.0.0-beta.0 <8" - "@jest/types": ^28.0.0 - babel-jest: ^28.0.0 - jest: ^28.0.0 - typescript: ">=4.3" - peerDependenciesMeta: - "@babel/core": - optional: true - "@jest/types": - optional: true - babel-jest: - optional: true - esbuild: - optional: true - bin: - ts-jest: cli.js - checksum: c72e9292709e77ce47ac7813cb24feaa9d01dc983598d29a821f224b5cc190dc7d67e17379cef089095404c00b9d582ee91c727916f9ec289cb1b723df408ae3 - languageName: node - linkType: hard - "ts-morph@npm:^13.0.2": version: 13.0.3 resolution: "ts-morph@npm:13.0.3" @@ -39259,7 +37263,7 @@ __metadata: languageName: node linkType: hard -"type-detect@npm:4.0.8": +"type-detect@npm:^4.0.0, type-detect@npm:^4.0.5": version: 4.0.8 resolution: "type-detect@npm:4.0.8" checksum: 62b5628bff67c0eb0b66afa371bd73e230399a8d2ad30d852716efcc4656a7516904570cd8631a49a3ce57c10225adf5d0cbdcb47f6b0255fe6557c453925a15 @@ -39329,7 +37333,7 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^1.0.2, type-fest@npm:^1.2.2": +"type-fest@npm:^1.2.2": version: 1.4.0 resolution: "type-fest@npm:1.4.0" checksum: b011c3388665b097ae6a109a437a04d6f61d81b7357f74cbcb02246f2f5bd72b888ae33631b99871388122ba0a87f4ff1c94078e7119ff22c70e52c0ff828201 @@ -39533,6 +37537,13 @@ __metadata: languageName: node linkType: hard +"ufo@npm:^1.1.2": + version: 1.1.2 + resolution: "ufo@npm:1.1.2" + checksum: 83c940a6a23b6d4fc0cd116265bb5dcf88ab34a408ad9196e413270ca607a4781c09b547dc518f43caee128a096f20fe80b5a0e62b4bcc0a868619896106d048 + languageName: node + linkType: hard + "uglify-js@npm:^3.1.4": version: 3.15.3 resolution: "uglify-js@npm:3.15.3" @@ -39573,16 +37584,6 @@ __metadata: languageName: node linkType: hard -"unbzip2-stream@npm:^1.0.9": - version: 1.4.3 - resolution: "unbzip2-stream@npm:1.4.3" - dependencies: - buffer: ^5.2.1 - through: ^2.3.8 - checksum: 0e67c4a91f4fa0fc7b4045f8b914d3498c2fc2e8c39c359977708ec85ac6d6029840e97f508675fdbdf21fcb8d276ca502043406f3682b70f075e69aae626d1d - languageName: node - linkType: hard - "unfetch@npm:^4.2.0": version: 4.2.0 resolution: "unfetch@npm:4.2.0" @@ -39670,20 +37671,6 @@ __metadata: languageName: node linkType: hard -"unified@npm:^9.1.0": - version: 9.2.2 - resolution: "unified@npm:9.2.2" - dependencies: - bail: ^1.0.0 - extend: ^3.0.0 - is-buffer: ^2.0.0 - is-plain-obj: ^2.0.0 - trough: ^1.0.0 - vfile: ^4.0.0 - checksum: 7c24461be7de4145939739ce50d18227c5fbdf9b3bc5a29dabb1ce26dd3e8bd4a1c385865f6f825f3b49230953ee8b591f23beab3bb3643e3e9dc37aa8a089d5 - languageName: node - linkType: hard - "union-value@npm:^1.0.0": version: 1.0.1 resolution: "union-value@npm:1.0.1" @@ -39776,15 +37763,6 @@ __metadata: languageName: node linkType: hard -"unist-util-position-from-estree@npm:^1.0.0, unist-util-position-from-estree@npm:^1.1.0": - version: 1.1.1 - resolution: "unist-util-position-from-estree@npm:1.1.1" - dependencies: - "@types/unist": ^2.0.0 - checksum: 63808bdcb8b49afa5231712d95b586fe877859ee03d23adb47485c30222007a5af55e95d103d4af51d1d16376aaa5a58fa985a08d63727c38b1515873df8b79b - languageName: node - linkType: hard - "unist-util-position@npm:^3.0.0": version: 3.1.0 resolution: "unist-util-position@npm:3.1.0" @@ -39810,16 +37788,6 @@ __metadata: languageName: node linkType: hard -"unist-util-remove-position@npm:^4.0.0": - version: 4.0.1 - resolution: "unist-util-remove-position@npm:4.0.1" - dependencies: - "@types/unist": ^2.0.0 - unist-util-visit: ^4.0.0 - checksum: 7d2808662ac65f2b2f615822b78060419f738fb3b074b10cec77c596ea966b8f5c47553d2d322822a5975c49d2b21cdd64c198ae9fb02a9d54d1afa6342cdd6a - languageName: node - linkType: hard - "unist-util-remove@npm:^2.0.0": version: 2.1.0 resolution: "unist-util-remove@npm:2.1.0" @@ -40078,15 +38046,6 @@ __metadata: languageName: node linkType: hard -"url-parse-lax@npm:^3.0.0": - version: 3.0.0 - resolution: "url-parse-lax@npm:3.0.0" - dependencies: - prepend-http: ^2.0.0 - checksum: 1040e357750451173132228036aff1fd04abbd43eac1fb3e4fca7495a078bcb8d33cb765fe71ad7e473d9c94d98fd67adca63bd2716c815a2da066198dd37217 - languageName: node - linkType: hard - "url-parse@npm:^1.4.3, url-parse@npm:^1.5.3, url-parse@npm:^1.5.8, url-parse@npm:^1.5.9": version: 1.5.10 resolution: "url-parse@npm:1.5.10" @@ -40384,7 +38343,7 @@ __metadata: languageName: node linkType: hard -"v8-to-istanbul@npm:^9.0.0, v8-to-istanbul@npm:^9.0.1": +"v8-to-istanbul@npm:^9.0.0": version: 9.0.1 resolution: "v8-to-istanbul@npm:9.0.1" dependencies: @@ -40520,6 +38479,22 @@ __metadata: languageName: node linkType: hard +"vite-node@npm:0.31.1": + version: 0.31.1 + resolution: "vite-node@npm:0.31.1" + dependencies: + cac: ^6.7.14 + debug: ^4.3.4 + mlly: ^1.2.0 + pathe: ^1.1.0 + picocolors: ^1.0.0 + vite: ^3.0.0 || ^4.0.0 + bin: + vite-node: vite-node.mjs + checksum: f70ffa3f6dcb4937cdc99f59bf360d42de83c556ba9a19eb1c3504ef20db4c1d1afa644d9a8e63240e851c0c95773b64c526bdb3eb4794b5e941ddcd57124aa9 + languageName: node + linkType: hard + "vite@npm:^2.9.15": version: 2.9.15 resolution: "vite@npm:2.9.15" @@ -40549,6 +38524,43 @@ __metadata: languageName: node linkType: hard +"vite@npm:^3.0.0 || ^4.0.0": + version: 4.3.8 + resolution: "vite@npm:4.3.8" + dependencies: + esbuild: ^0.17.5 + fsevents: ~2.3.2 + postcss: ^8.4.23 + rollup: ^3.21.0 + peerDependencies: + "@types/node": ">= 14" + less: "*" + sass: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 454a7c0c1bd1fd5611c9df28c62e3adbe75f48e87fc787179c5af60c4ab9a87aa0eda44be446d898851a135766d36f65f8e7d56317556aa807d30e561de369c4 + languageName: node + linkType: hard + "vite@npm:^3.2.4": version: 3.2.5 resolution: "vite@npm:3.2.5" @@ -40625,6 +38637,79 @@ __metadata: languageName: node linkType: hard +"vitest-mock-extended@npm:^1.1.3": + version: 1.1.3 + resolution: "vitest-mock-extended@npm:1.1.3" + dependencies: + ts-essentials: ^9.3.1 + peerDependencies: + typescript: 3.x || 4.x || 5.x + vitest: ">=0.29.2" + checksum: d457dc1dcbf6b0570e9845dff7423c2b88504ce6bb189e71c0984b09b4d6c5894d883706a7639262c92e12b0b4b0d5db747e1d0b93e253b4d6c189cba15c70f9 + languageName: node + linkType: hard + +"vitest@npm:^0.31.1": + version: 0.31.1 + resolution: "vitest@npm:0.31.1" + dependencies: + "@types/chai": ^4.3.5 + "@types/chai-subset": ^1.3.3 + "@types/node": "*" + "@vitest/expect": 0.31.1 + "@vitest/runner": 0.31.1 + "@vitest/snapshot": 0.31.1 + "@vitest/spy": 0.31.1 + "@vitest/utils": 0.31.1 + acorn: ^8.8.2 + acorn-walk: ^8.2.0 + cac: ^6.7.14 + chai: ^4.3.7 + concordance: ^5.0.4 + debug: ^4.3.4 + local-pkg: ^0.4.3 + magic-string: ^0.30.0 + pathe: ^1.1.0 + picocolors: ^1.0.0 + std-env: ^3.3.2 + strip-literal: ^1.0.1 + tinybench: ^2.5.0 + tinypool: ^0.5.0 + vite: ^3.0.0 || ^4.0.0 + vite-node: 0.31.1 + why-is-node-running: ^2.2.2 + peerDependencies: + "@edge-runtime/vm": "*" + "@vitest/browser": "*" + "@vitest/ui": "*" + happy-dom: "*" + jsdom: "*" + playwright: "*" + safaridriver: "*" + webdriverio: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + bin: + vitest: vitest.mjs + checksum: b3f64a36102edc5b8594c085da648c838c0d275c620bd3b780624f936903b9c06579d6ef137fe9859e468f16deb8f154a50f009093119f9adb8b60ff1b7597ee + languageName: node + linkType: hard + "vm-browserify@npm:^1.0.1": version: 1.1.2 resolution: "vm-browserify@npm:1.1.2" @@ -40639,21 +38724,12 @@ __metadata: languageName: node linkType: hard -"w3c-hr-time@npm:^1.0.2": - version: 1.0.2 - resolution: "w3c-hr-time@npm:1.0.2" - dependencies: - browser-process-hrtime: ^1.0.0 - checksum: ec3c2dacbf8050d917bbf89537a101a08c2e333b4c19155f7d3bedde43529d4339db6b3d049d9610789cb915f9515f8be037e0c54c079e9d4735c50b37ed52b9 - languageName: node - linkType: hard - -"w3c-xmlserializer@npm:^3.0.0": - version: 3.0.0 - resolution: "w3c-xmlserializer@npm:3.0.0" +"w3c-xmlserializer@npm:^4.0.0": + version: 4.0.0 + resolution: "w3c-xmlserializer@npm:4.0.0" dependencies: xml-name-validator: ^4.0.0 - checksum: 0af8589942eeb11c9fe29eb31a1a09f3d5dd136aea53a9848dfbabff79ac0dd26fe13eb54d330d5555fe27bb50b28dca0715e09f9cc2bfa7670ccc8b7f919ca2 + checksum: eba070e78deb408ae8defa4d36b429f084b2b47a4741c4a9be3f27a0a3d1845e277e3072b04391a138f7e43776842627d1334e448ff13ff90ad9fb1214ee7091 languageName: node linkType: hard @@ -40690,7 +38766,7 @@ __metadata: languageName: node linkType: hard -"walker@npm:^1.0.7, walker@npm:^1.0.8, walker@npm:~1.0.5": +"walker@npm:^1.0.7, walker@npm:~1.0.5": version: 1.0.8 resolution: "walker@npm:1.0.8" dependencies: @@ -41332,6 +39408,13 @@ __metadata: languageName: node linkType: hard +"well-known-symbols@npm:^2.0.0": + version: 2.0.0 + resolution: "well-known-symbols@npm:2.0.0" + checksum: 4f54bbc3012371cb4d228f436891b8e7536d34ac61a57541890257e96788608e096231e0121ac24d08ef2f908b3eb2dc0adba35023eaeb2a7df655da91415402 + languageName: node + linkType: hard + "whatwg-encoding@npm:^2.0.0": version: 2.0.0 resolution: "whatwg-encoding@npm:2.0.0" @@ -41366,16 +39449,6 @@ __metadata: languageName: node linkType: hard -"whatwg-url@npm:^10.0.0": - version: 10.0.0 - resolution: "whatwg-url@npm:10.0.0" - dependencies: - tr46: ^3.0.0 - webidl-conversions: ^7.0.0 - checksum: a21ec309c5cc743fe9414509408bedf65eaf0fb5c17ac66baa08ef12fce16da4dd30ce90abefbd5a716408301c58a73666dabfd5042cf4242992eb98b954f861 - languageName: node - linkType: hard - "whatwg-url@npm:^11.0.0": version: 11.0.0 resolution: "whatwg-url@npm:11.0.0" @@ -41386,6 +39459,16 @@ __metadata: languageName: node linkType: hard +"whatwg-url@npm:^12.0.0, whatwg-url@npm:^12.0.1": + version: 12.0.1 + resolution: "whatwg-url@npm:12.0.1" + dependencies: + tr46: ^4.1.1 + webidl-conversions: ^7.0.0 + checksum: 8698993b763c1e7eda5ed16c31dab24bca6489626aca7caf8b5a2b64684dda6578194786f10ec42ceb1c175feea16d0a915096e6419e08d154ce551c43176972 + languageName: node + linkType: hard + "whatwg-url@npm:^5.0.0": version: 5.0.0 resolution: "whatwg-url@npm:5.0.0" @@ -41462,6 +39545,18 @@ __metadata: languageName: node linkType: hard +"why-is-node-running@npm:^2.2.2": + version: 2.2.2 + resolution: "why-is-node-running@npm:2.2.2" + dependencies: + siginfo: ^2.0.0 + stackback: 0.0.2 + bin: + why-is-node-running: cli.js + checksum: 50820428f6a82dfc3cbce661570bcae9b658723217359b6037b67e495255409b4c8bc7931745f5c175df71210450464517cab32b2f7458ac9c40b4925065200a + languageName: node + linkType: hard + "wide-align@npm:^1.1.2, wide-align@npm:^1.1.5": version: 1.1.5 resolution: "wide-align@npm:1.1.5" @@ -41564,16 +39659,6 @@ __metadata: languageName: node linkType: hard -"write-file-atomic@npm:^4.0.1": - version: 4.0.1 - resolution: "write-file-atomic@npm:4.0.1" - dependencies: - imurmurhash: ^0.1.4 - signal-exit: ^3.0.7 - checksum: 8f780232533ca6223c63c9b9c01c4386ca8c625ebe5017a9ed17d037aec19462ae17109e0aa155bff5966ee4ae7a27b67a99f55caf3f32ffd84155e9da3929fc - languageName: node - linkType: hard - "ws@npm:7.4.6": version: 7.4.6 resolution: "ws@npm:7.4.6" @@ -41660,6 +39745,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.13.0": + version: 8.13.0 + resolution: "ws@npm:8.13.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 53e991bbf928faf5dc6efac9b8eb9ab6497c69feeb94f963d648b7a3530a720b19ec2e0ec037344257e05a4f35bd9ad04d9de6f289615ffb133282031b18c61c + languageName: node + linkType: hard + "ws@npm:^8.2.3": version: 8.8.0 resolution: "ws@npm:8.8.0" @@ -41977,7 +40077,7 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": +"yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c @@ -42066,16 +40166,6 @@ __metadata: languageName: node linkType: hard -"yauzl@npm:^2.4.2": - version: 2.10.0 - resolution: "yauzl@npm:2.10.0" - dependencies: - buffer-crc32: ~0.2.3 - fd-slicer: ~1.1.0 - checksum: 7f21fe0bbad6e2cb130044a5d1d0d5a0e5bf3d8d4f8c4e6ee12163ce798fee3de7388d22a7a0907f563ac5f9d40f8699a223d3d5c1718da90b0156da6904022b - languageName: node - linkType: hard - "yn@npm:3.1.1": version: 3.1.1 resolution: "yn@npm:3.1.1" @@ -42090,6 +40180,13 @@ __metadata: languageName: node linkType: hard +"yocto-queue@npm:^1.0.0": + version: 1.0.0 + resolution: "yocto-queue@npm:1.0.0" + checksum: 2cac84540f65c64ccc1683c267edce396b26b1e931aa429660aefac8fbe0188167b7aee815a3c22fa59a28a58d898d1a2b1825048f834d8d629f4c2a5d443801 + languageName: node + linkType: hard + "yoga-layout-prebuilt@npm:^1.10.0, yoga-layout-prebuilt@npm:^1.9.6": version: 1.10.0 resolution: "yoga-layout-prebuilt@npm:1.10.0"