Vital App - Auto reschedule based on health data (#2500)
* Add vital integration * Tidy up client_user_id creation * Rename vital app to vitalother to follow name rules * Added env var * App vital reschedule * Fix on app structure and api calls * Implemented user identification from webhook * WIP fix api call and read me * Save vital settings via api * Now saving userVitalSettings and trigger reschedule on selected param * Added translations * Fix type for vitalSettings * Using api to get env vars required for url, fix display of vital settings * Fix hours placeholder, translation not working * Renames vital app * Update seed-app-store.ts * Update package.json * Update yarn.lock * Refactored env variables * Update README.md * Migrates to api_keys * Extracts AppConfiguration * vitalClient fixes * Update index.ts * Update metadata.ts * Update index.ts * Update metadata.ts * Added namespace vital for translations Co-authored-by: Maitham <maithamdib@gmail.com> Co-authored-by: zomars <zomars@me.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
parent
67cc3a6409
commit
8c9096b55b
47 changed files with 2650 additions and 35 deletions
|
@ -71,4 +71,14 @@ ZOOM_CLIENT_SECRET=
|
|||
# Used for the Giphy integration
|
||||
# @see https://support.giphy.com/hc/en-us/articles/360020283431-Request-A-GIPHY-API-Key
|
||||
GIPHY_API_KEY=
|
||||
|
||||
# - VITAL
|
||||
# Used for the vital integration
|
||||
# @see https://github.com/calcom/cal.com/#obtaining-vital-api-keys
|
||||
VITAL_API_KEY=
|
||||
VITAL_WEBHOOK_SECRET=
|
||||
# "sandbox" | "prod" | "production" | "development"
|
||||
VITAL_DEVELOPMENT_MODE="sandbox"
|
||||
# "us" | "eu"
|
||||
VITAL_REGION="us"
|
||||
# *********************************************************************************************************
|
||||
|
|
19
README.md
19
README.md
|
@ -107,7 +107,7 @@ Here is what you need to be able to run Cal.
|
|||
```sh
|
||||
yarn
|
||||
```
|
||||
|
||||
|
||||
1. Use `openssl rand -base64 32` to generate a key and add it under `NEXTAUTH_SECRET` in the .env file.
|
||||
|
||||
#### Quick start with `yarn dx`
|
||||
|
@ -232,9 +232,9 @@ yarn workspace @calcom/web playwright-report
|
|||
|
||||
1. Check for `.env` variables changes
|
||||
|
||||
```sh
|
||||
yarn predev
|
||||
```
|
||||
```sh
|
||||
yarn predev
|
||||
```
|
||||
|
||||
1. Start the server. In a development environment, just do:
|
||||
|
||||
|
@ -403,6 +403,17 @@ Next make sure you have your app running `yarn dx`. Then in the slack chat type
|
|||
9. Click the "Save" button at the bottom footer.
|
||||
10. You're good to go. Now you can see any booking in Cal.com created as a meeting in HubSpot for your contacts.
|
||||
|
||||
### Obtaining Vital API Keys
|
||||
|
||||
1. Open [Vital](https://tryvital.io/) and click Get API Keys.
|
||||
1. Create a team with the team name you desire
|
||||
1. Head to the configuration section on the sidebar of the dashboard
|
||||
1. Click on API keys and you'll find your sandbox `api_key`.
|
||||
1. Copy your `api_key` to `VITAL_API_KEY` in the .env.appStore file.
|
||||
1. Open [Vital Webhooks](https://app.tryvital.io/team/{team_id}/webhooks) and add `<CALCOM BASE URL>/api/integrations/vital/webhook` as webhook for connected applications.
|
||||
1. Select all events for the webhook you interested, e.g. `sleep_created`
|
||||
1. Copy the webhook secret (`sec...`) to `VITAL_WEBHOOK_SECRET` in the .env.appStore file.
|
||||
|
||||
<!-- LICENSE -->
|
||||
|
||||
## License
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
/** @type {import("next-i18next").UserConfig} */
|
||||
const config = {
|
||||
i18n: {
|
||||
defaultLocale: "en",
|
||||
locales: [
|
||||
|
@ -31,3 +32,5 @@ module.exports = {
|
|||
localePath: path.resolve("./public/static/locales"),
|
||||
reloadOnPrerender: process.env.NODE_ENV !== "production",
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
|
|
@ -3,7 +3,7 @@ import Image from "next/image";
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { JSONObject } from "superjson/dist/types";
|
||||
|
||||
import { InstallAppButton } from "@calcom/app-store/components";
|
||||
import { AppConfiguration, InstallAppButton } from "@calcom/app-store/components";
|
||||
import showToast from "@calcom/lib/notification";
|
||||
import { App } from "@calcom/types/App";
|
||||
import { Alert } from "@calcom/ui/Alert";
|
||||
|
@ -161,8 +161,9 @@ function IntegrationsContainer() {
|
|||
isGlobal={item.isGlobal}
|
||||
installed={item.installed}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}>
|
||||
<AppConfiguration type={item.type} credentialIds={item.credentialIds} />
|
||||
</IntegrationListItem>
|
||||
))}
|
||||
</List>
|
||||
</>
|
||||
|
|
13
apps/web/public/static/locales/en/vital.json
Normal file
13
apps/web/public/static/locales/en/vital.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"connected_vital_app": "Connected with",
|
||||
"vital_app_sleep_automation": "Sleeping reschedule automation",
|
||||
"vital_app_automation_description": "You can select different parameters to trigger the reschedule based on your sleeping metrics.",
|
||||
"vital_app_parameter": "Parameter",
|
||||
"vital_app_trigger": "Trigger at below or equal than",
|
||||
"vital_app_save_button": "Save configuration",
|
||||
"vital_app_total_label": "Total (total = rem + light sleep + deep sleep)",
|
||||
"vital_app_duration_label": "Duration (duration = bedtime end - bedtime start)",
|
||||
"vital_app_hours": "hours",
|
||||
"vital_app_save_success": "Success saving your Vital Configurations",
|
||||
"vital_app_save_error": "An error ocurred saving your Vital Configurations"
|
||||
}
|
13
apps/web/public/static/locales/es/vital.json
Normal file
13
apps/web/public/static/locales/es/vital.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"connected_vital_app": "Conectado con",
|
||||
"vital_app_sleep_automation": "Automatización de reagendado en base al patron de sueño",
|
||||
"vital_app_automation_description": "Puedes seleccionar diferentes parámetros para activar el reagendado automático en base a tus patrones de sueño.",
|
||||
"vital_app_parameter": "Parámetro",
|
||||
"vital_app_trigger": "Activar cuando sea igual o menor que",
|
||||
"vital_app_save_button": "Guardar configuración",
|
||||
"vital_app_total_label": "Total (total = rem + sueño ligero + sueño profundo)",
|
||||
"vital_app_duration_label": "Duration (duration = Hora que te levantaste de la cama - Hora que te acostaste en la cama)",
|
||||
"vital_app_hours": "horas",
|
||||
"vital_app_save_success": "Fue un éxito el guardado de tus configuraciones de App Vital",
|
||||
"vital_app_save_error": "Ocurrió un error al intentar guardar tus configuraciones de App Vital"
|
||||
}
|
|
@ -105,7 +105,7 @@ export const createContext = async ({ req }: CreateContextOptions) => {
|
|||
|
||||
const user = await getUserFromSession({ session, req });
|
||||
const locale = user?.locale ?? getLocaleFromHeaders(req);
|
||||
const i18n = await serverSideTranslations(locale, ["common"]);
|
||||
const i18n = await serverSideTranslations(locale, ["common", "vital"]);
|
||||
return {
|
||||
i18n,
|
||||
prisma,
|
||||
|
|
19
packages/app-store/_components/AppConfiguration.tsx
Normal file
19
packages/app-store/_components/AppConfiguration.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import dynamic from "next/dynamic";
|
||||
|
||||
export const ConfigAppMap = {
|
||||
vital: dynamic(() => import("../vital/components/AppConfiguration")),
|
||||
};
|
||||
|
||||
export const AppConfiguration = (props: { type: string } & { credentialIds: number[] }) => {
|
||||
let appName = props.type.replace(/_/g, "");
|
||||
let ConfigAppComponent = ConfigAppMap[appName as keyof typeof ConfigAppMap];
|
||||
/** So we can either call it by simple name (ex. `slack`, `giphy`) instead of
|
||||
* `slackmessaging`, `giphyother` while maintaining retro-compatibility. */
|
||||
if (!ConfigAppComponent) {
|
||||
[appName] = props.type.split("_");
|
||||
ConfigAppComponent = ConfigAppMap[appName as keyof typeof ConfigAppMap];
|
||||
}
|
||||
if (!ConfigAppComponent) return null;
|
||||
|
||||
return <ConfigAppComponent credentialIds={props.credentialIds} />;
|
||||
};
|
|
@ -8,6 +8,7 @@ export const apiHandlers = {
|
|||
slackmessaging: import("./slackmessaging/api"),
|
||||
stripepayment: import("./stripepayment/api"),
|
||||
tandemvideo: import("./tandemvideo/api"),
|
||||
vital: import("./vital/api"),
|
||||
zoomvideo: import("@calcom/zoomvideo/api"),
|
||||
office365video: import("@calcom/office365video/api"),
|
||||
wipemycalother: import("./wipemycalother/api"),
|
||||
|
|
|
@ -27,6 +27,7 @@ export const InstallAppButtonMap = {
|
|||
metamask: dynamic(() => import("./metamask/components/InstallAppButton")),
|
||||
giphy: dynamic(() => import("./giphy/components/InstallAppButton")),
|
||||
spacebookingother: dynamic(() => import("./spacebooking/components/InstallAppButton")),
|
||||
vital: dynamic(() => import("./vital/components/InstallAppButton")),
|
||||
};
|
||||
|
||||
export const InstallAppButton = (
|
||||
|
@ -61,3 +62,5 @@ export const InstallAppButton = (
|
|||
);
|
||||
return <InstallAppButtonComponent render={props.render} onChanged={props.onChanged} />;
|
||||
};
|
||||
|
||||
export { AppConfiguration } from "./_components/AppConfiguration";
|
||||
|
|
|
@ -15,6 +15,7 @@ import * as slackmessaging from "./slackmessaging";
|
|||
import * as spacebooking from "./spacebooking";
|
||||
import * as stripepayment from "./stripepayment";
|
||||
import * as tandemvideo from "./tandemvideo";
|
||||
import * as vital from "./vital";
|
||||
import * as wipemycalother from "./wipemycalother";
|
||||
import * as zapier from "./zapier";
|
||||
import * as zoomvideo from "./zoomvideo";
|
||||
|
@ -35,6 +36,7 @@ const appStore = {
|
|||
stripepayment,
|
||||
spacebooking,
|
||||
tandemvideo,
|
||||
vital,
|
||||
zoomvideo,
|
||||
wipemycalother,
|
||||
metamask,
|
||||
|
|
|
@ -14,6 +14,7 @@ import { metadata as slackmessaging } from "./slackmessaging/_metadata";
|
|||
import { metadata as spacebooking } from "./spacebooking/_metadata";
|
||||
import { metadata as stripepayment } from "./stripepayment/_metadata";
|
||||
import { metadata as tandemvideo } from "./tandemvideo/_metadata";
|
||||
import { metadata as vital } from "./vital/_metadata";
|
||||
import { metadata as wipemycalother } from "./wipemycalother/_metadata";
|
||||
import { metadata as zapier } from "./zapier/_metadata";
|
||||
import { metadata as zoomvideo } from "./zoomvideo/_metadata";
|
||||
|
@ -33,6 +34,7 @@ export const appStoreMetadata = {
|
|||
stripepayment,
|
||||
spacebooking,
|
||||
tandemvideo,
|
||||
vital,
|
||||
zoomvideo,
|
||||
wipemycalother,
|
||||
metamask,
|
||||
|
|
6
packages/app-store/vital/README.mdx
Normal file
6
packages/app-store/vital/README.mdx
Normal file
|
@ -0,0 +1,6 @@
|
|||
Vital App is an app that can can help you combine your health peripherals with your calendar.
|
||||
|
||||
#### Supported Actions:
|
||||
|
||||
Sleep reschedule automation: Had a hard night? 🌕
|
||||
Automatically reschedule your whole day schedule based on your sleep parameters. (Setup your desired configuration on installed apps page.)
|
27
packages/app-store/vital/_metadata.ts
Normal file
27
packages/app-store/vital/_metadata.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import type { App } from "@calcom/types/App";
|
||||
|
||||
import _package from "./package.json";
|
||||
|
||||
export const metadata = {
|
||||
name: "Vital",
|
||||
description: _package.description,
|
||||
installed: true,
|
||||
category: "other",
|
||||
// If using static next public folder, can then be referenced from the base URL (/).
|
||||
imageSrc: "/api/app-store/vital/icon.svg",
|
||||
logo: "/api/app-store/vital/icon.svg",
|
||||
label: "Vital",
|
||||
publisher: "Vital",
|
||||
rating: 5,
|
||||
reviews: 69,
|
||||
slug: "vital-automation",
|
||||
title: "Vital",
|
||||
trending: true,
|
||||
type: "vital_other",
|
||||
url: "https://tryvital.io",
|
||||
variant: "other",
|
||||
verified: true,
|
||||
email: "support@tryvital.io",
|
||||
} as App;
|
||||
|
||||
export default metadata;
|
41
packages/app-store/vital/api/callback.ts
Normal file
41
packages/app-store/vital/api/callback.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { Prisma } from "@prisma/client";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
/**
|
||||
* This is will generate a user token for a client_user_id`
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const userWithMetadata = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: req?.session?.user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
metadata: true,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: req?.session?.user.id,
|
||||
},
|
||||
data: {
|
||||
metadata: {
|
||||
...(userWithMetadata?.metadata as Prisma.JsonObject),
|
||||
vitalSettings: {
|
||||
...((userWithMetadata?.metadata as Prisma.JsonObject)?.vitalSettings as Prisma.JsonObject),
|
||||
connected: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return res.redirect("/apps/installed");
|
||||
} catch (e) {
|
||||
return res.status(500);
|
||||
}
|
||||
}
|
5
packages/app-store/vital/api/index.ts
Normal file
5
packages/app-store/vital/api/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export { default as token } from "./token";
|
||||
export { default as callback } from "./callback";
|
||||
export { default as webhook } from "./webhook";
|
||||
export { default as settings } from "./settings";
|
||||
export { default as save } from "./save";
|
91
packages/app-store/vital/api/save.ts
Normal file
91
packages/app-store/vital/api/save.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
import { Prisma } from "@prisma/client";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { z, ZodError } from "zod";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export type VitalSettingsResponse = {
|
||||
connected: boolean;
|
||||
sleepValue: number;
|
||||
selectedParam: string;
|
||||
};
|
||||
|
||||
const vitalSettingsUpdateSchema = z.object({
|
||||
connected: z.boolean().optional(),
|
||||
selectedParam: z.string().optional(),
|
||||
sleepValue: z.number().optional(),
|
||||
});
|
||||
|
||||
const handler = async (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
): Promise<VitalSettingsResponse | NextApiResponse | void> => {
|
||||
if (req.method === "PUT" && req.session && req.session.user.id) {
|
||||
const userId = req.session.user.id;
|
||||
const body = req.body;
|
||||
try {
|
||||
const userWithMetadata = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
metadata: true,
|
||||
},
|
||||
});
|
||||
const userMetadata = userWithMetadata?.metadata as Prisma.JsonObject;
|
||||
const vitalSettings =
|
||||
((userWithMetadata?.metadata as Prisma.JsonObject)?.vitalSettings as Prisma.JsonObject) || {};
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
data: {
|
||||
metadata: {
|
||||
...userMetadata,
|
||||
vitalSettings: {
|
||||
...vitalSettings,
|
||||
...body,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (vitalSettings) {
|
||||
res.status(200).json(vitalSettings);
|
||||
} else {
|
||||
res.status(404);
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500);
|
||||
}
|
||||
} else {
|
||||
res.status(400);
|
||||
}
|
||||
res.end();
|
||||
};
|
||||
|
||||
function validate(
|
||||
handler: (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) => Promise<VitalSettingsResponse | NextApiResponse | void>
|
||||
) {
|
||||
return async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method === "POST" || req.method === "PUT") {
|
||||
try {
|
||||
vitalSettingsUpdateSchema.parse(req.body);
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError && error?.name === "ZodError") {
|
||||
return res.status(400).json(error?.issues);
|
||||
}
|
||||
return res.status(402);
|
||||
}
|
||||
} else {
|
||||
return res.status(405);
|
||||
}
|
||||
await handler(req, res);
|
||||
};
|
||||
}
|
||||
|
||||
export default validate(handler);
|
29
packages/app-store/vital/api/settings.ts
Normal file
29
packages/app-store/vital/api/settings.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { JSONObject } from "superjson/dist/types";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method === "GET" && req.session && req.session.user.id) {
|
||||
const userId = req.session.user.id;
|
||||
try {
|
||||
const user = await prisma?.user.findFirst({
|
||||
select: {
|
||||
metadata: true,
|
||||
},
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (user && user.metadata && (user.metadata as JSONObject)?.vitalSettings) {
|
||||
res.status(200).json((user.metadata as JSONObject).vitalSettings);
|
||||
} else {
|
||||
res.status(404);
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500);
|
||||
}
|
||||
} else {
|
||||
res.status(400);
|
||||
}
|
||||
res.end();
|
||||
}
|
23
packages/app-store/vital/api/sleep.create.payload.example
Normal file
23
packages/app-store/vital/api/sleep.create.payload.example
Normal file
|
@ -0,0 +1,23 @@
|
|||
https://docs.tryvital.io/summary-data#sleep-stream
|
||||
{
|
||||
event: {
|
||||
event_type: 'daily.data.sleep.created',
|
||||
data: {
|
||||
id: 'a27b89dc-4a43-4f59-b72a-267fa9a93c8c',
|
||||
date: '1974-12-15T18:28:10+00:00',
|
||||
bedtime_start: '1980-07-11T22:10:32+00:00',
|
||||
bedtime_stop: '1993-03-03T04:21:02+00:00',
|
||||
duration: 7225, // seconds
|
||||
total: 6909, // seconds
|
||||
awake: 7763,
|
||||
light: 2011, // Total amount of light sleep registered during the sleep period
|
||||
rem: 8106, // Total amount of REM sleep registered during the sleep period, minutes
|
||||
deep: 6553,
|
||||
hr_lowest: 9085,
|
||||
efficiency: 1780,
|
||||
latency: 8519,
|
||||
source: [Object],
|
||||
user_id: 'a1dfefe0-ccdb-46b8-adac-35e9ba597496'
|
||||
}
|
||||
}
|
||||
}
|
57
packages/app-store/vital/api/token.ts
Normal file
57
packages/app-store/vital/api/token.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { Prisma } from "@prisma/client";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { initVitalClient, vitalEnv } from "../lib/client";
|
||||
|
||||
/**
|
||||
* This is will generate a user token for a client_user_id`
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Get user id
|
||||
const calcomUserId = req.session?.user?.id;
|
||||
if (!calcomUserId) {
|
||||
return res.status(401).json({ message: "You must be logged in to do this" });
|
||||
}
|
||||
|
||||
const vitalClient = await initVitalClient();
|
||||
|
||||
if (!vitalClient || !vitalEnv)
|
||||
return res.status(400).json({ message: "Missing vital client, try calling `initVitalClient`" });
|
||||
|
||||
// Create a user on vital
|
||||
let userVital;
|
||||
try {
|
||||
userVital = await vitalClient.User.create(`cal_${calcomUserId}`);
|
||||
} catch (e) {
|
||||
userVital = await vitalClient.User.resolve(`cal_${calcomUserId}`);
|
||||
}
|
||||
|
||||
try {
|
||||
if (userVital?.user_id) {
|
||||
await prisma.credential.create({
|
||||
data: {
|
||||
type: "vital_other",
|
||||
key: { userVitalId: userVital.user_id } as unknown as Prisma.InputJsonObject,
|
||||
userId: calcomUserId,
|
||||
appId: "vital-automation",
|
||||
},
|
||||
});
|
||||
}
|
||||
const token = await vitalClient.Link.create(
|
||||
userVital?.user_id,
|
||||
undefined,
|
||||
WEBAPP_URL + "/api/integrations/vital/callback"
|
||||
);
|
||||
return res.status(200).json({
|
||||
token: token.link_token,
|
||||
url: `https://link.tryvital.io/?env=${vitalEnv.mode}®ion=${vitalEnv.region}`,
|
||||
});
|
||||
} catch (e) {
|
||||
return res.status(400).json({ error: JSON.stringify(e) });
|
||||
}
|
||||
}
|
158
packages/app-store/vital/api/webhook.ts
Normal file
158
packages/app-store/vital/api/webhook.ts
Normal file
|
@ -0,0 +1,158 @@
|
|||
import { BookingStatus, Prisma } from "@prisma/client";
|
||||
import dayjs from "dayjs";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import queue from "queue";
|
||||
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { HttpError as HttpCode } from "@calcom/lib/http-error";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { Reschedule } from "../lib";
|
||||
import { initVitalClient, vitalEnv } from "../lib/client";
|
||||
|
||||
// @Note: not being used anymore but left as example
|
||||
const getOuraSleepScore = async (user_id: string, bedtime_start: Date) => {
|
||||
const vitalClient = await initVitalClient();
|
||||
if (!vitalClient) throw Error("Missing vital client");
|
||||
const sleep_data = await vitalClient.Sleep.get_raw(user_id, bedtime_start, undefined, "oura");
|
||||
if (sleep_data.sleep.length === 0) {
|
||||
throw Error("No sleep score found");
|
||||
}
|
||||
return +sleep_data.sleep[0].data.score;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is will generate a user token for a client_user_id`
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
if (req.method !== "POST") {
|
||||
throw new HttpCode({ statusCode: 405, message: "Method Not Allowed" });
|
||||
}
|
||||
const sig = req.headers["svix-signature"];
|
||||
if (!sig) {
|
||||
throw new HttpCode({ statusCode: 400, message: "Missing svix-signature" });
|
||||
}
|
||||
|
||||
const vitalClient = await initVitalClient();
|
||||
|
||||
if (!vitalClient || !vitalEnv)
|
||||
return res.status(400).json({ message: "Missing vital client, try calling `initVitalClient`" });
|
||||
|
||||
const payload = JSON.stringify(req.body);
|
||||
|
||||
const event: any = vitalClient.Webhooks.constructWebhookEvent(
|
||||
payload,
|
||||
req.headers as Record<string, string>,
|
||||
vitalEnv.webhook_secret as string
|
||||
);
|
||||
|
||||
if (event.event_type == "daily.data.sleep.created") {
|
||||
// Carry out logic here to determine what to do if sleep is less
|
||||
// than 8 hours or readiness score is less than 70
|
||||
try {
|
||||
if (event.data.user_id) {
|
||||
const json = { userVitalId: event.data.user_id as string };
|
||||
const credential = await prisma.credential.findFirst({
|
||||
rejectOnNotFound: true,
|
||||
where: {
|
||||
type: "vital_other",
|
||||
key: {
|
||||
equals: json,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!credential) {
|
||||
return res.status(404).json({ message: "Missing vital credential" });
|
||||
}
|
||||
|
||||
// Getting total hours of sleep seconds/60/60 = hours
|
||||
const userWithMetadata = await prisma.user.findFirst({
|
||||
select: {
|
||||
metadata: true,
|
||||
},
|
||||
where: {
|
||||
id: credential.userId as number,
|
||||
},
|
||||
});
|
||||
let minimumSleepTime = 0;
|
||||
let parameterFilter = "";
|
||||
const userMetadata = userWithMetadata?.metadata as Prisma.JsonObject;
|
||||
const vitalSettings =
|
||||
((userWithMetadata?.metadata as Prisma.JsonObject)?.vitalSettings as Prisma.JsonObject) || {};
|
||||
if (!!userMetadata && !!vitalSettings) {
|
||||
minimumSleepTime = vitalSettings.sleepValue as number;
|
||||
parameterFilter = vitalSettings.parameter as string;
|
||||
} else {
|
||||
res.status(404).json({ message: "Vital configuration not found for user" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event.data.hasOwnProperty(parameterFilter)) {
|
||||
res.status(500).json({ message: "Selected param not available" });
|
||||
return;
|
||||
}
|
||||
const totalHoursSleep = event.data[parameterFilter] / 60 / 60;
|
||||
|
||||
if (minimumSleepTime > 0 && parameterFilter !== "" && totalHoursSleep <= minimumSleepTime) {
|
||||
// Trigger reschedule
|
||||
try {
|
||||
const todayDate = dayjs();
|
||||
const todayBookings = await prisma.booking.findMany({
|
||||
where: {
|
||||
startTime: {
|
||||
gte: todayDate.startOf("day").toISOString(),
|
||||
},
|
||||
endTime: {
|
||||
lte: todayDate.endOf("day").toISOString(),
|
||||
},
|
||||
status: {
|
||||
in: [BookingStatus.ACCEPTED, BookingStatus.PENDING],
|
||||
},
|
||||
// @NOTE: very important filter
|
||||
userId: credential?.userId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
uid: true,
|
||||
userId: true,
|
||||
status: true,
|
||||
},
|
||||
});
|
||||
|
||||
const q = queue({ results: [] });
|
||||
if (todayBookings.length > 0) {
|
||||
todayBookings.forEach((booking) =>
|
||||
q.push(() => {
|
||||
return Reschedule(booking.uid, "");
|
||||
})
|
||||
);
|
||||
}
|
||||
await q.start();
|
||||
} catch (error) {
|
||||
throw new Error("Failed to reschedule bookings");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
logger.error(error.message);
|
||||
}
|
||||
logger.error("Failed to get sleep score");
|
||||
}
|
||||
}
|
||||
return res.status(200).json({ body: req.body });
|
||||
} catch (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
console.error(`Webhook Error: ${err.message}`);
|
||||
res.status(err.statusCode ?? 500).send({
|
||||
message: err.message,
|
||||
stack: IS_PRODUCTION ? undefined : err.stack,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
177
packages/app-store/vital/components/AppConfiguration.tsx
Normal file
177
packages/app-store/vital/components/AppConfiguration.tsx
Normal file
|
@ -0,0 +1,177 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import showToast from "@calcom/lib/notification";
|
||||
import { Button, Select } from "@calcom/ui";
|
||||
|
||||
export interface IAppConfigurationProps {
|
||||
credentialIds: number[];
|
||||
}
|
||||
|
||||
const saveSettings = async ({
|
||||
parameter,
|
||||
sleepValue,
|
||||
}: {
|
||||
parameter: { label: string; value: string };
|
||||
sleepValue: number;
|
||||
}) => {
|
||||
try {
|
||||
const response = await fetch("/api/integrations/vital/save", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
sleepValue,
|
||||
parameter: parameter.value,
|
||||
}),
|
||||
});
|
||||
if (response.ok && response.status === 200) {
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const AppConfiguration = (props: IAppConfigurationProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [credentialId] = props.credentialIds;
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: t("vital_app_total_label", { ns: "vital" }),
|
||||
value: "total",
|
||||
},
|
||||
{
|
||||
label: t("vital_app_duration_label", { ns: "vital" }),
|
||||
value: "duration",
|
||||
},
|
||||
];
|
||||
const [selectedParam, setSelectedParam] = useState<{ label: string; value: string }>(options[0]);
|
||||
const [touchedForm, setTouchedForm] = useState(false);
|
||||
const defaultSleepValue = 0;
|
||||
const [sleepValue, setSleepValue] = useState(defaultSleepValue);
|
||||
const [connected, setConnected] = useState(false);
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
useEffect(() => {
|
||||
async function getVitalsConfig() {
|
||||
const response = await fetch("/api/integrations/vital/settings", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (response.status === 200) {
|
||||
const vitalSettings: {
|
||||
connected: boolean;
|
||||
parameter: string;
|
||||
sleepValue: number;
|
||||
} = await response.json();
|
||||
|
||||
if (vitalSettings && vitalSettings.connected) {
|
||||
setConnected(vitalSettings.connected);
|
||||
}
|
||||
if (vitalSettings.sleepValue && vitalSettings.parameter) {
|
||||
const selectedParam = options.find((item) => item.value === vitalSettings.parameter);
|
||||
if (selectedParam) {
|
||||
setSelectedParam(selectedParam);
|
||||
}
|
||||
setSleepValue(vitalSettings.sleepValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
getVitalsConfig();
|
||||
}, []);
|
||||
|
||||
if (!credentialId) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const disabledSaveButton = !touchedForm || sleepValue === 0;
|
||||
return (
|
||||
<div className="flex-col items-start p-3 text-sm">
|
||||
<p>
|
||||
<strong>
|
||||
{t("connected_vital_app", { ns: "vital" })} Vital App: {connected ? "Yes" : "No"}
|
||||
</strong>
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
<strong>{t("vital_app_sleep_automation", { ns: "vital" })}</strong>
|
||||
</p>
|
||||
<p className="mt-1">{t("vital_app_automation_description", { ns: "vital" })}</p>
|
||||
|
||||
<div className="w-100 mt-2">
|
||||
<div className="block sm:flex">
|
||||
<div className="min-w-24 mb-4 mt-5 sm:mb-0">
|
||||
<label htmlFor="description" className="text-sm font-bold">
|
||||
{t("vital_app_parameter", { ns: "vital" })}
|
||||
</label>
|
||||
</div>
|
||||
<div className="w-120 mt-2.5">
|
||||
<Select
|
||||
options={options}
|
||||
value={selectedParam}
|
||||
onChange={(e) => {
|
||||
e && setSelectedParam(e);
|
||||
setTouchedForm(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
<div className="min-w-24 mb-4 mt-3">
|
||||
<label htmlFor="value" className="text-sm font-bold">
|
||||
{t("vital_app_trigger", { ns: "vital" })}
|
||||
</label>
|
||||
</div>
|
||||
<div className={"mx-2 mt-0 inline-flex w-24 items-baseline"}>
|
||||
<input
|
||||
id="value"
|
||||
type="text"
|
||||
pattern="\d*"
|
||||
maxLength={2}
|
||||
value={sleepValue}
|
||||
onChange={(e) => {
|
||||
setSleepValue(Number(e.currentTarget.value));
|
||||
setTouchedForm(true);
|
||||
}}
|
||||
className={
|
||||
"pr-12shadow-sm mt-1 block w-full rounded-sm border border-gray-300 py-2 pl-6 focus:border-neutral-500 focus:outline-none focus:ring-neutral-500 sm:text-sm"
|
||||
}
|
||||
/>
|
||||
<p className="ml-2">
|
||||
<strong>{t("vital_app_hours", { ns: "vital" })}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
className="my-4"
|
||||
onClick={async () => {
|
||||
try {
|
||||
setSaveLoading(true);
|
||||
await saveSettings({ parameter: selectedParam, sleepValue: sleepValue });
|
||||
showToast(t("vital_app_save_success"), "success");
|
||||
} catch (error) {
|
||||
showToast(t("vital_app_save_error"), "error");
|
||||
setSaveLoading(false);
|
||||
}
|
||||
setTouchedForm(false);
|
||||
setSaveLoading(false);
|
||||
}}
|
||||
loading={saveLoading}
|
||||
disabled={disabledSaveButton}>
|
||||
{t("vital_app_save_button", { ns: "vital" })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppConfiguration;
|
39
packages/app-store/vital/components/InstallAppButton.tsx
Normal file
39
packages/app-store/vital/components/InstallAppButton.tsx
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { useState } from "react";
|
||||
|
||||
import { InstallAppButtonProps } from "../../types";
|
||||
|
||||
export default function InstallAppButton(props: InstallAppButtonProps) {
|
||||
const getLinkToken = async () => {
|
||||
const res = await fetch("/api/integrations/vital/token", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to get link token");
|
||||
}
|
||||
return await res.json();
|
||||
};
|
||||
const [loading, setLoading] = useState(false);
|
||||
return (
|
||||
<>
|
||||
{props.render({
|
||||
onClick() {
|
||||
setLoading(true);
|
||||
getLinkToken()
|
||||
.then((data) => {
|
||||
setLoading(false);
|
||||
window.open(`${data.url}&token=${data.token}`, "_self");
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoading(false);
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
loading: loading,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
2
packages/app-store/vital/components/index.ts
Normal file
2
packages/app-store/vital/components/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default as AppConfiguration } from "./AppConfiguration";
|
||||
export { default as InstallAppButton } from "./InstallAppButton";
|
4
packages/app-store/vital/index.ts
Normal file
4
packages/app-store/vital/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export * as api from "./api";
|
||||
export * as lib from "./lib";
|
||||
export * as components from "./components";
|
||||
export { metadata } from "./_metadata";
|
34
packages/app-store/vital/lib/client.ts
Normal file
34
packages/app-store/vital/lib/client.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { VitalClient } from "@tryvital/vital-node";
|
||||
import type { ClientConfig } from "@tryvital/vital-node/dist/lib/models";
|
||||
|
||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||
|
||||
type VitalEnv = ClientConfig & {
|
||||
mode: string;
|
||||
webhook_secret: string;
|
||||
};
|
||||
|
||||
export let vitalClient: VitalClient | null = null;
|
||||
export let vitalEnv: VitalEnv | null = null;
|
||||
|
||||
export async function initVitalClient(): Promise<VitalClient> {
|
||||
if (vitalClient) return vitalClient;
|
||||
const appKeys = (await getAppKeysFromSlug("vital-automation")) as unknown as VitalEnv;
|
||||
if (
|
||||
typeof appKeys !== "object" ||
|
||||
typeof appKeys.api_key !== "string" ||
|
||||
typeof appKeys.webhook_secret !== "string" ||
|
||||
typeof appKeys.region !== "string" ||
|
||||
typeof appKeys.mode !== "string"
|
||||
)
|
||||
throw Error("Missing properties in vital-automation DB keys");
|
||||
vitalEnv = appKeys;
|
||||
vitalClient = new VitalClient({
|
||||
region: appKeys.region,
|
||||
api_key: appKeys.api_key || "",
|
||||
environment: (appKeys.mode as ClientConfig["environment"]) || "sandbox",
|
||||
});
|
||||
return vitalClient;
|
||||
}
|
||||
|
||||
export default vitalClient;
|
35
packages/app-store/vital/lib/emailManager.ts
Normal file
35
packages/app-store/vital/lib/emailManager.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import AttendeeRequestRescheduledEmail from "./templates/attendee-request-reschedule-email";
|
||||
import OrganizerRequestRescheduledEmail from "./templates/organizer-request-reschedule-email";
|
||||
|
||||
export const sendRequestRescheduleEmail = async (
|
||||
calEvent: CalendarEvent,
|
||||
metadata: { rescheduleLink: string }
|
||||
) => {
|
||||
const emailsToSend: Promise<unknown>[] = [];
|
||||
|
||||
emailsToSend.push(
|
||||
new Promise((resolve, reject) => {
|
||||
try {
|
||||
const requestRescheduleEmail = new AttendeeRequestRescheduledEmail(calEvent, metadata);
|
||||
resolve(requestRescheduleEmail.sendEmail());
|
||||
} catch (e) {
|
||||
reject(console.error("AttendeeRequestRescheduledEmail.sendEmail failed", e));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
emailsToSend.push(
|
||||
new Promise((resolve, reject) => {
|
||||
try {
|
||||
const requestRescheduleEmail = new OrganizerRequestRescheduledEmail(calEvent, metadata);
|
||||
resolve(requestRescheduleEmail.sendEmail());
|
||||
} catch (e) {
|
||||
reject(console.error("OrganizerRequestRescheduledEmail.sendEmail failed", e));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(emailsToSend);
|
||||
};
|
34
packages/app-store/vital/lib/emailServerConfig.ts
Normal file
34
packages/app-store/vital/lib/emailServerConfig.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import SendmailTransport from "nodemailer/lib/sendmail-transport";
|
||||
import SMTPConnection from "nodemailer/lib/smtp-connection";
|
||||
|
||||
function detectTransport(): SendmailTransport.Options | SMTPConnection.Options | string {
|
||||
if (process.env.EMAIL_SERVER) {
|
||||
return process.env.EMAIL_SERVER;
|
||||
}
|
||||
|
||||
if (process.env.EMAIL_SERVER_HOST) {
|
||||
const port = parseInt(process.env.EMAIL_SERVER_PORT!);
|
||||
const transport = {
|
||||
host: process.env.EMAIL_SERVER_HOST,
|
||||
port,
|
||||
auth: {
|
||||
user: process.env.EMAIL_SERVER_USER,
|
||||
pass: process.env.EMAIL_SERVER_PASSWORD,
|
||||
},
|
||||
secure: port === 465,
|
||||
};
|
||||
|
||||
return transport;
|
||||
}
|
||||
|
||||
return {
|
||||
sendmail: true,
|
||||
newline: "unix",
|
||||
path: "/usr/sbin/sendmail",
|
||||
};
|
||||
}
|
||||
|
||||
export const serverConfig = {
|
||||
transport: detectTransport(),
|
||||
from: process.env.EMAIL_FROM,
|
||||
};
|
1
packages/app-store/vital/lib/index.ts
Normal file
1
packages/app-store/vital/lib/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as Reschedule } from "./reschedule";
|
170
packages/app-store/vital/lib/reschedule.ts
Normal file
170
packages/app-store/vital/lib/reschedule.ts
Normal file
|
@ -0,0 +1,170 @@
|
|||
import { BookingStatus, User, Booking, BookingReference } from "@prisma/client";
|
||||
import dayjs from "dayjs";
|
||||
import type { TFunction } from "next-i18next";
|
||||
|
||||
import EventManager from "@calcom/core/EventManager";
|
||||
import { CalendarEventBuilder } from "@calcom/core/builders/CalendarEvent/builder";
|
||||
import { CalendarEventDirector } from "@calcom/core/builders/CalendarEvent/director";
|
||||
import { deleteMeeting } from "@calcom/core/videoClient";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { getCalendar } from "../../_utils/getCalendar";
|
||||
import { sendRequestRescheduleEmail } from "./emailManager";
|
||||
|
||||
type PersonAttendeeCommonFields = Pick<User, "id" | "email" | "name" | "locale" | "timeZone" | "username">;
|
||||
|
||||
const Reschedule = async (bookingUid: string, cancellationReason: string) => {
|
||||
const bookingToReschedule = await prisma.booking.findFirst({
|
||||
select: {
|
||||
id: true,
|
||||
uid: true,
|
||||
title: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
userId: true,
|
||||
eventTypeId: true,
|
||||
location: true,
|
||||
attendees: true,
|
||||
references: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
timeZone: true,
|
||||
locale: true,
|
||||
username: true,
|
||||
credentials: true,
|
||||
destinationCalendar: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
rejectOnNotFound: true,
|
||||
where: {
|
||||
uid: bookingUid,
|
||||
NOT: {
|
||||
status: {
|
||||
in: [BookingStatus.CANCELLED, BookingStatus.REJECTED],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (bookingToReschedule && bookingToReschedule.eventTypeId && bookingToReschedule.user) {
|
||||
const userOwner = bookingToReschedule.user;
|
||||
const event = await prisma.eventType.findFirst({
|
||||
select: {
|
||||
title: true,
|
||||
users: true,
|
||||
schedulingType: true,
|
||||
},
|
||||
rejectOnNotFound: true,
|
||||
where: {
|
||||
id: bookingToReschedule.eventTypeId,
|
||||
},
|
||||
});
|
||||
await prisma.booking.update({
|
||||
where: {
|
||||
id: bookingToReschedule.id,
|
||||
},
|
||||
data: {
|
||||
rescheduled: true,
|
||||
cancellationReason,
|
||||
status: BookingStatus.CANCELLED,
|
||||
updatedAt: dayjs().toISOString(),
|
||||
},
|
||||
});
|
||||
const [mainAttendee] = bookingToReschedule.attendees;
|
||||
// @NOTE: Should we assume attendees language?
|
||||
const tAttendees = await getTranslation(mainAttendee.locale ?? "en", "common");
|
||||
const usersToPeopleType = (
|
||||
users: PersonAttendeeCommonFields[],
|
||||
selectedLanguage: TFunction
|
||||
): Person[] => {
|
||||
return users?.map((user) => {
|
||||
return {
|
||||
email: user.email || "",
|
||||
name: user.name || "",
|
||||
username: user?.username || "",
|
||||
language: { translate: selectedLanguage, locale: user.locale || "en" },
|
||||
timeZone: user?.timeZone,
|
||||
};
|
||||
});
|
||||
};
|
||||
const userOwnerTranslation = await getTranslation(userOwner.locale ?? "en", "common");
|
||||
const [userOwnerAsPeopleType] = usersToPeopleType([userOwner], userOwnerTranslation);
|
||||
const builder = new CalendarEventBuilder();
|
||||
builder.init({
|
||||
title: bookingToReschedule.title,
|
||||
type: event.title,
|
||||
startTime: bookingToReschedule.startTime.toISOString(),
|
||||
endTime: bookingToReschedule.endTime.toISOString(),
|
||||
attendees: usersToPeopleType(
|
||||
// username field doesn't exists on attendee but could be in the future
|
||||
bookingToReschedule.attendees as unknown as PersonAttendeeCommonFields[],
|
||||
tAttendees
|
||||
),
|
||||
organizer: userOwnerAsPeopleType,
|
||||
});
|
||||
const director = new CalendarEventDirector();
|
||||
director.setBuilder(builder);
|
||||
director.setExistingBooking(bookingToReschedule as unknown as Booking);
|
||||
director.setCancellationReason(cancellationReason);
|
||||
await director.buildForRescheduleEmail();
|
||||
// Handling calendar and videos cancellation
|
||||
// This can set previous time as available, until virtual calendar is done
|
||||
const credentialsMap = new Map();
|
||||
userOwner.credentials.forEach((credential) => {
|
||||
credentialsMap.set(credential.type, credential);
|
||||
});
|
||||
const bookingRefsFiltered: BookingReference[] = bookingToReschedule.references.filter(
|
||||
(ref) => !!credentialsMap.get(ref.type)
|
||||
);
|
||||
try {
|
||||
bookingRefsFiltered.forEach((bookingRef) => {
|
||||
if (bookingRef.uid) {
|
||||
if (bookingRef.type.endsWith("_calendar")) {
|
||||
const calendar = getCalendar(credentialsMap.get(bookingRef.type));
|
||||
return calendar?.deleteEvent(bookingRef.uid, builder.calendarEvent);
|
||||
} else if (bookingRef.type.endsWith("_video")) {
|
||||
return deleteMeeting(credentialsMap.get(bookingRef.type), bookingRef.uid);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
logger.error(error.message);
|
||||
}
|
||||
}
|
||||
// Creating cancelled event as placeholders in calendars, remove when virtual calendar handles it
|
||||
try {
|
||||
const eventManager = new EventManager({
|
||||
credentials: userOwner.credentials,
|
||||
destinationCalendar: userOwner.destinationCalendar,
|
||||
});
|
||||
builder.calendarEvent.title = `Cancelled: ${builder.calendarEvent.title}`;
|
||||
await eventManager.updateAndSetCancelledPlaceholder(builder.calendarEvent, bookingToReschedule);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
logger.error(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Send emails
|
||||
try {
|
||||
await sendRequestRescheduleEmail(builder.calendarEvent, {
|
||||
rescheduleLink: builder.rescheduleLink,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
logger.error(error.message);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
export default Reschedule;
|
|
@ -0,0 +1,208 @@
|
|||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { createEvent, DateArray, Person } from "ics";
|
||||
|
||||
import { getCancelLink } from "@calcom/lib/CalEventParser";
|
||||
import { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import BaseTemplate from "./base-template";
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class AttendeeRequestRescheduledEmail extends BaseTemplate {
|
||||
private metadata: { rescheduleLink: string };
|
||||
constructor(calEvent: CalendarEvent, metadata: { rescheduleLink: string }) {
|
||||
super(calEvent);
|
||||
this.metadata = metadata;
|
||||
}
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.attendees[0].email];
|
||||
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("requested_to_reschedule_subject_attendee", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
// @OVERRIDE
|
||||
protected getiCalEventAsString(): string | undefined {
|
||||
const icsEvent = createEvent({
|
||||
start: dayjs(this.calEvent.startTime)
|
||||
.utc()
|
||||
.toArray()
|
||||
.slice(0, 6)
|
||||
.map((v, i) => (i === 1 ? v + 1 : v)) as DateArray,
|
||||
startInputType: "utc",
|
||||
productId: "calendso/ics",
|
||||
title: this.calEvent.organizer.language.translate("ics_event_title", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
description: this.getTextBody(),
|
||||
duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") },
|
||||
organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email },
|
||||
attendees: this.calEvent.attendees.map((attendee: Person) => ({
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
})),
|
||||
status: "CANCELLED",
|
||||
method: "CANCEL",
|
||||
});
|
||||
if (icsEvent.error) {
|
||||
throw icsEvent.error;
|
||||
}
|
||||
return icsEvent.value;
|
||||
}
|
||||
// @OVERRIDE
|
||||
protected getWhen(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("when")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;text-decoration: line-through;">
|
||||
${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format(
|
||||
"YYYY"
|
||||
)} | ${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)} <span style="color: #888888">(${this.getTimezone()})</span>
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_title_attendee")}
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_subtitle", {
|
||||
organizer: this.calEvent.organizer.name,
|
||||
})},
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate("request_reschedule_title_attendee"),
|
||||
this.calEvent.organizer.language.translate("request_reschedule_subtitle", {
|
||||
organizer: this.calEvent.organizer.name,
|
||||
})
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 40px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getAdditionalNotes()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;text-align:center;color:#3E3E3E;">
|
||||
<a style="padding: 8px 16px;background-color: #292929;color: white;border-radius: 2px;display: inline-block;margin-bottom: 16px;"
|
||||
href="${this.metadata.rescheduleLink}" target="_blank"
|
||||
>
|
||||
Book a new time
|
||||
<img src="https://app.cal.com/emails/linkIcon.png" style="width:16px; margin-left: 5px;filter: brightness(0) invert(1); vertical-align: top;" />
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
409
packages/app-store/vital/lib/templates/base-template.ts
Normal file
409
packages/app-store/vital/lib/templates/base-template.ts
Normal file
|
@ -0,0 +1,409 @@
|
|||
import dayjs, { Dayjs } from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { createEvent, DateArray, Person } from "ics";
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
import { getAppName } from "@calcom/app-store/utils";
|
||||
import { getCancelLink, getRichDescription } from "@calcom/lib/CalEventParser";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { serverConfig } from "../emailServerConfig";
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
linkIcon,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class OrganizerScheduledEmail {
|
||||
calEvent: CalendarEvent;
|
||||
|
||||
constructor(calEvent: CalendarEvent) {
|
||||
this.calEvent = calEvent;
|
||||
}
|
||||
|
||||
public sendEmail() {
|
||||
new Promise((resolve, reject) =>
|
||||
nodemailer
|
||||
.createTransport(this.getMailerOptions().transport)
|
||||
.sendMail(this.getNodeMailerPayload(), (_err, info) => {
|
||||
if (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
this.printNodeMailerError(err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(info);
|
||||
}
|
||||
})
|
||||
).catch((e) => console.error("sendEmail", e));
|
||||
return new Promise((resolve) => resolve("send mail async"));
|
||||
}
|
||||
|
||||
protected getiCalEventAsString(): string | undefined {
|
||||
const icsEvent = createEvent({
|
||||
start: dayjs(this.calEvent.startTime)
|
||||
.utc()
|
||||
.toArray()
|
||||
.slice(0, 6)
|
||||
.map((v, i) => (i === 1 ? v + 1 : v)) as DateArray,
|
||||
startInputType: "utc",
|
||||
productId: "calendso/ics",
|
||||
title: this.calEvent.organizer.language.translate("ics_event_title", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
description: this.getTextBody(),
|
||||
duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") },
|
||||
organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email },
|
||||
attendees: this.calEvent.attendees.map((attendee: Person) => ({
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
})),
|
||||
status: "CONFIRMED",
|
||||
});
|
||||
if (icsEvent.error) {
|
||||
throw icsEvent.error;
|
||||
}
|
||||
return icsEvent.value;
|
||||
}
|
||||
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.organizer.email];
|
||||
if (this.calEvent.team) {
|
||||
this.calEvent.team.members.forEach((member) => {
|
||||
const memberAttendee = this.calEvent.attendees.find((attendee) => attendee.name === member);
|
||||
if (memberAttendee) {
|
||||
toAddresses.push(memberAttendee.email);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("confirmed_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getMailerOptions() {
|
||||
return {
|
||||
transport: serverConfig.transport,
|
||||
from: serverConfig.from,
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("new_event_scheduled")}
|
||||
${this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")}
|
||||
|
||||
${getRichDescription(this.calEvent)}
|
||||
`.trim();
|
||||
}
|
||||
|
||||
protected printNodeMailerError(error: Error): void {
|
||||
console.error("SEND_BOOKING_CONFIRMATION_ERROR", this.calEvent.organizer.email, error);
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("confirmed_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("checkCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate("new_event_scheduled"),
|
||||
this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:0px;text-align:left;color:#3E3E3E;">
|
||||
${this.getManageLink()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getManageLink(): string {
|
||||
const manageText = this.calEvent.organizer.language.translate("manage_this_event");
|
||||
return `<p>${this.calEvent.organizer.language.translate(
|
||||
"need_to_reschedule_or_cancel"
|
||||
)}</p><p style="font-weight: 400; line-height: 24px;"><a href="${getCancelLink(
|
||||
this.calEvent
|
||||
)}" style="color: #3E3E3E;" alt="${manageText}">${manageText}</a></p>`;
|
||||
}
|
||||
|
||||
protected getWhat(): string {
|
||||
return `
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("what")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.type}</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getWhen(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("when")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">
|
||||
${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format(
|
||||
"YYYY"
|
||||
)} | ${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)} <span style="color: #888888">(${this.getTimezone()})</span>
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getWho(): string {
|
||||
const attendees = this.calEvent.attendees
|
||||
.map((attendee) => {
|
||||
return `<div style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
attendee?.name || `${this.calEvent.organizer.language.translate("guest")}`
|
||||
} <span style="color: #888888"><a href="mailto:${attendee.email}" style="color: #888888;">${
|
||||
attendee.email
|
||||
}</a></span></div>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
const organizer = `<div style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
this.calEvent.organizer.name
|
||||
} - ${this.calEvent.organizer.language.translate(
|
||||
"organizer"
|
||||
)} <span style="color: #888888"><a href="mailto:${
|
||||
this.calEvent.organizer.email
|
||||
}" style="color: #888888;">${this.calEvent.organizer.email}</a></span></div>`;
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("who")}</p>
|
||||
${organizer + attendees}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getAdditionalNotes(): string {
|
||||
if (!this.calEvent.additionalNotes) return "";
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("additional_notes")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px; white-space: pre-wrap;">${
|
||||
this.calEvent.additionalNotes
|
||||
}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getDescription(): string {
|
||||
if (!this.calEvent.description) return "";
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("description")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px; white-space: pre-wrap;">${
|
||||
this.calEvent.description
|
||||
}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getLocation(): string {
|
||||
let providerName = this.calEvent.location ? getAppName(this.calEvent.location) : "";
|
||||
|
||||
if (this.calEvent.location && this.calEvent.location.includes("integrations:")) {
|
||||
const location = this.calEvent.location.split(":")[1];
|
||||
providerName = location[0].toUpperCase() + location.slice(1);
|
||||
}
|
||||
|
||||
// If location its a url, probably we should be validating it with a custom library
|
||||
if (this.calEvent.location && /^https?:\/\//.test(this.calEvent.location)) {
|
||||
providerName = this.calEvent.location;
|
||||
}
|
||||
|
||||
if (this.calEvent.videoCallData) {
|
||||
const meetingId = this.calEvent.videoCallData.id;
|
||||
const meetingPassword = this.calEvent.videoCallData.password;
|
||||
const meetingUrl = this.calEvent.videoCallData.url;
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("where")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${providerName} ${
|
||||
meetingUrl &&
|
||||
`<a href="${meetingUrl}" target="_blank" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}"><img src="${linkIcon()}" width="12px"></img></a>`
|
||||
}</p>
|
||||
${
|
||||
meetingId &&
|
||||
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.organizer.language.translate(
|
||||
"meeting_id"
|
||||
)}: <span>${meetingId}</span></div>`
|
||||
}
|
||||
${
|
||||
meetingPassword &&
|
||||
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.organizer.language.translate(
|
||||
"meeting_password"
|
||||
)}: <span>${meetingPassword}</span></div>`
|
||||
}
|
||||
${
|
||||
meetingUrl &&
|
||||
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}: <a href="${meetingUrl}" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}" style="color: #3E3E3E" target="_blank">${meetingUrl}</a></div>`
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (this.calEvent.additionInformation?.hangoutLink) {
|
||||
const hangoutLink: string = this.calEvent.additionInformation.hangoutLink;
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("where")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${providerName} ${
|
||||
hangoutLink &&
|
||||
`<a href="${hangoutLink}" target="_blank" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}"><img src="${linkIcon()}" width="12px"></img></a>`
|
||||
}</p>
|
||||
<div style="color: #494949; font-weight: 400; line-height: 24px;"><a href="${hangoutLink}" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}" style="color: #3E3E3E" target="_blank">${hangoutLink}</a></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("where")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
providerName || this.calEvent.location
|
||||
}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getTimezone(): string {
|
||||
return this.calEvent.organizer.timeZone;
|
||||
}
|
||||
|
||||
protected getOrganizerStart(): Dayjs {
|
||||
return dayjs(this.calEvent.startTime).tz(this.getTimezone());
|
||||
}
|
||||
|
||||
protected getOrganizerEnd(): Dayjs {
|
||||
return dayjs(this.calEvent.endTime).tz(this.getTimezone());
|
||||
}
|
||||
}
|
44
packages/app-store/vital/lib/templates/common/body-logo.ts
Normal file
44
packages/app-store/vital/lib/templates/common/body-logo.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { IS_PRODUCTION, BASE_URL } from "@lib/config/constants";
|
||||
|
||||
export const emailBodyLogo = (): string => {
|
||||
const image = IS_PRODUCTION
|
||||
? BASE_URL + "/emails/CalLogo@2x.png"
|
||||
: "https://app.cal.com/emails/CalLogo@2x.png";
|
||||
|
||||
return `
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;padding-top:32px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:89px;">
|
||||
<a href="${BASE_URL}" target="_blank">
|
||||
<img height="19" src="${image}" style="border:0;display:block;outline:none;text-decoration:none;height:19px;width:100%;font-size:13px;" width="89" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
};
|
91
packages/app-store/vital/lib/templates/common/head.ts
Normal file
91
packages/app-store/vital/lib/templates/common/head.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
export const emailHead = (headerContent: string): string => {
|
||||
return `
|
||||
<head>
|
||||
<title>${headerContent}</title>
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<!--<![endif]-->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type="text/css">
|
||||
#outlook a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
table,
|
||||
td {
|
||||
border-collapse: collapse;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin: 13px 0;
|
||||
}
|
||||
</style>
|
||||
<!--[if mso]>
|
||||
<noscript>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
</noscript>
|
||||
<![endif]-->
|
||||
<!--[if lte mso 11]>
|
||||
<style type="text/css">
|
||||
.mj-outlook-group-fix { width:100% !important; }
|
||||
</style>
|
||||
<![endif]-->
|
||||
<!--[if !mso]><!-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700" rel="stylesheet" type="text/css">
|
||||
<style type="text/css">
|
||||
@import url(https://fonts.googleapis.com/css?family=Roboto:400,500,700);
|
||||
</style>
|
||||
<!--<![endif]-->
|
||||
<style type="text/css">
|
||||
@media only screen and (min-width:480px) {
|
||||
.mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style media="screen and (min-width:480px)">
|
||||
.moz-text-html .mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
@media only screen and (max-width:480px) {
|
||||
table.mj-full-width-mobile {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
td.mj-full-width-mobile {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
`;
|
||||
};
|
6
packages/app-store/vital/lib/templates/common/index.ts
Normal file
6
packages/app-store/vital/lib/templates/common/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export { emailHead } from "./head";
|
||||
export { emailSchedulingBodyHeader } from "./scheduling-body-head";
|
||||
export { emailBodyLogo } from "./body-logo";
|
||||
export { emailScheduledBodyHeaderContent } from "./scheduling-body-head-content";
|
||||
export { emailSchedulingBodyDivider } from "./scheduling-body-divider";
|
||||
export { linkIcon } from "./link-icon";
|
|
@ -0,0 +1,5 @@
|
|||
import { IS_PRODUCTION, BASE_URL } from "@lib/config/constants";
|
||||
|
||||
export const linkIcon = (): string => {
|
||||
return IS_PRODUCTION ? BASE_URL + "/emails/linkIcon.png" : "https://app.cal.com/emails/linkIcon.png";
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
export const emailSchedulingBodyDivider = (): string => {
|
||||
return `
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:15px 0px 0 0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;padding-bottom:15px;word-break:break-word;">
|
||||
<p style="border-top:solid 1px #E1E1E1;font-size:1px;margin:0px auto;width:100%;">
|
||||
</p>
|
||||
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 1px #E1E1E1;font-size:1px;margin:0px auto;width:548px;" role="presentation" width="548px" ><tr><td style="height:0;line-height:0;">
|
||||
</td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
export const emailScheduledBodyHeaderContent = (title: string, subtitle: string): string => {
|
||||
return `
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;padding-top:24px;padding-bottom:0px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:24px;font-weight:700;line-height:24px;text-align:center;color:#292929;">${title}</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:400;line-height:24px;text-align:center;color:#494949;">${subtitle}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
};
|
|
@ -0,0 +1,71 @@
|
|||
import { IS_PRODUCTION, BASE_URL } from "@lib/config/constants";
|
||||
|
||||
export type BodyHeadType = "checkCircle" | "xCircle" | "calendarCircle";
|
||||
|
||||
export const getHeadImage = (headerType: BodyHeadType): string => {
|
||||
switch (headerType) {
|
||||
case "checkCircle":
|
||||
return IS_PRODUCTION
|
||||
? BASE_URL + "/emails/checkCircle@2x.png"
|
||||
: "https://app.cal.com/emails/checkCircle@2x.png";
|
||||
case "xCircle":
|
||||
return IS_PRODUCTION
|
||||
? BASE_URL + "/emails/xCircle@2x.png"
|
||||
: "https://app.cal.com/emails/xCircle@2x.png";
|
||||
case "calendarCircle":
|
||||
return IS_PRODUCTION
|
||||
? BASE_URL + "/emails/calendarCircle@2x.png"
|
||||
: "https://app.cal.com/emails/calendarCircle@2x.png";
|
||||
}
|
||||
};
|
||||
|
||||
export const emailSchedulingBodyHeader = (headerType: BodyHeadType): string => {
|
||||
const image = getHeadImage(headerType);
|
||||
|
||||
return `
|
||||
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:0px;padding-top:40px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;border-top:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:30px 30px 0 30px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:558px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:64px;">
|
||||
<img height="64" src="${image}" style="border:0;display:block;outline:none;text-decoration:none;height:64px;width:100%;font-size:13px;" width="64" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
};
|
|
@ -0,0 +1,188 @@
|
|||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { createEvent, DateArray, Person } from "ics";
|
||||
|
||||
import { getCancelLink } from "@calcom/lib/CalEventParser";
|
||||
import { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import BaseTemplate from "./base-template";
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class OrganizerRequestRescheduledEmail extends BaseTemplate {
|
||||
private metadata: { rescheduleLink: string };
|
||||
constructor(calEvent: CalendarEvent, metadata: { rescheduleLink: string }) {
|
||||
super(calEvent);
|
||||
this.metadata = metadata;
|
||||
}
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.organizer.email];
|
||||
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
// @OVERRIDE
|
||||
protected getiCalEventAsString(): string | undefined {
|
||||
const icsEvent = createEvent({
|
||||
start: dayjs(this.calEvent.startTime)
|
||||
.utc()
|
||||
.toArray()
|
||||
.slice(0, 6)
|
||||
.map((v, i) => (i === 1 ? v + 1 : v)) as DateArray,
|
||||
startInputType: "utc",
|
||||
productId: "calendso/ics",
|
||||
title: this.calEvent.organizer.language.translate("ics_event_title", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
description: this.getTextBody(),
|
||||
duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") },
|
||||
organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email },
|
||||
attendees: this.calEvent.attendees.map((attendee: Person) => ({
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
})),
|
||||
status: "CANCELLED",
|
||||
method: "CANCEL",
|
||||
});
|
||||
if (icsEvent.error) {
|
||||
throw icsEvent.error;
|
||||
}
|
||||
return icsEvent.value;
|
||||
}
|
||||
// @OVERRIDE
|
||||
protected getWhen(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("when")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;text-decoration: line-through;">
|
||||
${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format(
|
||||
"YYYY"
|
||||
)} | ${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)} <span style="color: #888888">(${this.getTimezone()})</span>
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_title_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
})}
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_subtitle_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
})},
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate("request_reschedule_title_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
this.calEvent.organizer.language.translate("request_reschedule_subtitle_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
})
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 40px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getAdditionalNotes()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
16
packages/app-store/vital/package.json
Normal file
16
packages/app-store/vital/package.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"name": "@calcom/vital",
|
||||
"version": "0.1.0",
|
||||
"main": "./index.ts",
|
||||
"description": "Connect your health data or wearables to trigger actions on your calendar.",
|
||||
"dependencies": {
|
||||
"@calcom/prisma": "*",
|
||||
"@tryvital/vital-node": "^1.3.6",
|
||||
"queue": "^6.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
}
|
||||
}
|
4
packages/app-store/vital/static/icon.svg
Normal file
4
packages/app-store/vital/static/icon.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="372" height="314" viewBox="0 0 372 314" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M185.53 72.5053C204.281 44.0924 258.928 -4.57799 318.669 35.9042C378.41 76.3865 348.801 138.092 326.53 163.885" stroke="black" stroke-width="35" stroke-linecap="round" stroke-dasharray="1 70"/>
|
||||
<path d="M327.512 163.093L192.873 293.229C188.975 296.997 182.784 296.972 178.916 293.173L46.4675 163.093C28.5125 147.494 -2.90857 98.1249 38.2381 47.345C79.3848 -3.43492 141.978 15.9809 185.868 72.735" stroke="black" stroke-width="35" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 571 B |
|
@ -1,7 +1,7 @@
|
|||
import { useTranslation } from "next-i18next";
|
||||
|
||||
export const useLocale = () => {
|
||||
const { i18n, t } = useTranslation("common");
|
||||
export const useLocale = (namespace: Parameters<typeof useTranslation>[0] = "common") => {
|
||||
const { i18n, t } = useTranslation(namespace);
|
||||
|
||||
return {
|
||||
i18n,
|
||||
|
|
|
@ -5,6 +5,7 @@ import prisma from ".";
|
|||
require("dotenv").config({ path: "../../.env.appStore" });
|
||||
|
||||
async function createApp(
|
||||
/** The App identifier in the DB also used for public page in `/apps/[slug]` */
|
||||
slug: Prisma.AppCreateInput["slug"],
|
||||
/** The directory name for `/packages/app-store/[dirName]` */
|
||||
dirName: Prisma.AppCreateInput["dirName"],
|
||||
|
@ -86,6 +87,14 @@ async function main() {
|
|||
});
|
||||
}
|
||||
await createApp("space-booking", "spacebooking", ["other"], "spacebooking_other");
|
||||
if (process.env.VITAL_API_KEY && process.env.VITAL_WEBHOOK_SECRET) {
|
||||
await createApp("vital-automation", "vital", ["other"], "vital_other", {
|
||||
mode: process.env.VITAL_DEVELOPMENT_MODE || "sandbox",
|
||||
region: process.env.VITAL_REGION || "us",
|
||||
api_key: process.env.VITAL_API_KEY,
|
||||
webhook_secret: process.env.VITAL_WEBHOOK_SECRET,
|
||||
});
|
||||
}
|
||||
await createApp("zapier", "zapier", ["other"], "zapier_other");
|
||||
// Web3 apps
|
||||
await createApp("huddle01", "huddle01video", ["web3", "video"], "huddle01_video");
|
||||
|
|
63
packages/ui/form/Select.tsx
Normal file
63
packages/ui/form/Select.tsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
import ReactSelect, { components, GroupBase, InputProps, Props } from "react-select";
|
||||
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
|
||||
export type SelectProps<
|
||||
Option,
|
||||
IsMulti extends boolean = false,
|
||||
Group extends GroupBase<Option> = GroupBase<Option>
|
||||
> = Props<Option, IsMulti, Group>;
|
||||
|
||||
export const InputComponent = <Option, IsMulti extends boolean, Group extends GroupBase<Option>>({
|
||||
inputClassName,
|
||||
...props
|
||||
}: InputProps<Option, IsMulti, Group>) => {
|
||||
return (
|
||||
<components.Input
|
||||
// disables our default form focus hightlight on the react-select input element
|
||||
inputClassName={classNames("focus:ring-0 focus:ring-offset-0", inputClassName)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
function Select<
|
||||
Option,
|
||||
IsMulti extends boolean = false,
|
||||
Group extends GroupBase<Option> = GroupBase<Option>
|
||||
>({ className, ...props }: SelectProps<Option, IsMulti, Group>) {
|
||||
return (
|
||||
<ReactSelect
|
||||
theme={(theme) => ({
|
||||
...theme,
|
||||
borderRadius: 2,
|
||||
colors: {
|
||||
...theme.colors,
|
||||
primary: "var(--brand-color)",
|
||||
|
||||
primary50: "rgba(209 , 213, 219, var(--tw-bg-opacity))",
|
||||
primary25: "rgba(244, 245, 246, var(--tw-bg-opacity))",
|
||||
},
|
||||
})}
|
||||
styles={{
|
||||
option: (provided, state) => ({
|
||||
...provided,
|
||||
color: state.isSelected ? "var(--brand-text-color)" : "black",
|
||||
":active": {
|
||||
backgroundColor: state.isSelected ? "" : "var(--brand-color)",
|
||||
color: "var(--brand-text-color)",
|
||||
},
|
||||
}),
|
||||
}}
|
||||
components={{
|
||||
...components,
|
||||
IndicatorSeparator: () => null,
|
||||
Input: InputComponent,
|
||||
}}
|
||||
className={classNames("text-sm shadow-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Select;
|
|
@ -1,4 +1,5 @@
|
|||
export { default as Button } from "./Button";
|
||||
export { default as EmptyScreen } from "./EmptyScreen";
|
||||
export { default as Switch } from "./Switch";
|
||||
export { default as Select } from "./form/Select";
|
||||
export * from "./skeleton";
|
||||
export { default as Switch } from "./Switch";
|
||||
|
|
471
yarn.lock
471
yarn.lock
|
@ -519,6 +519,13 @@
|
|||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.16.7"
|
||||
|
||||
"@babel/plugin-syntax-typescript@^7.7.2":
|
||||
version "7.17.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.10.tgz#80031e6042cad6a95ed753f672ebd23c30933195"
|
||||
integrity sha512-xJefea1DWXW09pW4Tm9bjwVlPDyYA2it3fWlmEjpYz6alPvTUjL0EOzNzI/FEOyI3r4/J7uVH5UqKgl1TQ5hqQ==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.16.7"
|
||||
|
||||
"@babel/plugin-transform-modules-commonjs@7.16.8":
|
||||
version "7.16.8"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz#cdee19aae887b16b9d331009aa9a219af7c86afe"
|
||||
|
@ -1143,9 +1150,9 @@
|
|||
integrity sha512-aaRnYxBb3MU2FNJf3Ut9RMTUqqU3as0aI1lQhgo2n9Fa67wRu14iOGqx93xB+uMNVfNwZ5B3y/Ndm7qZGuFeMQ==
|
||||
|
||||
"@headlessui/react@^1.5.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.6.0.tgz#5943c0b1e5b1a02566ab45f665a05188b1d079c5"
|
||||
integrity sha512-PlDuytBC6iDC/uMvpANm5VpRSuayyXMEeo/dNIwAZNHCfhZUqDQgLXjGu48SHsvMw22Kc3c3u9TOAMZNg+1vzw==
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.6.1.tgz#d822792e589aac005462491dd62f86095e0c3bef"
|
||||
integrity sha512-gMd6uIs1U4Oz718Z5gFoV0o/vD43/4zvbyiJN9Dt7PK9Ubxn+TmJwTmYwyNJc5KxxU1t0CmgTNgwZX9+4NjCnQ==
|
||||
|
||||
"@heroicons/react@^1.0.4", "@heroicons/react@^1.0.6":
|
||||
version "1.0.6"
|
||||
|
@ -2837,6 +2844,16 @@
|
|||
resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.3.tgz#1185726610acc37317ddab11c3c7f9066966bd20"
|
||||
integrity sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==
|
||||
|
||||
"@stablelib/base64@^1.0.0":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@stablelib/base64/-/base64-1.0.1.tgz#bdfc1c6d3a62d7a3b7bbc65b6cce1bb4561641be"
|
||||
integrity sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==
|
||||
|
||||
"@stablelib/utf8@^1.0.0":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@stablelib/utf8/-/utf8-1.0.1.tgz#fd5e7ad353b7e3cd1ce85e49099360e57693cfd1"
|
||||
integrity sha512-FrYD1xadah/TtAP6VJ04lDD5h9rdDj/d8wH/jMYTtHqZBv9z2btdvEU8vTxdjdkFmo1b/BH+t3R1wi/mYhCCNg==
|
||||
|
||||
"@stripe/react-stripe-js@^1.4.1":
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-1.7.0.tgz#83c993a09a903703205d556617f9729784a896c3"
|
||||
|
@ -2917,6 +2934,17 @@
|
|||
resolved "https://registry.yarnpkg.com/@trpc/server/-/server-9.22.0.tgz#5c6442666efd774069af938016a7ab6def32f746"
|
||||
integrity sha512-MwMVLjXXeTFKdGtFQokI6odGZifMBvMpfaddqg3RJp2wcERzTm1A33MB5mmCxH9opjmPjqMMkT/3tRunRFaitw==
|
||||
|
||||
"@tryvital/vital-node@^1.3.6":
|
||||
version "1.3.6"
|
||||
resolved "https://registry.yarnpkg.com/@tryvital/vital-node/-/vital-node-1.3.6.tgz#8919c02c24969edddcd41be8c253788f76b769d9"
|
||||
integrity sha512-RcUsiS/+DiQkv3jDU54ECorLKEmxi0FmVLa0scPnehO/sZgzc9356oAdKF7cglAie3+yxUAYrHlIfaebHU8yNA==
|
||||
dependencies:
|
||||
auth0 "^2.35.1"
|
||||
axios ">=0.21.2"
|
||||
axios-retry "^3.2.4"
|
||||
crypto "^1.0.1"
|
||||
svix "0.42.3"
|
||||
|
||||
"@ts-morph/common@~0.12.3":
|
||||
version "0.12.3"
|
||||
resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.12.3.tgz#a96e250217cd30e480ab22ec6a0ebbe65fd784ff"
|
||||
|
@ -3021,6 +3049,14 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/body-parser@*":
|
||||
version "1.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0"
|
||||
integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==
|
||||
dependencies:
|
||||
"@types/connect" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/chrome@^0.0.136":
|
||||
version "0.0.136"
|
||||
resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.136.tgz#7c011b9f997b0156f25a140188a0c5689d3f368f"
|
||||
|
@ -3029,6 +3065,13 @@
|
|||
"@types/filesystem" "*"
|
||||
"@types/har-format" "*"
|
||||
|
||||
"@types/connect@*":
|
||||
version "3.4.35"
|
||||
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"
|
||||
integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/cross-spawn@6.0.2":
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/cross-spawn/-/cross-spawn-6.0.2.tgz#168309de311cd30a2b8ae720de6475c2fbf33ac7"
|
||||
|
@ -3070,6 +3113,40 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83"
|
||||
integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==
|
||||
|
||||
"@types/express-jwt@0.0.42":
|
||||
version "0.0.42"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-jwt/-/express-jwt-0.0.42.tgz#4f04e1fadf9d18725950dc041808a4a4adf7f5ae"
|
||||
integrity sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==
|
||||
dependencies:
|
||||
"@types/express" "*"
|
||||
"@types/express-unless" "*"
|
||||
|
||||
"@types/express-serve-static-core@^4.17.18":
|
||||
version "4.17.28"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8"
|
||||
integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/qs" "*"
|
||||
"@types/range-parser" "*"
|
||||
|
||||
"@types/express-unless@*":
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-unless/-/express-unless-0.5.3.tgz#271f8603617445568ed0d6efe25a7d2f338544c1"
|
||||
integrity sha512-TyPLQaF6w8UlWdv4gj8i46B+INBVzURBNRahCozCSXfsK2VTlL1wNyTlMKw817VHygBtlcl5jfnPadlydr06Yw==
|
||||
dependencies:
|
||||
"@types/express" "*"
|
||||
|
||||
"@types/express@*":
|
||||
version "4.17.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034"
|
||||
integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==
|
||||
dependencies:
|
||||
"@types/body-parser" "*"
|
||||
"@types/express-serve-static-core" "^4.17.18"
|
||||
"@types/qs" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/filesystem@*":
|
||||
version "0.0.32"
|
||||
resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.32.tgz#307df7cc084a2293c3c1a31151b178063e0a8edf"
|
||||
|
@ -3197,6 +3274,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.1.tgz#d9ba43490fa3a3df958759adf69396c3532cf2c1"
|
||||
integrity sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==
|
||||
|
||||
"@types/mime@^1":
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
|
||||
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
|
||||
|
||||
"@types/module-alias@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/module-alias/-/module-alias-2.0.1.tgz#e5893236ce922152d57c5f3f978f764f4deeb45f"
|
||||
|
@ -3278,6 +3360,16 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/qs@*":
|
||||
version "6.9.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
|
||||
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
|
||||
|
||||
"@types/range-parser@*":
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
||||
|
||||
"@types/react-calendar@^3.0.0":
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-calendar/-/react-calendar-3.5.0.tgz#8195a33e20395e1f5171eea9c80156f3a5115b43"
|
||||
|
@ -3927,6 +4019,19 @@ atob@^2.1.2:
|
|||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||
|
||||
auth0@^2.35.1:
|
||||
version "2.40.0"
|
||||
resolved "https://registry.yarnpkg.com/auth0/-/auth0-2.40.0.tgz#bc3bb047589e7ba93a729dd9973fcabe6c175dcd"
|
||||
integrity sha512-xrnyLpKw+BP7u6OqHMYcSkLA2yDbTDefMeqArpwAU8UG5MPkhsTFQGPMXLzxHr2M5mV+elJOQ6w1acSY/2uRbA==
|
||||
dependencies:
|
||||
axios "^0.25.0"
|
||||
form-data "^3.0.1"
|
||||
jsonwebtoken "^8.5.1"
|
||||
jwks-rsa "^1.12.1"
|
||||
lru-memoizer "^2.1.4"
|
||||
rest-facade "^1.16.3"
|
||||
retry "^0.13.1"
|
||||
|
||||
autolinker@^3.11.0:
|
||||
version "3.15.0"
|
||||
resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-3.15.0.tgz#03956088648f236642a5783612f9ca16adbbed38"
|
||||
|
@ -3947,12 +4052,12 @@ autoprefixer@^10.4.0, autoprefixer@^10.4.4:
|
|||
postcss-value-parser "^4.2.0"
|
||||
|
||||
autoprefixer@^10.4.2:
|
||||
version "10.4.5"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.5.tgz#662193c744094b53d3637f39be477e07bd904998"
|
||||
integrity sha512-Fvd8yCoA7lNX/OUllvS+aS1I7WRBclGXsepbvT8ZaPgrH24rgXpZzF0/6Hh3ZEkwg+0AES/Osd196VZmYoEFtw==
|
||||
version "10.4.7"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.7.tgz#1db8d195f41a52ca5069b7593be167618edbbedf"
|
||||
integrity sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA==
|
||||
dependencies:
|
||||
browserslist "^4.20.2"
|
||||
caniuse-lite "^1.0.30001332"
|
||||
browserslist "^4.20.3"
|
||||
caniuse-lite "^1.0.30001335"
|
||||
fraction.js "^4.2.0"
|
||||
normalize-range "^0.1.2"
|
||||
picocolors "^1.0.0"
|
||||
|
@ -3978,13 +4083,35 @@ axe-core@^4.3.5:
|
|||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413"
|
||||
integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==
|
||||
|
||||
axios@^0.26.1:
|
||||
axios-retry@^3.2.4:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/axios-retry/-/axios-retry-3.2.4.tgz#f447a53c3456f5bfeca18f20c3a3272207d082ae"
|
||||
integrity sha512-Co3UXiv4npi6lM963mfnuH90/YFLKWWDmoBYfxkHT5xtkSSWNqK9zdG3fw5/CP/dsoKB5aMMJCsgab+tp1OxLQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
is-retry-allowed "^2.2.0"
|
||||
|
||||
axios@>=0.21.2, axios@^0.26.1:
|
||||
version "0.26.1"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9"
|
||||
integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==
|
||||
dependencies:
|
||||
follow-redirects "^1.14.8"
|
||||
|
||||
axios@^0.21.1:
|
||||
version "0.21.4"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
|
||||
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
|
||||
dependencies:
|
||||
follow-redirects "^1.14.0"
|
||||
|
||||
axios@^0.25.0:
|
||||
version "0.25.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a"
|
||||
integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==
|
||||
dependencies:
|
||||
follow-redirects "^1.14.7"
|
||||
|
||||
axobject-query@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
|
||||
|
@ -4437,6 +4564,17 @@ browserslist@^4.20.2:
|
|||
node-releases "^2.0.2"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
browserslist@^4.20.3:
|
||||
version "4.20.3"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf"
|
||||
integrity sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30001332"
|
||||
electron-to-chromium "^1.4.118"
|
||||
escalade "^3.1.1"
|
||||
node-releases "^2.0.3"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
bs-logger@0.x:
|
||||
version "0.2.6"
|
||||
resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8"
|
||||
|
@ -4629,6 +4767,14 @@ callsites@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||
|
||||
camel-case@^1.1.1:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-1.2.2.tgz#1aca7c4d195359a2ce9955793433c6e5542511f2"
|
||||
integrity sha1-Gsp8TRlTWaLOmVV5NDPG5VQlEfI=
|
||||
dependencies:
|
||||
sentence-case "^1.1.1"
|
||||
upper-case "^1.1.1"
|
||||
|
||||
camelcase-css@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
|
||||
|
@ -4649,10 +4795,10 @@ caniuse-lite@^1.0.30001283, caniuse-lite@^1.0.30001313, caniuse-lite@^1.0.300013
|
|||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001325.tgz#2b4ad19b77aa36f61f2eaf72e636d7481d55e606"
|
||||
integrity sha512-sB1bZHjseSjDtijV1Hb7PB2Zd58Kyx+n/9EotvZ4Qcz2K3d0lWB8dB4nb8wN/TsOGFq3UuAm0zQZNQ4SoR7TrQ==
|
||||
|
||||
caniuse-lite@^1.0.30001332:
|
||||
version "1.0.30001334"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001334.tgz#892e9965b35285033fc2b8a8eff499fe02f13d8b"
|
||||
integrity sha512-kbaCEBRRVSoeNs74sCuq92MJyGrMtjWVfhltoHUCW4t4pXFvGjUBrfo47weBRViHkiV3eBYyIsfl956NtHGazw==
|
||||
caniuse-lite@^1.0.30001332, caniuse-lite@^1.0.30001335:
|
||||
version "1.0.30001335"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001335.tgz#899254a0b70579e5a957c32dced79f0727c61f2a"
|
||||
integrity sha512-ddP1Tgm7z2iIxu6QTtbZUv6HJxSaV/PZeSrWFZtbY4JZ69tOeNhBCl3HyRQgeNZKE5AOn1kpV7fhljigy0Ty3w==
|
||||
|
||||
capture-exit@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
@ -4713,6 +4859,28 @@ chalk@^2.0.0, chalk@^2.4.1:
|
|||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
change-case@^2.3.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/change-case/-/change-case-2.3.1.tgz#2c4fde3f063bb41d00cd68e0d5a09db61cbe894f"
|
||||
integrity sha1-LE/ePwY7tB0AzWjg1aCdthy+iU8=
|
||||
dependencies:
|
||||
camel-case "^1.1.1"
|
||||
constant-case "^1.1.0"
|
||||
dot-case "^1.1.0"
|
||||
is-lower-case "^1.1.0"
|
||||
is-upper-case "^1.1.0"
|
||||
lower-case "^1.1.1"
|
||||
lower-case-first "^1.0.0"
|
||||
param-case "^1.1.0"
|
||||
pascal-case "^1.1.0"
|
||||
path-case "^1.1.0"
|
||||
sentence-case "^1.1.1"
|
||||
snake-case "^1.1.0"
|
||||
swap-case "^1.1.0"
|
||||
title-case "^1.1.0"
|
||||
upper-case "^1.1.1"
|
||||
upper-case-first "^1.1.0"
|
||||
|
||||
char-regex@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
|
||||
|
@ -5041,7 +5209,7 @@ commander@^6.2.0:
|
|||
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
|
||||
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
|
||||
|
||||
component-emitter@^1.2.1:
|
||||
component-emitter@^1.2.1, component-emitter@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
||||
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
|
||||
|
@ -5104,7 +5272,7 @@ cookie@0.4.2, cookie@^0.4.1, cookie@~0.4.1:
|
|||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
|
||||
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
|
||||
|
||||
cookiejar@^2.1.1:
|
||||
cookiejar@^2.1.1, cookiejar@^2.1.2:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc"
|
||||
integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==
|
||||
|
@ -5273,6 +5441,11 @@ crypto-browserify@3.12.0, crypto-browserify@^3.11.0:
|
|||
randombytes "^2.0.0"
|
||||
randomfill "^1.0.3"
|
||||
|
||||
crypto@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037"
|
||||
integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==
|
||||
|
||||
css.escape@1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
|
||||
|
@ -5466,6 +5639,11 @@ deep-is@^0.1.3, deep-is@~0.1.3:
|
|||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
||||
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
||||
|
||||
deepmerge@^3.2.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.3.0.tgz#d3c47fd6f3a93d517b14426b0628a17b0125f5f7"
|
||||
integrity sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==
|
||||
|
||||
deepmerge@^4.2.2, deepmerge@~4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||
|
@ -5701,6 +5879,13 @@ dompurify@=2.3.3:
|
|||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.3.tgz#c1af3eb88be47324432964d8abc75cf4b98d634c"
|
||||
integrity sha512-dqnqRkPMAjOZE0FogZ+ceJNM2dZ3V/yNOuFB7+39qpO93hHhfRpHw3heYQC7DPK9FqbQTfBKUJhiSfz4MvXYwg==
|
||||
|
||||
dot-case@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-1.1.2.tgz#1e73826900de28d6de5480bc1de31d0842b06bec"
|
||||
integrity sha1-HnOCaQDeKNbeVIC8HeMdCEKwa+w=
|
||||
dependencies:
|
||||
sentence-case "^1.1.2"
|
||||
|
||||
dotenv-checker@^1.1.5:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/dotenv-checker/-/dotenv-checker-1.1.5.tgz#0bdd140ce50c560d70fb8103ffe1a14c9ad65c74"
|
||||
|
@ -5775,6 +5960,11 @@ ee-first@1.1.1:
|
|||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||
|
||||
electron-to-chromium@^1.4.118:
|
||||
version "1.4.132"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.132.tgz#b64599eb018221e52e2e4129de103b03a413c55d"
|
||||
integrity sha512-JYdZUw/1068NWN+SwXQ7w6Ue0bWYGihvSUNNQwurvcDV/SM7vSiGZ3NuFvFgoEiCs4kB8xs3cX2an3wB7d4TBw==
|
||||
|
||||
electron-to-chromium@^1.4.76, electron-to-chromium@^1.4.84:
|
||||
version "1.4.103"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.103.tgz#abfe376a4d70fa1e1b4b353b95df5d6dfd05da3a"
|
||||
|
@ -6865,11 +7055,16 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
|
|||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
||||
|
||||
fast-safe-stringify@^2.0.6:
|
||||
fast-safe-stringify@^2.0.6, fast-safe-stringify@^2.0.7:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
|
||||
integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
|
||||
|
||||
fast-sha256@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-sha256/-/fast-sha256-1.3.0.tgz#7916ba2054eeb255982608cccd0f6660c79b7ae6"
|
||||
integrity sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==
|
||||
|
||||
fast-text-encoding@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53"
|
||||
|
@ -7029,7 +7224,7 @@ focus-visible@^5.1.0:
|
|||
resolved "https://registry.yarnpkg.com/focus-visible/-/focus-visible-5.2.0.tgz#3a9e41fccf587bd25dcc2ef045508284f0a4d6b3"
|
||||
integrity sha512-Rwix9pBtC1Nuy5wysTmKy+UjbDJpIfg8eHjw0rjZ1mX4GNLz1Bmd16uDpI3Gk1i70Fgcs8Csg2lPm8HULFg9DQ==
|
||||
|
||||
follow-redirects@^1.14.8:
|
||||
follow-redirects@^1.14.0, follow-redirects@^1.14.7, follow-redirects@^1.14.8:
|
||||
version "1.14.9"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
|
||||
integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
|
||||
|
@ -7072,7 +7267,7 @@ form-data@^2.5.0:
|
|||
combined-stream "^1.0.6"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@^3.0.0:
|
||||
form-data@^3.0.0, form-data@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
|
||||
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
|
||||
|
@ -7103,6 +7298,11 @@ formdata-node@^4.0.0:
|
|||
node-domexception "1.0.0"
|
||||
web-streams-polyfill "4.0.0-beta.1"
|
||||
|
||||
formidable@^1.2.2:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.6.tgz#d2a51d60162bbc9b4a055d8457a7c75315d1a168"
|
||||
integrity sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==
|
||||
|
||||
forwarded@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
||||
|
@ -8356,6 +8556,13 @@ is-interactive@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
|
||||
integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
|
||||
|
||||
is-lower-case@^1.1.0:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/is-lower-case/-/is-lower-case-1.1.3.tgz#7e147be4768dc466db3bfb21cc60b31e6ad69393"
|
||||
integrity sha1-fhR75HaNxGbbO/shzGCzHmrWk5M=
|
||||
dependencies:
|
||||
lower-case "^1.1.0"
|
||||
|
||||
is-natural-number@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
|
||||
|
@ -8462,6 +8669,11 @@ is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
|
||||
integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==
|
||||
|
||||
is-retry-allowed@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz#88f34cbd236e043e71b6932d09b0c65fb7b4d71d"
|
||||
integrity sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==
|
||||
|
||||
is-shared-array-buffer@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79"
|
||||
|
@ -8514,6 +8726,13 @@ is-unicode-supported@^0.1.0:
|
|||
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
|
||||
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
|
||||
|
||||
is-upper-case@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/is-upper-case/-/is-upper-case-1.1.2.tgz#8d0b1fa7e7933a1e58483600ec7d9661cbaf756f"
|
||||
integrity sha1-jQsfp+eTOh5YSDYA7H2WYcuvdW8=
|
||||
dependencies:
|
||||
upper-case "^1.1.0"
|
||||
|
||||
is-weakref@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2"
|
||||
|
@ -8611,6 +8830,17 @@ istanbul-lib-instrument@^5.0.4:
|
|||
istanbul-lib-coverage "^3.2.0"
|
||||
semver "^6.3.0"
|
||||
|
||||
istanbul-lib-instrument@^5.1.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f"
|
||||
integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==
|
||||
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"
|
||||
|
||||
istanbul-lib-report@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6"
|
||||
|
@ -9333,6 +9563,15 @@ jsprim@^1.2.2:
|
|||
array-includes "^3.1.4"
|
||||
object.assign "^4.1.2"
|
||||
|
||||
jwa@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
|
||||
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
|
||||
dependencies:
|
||||
buffer-equal-constant-time "1.0.1"
|
||||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jwa@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc"
|
||||
|
@ -9342,6 +9581,30 @@ jwa@^2.0.0:
|
|||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jwks-rsa@^1.12.1:
|
||||
version "1.12.3"
|
||||
resolved "https://registry.yarnpkg.com/jwks-rsa/-/jwks-rsa-1.12.3.tgz#40232f85d16734cb82837f38bb3e350a34435400"
|
||||
integrity sha512-cFipFDeYYaO9FhhYJcZWX/IyZgc0+g316rcHnDpT2dNRNIE/lMOmWKKqp09TkJoYlNFzrEVODsR4GgXJMgWhnA==
|
||||
dependencies:
|
||||
"@types/express-jwt" "0.0.42"
|
||||
axios "^0.21.1"
|
||||
debug "^4.1.0"
|
||||
http-proxy-agent "^4.0.1"
|
||||
https-proxy-agent "^5.0.0"
|
||||
jsonwebtoken "^8.5.1"
|
||||
limiter "^1.1.5"
|
||||
lru-memoizer "^2.1.2"
|
||||
ms "^2.1.2"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
jws@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
|
||||
dependencies:
|
||||
jwa "^1.4.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jws@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4"
|
||||
|
@ -9540,6 +9803,11 @@ lilconfig@^2.0.5:
|
|||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25"
|
||||
integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==
|
||||
|
||||
limiter@^1.1.5:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2"
|
||||
integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==
|
||||
|
||||
lines-and-columns@^1.1.6:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
|
||||
|
@ -9652,6 +9920,11 @@ lodash.isplainobject@^4.0.6:
|
|||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
|
||||
|
||||
lodash.isstring@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
|
||||
|
||||
lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
|
@ -9707,6 +9980,18 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3
|
|||
dependencies:
|
||||
js-tokens "^3.0.0 || ^4.0.0"
|
||||
|
||||
lower-case-first@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/lower-case-first/-/lower-case-first-1.0.2.tgz#e5da7c26f29a7073be02d52bac9980e5922adfa1"
|
||||
integrity sha1-5dp8JvKacHO+AtUrrJmA5ZIq36E=
|
||||
dependencies:
|
||||
lower-case "^1.1.2"
|
||||
|
||||
lower-case@^1.1.0, lower-case@^1.1.1, lower-case@^1.1.2:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
|
||||
integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw=
|
||||
|
||||
lowercase-keys@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
|
||||
|
@ -10506,6 +10791,11 @@ mime@3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7"
|
||||
integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==
|
||||
|
||||
mime@^2.4.6:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
|
||||
integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
|
||||
|
||||
mimic-fn@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||
|
@ -10674,7 +10964,7 @@ ms@2.1.2:
|
|||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
ms@2.1.3, ms@^2.1.1:
|
||||
ms@2.1.3, ms@^2.1.1, ms@^2.1.2:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
@ -10776,6 +11066,11 @@ nanoid@^3.1.23, nanoid@^3.1.30, nanoid@^3.3.1:
|
|||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557"
|
||||
integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==
|
||||
|
||||
nanoid@^3.3.3:
|
||||
version "3.3.4"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
|
||||
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
|
||||
|
||||
nanomatch@^1.2.9:
|
||||
version "1.2.13"
|
||||
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
|
||||
|
@ -11039,6 +11334,11 @@ node-releases@^2.0.2:
|
|||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01"
|
||||
integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==
|
||||
|
||||
node-releases@^2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.4.tgz#f38252370c43854dc48aa431c766c6c398f40476"
|
||||
integrity sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==
|
||||
|
||||
nodemailer@^6.7.2:
|
||||
version "6.7.3"
|
||||
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.3.tgz#b73f9a81b9c8fa8acb4ea14b608f5e725ea8e018"
|
||||
|
@ -11526,6 +11826,13 @@ pako@^1.0.5:
|
|||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||
|
||||
param-case@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/param-case/-/param-case-1.1.2.tgz#dcb091a43c259b9228f1c341e7b6a44ea0bf9743"
|
||||
integrity sha1-3LCRpDwlm5Io8cNB57akTqC/l0M=
|
||||
dependencies:
|
||||
sentence-case "^1.1.2"
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||
|
@ -11643,6 +11950,14 @@ parseurl@~1.3.3:
|
|||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||
|
||||
pascal-case@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-1.1.2.tgz#3e5d64a20043830a7c49344c2d74b41be0c9c99b"
|
||||
integrity sha1-Pl1kogBDgwp8STRMLXS0G+DJyZs=
|
||||
dependencies:
|
||||
camel-case "^1.1.1"
|
||||
upper-case-first "^1.1.0"
|
||||
|
||||
pascalcase@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
|
||||
|
@ -11653,6 +11968,13 @@ path-browserify@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
|
||||
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
|
||||
|
||||
path-case@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/path-case/-/path-case-1.1.2.tgz#50ce6ba0d3bed3dd0b5c2a9c4553697434409514"
|
||||
integrity sha1-UM5roNO+090LXCqcRVNpdDRAlRQ=
|
||||
dependencies:
|
||||
sentence-case "^1.1.2"
|
||||
|
||||
path-exists@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
|
||||
|
@ -12013,7 +12335,16 @@ postcss@8.4.5:
|
|||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.1"
|
||||
|
||||
postcss@^8.3.6, postcss@^8.4.12, postcss@^8.4.4, postcss@^8.4.6, postcss@^8.4.8:
|
||||
postcss@^8.3.6, postcss@^8.4.8:
|
||||
version "8.4.13"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575"
|
||||
integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==
|
||||
dependencies:
|
||||
nanoid "^3.3.3"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
postcss@^8.4.12, postcss@^8.4.4, postcss@^8.4.6:
|
||||
version "8.4.12"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905"
|
||||
integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==
|
||||
|
@ -12231,7 +12562,7 @@ prr@~1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
|
||||
integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY=
|
||||
|
||||
pseudomap@^1.0.2:
|
||||
pseudomap@^1.0.1, pseudomap@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
|
||||
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
|
||||
|
@ -12298,7 +12629,7 @@ qrcode@^1.5.0:
|
|||
pngjs "^5.0.0"
|
||||
yargs "^15.3.1"
|
||||
|
||||
qs@6.10.3, qs@^6.10.2, qs@^6.10.3, qs@^6.7.0:
|
||||
qs@6.10.3, qs@^6.10.2, qs@^6.10.3, qs@^6.7.0, qs@^6.9.4:
|
||||
version "6.10.3"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
|
||||
integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
|
||||
|
@ -13102,6 +13433,16 @@ responselike@1.0.2, responselike@^1.0.2:
|
|||
dependencies:
|
||||
lowercase-keys "^1.0.0"
|
||||
|
||||
rest-facade@^1.16.3:
|
||||
version "1.16.3"
|
||||
resolved "https://registry.yarnpkg.com/rest-facade/-/rest-facade-1.16.3.tgz#e4d4b44a4f6a4268f30f02e1f544dd49b040499e"
|
||||
integrity sha512-9BQTPLiwg23XZwcWi0ys1wTizfc//0b2G3U6vBWcgqh56ozs2K6CD+Jw4DYcw3AqdPQN7jj8nzRUcUXFVGzb0Q==
|
||||
dependencies:
|
||||
change-case "^2.3.0"
|
||||
deepmerge "^3.2.0"
|
||||
lodash.get "^4.4.2"
|
||||
superagent "^5.1.1"
|
||||
|
||||
restore-cursor@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
|
||||
|
@ -13360,6 +13701,13 @@ send@0.17.2:
|
|||
range-parser "~1.2.1"
|
||||
statuses "~1.5.0"
|
||||
|
||||
sentence-case@^1.1.1, sentence-case@^1.1.2:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-1.1.3.tgz#8034aafc2145772d3abe1509aa42c9e1042dc139"
|
||||
integrity sha1-gDSq/CFFdy06vhUJqkLJ4QQtwTk=
|
||||
dependencies:
|
||||
lower-case "^1.1.1"
|
||||
|
||||
seq-queue@^0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e"
|
||||
|
@ -13556,6 +13904,13 @@ smart-buffer@^4.2.0:
|
|||
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
|
||||
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
|
||||
|
||||
snake-case@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-1.1.2.tgz#0c2f25e305158d9a18d3d977066187fef8a5a66a"
|
||||
integrity sha1-DC8l4wUVjZoY09l3BmGH/vilpmo=
|
||||
dependencies:
|
||||
sentence-case "^1.1.2"
|
||||
|
||||
snapdragon-node@^2.0.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
|
||||
|
@ -14027,6 +14382,23 @@ stylis@4.0.13:
|
|||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91"
|
||||
integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==
|
||||
|
||||
superagent@^5.1.1:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/superagent/-/superagent-5.3.1.tgz#d62f3234d76b8138c1320e90fa83dc1850ccabf1"
|
||||
integrity sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==
|
||||
dependencies:
|
||||
component-emitter "^1.3.0"
|
||||
cookiejar "^2.1.2"
|
||||
debug "^4.1.1"
|
||||
fast-safe-stringify "^2.0.7"
|
||||
form-data "^3.0.0"
|
||||
formidable "^1.2.2"
|
||||
methods "^1.1.2"
|
||||
mime "^2.4.6"
|
||||
qs "^6.9.4"
|
||||
readable-stream "^3.6.0"
|
||||
semver "^7.3.2"
|
||||
|
||||
superjson@1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/superjson/-/superjson-1.8.1.tgz#c0fe510b8ff71c3bde5733a125623994ca9ec608"
|
||||
|
@ -14086,6 +14458,26 @@ supports-preserve-symlinks-flag@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
svix-fetch@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/svix-fetch/-/svix-fetch-3.0.0.tgz#c13e20b69ceb3ad43b52dd3933ec30bf278c571d"
|
||||
integrity sha512-rcADxEFhSqHbraZIsjyZNh4TF6V+koloX1OzZ+AQuObX9mZ2LIMhm1buZeuc5BIZPftZpJCMBsSiBaeszo9tRw==
|
||||
dependencies:
|
||||
node-fetch "^2.6.1"
|
||||
whatwg-fetch "^3.4.1"
|
||||
|
||||
svix@0.42.3:
|
||||
version "0.42.3"
|
||||
resolved "https://registry.yarnpkg.com/svix/-/svix-0.42.3.tgz#1ee7804f54e58017ed6bf03a9231034145a7ae90"
|
||||
integrity sha512-Ynao0T64OadkG5Lgd9YDvrYFAGsydXck4Id5FQQZ+n0oMXyRYzVoYzeLE/1duChQSzhc9V3FOJGLWl0d11wjLQ==
|
||||
dependencies:
|
||||
"@stablelib/base64" "^1.0.0"
|
||||
"@stablelib/utf8" "^1.0.0"
|
||||
es6-promise "^4.2.4"
|
||||
fast-sha256 "^1.3.0"
|
||||
svix-fetch "^3.0.0"
|
||||
url-parse "^1.4.3"
|
||||
|
||||
swagger-client@^3.18.4:
|
||||
version "3.18.4"
|
||||
resolved "https://registry.yarnpkg.com/swagger-client/-/swagger-client-3.18.4.tgz#71be9df585157a3335a542c407733d2134fa75e9"
|
||||
|
@ -14164,6 +14556,14 @@ swagger-ui-react@4.10.3:
|
|||
xml-but-prettier "^1.0.1"
|
||||
zenscroll "^4.0.2"
|
||||
|
||||
swap-case@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/swap-case/-/swap-case-1.1.2.tgz#c39203a4587385fad3c850a0bd1bcafa081974e3"
|
||||
integrity sha1-w5IDpFhzhfrTyFCgvRvK+ggZdOM=
|
||||
dependencies:
|
||||
lower-case "^1.1.1"
|
||||
upper-case "^1.1.1"
|
||||
|
||||
swarm-js@^0.1.40:
|
||||
version "0.1.40"
|
||||
resolved "https://registry.yarnpkg.com/swarm-js/-/swarm-js-0.1.40.tgz#b1bc7b6dcc76061f6c772203e004c11997e06b99"
|
||||
|
@ -14370,6 +14770,14 @@ tinygradient@^1.1.5:
|
|||
"@types/tinycolor2" "^1.4.0"
|
||||
tinycolor2 "^1.0.0"
|
||||
|
||||
title-case@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/title-case/-/title-case-1.1.2.tgz#fae4a6ae546bfa22d083a0eea910a40d12ed4f5a"
|
||||
integrity sha1-+uSmrlRr+iLQg6DuqRCkDRLtT1o=
|
||||
dependencies:
|
||||
sentence-case "^1.1.1"
|
||||
upper-case "^1.0.3"
|
||||
|
||||
title@^3.4.2:
|
||||
version "3.4.4"
|
||||
resolved "https://registry.yarnpkg.com/title/-/title-3.4.4.tgz#5c0ab11fd69643bc05dc006bba52aaf5c1630f5e"
|
||||
|
@ -15024,6 +15432,18 @@ update-input-width@^1.2.2:
|
|||
resolved "https://registry.yarnpkg.com/update-input-width/-/update-input-width-1.2.2.tgz#9a6a35858ae8e66fbfe0304437b23a4934fc7d37"
|
||||
integrity sha512-6QwD9ZVSXb96PxOZ01DU0DJTPwQGY7qBYgdniZKJN02Xzom2m+9J6EPxMbefskqtj4x78qbe5psDSALq9iNEYg==
|
||||
|
||||
upper-case-first@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-1.1.2.tgz#5d79bedcff14419518fd2edb0a0507c9b6859115"
|
||||
integrity sha1-XXm+3P8UQZUY/S7bCgUHybaFkRU=
|
||||
dependencies:
|
||||
upper-case "^1.1.1"
|
||||
|
||||
upper-case@^1.0.3, upper-case@^1.1.0, upper-case@^1.1.1:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
|
||||
integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=
|
||||
|
||||
uri-js@^4.2.2:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
|
||||
|
@ -15649,6 +16069,11 @@ whatwg-encoding@^1.0.5:
|
|||
dependencies:
|
||||
iconv-lite "0.4.24"
|
||||
|
||||
whatwg-fetch@^3.4.1:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c"
|
||||
integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==
|
||||
|
||||
whatwg-mimetype@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
|
||||
|
@ -15935,7 +16360,7 @@ yallist@4.0.0, yallist@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yallist@^2.1.2:
|
||||
yallist@^2.0.0, yallist@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
|
||||
integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
|
||||
|
|
Loading…
Reference in a new issue