added QR code app, needs eventType.URL (#4701)

* wip added QR code app, needs settings

* added QR code settings, needs eventType.URL

* Make URL prop available to apps

* Add recurringEvent in available. It was missing earlier

* added autoAnimate to AppSettings

* Remove isSunrise demo prop

* Simplify schema even more

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
pull/5043/head
Peer Richelsen 2022-10-17 17:38:43 +01:00 committed by GitHub
parent 9e3e1418c2
commit 44ef1f80e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 406 additions and 8 deletions

View File

@ -4,6 +4,7 @@ import { useFormContext } from "react-hook-form";
import EventTypeAppContext, { GetAppData, SetAppData } from "@calcom/app-store/EventTypeAppContext";
import { EventTypeAddonMap } from "@calcom/app-store/apps.browser.generated";
import { EventTypeAppCardComponentProps } from "@calcom/app-store/types";
import { EventTypeAppsList } from "@calcom/app-store/utils";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
@ -11,7 +12,9 @@ import { Icon } from "@calcom/ui";
import ErrorBoundary from "@calcom/ui/ErrorBoundary";
import { Button, EmptyScreen } from "@calcom/ui/v2";
type EventType = Pick<EventTypeSetupInfered, "eventType">["eventType"];
type EventType = Pick<EventTypeSetupInfered, "eventType">["eventType"] &
EventTypeAppCardComponentProps["eventType"];
function AppCardWrapper({
app,
eventType,

View File

@ -178,6 +178,10 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
).length;
}
const permalink = `${CAL_URL}/${team ? `team/${team.slug}` : eventType.users[0].username}/${
eventType.slug
}`;
const tabMap = {
setup: (
<EventSetupTab
@ -199,7 +203,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
limits: <EventLimitsTab eventType={eventType} />,
advanced: <EventAdvancedTab eventType={eventType} team={team} />,
recurring: <EventRecurringTab eventType={eventType} />,
apps: <EventAppsTab eventType={eventType} />,
apps: <EventAppsTab eventType={{ ...eventType, URL: permalink }} />,
workflows: (
<EventWorkflowsTab
eventType={eventType}

View File

@ -1,4 +1,6 @@
import autoAnimate from "@formkit/auto-animate";
import Link from "next/link";
import { useEffect, useRef } from "react";
import { inferQueryOutput } from "@calcom/trpc/react";
import { Switch } from "@calcom/ui/v2";
@ -22,8 +24,14 @@ export default function AppCard({
children?: React.ReactNode;
setAppData: SetAppDataGeneric<typeof eventTypeAppCardZod>;
}) {
const animationRef = useRef(null);
useEffect(() => {
animationRef.current && autoAnimate(animationRef.current);
}, [animationRef]);
return (
<div className="mb-4 mt-2 rounded-md border border-gray-200 p-8 text-sm">
<div ref={animationRef} className="mb-4 mt-2 rounded-md border border-gray-200 p-8 text-sm">
<div className="flex w-full">
{/* Don't know why but w-[42px] isn't working, started happening when I started using next/dynamic */}
<Link href={"/apps/" + app.slug}>

View File

@ -36,6 +36,7 @@ export const AppSettingsComponentsMap = {
export const EventTypeAddonMap = {
fathom: dynamic(() => import("./fathom/extensions/EventTypeAppCard")),
giphy: dynamic(() => import("./giphy/extensions/EventTypeAppCard")),
qr_code: dynamic(() => import("./qr_code/extensions/EventTypeAppCard")),
rainbow: dynamic(() => import("./rainbow/extensions/EventTypeAppCard")),
stripepayment: dynamic(() => import("./stripepayment/extensions/EventTypeAppCard")),
};

View File

@ -24,6 +24,7 @@ import { metadata as n8n_meta } from "./n8n/_metadata";
import { metadata as office365calendar_meta } from "./office365calendar/_metadata";
import { metadata as office365video_meta } from "./office365video/_metadata";
import { metadata as ping_meta } from "./ping/_metadata";
import { metadata as qr_code_meta } from "./qr_code/_metadata";
import { metadata as rainbow_meta } from "./rainbow/_metadata";
import { metadata as raycast_meta } from "./raycast/_metadata";
import { metadata as riverside_meta } from "./riverside/_metadata";
@ -61,6 +62,7 @@ export const appStoreMetadata = {
office365calendar: office365calendar_meta,
office365video: office365video_meta,
ping: ping_meta,
qr_code: qr_code_meta,
rainbow: rainbow_meta,
raycast: raycast_meta,
riverside: riverside_meta,

View File

@ -5,6 +5,7 @@
import { appDataSchema as routing_forms_schema } from "./ee/routing-forms/zod";
import { appDataSchema as fathom_schema } from "./fathom/zod";
import { appDataSchema as giphy_schema } from "./giphy/zod";
import { appDataSchema as qr_code_schema } from "./qr_code/zod";
import { appDataSchema as rainbow_schema } from "./rainbow/zod";
import { appDataSchema as stripepayment_schema } from "./stripepayment/zod";
@ -12,6 +13,7 @@ export const appDataSchemas = {
"routing-forms": routing_forms_schema,
fathom: fathom_schema,
giphy: giphy_schema,
qr_code: qr_code_schema,
rainbow: rainbow_schema,
stripe: stripepayment_schema,
};

View File

@ -23,6 +23,7 @@ export const apiHandlers = {
office365calendar: import("./office365calendar/api"),
office365video: import("./office365video/api"),
ping: import("./ping/api"),
qr_code: import("./qr_code/api"),
rainbow: import("./rainbow/api"),
raycast: import("./raycast/api"),
riverside: import("./riverside/api"),

View File

@ -0,0 +1,5 @@
---
description: Easily generate a QR code for your links to print, share, or embed.
---
{description}

View File

@ -0,0 +1,10 @@
import type { AppMeta } from "@calcom/types/App";
import config from "./config.json";
export const metadata = {
category: "other",
...config,
} as AppMeta;
export default metadata;

View File

@ -0,0 +1,17 @@
import { AppDeclarativeHandler } from "@calcom/types/AppHandler";
import { createDefaultInstallation } from "../../_utils/installation";
import appConfig from "../config.json";
const handler: AppDeclarativeHandler = {
// Instead of passing appType and slug from here, api/integrations/[..args] should be able to derive and pass these directly to createCredential
appType: appConfig.type,
variant: appConfig.variant,
slug: appConfig.slug,
supportsMultipleInstalls: false,
handlerType: "add",
createCredential: ({ appType, user, slug }) =>
createDefaultInstallation({ appType, userId: user.id, slug, key: {} }),
};
export default handler;

View File

@ -0,0 +1 @@
export { default as add } from "./add";

View File

@ -0,0 +1,16 @@
{
"/*": "Don't modify slug - If required, do it using cli edit command",
"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",
"url": "https://cal.com/apps/qr_code",
"variant": "other",
"categories": ["other"],
"extendsFeature": "EventType",
"publisher": "Cal.com, Inc.",
"email": "support@cal.com",
"description": "Easily generate a QR code for your links to print, share, or embed.",
"__createdUsingCli": true
}

View File

@ -0,0 +1,54 @@
import { useState } from "react";
import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext";
import AppCard from "@calcom/app-store/_components/AppCard";
import type { EventTypeAppCardComponent } from "@calcom/app-store/types";
import { Tooltip } from "@calcom/ui/v2";
import { appDataSchema } from "../zod";
const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ eventType, app }) {
const [getAppData, setAppData] = useAppContextWithSchema<typeof appDataSchema>();
const [enabled, setEnabled] = useState(getAppData("enabled"));
const eventTypeURL = eventType.URL;
function QRCode({ size, data }: { size: number; data: string }) {
const QR_URL = "https://api.qrserver.com/v1/create-qr-code/?size=" + size + "&data=" + data;
return (
<Tooltip content={eventTypeURL}>
<a download href={QR_URL} target="_blank" rel="noreferrer">
<img
className="border hover:bg-gray-50 hover:shadow-sm"
style={{ padding: size / 16, borderRadius: size / 20 }}
width={size}
src={QR_URL}
alt={eventTypeURL}
/>
</a>
</Tooltip>
);
}
return (
<AppCard
setAppData={setAppData}
app={app}
switchOnClick={(e) => {
if (!e) {
setEnabled(false);
} else {
setEnabled(true);
}
}}
switchChecked={enabled}>
<div className="max-w-60 flex items-baseline justify-between gap-2 text-sm ">
<QRCode size={256} data={eventTypeURL} />
<QRCode size={128} data={eventTypeURL} />
<QRCode size={64} data={eventTypeURL} />
</div>
</AppCard>
);
};
export default EventTypeAppCard;

View File

@ -0,0 +1,2 @@
export * as api from "./api";
export { metadata } from "./_metadata";

View File

@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"name": "@calcom/qr_code",
"version": "0.0.0",
"main": "./index.ts",
"description": "Easily generate a QR code of your links",
"dependencies": {
"@calcom/lib": "*"
},
"devDependencies": {
"@calcom/types": "*"
}
}

View File

@ -0,0 +1,244 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="232" height="232" viewBox="0 0 232 232"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events">
<rect x="0" y="0" width="232" height="232" fill="#ffffff"/>
<defs>
<rect id="p" width="8" height="8"/>
</defs>
<g fill="#000000">
<use xlink:href="#p" x="32" y="32"/>
<use xlink:href="#p" x="32" y="40"/>
<use xlink:href="#p" x="32" y="48"/>
<use xlink:href="#p" x="32" y="56"/>
<use xlink:href="#p" x="32" y="64"/>
<use xlink:href="#p" x="32" y="72"/>
<use xlink:href="#p" x="32" y="80"/>
<use xlink:href="#p" x="32" y="96"/>
<use xlink:href="#p" x="32" y="104"/>
<use xlink:href="#p" x="32" y="120"/>
<use xlink:href="#p" x="32" y="144"/>
<use xlink:href="#p" x="32" y="152"/>
<use xlink:href="#p" x="32" y="160"/>
<use xlink:href="#p" x="32" y="168"/>
<use xlink:href="#p" x="32" y="176"/>
<use xlink:href="#p" x="32" y="184"/>
<use xlink:href="#p" x="32" y="192"/>
<use xlink:href="#p" x="40" y="32"/>
<use xlink:href="#p" x="40" y="80"/>
<use xlink:href="#p" x="40" y="96"/>
<use xlink:href="#p" x="40" y="144"/>
<use xlink:href="#p" x="40" y="192"/>
<use xlink:href="#p" x="48" y="32"/>
<use xlink:href="#p" x="48" y="48"/>
<use xlink:href="#p" x="48" y="56"/>
<use xlink:href="#p" x="48" y="64"/>
<use xlink:href="#p" x="48" y="80"/>
<use xlink:href="#p" x="48" y="96"/>
<use xlink:href="#p" x="48" y="104"/>
<use xlink:href="#p" x="48" y="112"/>
<use xlink:href="#p" x="48" y="120"/>
<use xlink:href="#p" x="48" y="144"/>
<use xlink:href="#p" x="48" y="160"/>
<use xlink:href="#p" x="48" y="168"/>
<use xlink:href="#p" x="48" y="176"/>
<use xlink:href="#p" x="48" y="192"/>
<use xlink:href="#p" x="56" y="32"/>
<use xlink:href="#p" x="56" y="48"/>
<use xlink:href="#p" x="56" y="56"/>
<use xlink:href="#p" x="56" y="64"/>
<use xlink:href="#p" x="56" y="80"/>
<use xlink:href="#p" x="56" y="96"/>
<use xlink:href="#p" x="56" y="104"/>
<use xlink:href="#p" x="56" y="120"/>
<use xlink:href="#p" x="56" y="128"/>
<use xlink:href="#p" x="56" y="144"/>
<use xlink:href="#p" x="56" y="160"/>
<use xlink:href="#p" x="56" y="168"/>
<use xlink:href="#p" x="56" y="176"/>
<use xlink:href="#p" x="56" y="192"/>
<use xlink:href="#p" x="64" y="32"/>
<use xlink:href="#p" x="64" y="48"/>
<use xlink:href="#p" x="64" y="56"/>
<use xlink:href="#p" x="64" y="64"/>
<use xlink:href="#p" x="64" y="80"/>
<use xlink:href="#p" x="64" y="104"/>
<use xlink:href="#p" x="64" y="112"/>
<use xlink:href="#p" x="64" y="144"/>
<use xlink:href="#p" x="64" y="160"/>
<use xlink:href="#p" x="64" y="168"/>
<use xlink:href="#p" x="64" y="176"/>
<use xlink:href="#p" x="64" y="192"/>
<use xlink:href="#p" x="72" y="32"/>
<use xlink:href="#p" x="72" y="80"/>
<use xlink:href="#p" x="72" y="104"/>
<use xlink:href="#p" x="72" y="112"/>
<use xlink:href="#p" x="72" y="144"/>
<use xlink:href="#p" x="72" y="192"/>
<use xlink:href="#p" x="80" y="32"/>
<use xlink:href="#p" x="80" y="40"/>
<use xlink:href="#p" x="80" y="48"/>
<use xlink:href="#p" x="80" y="56"/>
<use xlink:href="#p" x="80" y="64"/>
<use xlink:href="#p" x="80" y="72"/>
<use xlink:href="#p" x="80" y="80"/>
<use xlink:href="#p" x="80" y="96"/>
<use xlink:href="#p" x="80" y="112"/>
<use xlink:href="#p" x="80" y="128"/>
<use xlink:href="#p" x="80" y="144"/>
<use xlink:href="#p" x="80" y="152"/>
<use xlink:href="#p" x="80" y="160"/>
<use xlink:href="#p" x="80" y="168"/>
<use xlink:href="#p" x="80" y="176"/>
<use xlink:href="#p" x="80" y="184"/>
<use xlink:href="#p" x="80" y="192"/>
<use xlink:href="#p" x="88" y="112"/>
<use xlink:href="#p" x="88" y="120"/>
<use xlink:href="#p" x="96" y="32"/>
<use xlink:href="#p" x="96" y="48"/>
<use xlink:href="#p" x="96" y="56"/>
<use xlink:href="#p" x="96" y="64"/>
<use xlink:href="#p" x="96" y="80"/>
<use xlink:href="#p" x="96" y="96"/>
<use xlink:href="#p" x="96" y="104"/>
<use xlink:href="#p" x="96" y="112"/>
<use xlink:href="#p" x="96" y="120"/>
<use xlink:href="#p" x="96" y="136"/>
<use xlink:href="#p" x="96" y="168"/>
<use xlink:href="#p" x="96" y="176"/>
<use xlink:href="#p" x="96" y="184"/>
<use xlink:href="#p" x="96" y="192"/>
<use xlink:href="#p" x="104" y="48"/>
<use xlink:href="#p" x="104" y="56"/>
<use xlink:href="#p" x="104" y="72"/>
<use xlink:href="#p" x="104" y="96"/>
<use xlink:href="#p" x="104" y="104"/>
<use xlink:href="#p" x="104" y="112"/>
<use xlink:href="#p" x="104" y="120"/>
<use xlink:href="#p" x="104" y="136"/>
<use xlink:href="#p" x="104" y="144"/>
<use xlink:href="#p" x="104" y="152"/>
<use xlink:href="#p" x="104" y="160"/>
<use xlink:href="#p" x="104" y="168"/>
<use xlink:href="#p" x="104" y="176"/>
<use xlink:href="#p" x="112" y="32"/>
<use xlink:href="#p" x="112" y="40"/>
<use xlink:href="#p" x="112" y="72"/>
<use xlink:href="#p" x="112" y="80"/>
<use xlink:href="#p" x="112" y="96"/>
<use xlink:href="#p" x="112" y="104"/>
<use xlink:href="#p" x="112" y="136"/>
<use xlink:href="#p" x="112" y="144"/>
<use xlink:href="#p" x="112" y="184"/>
<use xlink:href="#p" x="112" y="192"/>
<use xlink:href="#p" x="120" y="40"/>
<use xlink:href="#p" x="120" y="48"/>
<use xlink:href="#p" x="120" y="56"/>
<use xlink:href="#p" x="120" y="64"/>
<use xlink:href="#p" x="120" y="72"/>
<use xlink:href="#p" x="120" y="96"/>
<use xlink:href="#p" x="120" y="120"/>
<use xlink:href="#p" x="120" y="136"/>
<use xlink:href="#p" x="120" y="144"/>
<use xlink:href="#p" x="120" y="184"/>
<use xlink:href="#p" x="120" y="192"/>
<use xlink:href="#p" x="128" y="32"/>
<use xlink:href="#p" x="128" y="64"/>
<use xlink:href="#p" x="128" y="80"/>
<use xlink:href="#p" x="128" y="88"/>
<use xlink:href="#p" x="128" y="96"/>
<use xlink:href="#p" x="128" y="120"/>
<use xlink:href="#p" x="128" y="128"/>
<use xlink:href="#p" x="128" y="136"/>
<use xlink:href="#p" x="128" y="160"/>
<use xlink:href="#p" x="128" y="168"/>
<use xlink:href="#p" x="128" y="176"/>
<use xlink:href="#p" x="128" y="192"/>
<use xlink:href="#p" x="136" y="96"/>
<use xlink:href="#p" x="136" y="104"/>
<use xlink:href="#p" x="136" y="112"/>
<use xlink:href="#p" x="136" y="152"/>
<use xlink:href="#p" x="136" y="168"/>
<use xlink:href="#p" x="136" y="176"/>
<use xlink:href="#p" x="136" y="184"/>
<use xlink:href="#p" x="136" y="192"/>
<use xlink:href="#p" x="144" y="32"/>
<use xlink:href="#p" x="144" y="40"/>
<use xlink:href="#p" x="144" y="48"/>
<use xlink:href="#p" x="144" y="56"/>
<use xlink:href="#p" x="144" y="64"/>
<use xlink:href="#p" x="144" y="72"/>
<use xlink:href="#p" x="144" y="80"/>
<use xlink:href="#p" x="144" y="104"/>
<use xlink:href="#p" x="144" y="128"/>
<use xlink:href="#p" x="144" y="136"/>
<use xlink:href="#p" x="144" y="144"/>
<use xlink:href="#p" x="144" y="184"/>
<use xlink:href="#p" x="144" y="192"/>
<use xlink:href="#p" x="152" y="32"/>
<use xlink:href="#p" x="152" y="80"/>
<use xlink:href="#p" x="152" y="104"/>
<use xlink:href="#p" x="152" y="120"/>
<use xlink:href="#p" x="152" y="152"/>
<use xlink:href="#p" x="152" y="168"/>
<use xlink:href="#p" x="152" y="176"/>
<use xlink:href="#p" x="152" y="184"/>
<use xlink:href="#p" x="160" y="32"/>
<use xlink:href="#p" x="160" y="48"/>
<use xlink:href="#p" x="160" y="56"/>
<use xlink:href="#p" x="160" y="64"/>
<use xlink:href="#p" x="160" y="80"/>
<use xlink:href="#p" x="160" y="96"/>
<use xlink:href="#p" x="160" y="104"/>
<use xlink:href="#p" x="160" y="128"/>
<use xlink:href="#p" x="160" y="136"/>
<use xlink:href="#p" x="160" y="144"/>
<use xlink:href="#p" x="160" y="152"/>
<use xlink:href="#p" x="160" y="160"/>
<use xlink:href="#p" x="160" y="184"/>
<use xlink:href="#p" x="160" y="192"/>
<use xlink:href="#p" x="168" y="32"/>
<use xlink:href="#p" x="168" y="48"/>
<use xlink:href="#p" x="168" y="56"/>
<use xlink:href="#p" x="168" y="64"/>
<use xlink:href="#p" x="168" y="80"/>
<use xlink:href="#p" x="168" y="96"/>
<use xlink:href="#p" x="168" y="104"/>
<use xlink:href="#p" x="168" y="112"/>
<use xlink:href="#p" x="168" y="120"/>
<use xlink:href="#p" x="168" y="128"/>
<use xlink:href="#p" x="168" y="152"/>
<use xlink:href="#p" x="168" y="192"/>
<use xlink:href="#p" x="176" y="32"/>
<use xlink:href="#p" x="176" y="48"/>
<use xlink:href="#p" x="176" y="56"/>
<use xlink:href="#p" x="176" y="64"/>
<use xlink:href="#p" x="176" y="80"/>
<use xlink:href="#p" x="176" y="96"/>
<use xlink:href="#p" x="176" y="104"/>
<use xlink:href="#p" x="176" y="152"/>
<use xlink:href="#p" x="176" y="160"/>
<use xlink:href="#p" x="176" y="176"/>
<use xlink:href="#p" x="176" y="192"/>
<use xlink:href="#p" x="184" y="32"/>
<use xlink:href="#p" x="184" y="80"/>
<use xlink:href="#p" x="184" y="104"/>
<use xlink:href="#p" x="184" y="112"/>
<use xlink:href="#p" x="184" y="120"/>
<use xlink:href="#p" x="184" y="152"/>
<use xlink:href="#p" x="184" y="160"/>
<use xlink:href="#p" x="184" y="168"/>
<use xlink:href="#p" x="192" y="32"/>
<use xlink:href="#p" x="192" y="40"/>
<use xlink:href="#p" x="192" y="48"/>
<use xlink:href="#p" x="192" y="56"/>
<use xlink:href="#p" x="192" y="64"/>
<use xlink:href="#p" x="192" y="72"/>
<use xlink:href="#p" x="192" y="80"/>
<use xlink:href="#p" x="192" y="96"/>
<use xlink:href="#p" x="192" y="104"/>
<use xlink:href="#p" x="192" y="112"/>
<use xlink:href="#p" x="192" y="128"/>
<use xlink:href="#p" x="192" y="152"/>
<use xlink:href="#p" x="192" y="184"/>
</g>
<g></g></svg>

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@ -0,0 +1,3 @@
import { eventTypeAppCardZod } from "../eventTypeAppCardZod";
export const appDataSchema = eventTypeAppCardZod;

View File

@ -22,9 +22,14 @@ export interface InstallAppButtonProps {
) => JSX.Element;
onChanged?: () => unknown;
}
export type EventTypeAppCardComponent = React.FC<{
export type EventTypeAppCardComponentProps = {
// Limit what data should be accessible to apps
eventType: Pick<z.infer<typeof _EventTypeModel>, "id", "title" | "description" | "teamId" | "length">;
eventType: Pick<
z.infer<typeof _EventTypeModel>,
"id" | "title" | "description" | "teamId" | "length" | "recurringEvent"
> & {
URL: string;
};
app: inferQueryOutput<"viewer.apps">[number];
}>;
};
export type EventTypeAppCardComponent = React.FC<EventTypeAppCardComponentProps>;

View File

@ -88,6 +88,12 @@
"slug": "exchange",
"type": "exchange_calendar"
},
{
"dirName": "qr_code",
"categories": ["other"],
"slug": "qr_code",
"type": "qr_code_other"
},
{
"dirName": "weather_in_your_calendar",
"categories": ["other"],

View File

@ -7,7 +7,7 @@ export const _WebhookModel = z.object({
id: z.string(),
userId: z.number().int().nullish(),
eventTypeId: z.number().int().nullish(),
subscriberUrl: z.string(),
subscriberUrl: z.string().url(),
payloadTemplate: z.string().nullish(),
createdAt: z.date(),
active: z.boolean(),