Merge branch 'main' of https://github.com/calcom/cal.com
commit
807f186f6b
|
@ -1,42 +0,0 @@
|
|||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-outside-of-docker
|
||||
{
|
||||
"name": "Docker outside of Docker",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/base:bullseye",
|
||||
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-from-docker:1": {
|
||||
"version": "latest",
|
||||
"enableNonRootDocker": "true",
|
||||
"moby": "true"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {},
|
||||
"ghcr.io/devcontainers-contrib/features/npm-package:1": {},
|
||||
"ghcr.io/devcontainers-contrib/features/jest:2": {},
|
||||
"ghcr.io/devcontainers-contrib/features/prisma:2": {},
|
||||
"ghcr.io/guiyomh/features/vim:0": {}
|
||||
},
|
||||
|
||||
// Use this environment variable if you need to bind mount your local source code into a new container.
|
||||
"remoteEnv": {
|
||||
"LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}"
|
||||
},
|
||||
|
||||
"hostRequirements": {
|
||||
"cpus": 4,
|
||||
"memory": "8gb"
|
||||
},
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "./deploy/install.sh"
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
|
@ -6,6 +6,6 @@
|
|||
},
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"spellright.language": ["en"],
|
||||
"spellright.documentTypes": ["markdown", "typescript"],
|
||||
"spellright.documentTypes": ["markdown", "typescript", "typescriptreact"],
|
||||
"tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]]
|
||||
}
|
||||
|
|
|
@ -71,5 +71,5 @@ module.exports = {
|
|||
|
||||
return config;
|
||||
},
|
||||
typescript: { reactDocgen: 'react-docgen' }
|
||||
typescript: { reactDocgen: "react-docgen" },
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
|||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import { nameOfDay } from "@calcom/lib/weekday";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots";
|
||||
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots/types";
|
||||
import { SkeletonContainer, SkeletonText, ToggleGroup } from "@calcom/ui";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
|
|
|
@ -280,13 +280,13 @@ function BookingListItem(booking: BookingItemProps) {
|
|||
isOpenDialog={isOpenSetLocationDialog}
|
||||
setShowLocationModal={setIsOpenLocationDialog}
|
||||
/>
|
||||
{booking.paid && (
|
||||
{booking.paid && booking.payment[0] && (
|
||||
<ChargeCardDialog
|
||||
isOpenDialog={chargeCardDialogIsOpen}
|
||||
setIsOpenDialog={setChargeCardDialogIsOpen}
|
||||
bookingId={booking.id}
|
||||
paymentAmount={booking?.payment[0].amount}
|
||||
paymentCurrency={booking?.payment[0].currency}
|
||||
paymentAmount={booking.payment[0].amount}
|
||||
paymentCurrency={booking.payment[0].currency}
|
||||
/>
|
||||
)}
|
||||
{showRecordingsButtons && (
|
||||
|
@ -354,11 +354,15 @@ function BookingListItem(booking: BookingItemProps) {
|
|||
{booking.eventType.team.name}
|
||||
</Badge>
|
||||
)}
|
||||
{booking.paid && (
|
||||
{booking.paid && !booking.payment[0] ? (
|
||||
<Badge className="ltr:mr-2 rtl:ml-2" variant="orange">
|
||||
{t("error_collecting_card")}
|
||||
</Badge>
|
||||
) : booking.paid ? (
|
||||
<Badge className="ltr:mr-2 rtl:ml-2" variant="green">
|
||||
{booking.payment[0].paymentOption === "HOLD" ? t("card_held") : t("paid")}
|
||||
</Badge>
|
||||
)}
|
||||
) : null}
|
||||
{recurringDates !== undefined && (
|
||||
<div className="text-muted mt-2 text-sm">
|
||||
<RecurringBookingsTooltip booking={booking} recurringDates={recurringDates} />
|
||||
|
@ -458,7 +462,7 @@ function BookingListItem(booking: BookingItemProps) {
|
|||
<RequestSentMessage />
|
||||
</div>
|
||||
)}
|
||||
{booking.status === "ACCEPTED" && booking.paid && booking?.payment[0]?.paymentOption === "HOLD" && (
|
||||
{booking.status === "ACCEPTED" && booking.paid && booking.payment[0]?.paymentOption === "HOLD" && (
|
||||
<div className="ml-2">
|
||||
<TableActions actions={chargeCardActions} />
|
||||
</div>
|
||||
|
@ -483,12 +487,12 @@ const RecurringBookingsTooltip = ({ booking, recurringDates }: RecurringBookings
|
|||
i18n: { language },
|
||||
} = useLocale();
|
||||
const now = new Date();
|
||||
const recurringCount = recurringDates.filter((date) => {
|
||||
const recurringCount = recurringDates.filter((recurringDate) => {
|
||||
return (
|
||||
date >= now &&
|
||||
recurringDate >= now &&
|
||||
!booking.recurringInfo?.bookings[BookingStatus.CANCELLED]
|
||||
.map((date) => date.toDateString())
|
||||
.includes(date.toDateString())
|
||||
.includes(recurringDate.toDateString())
|
||||
);
|
||||
}).length;
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ import { MetaProvider } from "@calcom/ui";
|
|||
import usePublicPage from "@lib/hooks/usePublicPage";
|
||||
import type { WithNonceProps } from "@lib/withNonce";
|
||||
|
||||
import { useViewerI18n } from "@components/I18nLanguageHandler";
|
||||
|
||||
const I18nextAdapter = appWithTranslation<NextJsAppProps<SSRConfig> & { children: React.ReactNode }>(
|
||||
({ children }) => <>{children}</>
|
||||
);
|
||||
|
@ -46,9 +48,7 @@ const CustomI18nextProvider = (props: AppPropsWithChildren) => {
|
|||
* i18n should never be clubbed with other queries, so that it's caching can be managed independently.
|
||||
* We intend to not cache i18n query
|
||||
**/
|
||||
const { i18n, locale } = trpc.viewer.public.i18n.useQuery(undefined, {
|
||||
trpc: { context: { skipBatch: true } },
|
||||
}).data ?? {
|
||||
const { i18n, locale } = useViewerI18n().data ?? {
|
||||
locale: "en",
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import { defaultResponder } from "@calcom/lib/server";
|
|||
import prisma from "@calcom/prisma";
|
||||
import { TRPCError } from "@calcom/trpc/server";
|
||||
import { createContext } from "@calcom/trpc/server/createContext";
|
||||
import { viewerRouter } from "@calcom/trpc/server/routers/viewer";
|
||||
import { viewerRouter } from "@calcom/trpc/server/routers/viewer/_router";
|
||||
|
||||
enum DirectAction {
|
||||
ACCEPT = "accept",
|
||||
|
@ -51,7 +51,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse<Response>) {
|
|||
try {
|
||||
/** @see https://trpc.io/docs/server-side-calls */
|
||||
const ctx = await createContext({ req, res }, sessionGetter);
|
||||
const caller = viewerRouter.createCaller(ctx);
|
||||
const caller = viewerRouter.createCaller({ ...ctx, req, res });
|
||||
|
||||
await caller.bookings.confirm({
|
||||
bookingId: booking.id,
|
||||
recurringEventId: booking.recurringEventId || undefined,
|
||||
|
|
|
@ -6,6 +6,7 @@ import path from "path";
|
|||
import { z } from "zod";
|
||||
|
||||
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
|
||||
import { getAppAssetFullPath } from "@calcom/app-store/getAppAssetFullPath";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
@ -108,9 +109,11 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
|
|||
const { content, data } = sourceSchema.parse({ content: result.content, data: result.data });
|
||||
if (data.items) {
|
||||
data.items = data.items.map((item) => {
|
||||
if (typeof item === "string" && !item.includes("/api/app-store")) {
|
||||
// Make relative paths absolute
|
||||
return `/api/app-store/${appDirname}/${item}`;
|
||||
if (typeof item === "string") {
|
||||
return getAppAssetFullPath(item, {
|
||||
dirName: singleApp.dirName,
|
||||
isTemplate: singleApp.isTemplate,
|
||||
});
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useRouter } from "next/router";
|
|||
|
||||
import { AppSetupPage } from "@calcom/app-store/_pages/setup";
|
||||
import { getStaticProps } from "@calcom/app-store/_pages/setup/_getStaticProps";
|
||||
import { HeadSeo } from "@calcom/ui";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
|
@ -25,7 +26,13 @@ export default function SetupInformation(props: InferGetStaticPropsType<typeof g
|
|||
});
|
||||
}
|
||||
|
||||
return <AppSetupPage slug={slug} {...props} />;
|
||||
return (
|
||||
<>
|
||||
{/* So that the set up page does not get indexed by search engines */}
|
||||
<HeadSeo nextSeoProps={{ noindex: true, nofollow: true }} title={`${slug} | Cal.com`} description="" />
|
||||
<AppSetupPage slug={slug} {...props} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
SetupInformation.PageWrapper = PageWrapper;
|
||||
|
|
|
@ -46,10 +46,10 @@ const TwoFactorAuthView = () => {
|
|||
user?.twoFactorEnabled ? setDisableModalOpen(true) : setEnableModalOpen(true)
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
<div className="!mx-4">
|
||||
<div className="flex">
|
||||
<p className="text-default font-semibold">{t("two_factor_auth")}</p>
|
||||
<Badge className="ml-2 text-xs" variant={user?.twoFactorEnabled ? "success" : "gray"}>
|
||||
<Badge className="mx-2 text-xs" variant={user?.twoFactorEnabled ? "success" : "gray"}>
|
||||
{user?.twoFactorEnabled ? t("enabled") : t("disabled")}
|
||||
</Badge>
|
||||
</div>
|
||||
|
|
|
@ -263,7 +263,7 @@ export function VideoMeetingInfo(props: VideoMeetingInfo) {
|
|||
);
|
||||
}
|
||||
|
||||
VideoMeetingInfo.PageWrapper = PageWrapper;
|
||||
JoinCall.PageWrapper = PageWrapper;
|
||||
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const { req, res } = context;
|
||||
|
|
|
@ -1799,5 +1799,6 @@
|
|||
"charge_attendee": "Charge attendee {{amount, currency}}",
|
||||
"payment_app_commission": "Require payment ({{paymentFeePercentage}}% + {{fee, currency}} commission per transaction)",
|
||||
"email_invite_team": "{{email}} has been invited",
|
||||
"error_collecting_card": "Error collecting card",
|
||||
"image_size_limit_exceed": "Uploaded image shouldn't exceed 5mb size limit"
|
||||
}
|
||||
|
|
|
@ -465,6 +465,7 @@
|
|||
"friday": "Vendredi",
|
||||
"saturday": "Samedi",
|
||||
"sunday": "Dimanche",
|
||||
"all_booked_today": "Tout est réservé.",
|
||||
"slots_load_fail": "Impossible de charger les créneaux disponibles.",
|
||||
"additional_guests": "Ajouter des invités",
|
||||
"your_name": "Votre nom",
|
||||
|
@ -1792,10 +1793,12 @@
|
|||
"seats_and_no_show_fee_error": "Il n'est pas possible d'activer les places et de facturer des frais d'absence pour le moment",
|
||||
"complete_your_booking": "Terminer votre réservation",
|
||||
"complete_your_booking_subject": "Terminer votre réservation : {{title}} le {{date}}",
|
||||
"confirm_your_details": "Confirmez vos coordonnées",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Vous êtes sur le point de facturer {{amount, currency}} au participant. Voulez-vous vraiment continuer ?",
|
||||
"charge_attendee": "Facturer {{amount, currency}} au participant",
|
||||
"payment_app_commission": "Exiger un paiement ({{paymentFeePercentage}} % + {{fee, currency}} de commission par transaction)",
|
||||
"email_invite_team": "{{email}} a été invité",
|
||||
"error_collecting_card": "Erreur lors de la collecte de la carte",
|
||||
"image_size_limit_exceed": "L'image téléchargée ne doit pas dépasser 5 Mo"
|
||||
}
|
||||
|
|
|
@ -15,15 +15,14 @@ import { v4 as uuidv4 } from "uuid";
|
|||
import logger from "@calcom/lib/logger";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { BookingStatus } from "@calcom/prisma/client";
|
||||
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots";
|
||||
import { getSchedule } from "@calcom/trpc/server/routers/viewer/slots";
|
||||
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots/types";
|
||||
import { getSchedule } from "@calcom/trpc/server/routers/viewer/slots/util";
|
||||
|
||||
import { prismaMock, CalendarManagerMock } from "../../../../tests/config/singleton";
|
||||
|
||||
// TODO: Mock properly
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(null);
|
||||
prismaMock.user.findMany.mockResolvedValue([]);
|
||||
prismaMock.selectedSlots.findMany.mockResolvedValue([]);
|
||||
|
||||
jest.mock("@calcom/lib/constants", () => ({
|
||||
IS_PRODUCTION: true,
|
||||
|
@ -271,16 +270,13 @@ describe("getSchedule", () => {
|
|||
end: `${plus2DateString}T23:00:00.000Z`,
|
||||
},
|
||||
]);
|
||||
const scheduleForDayWithAGoogleCalendarBooking = await getSchedule(
|
||||
{
|
||||
const scheduleForDayWithAGoogleCalendarBooking = await getSchedule({
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
});
|
||||
|
||||
// As per Google Calendar Availability, only 4PM(4-4:45PM) GMT slot would be available
|
||||
expect(scheduleForDayWithAGoogleCalendarBooking).toHaveTimeSlots([`04:00:00.000Z`], {
|
||||
|
@ -357,17 +353,14 @@ describe("getSchedule", () => {
|
|||
});
|
||||
|
||||
// Day Plus 2 is completely free - It only has non accepted bookings
|
||||
const scheduleOnCompletelyFreeDay = await getSchedule(
|
||||
{
|
||||
const scheduleOnCompletelyFreeDay = await getSchedule({
|
||||
eventTypeId: 1,
|
||||
// EventTypeSlug doesn't matter for non-dynamic events
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
});
|
||||
|
||||
// getSchedule returns timeslots in GMT
|
||||
expect(scheduleOnCompletelyFreeDay).toHaveTimeSlots(
|
||||
|
@ -390,16 +383,13 @@ describe("getSchedule", () => {
|
|||
);
|
||||
|
||||
// Day plus 3
|
||||
const scheduleForDayWithOneBooking = await getSchedule(
|
||||
{
|
||||
const scheduleForDayWithOneBooking = await getSchedule({
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus2DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus3DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
});
|
||||
|
||||
expect(scheduleForDayWithOneBooking).toHaveTimeSlots(
|
||||
[
|
||||
|
@ -455,16 +445,13 @@ describe("getSchedule", () => {
|
|||
});
|
||||
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
||||
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
|
||||
const scheduleForEventWith30Length = await getSchedule(
|
||||
{
|
||||
const scheduleForEventWith30Length = await getSchedule({
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
});
|
||||
expect(scheduleForEventWith30Length).toHaveTimeSlots(
|
||||
[
|
||||
`04:00:00.000Z`,
|
||||
|
@ -490,16 +477,13 @@ describe("getSchedule", () => {
|
|||
}
|
||||
);
|
||||
|
||||
const scheduleForEventWith30minsLengthAndSlotInterval2hrs = await getSchedule(
|
||||
{
|
||||
const scheduleForEventWith30minsLengthAndSlotInterval2hrs = await getSchedule({
|
||||
eventTypeId: 2,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
});
|
||||
// `slotInterval` takes precedence over `length`
|
||||
expect(scheduleForEventWith30minsLengthAndSlotInterval2hrs).toHaveTimeSlots(
|
||||
[`04:00:00.000Z`, `06:00:00.000Z`, `08:00:00.000Z`, `10:00:00.000Z`, `12:00:00.000Z`],
|
||||
|
@ -553,16 +537,13 @@ describe("getSchedule", () => {
|
|||
});
|
||||
const { dateString: todayDateString } = getDate();
|
||||
const { dateString: minus1DateString } = getDate({ dateIncrement: -1 });
|
||||
const scheduleForEventWithBookingNotice13Hrs = await getSchedule(
|
||||
{
|
||||
const scheduleForEventWithBookingNotice13Hrs = await getSchedule({
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${minus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${todayDateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
});
|
||||
expect(scheduleForEventWithBookingNotice13Hrs).toHaveTimeSlots(
|
||||
[
|
||||
/*`04:00:00.000Z`, `06:00:00.000Z`, - Minimum time slot is 07:30 UTC*/ `08:00:00.000Z`,
|
||||
|
@ -574,16 +555,13 @@ describe("getSchedule", () => {
|
|||
}
|
||||
);
|
||||
|
||||
const scheduleForEventWithBookingNotice10Hrs = await getSchedule(
|
||||
{
|
||||
const scheduleForEventWithBookingNotice10Hrs = await getSchedule({
|
||||
eventTypeId: 2,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${minus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${todayDateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
});
|
||||
expect(scheduleForEventWithBookingNotice10Hrs).toHaveTimeSlots(
|
||||
[
|
||||
/*`04:00:00.000Z`, - Minimum bookable time slot is 04:30 UTC but next available is 06:00*/
|
||||
|
@ -639,16 +617,13 @@ describe("getSchedule", () => {
|
|||
},
|
||||
]);
|
||||
|
||||
const scheduleForEventOnADayWithNonCalBooking = await getSchedule(
|
||||
{
|
||||
const scheduleForEventOnADayWithNonCalBooking = await getSchedule({
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus2DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus3DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
});
|
||||
|
||||
expect(scheduleForEventOnADayWithNonCalBooking).toHaveTimeSlots(
|
||||
[
|
||||
|
@ -714,16 +689,13 @@ describe("getSchedule", () => {
|
|||
},
|
||||
]);
|
||||
|
||||
const scheduleForEventOnADayWithCalBooking = await getSchedule(
|
||||
{
|
||||
const scheduleForEventOnADayWithCalBooking = await getSchedule({
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
});
|
||||
|
||||
expect(scheduleForEventOnADayWithCalBooking).toHaveTimeSlots(
|
||||
[
|
||||
|
@ -767,16 +739,13 @@ describe("getSchedule", () => {
|
|||
|
||||
createBookingScenario(scenarioData);
|
||||
|
||||
const scheduleForEventOnADayWithDateOverride = await getSchedule(
|
||||
{
|
||||
const scheduleForEventOnADayWithDateOverride = await getSchedule({
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
});
|
||||
|
||||
expect(scheduleForEventOnADayWithDateOverride).toHaveTimeSlots(
|
||||
["08:30:00.000Z", "09:30:00.000Z", "10:30:00.000Z", "11:30:00.000Z"],
|
||||
|
@ -853,16 +822,13 @@ describe("getSchedule", () => {
|
|||
|
||||
// Requesting this user's availability for their
|
||||
// individual Event Type
|
||||
const thisUserAvailability = await getSchedule(
|
||||
{
|
||||
const thisUserAvailability = await getSchedule({
|
||||
eventTypeId: 2,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
});
|
||||
|
||||
expect(thisUserAvailability).toHaveTimeSlots(
|
||||
[
|
||||
|
@ -951,16 +917,13 @@ describe("getSchedule", () => {
|
|||
hosts: [],
|
||||
});
|
||||
|
||||
const scheduleForTeamEventOnADayWithNoBooking = await getSchedule(
|
||||
{
|
||||
const scheduleForTeamEventOnADayWithNoBooking = await getSchedule({
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${todayDateString}T18:30:00.000Z`,
|
||||
endTime: `${plus1DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
});
|
||||
|
||||
expect(scheduleForTeamEventOnADayWithNoBooking).toHaveTimeSlots(
|
||||
[
|
||||
|
@ -981,16 +944,13 @@ describe("getSchedule", () => {
|
|||
}
|
||||
);
|
||||
|
||||
const scheduleForTeamEventOnADayWithOneBookingForEachUser = await getSchedule(
|
||||
{
|
||||
const scheduleForTeamEventOnADayWithOneBookingForEachUser = await getSchedule({
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
});
|
||||
// A user with blocked time in another event, still affects Team Event availability
|
||||
// It's a collective availability, so both user 101 and 102 are considered for timeslots
|
||||
expect(scheduleForTeamEventOnADayWithOneBookingForEachUser).toHaveTimeSlots(
|
||||
|
@ -1088,16 +1048,13 @@ describe("getSchedule", () => {
|
|||
],
|
||||
hosts: [],
|
||||
});
|
||||
const scheduleForTeamEventOnADayWithOneBookingForEachUserButOnDifferentTimeslots = await getSchedule(
|
||||
{
|
||||
const scheduleForTeamEventOnADayWithOneBookingForEachUserButOnDifferentTimeslots = await getSchedule({
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
});
|
||||
// A user with blocked time in another event, still affects Team Event availability
|
||||
expect(scheduleForTeamEventOnADayWithOneBookingForEachUserButOnDifferentTimeslots).toHaveTimeSlots(
|
||||
[
|
||||
|
@ -1116,16 +1073,13 @@ describe("getSchedule", () => {
|
|||
{ dateString: plus2DateString }
|
||||
);
|
||||
|
||||
const scheduleForTeamEventOnADayWithOneBookingForEachUserOnSameTimeSlot = await getSchedule(
|
||||
{
|
||||
const scheduleForTeamEventOnADayWithOneBookingForEachUserOnSameTimeSlot = await getSchedule({
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus2DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus3DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
});
|
||||
// A user with blocked time in another event, still affects Team Event availability
|
||||
expect(scheduleForTeamEventOnADayWithOneBookingForEachUserOnSameTimeSlot).toHaveTimeSlots(
|
||||
[
|
||||
|
|
|
@ -11,10 +11,6 @@ export async function getAppWithMetadata(app: { dirName: string }) {
|
|||
// Let's not leak api keys to the front end
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { key, ...metadata } = appMetadata;
|
||||
if (metadata.logo && !metadata.logo.includes("/api/app-store/")) {
|
||||
const appDirName = `${metadata.isTemplate ? "templates" : ""}/${app.dirName}`;
|
||||
metadata.logo = `/api/app-store/${appDirName}/${metadata.logo}`;
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/amie/1.jpg
|
||||
- /api/app-store/amie/2.jpg
|
||||
- /api/app-store/amie/3.jpg
|
||||
- 1.jpg
|
||||
- 2.jpg
|
||||
- 3.jpg
|
||||
---
|
||||
|
||||
<iframe class="w-full aspect-video -mx-2" width="560" height="315" src="https://www.youtube.com/embed/OGe1NYKhZE8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "Amie",
|
||||
"slug": "amie",
|
||||
"type": "amie_other",
|
||||
"imageSrc": "/api/app-store/amie/icon.svg",
|
||||
"logo": "/api/app-store/amie/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/amie",
|
||||
"variant": "other",
|
||||
"categories": ["calendar"],
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { AppMeta } from "@calcom/types/App";
|
||||
|
||||
import { appStoreMetadata as rawAppStoreMetadata } from "./apps.metadata.generated";
|
||||
import { getAppAssetFullPath } from "./getAppAssetFullPath";
|
||||
|
||||
type RawAppStoreMetaData = typeof rawAppStoreMetadata;
|
||||
type AppStoreMetaData = {
|
||||
|
@ -8,12 +9,19 @@ type AppStoreMetaData = {
|
|||
};
|
||||
|
||||
export const appStoreMetadata = {} as AppStoreMetaData;
|
||||
|
||||
for (const [key, value] of Object.entries(rawAppStoreMetadata)) {
|
||||
appStoreMetadata[key as keyof typeof appStoreMetadata] = {
|
||||
const dirName = "dirName" in value ? value.dirName : value.slug;
|
||||
if (!dirName) {
|
||||
throw new Error(`Couldn't derive dirName for app ${key}`);
|
||||
}
|
||||
const metadata = (appStoreMetadata[key as keyof typeof appStoreMetadata] = {
|
||||
appData: null,
|
||||
dirName: "dirName" in value ? value.dirName : value.slug,
|
||||
dirName,
|
||||
__template: "",
|
||||
...value,
|
||||
} as AppStoreMetaData[keyof AppStoreMetaData];
|
||||
} as AppStoreMetaData[keyof AppStoreMetaData]);
|
||||
metadata.logo = getAppAssetFullPath(metadata.logo, {
|
||||
dirName,
|
||||
isTemplate: metadata.isTemplate,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/applecalendar/1.jpg
|
||||
- 1.jpg
|
||||
---
|
||||
|
||||
Apple calendar runs both the macOS and iOS mobile operating systems. Offering online cloud backup of calendars using Apple’s iCloud service, it can sync with Google Calendar and Microsoft Exchange Server. Users can schedule events in their day that include time, location, duration, and extra notes.
|
||||
|
|
|
@ -8,11 +8,10 @@ export const metadata = {
|
|||
installed: true,
|
||||
type: "apple_calendar",
|
||||
title: "Apple Calendar",
|
||||
imageSrc: "/api/app-store/applecalendar/icon.svg",
|
||||
variant: "calendar",
|
||||
categories: ["calendar"],
|
||||
category: "calendar",
|
||||
logo: "/api/app-store/applecalendar/icon.svg",
|
||||
logo: "icon.svg",
|
||||
publisher: "Cal.com",
|
||||
slug: "apple-calendar",
|
||||
url: "https://cal.com/",
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/around/1.jpg
|
||||
- /api/app-store/around/2.jpg
|
||||
- /api/app-store/around/3.jpg
|
||||
- /api/app-store/around/4.jpg
|
||||
- /api/app-store/around/5.jpg
|
||||
- /api/app-store/around/6.jpg
|
||||
- /api/app-store/around/7.jpg
|
||||
- /api/app-store/around/8.jpg
|
||||
- 1.jpg
|
||||
- 2.jpg
|
||||
- 3.jpg
|
||||
- 4.jpg
|
||||
- 5.jpg
|
||||
- 6.jpg
|
||||
- 7.jpg
|
||||
- 8.jpg
|
||||
---
|
||||
|
||||
Discover radically unique video calls designed to help hybrid-remote teams create, collaborate and celebrate together.
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"title": "Around",
|
||||
"slug": "around",
|
||||
"type": "around_video",
|
||||
"logo": "/api/app-store/around/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/around",
|
||||
"variant": "conferencing",
|
||||
"categories": ["video"],
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/caldavcalendar/1.jpg
|
||||
- 1.jpg
|
||||
---
|
||||
|
||||
Caldav is a protocol that allows different clients/servers to access scheduling information on remote servers as well as schedule meetings with other users on the same server or other servers. It extends WebDAV specification and uses iCalendar format for the data.
|
||||
|
|
|
@ -8,15 +8,15 @@ export const metadata = {
|
|||
installed: true,
|
||||
type: "caldav_calendar",
|
||||
title: "CalDav (Beta)",
|
||||
imageSrc: "/api/app-store/caldavcalendar/icon.svg",
|
||||
variant: "calendar",
|
||||
category: "calendar",
|
||||
categories: ["calendar"],
|
||||
logo: "/api/app-store/caldavcalendar/icon.svg",
|
||||
logo: "icon.svg",
|
||||
publisher: "Cal.com",
|
||||
slug: "caldav-calendar",
|
||||
url: "https://cal.com/",
|
||||
email: "ali@cal.com",
|
||||
dirName: "caldavcalendar",
|
||||
} as AppMeta;
|
||||
|
||||
export default metadata;
|
||||
|
|
|
@ -8,11 +8,10 @@ export const metadata = {
|
|||
installed: true,
|
||||
type: "caldav_calendar",
|
||||
title: "CalDav (Beta)",
|
||||
imageSrc: "/api/app-store/caldavcalendar/icon.svg",
|
||||
variant: "calendar",
|
||||
category: "calendar",
|
||||
categories: ["calendar"],
|
||||
logo: "/api/app-store/caldavcalendar/icon.svg",
|
||||
logo: "icon.svg",
|
||||
publisher: "Cal.com",
|
||||
slug: "caldav-calendar",
|
||||
url: "https://cal.com/",
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/campfire/1.jpg
|
||||
- /api/app-store/campfire/2.jpg
|
||||
- /api/app-store/campfire/3.jpg
|
||||
- /api/app-store/campfire/4.jpg
|
||||
- 1.jpg
|
||||
- 2.jpg
|
||||
- 3.jpg
|
||||
- 4.jpg
|
||||
---
|
||||
|
||||
<iframe class="w-full aspect-video -mx-2" width="560" height="315" src="https://player.vimeo.com/video/683733529?app_id=122963&h=025a2fae94&referrer=https%3A%2F%2Fwww.campfire.to%2F" />
|
||||
<iframe class="w-full aspect-video -mx-2" width="560" height="315" src="https://player.vimeo.com/video/683733529?app_id=122963&h=025a2fae94&referrer=https%3A%2F%2Fwww.campfire.to%2F" ></iframe>
|
||||
|
||||
## Feel connected with your remote team
|
||||
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "Campfire",
|
||||
"slug": "campfire",
|
||||
"type": "campfire_video",
|
||||
"imageSrc": "/api/app-store/campfire/icon.svg",
|
||||
"logo": "/api/app-store/campfire/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/campfire",
|
||||
"variant": "conferencing",
|
||||
"categories": ["video"],
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/closecom/1.jpg
|
||||
- /api/app-store/closecom/2.jpg
|
||||
- /api/app-store/closecom/3.jpg
|
||||
- /api/app-store/closecom/4.jpg
|
||||
- /api/app-store/closecom/5.jpg
|
||||
- 1.jpg
|
||||
- 2.jpg
|
||||
- 3.jpg
|
||||
- 4.jpg
|
||||
- 5.jpg
|
||||
---
|
||||
|
||||
- Close is a modern CRM with build-in sales communication tools for email, phone, SMS, and meetings.
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
"title": "Close.com",
|
||||
"slug": "closecom",
|
||||
"type": "closecom_other_calendar",
|
||||
"imageSrc": "/api/app-store/closecom/icon.svg",
|
||||
"logo": "/api/app-store/closecom/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/closecom",
|
||||
"variant": "other",
|
||||
"categories": ["other"],
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
"name": "Cron",
|
||||
"slug": "cron",
|
||||
"type": "cron_other",
|
||||
"imageSrc": "logo.png",
|
||||
"logo": "logo.png",
|
||||
"url": "https://cal.com/apps/cron",
|
||||
"variant": "other",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/dailyvideo/1.jpg
|
||||
- /api/app-store/dailyvideo/2.jpg
|
||||
- /api/app-store/dailyvideo/3.jpg
|
||||
- 1.jpg
|
||||
- 2.jpg
|
||||
- 3.jpg
|
||||
---
|
||||
|
||||
- **Recordings require a team plan**
|
||||
|
|
|
@ -7,11 +7,10 @@ export const metadata = {
|
|||
description: _package.description,
|
||||
installed: !!process.env.DAILY_API_KEY,
|
||||
type: "daily_video",
|
||||
imageSrc: "/api/app-store/dailyvideo/icon.svg",
|
||||
variant: "conferencing",
|
||||
url: "https://daily.co",
|
||||
categories: ["video"],
|
||||
logo: "/api/app-store/dailyvideo/icon.svg",
|
||||
logo: "icon.svg",
|
||||
publisher: "Cal.com",
|
||||
category: "video",
|
||||
slug: "daily-video",
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "Discord",
|
||||
"slug": "discord",
|
||||
"type": "discord_video",
|
||||
"imageSrc": "/api/app-store/discord/icon.svg",
|
||||
"logo": "/api/app-store/discord/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://discord.com/",
|
||||
"variant": "conferencing",
|
||||
"categories": ["video"],
|
||||
|
@ -16,7 +15,7 @@
|
|||
"label": "{TITLE}",
|
||||
"linkType": "static",
|
||||
"organizerInputPlaceholder": "https://discord.gg/420gg69",
|
||||
"urlRegExp": "^http(s)?:\\/\\/(www\\.)?discord.gg\\/[a-zA-Z0-9]*"
|
||||
"urlRegExp": "^http(s)?:\\/\\/(www\\.)?(discord.gg|discord.com)\\/[a-zA-Z0-9]*"
|
||||
}
|
||||
},
|
||||
"description": "Copy your server invite link and start scheduling calls in Discord! Discord is a VoIP and instant messaging social platform. Users have the ability to communicate with voice calls, video calls, text messaging, media and files in private chats or as part of communities.",
|
||||
|
|
|
@ -8,12 +8,11 @@ export const metadata = {
|
|||
installed: true,
|
||||
type: "exchange2013_calendar",
|
||||
title: "Microsoft Exchange 2013 Calendar",
|
||||
imageSrc: "/api/app-store/exchange2013calendar/icon.svg",
|
||||
variant: "calendar",
|
||||
category: "calendar",
|
||||
categories: ["calendar"],
|
||||
label: "Exchange Calendar",
|
||||
logo: "/api/app-store/exchange2013calendar/icon.svg",
|
||||
logo: "icon.svg",
|
||||
publisher: "Cal.com",
|
||||
slug: "exchange2013-calendar",
|
||||
url: "https://cal.com/",
|
||||
|
|
|
@ -8,12 +8,11 @@ export const metadata = {
|
|||
installed: true,
|
||||
type: "exchange2016_calendar",
|
||||
title: "Microsoft Exchange 2016 Calendar",
|
||||
imageSrc: "/api/app-store/exchange2016calendar/icon.svg",
|
||||
variant: "calendar",
|
||||
category: "calendar",
|
||||
categories: ["calendar"],
|
||||
label: "Exchange Calendar",
|
||||
logo: "/api/app-store/exchange2016calendar/icon.svg",
|
||||
logo: "icon.svg",
|
||||
publisher: "Cal.com",
|
||||
slug: "exchange2016-calendar",
|
||||
url: "https://cal.com/",
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
"title": "Microsoft Exchange",
|
||||
"name": "Microsoft Exchange",
|
||||
"slug": "exchange",
|
||||
"dirName": "exchangecalendar",
|
||||
"type": "exchange_calendar",
|
||||
"imageSrc": "/api/app-store/exchangecalendar/icon.svg",
|
||||
"logo": "/api/app-store/exchangecalendar/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/exchange",
|
||||
"variant": "calendar",
|
||||
"categories": ["calendar"],
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/facetime/facetime1.png
|
||||
- /api/app-store/facetime/facetime2.png
|
||||
- facetime1.png
|
||||
- facetime2.png
|
||||
---
|
||||
|
||||
With FaceTime, it’s easy to stay in touch. You can make audio and video calls with up to 32 people, share your screen, enjoy films and music together, and more.
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
"title": "Facetime",
|
||||
"slug": "facetime",
|
||||
"type": "facetime_video",
|
||||
"imageSrc": "/api/app-store/facetime/icon.svg",
|
||||
"logo": "/api/app-store/facetime/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/facetime",
|
||||
"variant": "conferencing",
|
||||
"categories": ["video"],
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/fathom/1.jpg
|
||||
- 1.jpg
|
||||
---
|
||||
|
||||
Fathom Analytics provides simple, privacy-focused website analytics. We're a GDPR-compliant, Google Analytics alternative.
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "Fathom",
|
||||
"slug": "fathom",
|
||||
"type": "fathom_analytics",
|
||||
"imageSrc": "/api/app-store/fathom/icon.svg",
|
||||
"logo": "/api/app-store/fathom/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/fathom",
|
||||
"variant": "analytics",
|
||||
"categories": ["analytics"],
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
description: Google Analytics is a web analytics service offered by Google that tracks and reports website traffic, currently as a platform inside the Google Marketing Platform brand.
|
||||
items:
|
||||
- /api/app-store/ga4/1.jpeg
|
||||
- /api/app-store/ga4/2.jpeg
|
||||
- /api/app-store/ga4/3.jpeg
|
||||
- /api/app-store/ga4/4.jpeg
|
||||
- /api/app-store/ga4/5.jpeg
|
||||
- 1.jpeg
|
||||
- 2.jpeg
|
||||
- 3.jpeg
|
||||
- 4.jpeg
|
||||
- 5.jpeg
|
||||
---
|
||||
|
||||
Google Analytics is a web analytics service offered by Google that tracks and reports website traffic, currently as a platform inside the Google Marketing Platform brand.
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "Google Analytics",
|
||||
"slug": "ga4",
|
||||
"type": "ga4_analytics",
|
||||
"imageSrc": "/api/app-store/ga4/icon.svg",
|
||||
"logo": "/api/app-store/ga4/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://marketingplatform.google.com",
|
||||
"variant": "analytics",
|
||||
"categories": ["analytics"],
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import type { App } from "@calcom/types/App";
|
||||
|
||||
export function getAppAssetFullPath(assetPath: string, metadata: Pick<App, "dirName" | "isTemplate">) {
|
||||
const appDirName = `${metadata.isTemplate ? "templates/" : ""}${metadata.dirName}`;
|
||||
let assetFullPath = assetPath;
|
||||
if (!assetPath.startsWith("/app-store/") && !/^https?/.test(assetPath)) {
|
||||
assetFullPath = `/app-store/${appDirName}/${assetPath}`;
|
||||
}
|
||||
return assetFullPath;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/giphy/GIPHY1.png
|
||||
- /api/app-store/giphy/GIPHY2.png
|
||||
- GIPHY1.png
|
||||
- GIPHY2.png
|
||||
---
|
||||
|
||||
An online database and search engine that allows users to search for and share short looping videos with no sound that resemble animated GIF files. GIPHY is your top source for the best & newest GIFs & Animated Stickers online. Find everything from funny GIFs, reaction GIFs, unique GIFs and more to add to your custom booking page. Located under advanced settings in each event type.
|
||||
|
|
|
@ -7,9 +7,7 @@ export const metadata = {
|
|||
description: _package.description,
|
||||
installed: true,
|
||||
categories: ["other"],
|
||||
// If using static next public folder, can then be referenced from the base URL (/).
|
||||
imageSrc: "/api/app-store/giphy/icon.svg",
|
||||
logo: "/api/app-store/giphy/icon.svg",
|
||||
logo: "icon.svg",
|
||||
publisher: "Cal.com",
|
||||
slug: "giphy",
|
||||
title: "Giphy",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/googlecalendar/GCal1.png
|
||||
- /api/app-store/googlecalendar/GCal2.png
|
||||
- GCal1.png
|
||||
- GCal2.png
|
||||
---
|
||||
|
||||
Google Calendar is a time management and scheduling service developed by Google. Allows users to create and edit events, with options available for type and time. Available to anyone that has a Gmail account on both mobile and web versions.
|
||||
|
|
|
@ -12,7 +12,7 @@ export const metadata = {
|
|||
variant: "calendar",
|
||||
category: "calendar",
|
||||
categories: ["calendar"],
|
||||
logo: "/api/app-store/googlecalendar/icon.svg",
|
||||
logo: "icon.svg",
|
||||
publisher: "Cal.com",
|
||||
slug: "google-calendar",
|
||||
url: "https://cal.com/",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/googlevideo/gmeet1.png
|
||||
- /api/app-store/googlevideo/gmeet2.png
|
||||
- gmeet1.png
|
||||
- gmeet2.png
|
||||
---
|
||||
|
||||
Google Meet is Google's web-based video conferencing platform, designed to compete with major conferencing platforms.
|
||||
|
|
|
@ -12,9 +12,8 @@ export const metadata = {
|
|||
categories: ["video"],
|
||||
type: "google_video",
|
||||
title: "Google Meet",
|
||||
imageSrc: "/api/app-store/googlevideo/logo.webp",
|
||||
variant: "conferencing",
|
||||
logo: "/api/app-store/googlevideo/logo.webp",
|
||||
logo: "logo.webp",
|
||||
publisher: "Cal.com",
|
||||
url: "https://cal.com/",
|
||||
isGlobal: false,
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
"name": "Google Tag Manager",
|
||||
"slug": "gtm",
|
||||
"type": "gtm_analytics",
|
||||
"imageSrc": "icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://tagmanager.google.com",
|
||||
"variant": "analytics",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/hubspot/hubspot01.webp
|
||||
- hubspot01.webp
|
||||
---
|
||||
|
||||
HubSpot is a cloud-based CRM designed to help align sales and marketing teams, foster sales enablement, boost ROI and optimize your inbound marketing strategy to generate more, qualified leads.
|
||||
|
|
|
@ -7,9 +7,8 @@ export const metadata = {
|
|||
installed: !!process.env.HUBSPOT_CLIENT_ID,
|
||||
description: _package.description,
|
||||
type: "hubspot_other_calendar",
|
||||
imageSrc: "/api/app-store/hubspot/icon.svg",
|
||||
variant: "other_calendar",
|
||||
logo: "/api/app-store/hubspot/icon.svg",
|
||||
logo: "icon.svg",
|
||||
publisher: "Cal.com",
|
||||
url: "https://hubspot.com/",
|
||||
categories: ["other"],
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/huddle01video/1.png
|
||||
- /api/app-store/huddle01video/2.png
|
||||
- /api/app-store/huddle01video/3.png
|
||||
- /api/app-store/huddle01video/4.png
|
||||
- /api/app-store/huddle01video/5.png
|
||||
- /api/app-store/huddle01video/6.png
|
||||
- 1.png
|
||||
- 2.png
|
||||
- 3.png
|
||||
- 4.png
|
||||
- 5.png
|
||||
- 6.png
|
||||
---
|
||||
|
||||
Huddle01 is a new video conferencing software native to Web3 and is comparable to a decentralized version of Zoom. It supports conversations for NFT communities, DAOs, Builders and also has features such as token gating, NFTs as avatars, Web3 Login + ENS and recording over IPFS.
|
||||
|
|
|
@ -8,10 +8,9 @@ export const metadata = {
|
|||
description: _package.description,
|
||||
installed: true,
|
||||
type: "huddle01_video",
|
||||
imageSrc: "/api/app-store/huddle01video/icon.svg",
|
||||
variant: "conferencing",
|
||||
categories: ["video", "web3"],
|
||||
logo: "/api/app-store/huddle01video/icon.svg",
|
||||
logo: "icon.svg",
|
||||
publisher: "huddle01.com",
|
||||
url: "https://huddle01.com",
|
||||
category: "web3",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/jitsivideo/jitsi1.jpg
|
||||
- jitsi1.jpg
|
||||
---
|
||||
|
||||
Jitsi is a free open-source video conferencing software for web and mobile. Make a call, launch on your own servers, integrate into your app, and more.
|
||||
|
|
|
@ -7,10 +7,9 @@ export const metadata = {
|
|||
description: _package.description,
|
||||
installed: true,
|
||||
type: "jitsi_video",
|
||||
imageSrc: "/api/app-store/jitsivideo/icon.svg",
|
||||
variant: "conferencing",
|
||||
categories: ["video"],
|
||||
logo: "/api/app-store/jitsivideo/icon.svg",
|
||||
logo: "icon.svg",
|
||||
publisher: "Cal.com",
|
||||
url: "https://jitsi.org/",
|
||||
slug: "jitsi",
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/larkcalendar/1.png
|
||||
- /api/app-store/larkcalendar/2.png
|
||||
- /api/app-store/larkcalendar/3.png
|
||||
- /api/app-store/larkcalendar/4.png
|
||||
- 1.png
|
||||
- 2.png
|
||||
- 3.png
|
||||
- 4.png
|
||||
---
|
||||
|
||||
<iframe class="w-full aspect-video" width="560" height="315" src="https://www.youtube.com/embed/ciqbZ466XSQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
|
|
@ -8,10 +8,9 @@ export const metadata = {
|
|||
installed: true,
|
||||
type: "lark_calendar",
|
||||
title: "Lark Calendar",
|
||||
imageSrc: "/api/app-store/larkcalendar/icon.svg",
|
||||
variant: "calendar",
|
||||
categories: ["calendar"],
|
||||
logo: "/api/app-store/larkcalendar/icon.svg",
|
||||
logo: "icon.svg",
|
||||
publisher: "Lark",
|
||||
slug: "lark-calendar",
|
||||
url: "https://larksuite.com/",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/n8n/1.png
|
||||
- /api/app-store/n8n/2.png
|
||||
- /api/app-store/n8n/3.png
|
||||
- 1.png
|
||||
- 2.png
|
||||
- 3.png
|
||||
- https://docs.n8n.io/_images/integrations/builtin/credentials/cal/getting-api-key.gif
|
||||
---
|
||||
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "n8n",
|
||||
"slug": "n8n",
|
||||
"type": "n8n_automation",
|
||||
"imageSrc": "/api/app-store/n8n/icon.svg",
|
||||
"logo": "/api/app-store/n8n/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/n8n",
|
||||
"variant": "automation",
|
||||
"categories": ["automation"],
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/office365calendar/1.jpg
|
||||
- /api/app-store/office365calendar/2.jpg
|
||||
- /api/app-store/office365calendar/3.jpg
|
||||
- /api/app-store/office365calendar/4.jpg
|
||||
- 1.jpg
|
||||
- 2.jpg
|
||||
- 3.jpg
|
||||
- 4.jpg
|
||||
---
|
||||
|
||||
Microsoft Office 365 is a suite of apps that helps you stay connected with others and get things done. It includes but is not limited to Microsoft Word, PowerPoint, Excel, Teams, OneNote and OneDrive. Office 365 allows you to work remotely with others on a team and collaborate in an online environment. Both web versions and desktop/mobile applications are available.
|
||||
|
|
|
@ -7,13 +7,13 @@ export const metadata = {
|
|||
description: _package.description,
|
||||
type: "office365_calendar",
|
||||
title: "Outlook Calendar",
|
||||
imageSrc: "/api/app-store/office365calendar/icon.svg",
|
||||
variant: "calendar",
|
||||
category: "calendar",
|
||||
categories: ["calendar"],
|
||||
logo: "/api/app-store/office365calendar/icon.svg",
|
||||
logo: "icon.svg",
|
||||
publisher: "Cal.com",
|
||||
slug: "office365-calendar",
|
||||
dirName: "office365calendar",
|
||||
url: "https://cal.com/",
|
||||
email: "help@cal.com",
|
||||
} as AppMeta;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/office365video/teams1.png
|
||||
- /api/app-store/office365video/teams2.png
|
||||
- /api/app-store/office365video/teams3.jpeg
|
||||
- /api/app-store/office365video/teams4.png
|
||||
- /api/app-store/office365video/teams5.png
|
||||
- teams1.png
|
||||
- teams2.png
|
||||
- teams3.jpeg
|
||||
- teams4.png
|
||||
- teams5.png
|
||||
---
|
||||
|
||||
Microsoft Teams is a business communication platform and collaborative workspace included in Microsoft 365. It offers workspace chat and video conferencing, file storage, and application integration. Both web versions and desktop/mobile applications are available. NOTE: MUST HAVE A WORK / SCHOOL ACCOUNT
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
"name": "Microsoft 365/Teams (Requires work/school account)",
|
||||
"description": "Microsoft Teams is a business communication platform and collaborative workspace included in Microsoft 365. It offers workspace chat and video conferencing, file storage, and application integration. Both web versions and desktop/mobile applications are available. NOTE: MUST HAVE A WORK / SCHOOL ACCOUNT",
|
||||
"type": "office365_video",
|
||||
"imageSrc": "/api/app-store/office365video/icon.svg",
|
||||
"variant": "conferencing",
|
||||
"logo": "/api/app-store/office365video/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"publisher": "Cal.com",
|
||||
"url": "https://www.microsoft.com/en-ca/microsoft-teams/group-chat-software",
|
||||
"verified": true,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/ping/1.png
|
||||
- /api/app-store/ping/2.png
|
||||
- /api/app-store/ping/3.png
|
||||
- 1.png
|
||||
- 2.png
|
||||
- 3.png
|
||||
---
|
||||
|
||||
Ping.gg makes high quality video collaborations easier than ever. Think "Zoom for streamers and creators". Join a call in 3 clicks, manage audio and video like a pro, and copy-paste your guests straight into OBS
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
"title": "Ping.gg",
|
||||
"slug": "ping",
|
||||
"type": "ping_video",
|
||||
"imageSrc": "/api/app-store/ping/icon.svg",
|
||||
"logo": "/api/app-store/ping/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://ping.gg",
|
||||
"variant": "conferencing",
|
||||
"categories": ["video"],
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
description: Connect APIs, remarkably fast. Stop writing boilerplate code, struggling with authentication and managing infrastructure. Start connecting APIs with code-level control when you need it — and no code when you don't
|
||||
items:
|
||||
- /api/app-store/pipedream/1.png
|
||||
- /api/app-store/pipedream/2.png
|
||||
- /api/app-store/pipedream/3.png
|
||||
- /api/app-store/pipedream/4.png
|
||||
- /api/app-store/pipedream/5.png
|
||||
- 1.png
|
||||
- 2.png
|
||||
- 3.png
|
||||
- 4.png
|
||||
- 5.png
|
||||
---
|
||||
|
||||
Connect APIs, remarkably fast. Stop writing boilerplate code, struggling with authentication and managing infrastructure. Start connecting APIs with code-level control when you need it — and no code when you don't
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "Pipedream",
|
||||
"slug": "pipedream",
|
||||
"type": "pipedream_automation",
|
||||
"imageSrc": "/api/app-store/pipedream/icon.svg",
|
||||
"logo": "/api/app-store/pipedream/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://pipedream.com/apps/cal-com",
|
||||
"variant": "automation",
|
||||
"categories": ["automation"],
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/plausible/1.jpg
|
||||
- 1.jpg
|
||||
---
|
||||
|
||||
Simple, privacy-friendly Google Analytics alternative.
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "Plausible",
|
||||
"slug": "plausible",
|
||||
"type": "plausible_analytics",
|
||||
"imageSrc": "/api/app-store/plausible/icon.svg",
|
||||
"logo": "/api/app-store/plausible/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/plausible",
|
||||
"variant": "analytics",
|
||||
"categories": ["analytics"],
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "QR Code",
|
||||
"slug": "qr_code",
|
||||
"type": "qr_code_other",
|
||||
"imageSrc": "/api/app-store/qr_code/icon.svg",
|
||||
"logo": "/api/app-store/qr_code/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/qr_code",
|
||||
"variant": "other",
|
||||
"categories": ["other"],
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/rainbow/1.jpg
|
||||
- /api/app-store/rainbow/2.jpg
|
||||
- /api/app-store/rainbow/3.jpg
|
||||
- 1.jpg
|
||||
- 2.jpg
|
||||
- 3.jpg
|
||||
---
|
||||
|
||||
Token gate bookings based on NFTs, DAO tokens, and ERC-20 tokens. Rainbow supports dozens of trusted Ethereum wallet apps to verify token ownership. Available blockchains are Ethereum mainnet, Arbitrum, Optimism, and Polygon mainnet.
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "Rainbow",
|
||||
"slug": "rainbow",
|
||||
"type": "rainbow_web3",
|
||||
"imageSrc": "/api/app-store/rainbow/icon.svg",
|
||||
"logo": "/api/app-store/rainbow/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/rainbow",
|
||||
"variant": "web3",
|
||||
"categories": ["web3"],
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { checkBalance } from "../utils/ethereum";
|
||||
import type { TBalanceInputSchema } from "./balance.schema";
|
||||
|
||||
interface BalanceHandlerOptions {
|
||||
input: TBalanceInputSchema;
|
||||
}
|
||||
|
||||
export const balanceHandler = async ({ input }: BalanceHandlerOptions) => {
|
||||
const { address, tokenAddress, chainId } = input;
|
||||
try {
|
||||
const hasBalance = await checkBalance(address, tokenAddress, chainId);
|
||||
|
||||
return {
|
||||
data: {
|
||||
hasBalance,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
data: {
|
||||
hasBalance: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
import z from "zod";
|
||||
|
||||
export const ZBalanceInputSchema = z.object({
|
||||
address: z.string(),
|
||||
tokenAddress: z.string(),
|
||||
chainId: z.number(),
|
||||
});
|
||||
|
||||
export const ZBalanceOutputSchema = z.object({
|
||||
data: z
|
||||
.object({
|
||||
hasBalance: z.boolean(),
|
||||
})
|
||||
.nullish(),
|
||||
error: z.string().nullish(),
|
||||
});
|
||||
|
||||
export type TBalanceOutputSchema = z.infer<typeof ZBalanceOutputSchema>;
|
||||
export type TBalanceInputSchema = z.infer<typeof ZBalanceInputSchema>;
|
|
@ -0,0 +1,42 @@
|
|||
import { ethers } from "ethers";
|
||||
import { configureChains, createClient } from "wagmi";
|
||||
|
||||
import abi from "../utils/abi.json";
|
||||
import { getProviders, SUPPORTED_CHAINS } from "../utils/ethereum";
|
||||
import type { TContractInputSchema } from "./contract.schema";
|
||||
|
||||
interface ContractHandlerOptions {
|
||||
input: TContractInputSchema;
|
||||
}
|
||||
export const contractHandler = async ({ input }: ContractHandlerOptions) => {
|
||||
const { address, chainId } = input;
|
||||
const { provider } = configureChains(
|
||||
SUPPORTED_CHAINS.filter((chain) => chain.id === chainId),
|
||||
getProviders()
|
||||
);
|
||||
|
||||
const client = createClient({
|
||||
provider,
|
||||
});
|
||||
|
||||
const contract = new ethers.Contract(address, abi, client.provider);
|
||||
|
||||
try {
|
||||
const name = await contract.name();
|
||||
const symbol = await contract.symbol();
|
||||
|
||||
return {
|
||||
data: {
|
||||
name,
|
||||
symbol: `$${symbol}`,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
data: {
|
||||
name: address,
|
||||
symbol: "$UNKNOWN",
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
import z from "zod";
|
||||
|
||||
export const ZContractInputSchema = z.object({
|
||||
address: z.string(),
|
||||
chainId: z.number(),
|
||||
});
|
||||
|
||||
export const ZContractOutputSchema = z.object({
|
||||
data: z
|
||||
.object({
|
||||
name: z.string(),
|
||||
symbol: z.string(),
|
||||
})
|
||||
.nullish(),
|
||||
error: z.string().nullish(),
|
||||
});
|
||||
|
||||
export type TContractInputSchema = z.infer<typeof ZContractInputSchema>;
|
||||
export type TContractOutputSchema = z.infer<typeof ZContractOutputSchema>;
|
|
@ -1,100 +1,53 @@
|
|||
import { ethers } from "ethers";
|
||||
import { configureChains, createClient } from "wagmi";
|
||||
import { z } from "zod";
|
||||
|
||||
import { router, publicProcedure } from "@calcom/trpc/server/trpc";
|
||||
|
||||
import abi from "../utils/abi.json";
|
||||
import { checkBalance, getProviders, SUPPORTED_CHAINS } from "../utils/ethereum";
|
||||
import { ZBalanceInputSchema, ZBalanceOutputSchema } from "./balance.schema";
|
||||
import { ZContractInputSchema, ZContractOutputSchema } from "./contract.schema";
|
||||
|
||||
interface EthRouterHandlersCache {
|
||||
contract?: typeof import("./contract.handler").contractHandler;
|
||||
balance?: typeof import("./balance.handler").balanceHandler;
|
||||
}
|
||||
|
||||
const UNSTABLE_HANDLER_CACHE: EthRouterHandlersCache = {};
|
||||
|
||||
const ethRouter = router({
|
||||
// Fetch contract `name` and `symbol` or error
|
||||
contract: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
address: z.string(),
|
||||
chainId: z.number(),
|
||||
})
|
||||
)
|
||||
.output(
|
||||
z.object({
|
||||
data: z
|
||||
.object({
|
||||
name: z.string(),
|
||||
symbol: z.string(),
|
||||
})
|
||||
.nullish(),
|
||||
error: z.string().nullish(),
|
||||
})
|
||||
)
|
||||
.input(ZContractInputSchema)
|
||||
.output(ZContractOutputSchema)
|
||||
.query(async ({ input }) => {
|
||||
const { address, chainId } = input;
|
||||
const { provider } = configureChains(
|
||||
SUPPORTED_CHAINS.filter((chain) => chain.id === chainId),
|
||||
getProviders()
|
||||
if (!UNSTABLE_HANDLER_CACHE.contract) {
|
||||
UNSTABLE_HANDLER_CACHE.contract = await import("./contract.handler").then(
|
||||
(mod) => mod.contractHandler
|
||||
);
|
||||
|
||||
const client = createClient({
|
||||
provider,
|
||||
});
|
||||
|
||||
const contract = new ethers.Contract(address, abi, client.provider);
|
||||
|
||||
try {
|
||||
const name = await contract.name();
|
||||
const symbol = await contract.symbol();
|
||||
|
||||
return {
|
||||
data: {
|
||||
name,
|
||||
symbol: `$${symbol}`,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
data: {
|
||||
name: address,
|
||||
symbol: "$UNKNOWN",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Unreachable code but required for type safety
|
||||
if (!UNSTABLE_HANDLER_CACHE.contract) {
|
||||
throw new Error("Failed to load handler");
|
||||
}
|
||||
|
||||
return UNSTABLE_HANDLER_CACHE.contract({
|
||||
input,
|
||||
});
|
||||
}),
|
||||
// Fetch user's `balance` of either ERC-20 or ERC-721 compliant token or error
|
||||
balance: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
address: z.string(),
|
||||
tokenAddress: z.string(),
|
||||
chainId: z.number(),
|
||||
})
|
||||
)
|
||||
.output(
|
||||
z.object({
|
||||
data: z
|
||||
.object({
|
||||
hasBalance: z.boolean(),
|
||||
})
|
||||
.nullish(),
|
||||
error: z.string().nullish(),
|
||||
})
|
||||
)
|
||||
.input(ZBalanceInputSchema)
|
||||
.output(ZBalanceOutputSchema)
|
||||
.query(async ({ input }) => {
|
||||
const { address, tokenAddress, chainId } = input;
|
||||
try {
|
||||
const hasBalance = await checkBalance(address, tokenAddress, chainId);
|
||||
|
||||
return {
|
||||
data: {
|
||||
hasBalance,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
data: {
|
||||
hasBalance: false,
|
||||
},
|
||||
};
|
||||
if (!UNSTABLE_HANDLER_CACHE.balance) {
|
||||
UNSTABLE_HANDLER_CACHE.balance = await import("./balance.handler").then((mod) => mod.balanceHandler);
|
||||
}
|
||||
|
||||
// Unreachable code but required for type safety
|
||||
if (!UNSTABLE_HANDLER_CACHE.balance) {
|
||||
throw new Error("Failed to load handler");
|
||||
}
|
||||
|
||||
return UNSTABLE_HANDLER_CACHE.balance({
|
||||
input,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/raycast/1.png
|
||||
- /api/app-store/raycast/2.png
|
||||
- /api/app-store/raycast/3.png
|
||||
- /api/app-store/raycast/4.png
|
||||
- 1.png
|
||||
- 2.png
|
||||
- 3.png
|
||||
- 4.png
|
||||
---
|
||||
|
||||
Quickly share your Cal.com meeting links with Raycast. Requires Raycast.com to be installed. You can create an API token in your Developer Cal.com Settings.
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "Raycast",
|
||||
"slug": "raycast",
|
||||
"type": "raycast_other",
|
||||
"imageSrc": "/api/app-store/raycast/icon.svg",
|
||||
"logo": "/api/app-store/raycast/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/raycast",
|
||||
"variant": "other",
|
||||
"categories": ["other"],
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/riverside/riverside1.png
|
||||
- riverside1.png
|
||||
---
|
||||
|
||||
Your online recording studio. The easiest way to record podcasts and videos in studio quality from anywhere. All from the browser.
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "Riverside",
|
||||
"slug": "riverside",
|
||||
"type": "riverside_video",
|
||||
"imageSrc": "/api/app-store/riverside/icon-dark.svg",
|
||||
"logo": "/api/app-store/riverside/icon-dark.svg",
|
||||
"logo": "icon-dark.svg",
|
||||
"url": "https://cal.com/apps/riverside",
|
||||
"variant": "conferencing",
|
||||
"categories": ["video"],
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/routing-forms/1.jpg
|
||||
- /api/app-store/routing-forms/2.jpg
|
||||
- /api/app-store/routing-forms/3.jpg
|
||||
- 1.jpg
|
||||
- 2.jpg
|
||||
- 3.jpg
|
||||
---
|
||||
|
||||
It would allow a booker to connect with the right person or choose the right event, faster. It would work by taking inputs from the booker and using that data to route to the correct booker/event as configured by Cal user
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
"title": "Routing Forms",
|
||||
"slug": "routing-forms",
|
||||
"type": "routing-forms_other",
|
||||
"imageSrc": "/api/app-store/routing-forms/icon-dark.svg",
|
||||
"logo": "/api/app-store/routing-forms/icon-dark.svg",
|
||||
"logo": "icon-dark.svg",
|
||||
"url": "https://cal.com/apps/routing-forms",
|
||||
"variant": "other",
|
||||
"categories": ["other"],
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
description: Salesforce (Sales Cloud) is a cloud-based application designed to help your salespeople sell smarter and faster by centralizing customer information, logging their interactions with your company, and automating many of the tasks salespeople do every day.
|
||||
items:
|
||||
- /api/app-store/salesforce/1.png
|
||||
- 1.png
|
||||
---
|
||||
|
||||
Salesforce (Sales Cloud) is a cloud-based application designed to help your salespeople sell smarter and faster by centralizing customer information, logging their interactions with your company, and automating many of the tasks salespeople do every day.
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "Salesforce",
|
||||
"slug": "salesforce",
|
||||
"type": "salesforce_other_calendar",
|
||||
"imageSrc": "/api/app-store/salesforce/icon.png",
|
||||
"logo": "/api/app-store/salesforce/icon.png",
|
||||
"logo": "icon.png",
|
||||
"url": "https://cal.com/apps/salesforce",
|
||||
"variant": "other_calendar",
|
||||
"categories": ["other"],
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
description: SendGrid delivers your transactional and marketing emails through the world's largest cloud-based email delivery platform.
|
||||
items:
|
||||
- /api/app-store/sendgrid/1.png
|
||||
- 1.png
|
||||
---
|
||||
|
||||
SendGrid delivers your transactional and marketing emails through the world's largest cloud-based email delivery platform.
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "Sendgrid",
|
||||
"slug": "sendgrid",
|
||||
"type": "sendgrid_other_calendar",
|
||||
"imageSrc": "/api/app-store/sendgrid/logo.png",
|
||||
"logo": "/api/app-store/sendgrid/logo.png",
|
||||
"logo": "logo.png",
|
||||
"url": "https://cal.com/apps/sendgrid",
|
||||
"variant": "other_calendar",
|
||||
"categories": ["other"],
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
description: Schedule a chat with your guests or have a Signal Video call.
|
||||
items:
|
||||
- /api/app-store/signal/1.jpg
|
||||
- /api/app-store/signal/2.jpg
|
||||
- 1.jpg
|
||||
- 2.jpg
|
||||
---
|
||||
|
||||
Schedule a chat with your guests or have a Signal Video call.
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "Signal",
|
||||
"slug": "signal",
|
||||
"type": "signal_video",
|
||||
"imageSrc": "/api/app-store/signal/icon.svg",
|
||||
"logo": "/api/app-store/signal/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/signal",
|
||||
"variant": "conferencing",
|
||||
"categories": ["video"],
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
description: Video meetings made for music. Create your own virtual music classroom, easily.
|
||||
items:
|
||||
- /api/app-store/sirius_video/1.jpg
|
||||
- /api/app-store/sirius_video/2.jpg
|
||||
- /api/app-store/sirius_video/3.jpg
|
||||
- 1.jpg
|
||||
- 2.jpg
|
||||
- 3.jpg
|
||||
---
|
||||
|
||||
Video meetings made for music. Create your own virtual music classroom, easily.
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "Sirius Video",
|
||||
"slug": "sirius_video",
|
||||
"type": "sirius_video_video",
|
||||
"imageSrc": "/api/app-store/sirius_video/icon-dark.svg",
|
||||
"logo": "/api/app-store/sirius_video/icon-dark.svg",
|
||||
"logo": "icon-dark.svg",
|
||||
"url": "https://cal.com/apps/sirius_video",
|
||||
"variant": "conferencing",
|
||||
"categories": ["video"],
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/stripepayment/stripe1.jpg
|
||||
- /api/app-store/stripepayment/stripe2.jpg
|
||||
- /api/app-store/stripepayment/stripe3.jpg
|
||||
- /api/app-store/stripepayment/stripe4.jpg
|
||||
- /api/app-store/stripepayment/stripe5.jpg
|
||||
- stripe1.jpg
|
||||
- stripe2.jpg
|
||||
- stripe3.jpg
|
||||
- stripe4.jpg
|
||||
- stripe5.jpg
|
||||
---
|
||||
|
||||
Stripe provides payment infrastructure for everyone from startups to Fortune 500 companies. They provide payment processing software as well as application programming interfaces (APIs) for mobile applications as well as e-commerce websites processing payments from (but not limited to) credit cards, debit cards, digital wallets, Google Pay, Apple Pay, Bank Transfers, Alipay and WeChat.
|
||||
|
|
|
@ -13,8 +13,7 @@ export const metadata = {
|
|||
slug: "stripe",
|
||||
category: "payment",
|
||||
categories: ["payment"],
|
||||
logo: "/api/app-store/stripepayment/icon.svg",
|
||||
imageSrc: "/api/app-store/stripepayment/icon.svg",
|
||||
logo: "icon.svg",
|
||||
publisher: "Cal.com",
|
||||
title: "Stripe",
|
||||
type: "stripe_payment",
|
||||
|
|
|
@ -113,7 +113,7 @@ export class PaymentService implements IAbstractPaymentService {
|
|||
}
|
||||
return paymentData;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error(`Payment could not be created for bookingId ${bookingId}`, error);
|
||||
throw new Error("Payment could not be created");
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +124,7 @@ export class PaymentService implements IAbstractPaymentService {
|
|||
bookerEmail: string,
|
||||
paymentOption: PaymentOption
|
||||
): Promise<Payment> {
|
||||
try {
|
||||
// Ensure that the payment service can support the passed payment option
|
||||
if (paymentOptionEnum.parse(paymentOption) !== "HOLD") {
|
||||
throw new Error("Payment option is not compatible with create method");
|
||||
|
@ -193,6 +194,10 @@ export class PaymentService implements IAbstractPaymentService {
|
|||
});
|
||||
|
||||
return paymentData;
|
||||
} catch (error) {
|
||||
console.error(`Payment method could not be collected for bookingId ${bookingId}`, error);
|
||||
throw new Error("Payment could not be created");
|
||||
}
|
||||
}
|
||||
|
||||
async chargeCard(payment: Payment): Promise<Payment> {
|
||||
|
@ -264,7 +269,7 @@ export class PaymentService implements IAbstractPaymentService {
|
|||
|
||||
return paymentData;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error(`Could not charge card for payment ${payment.id}`, error);
|
||||
throw new Error("Payment could not be created");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
"title": "Sylaps",
|
||||
"slug": "sylapsvideo",
|
||||
"type": "sylaps_video",
|
||||
"imageSrc": "icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/sylaps",
|
||||
"variant": "conferencing",
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/tandemvideo/tandem1.jpg
|
||||
- /api/app-store/tandemvideo/tandem2.jpg
|
||||
- /api/app-store/tandemvideo/tandem3.jpg
|
||||
- /api/app-store/tandemvideo/tandem4.jpg
|
||||
- /api/app-store/tandemvideo/tandem5.jpg
|
||||
- /api/app-store/tandemvideo/tandem6.jpg
|
||||
- tandem1.jpg
|
||||
- tandem2.jpg
|
||||
- tandem3.jpg
|
||||
- tandem4.jpg
|
||||
- tandem5.jpg
|
||||
- tandem6.jpg
|
||||
---
|
||||
|
||||
Tandem is a new virtual office space that allows teams to effortlessly connect as though they are in a physical office, online. Through co-working rooms, available statuses, live real-time video call, and chat options, you can see who’s around, talk and collaborate in one click. It works cross-platform with both desktop and mobile versions.
|
||||
|
|
|
@ -7,12 +7,11 @@ export const metadata = {
|
|||
description: _package.description,
|
||||
type: "tandem_video",
|
||||
title: "Tandem Video",
|
||||
imageSrc: "/api/app-store/tandemvideo/icon.svg",
|
||||
variant: "conferencing",
|
||||
categories: ["video"],
|
||||
slug: "tandem",
|
||||
category: "video",
|
||||
logo: "/api/app-store/tandemvideo/icon.svg",
|
||||
logo: "icon.svg",
|
||||
publisher: "",
|
||||
url: "",
|
||||
isGlobal: false,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/telegram/1.jpg
|
||||
- /api/app-store/telegram/2.jpg
|
||||
- /api/app-store/telegram/3.jpg
|
||||
- 1.jpg
|
||||
- 2.jpg
|
||||
- 3.jpg
|
||||
---
|
||||
|
||||
Schedule a chat with your guests or have a Telegram Video call.
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
"name": "Telegram",
|
||||
"slug": "telegram",
|
||||
"type": "telegram_video",
|
||||
"imageSrc": "/api/app-store/telegram/icon.svg",
|
||||
"logo": "/api/app-store/telegram/icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/telegram",
|
||||
"variant": "conferencing",
|
||||
"categories": ["video"],
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue