Delete slack app (#5462)

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
Hariom Balhara 2022-11-10 17:24:15 +05:30 committed by GitHub
parent 7bda15aaa3
commit 29c4efe4a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1 additions and 806 deletions

View File

@ -354,56 +354,6 @@ following
5. Use **Application (client) ID** as the **MS_GRAPH_CLIENT_ID** attribute value in .env
6. Click **Certificates & secrets** create a new client secret and use the value as the **MS_GRAPH_CLIENT_SECRET** attribute
### Obtaining Slack Client ID and Secret and Signing Secret
To test this you will need to create a Slack app for yourself on [their apps website](https://api.slack.com/apps).
Copy and paste the app manifest below into the setting on your slack app. Be sure to replace `YOUR_DOMAIN` with your own domain or your proxy host if you're testing locally.
<details>
<summary>App Manifest</summary>
```yaml
display_information:
name: Cal.com Slack
features:
bot_user:
display_name: Cal.com Slack
always_online: false
slash_commands:
- command: /create-event
url: https://YOUR_DOMAIN/api/integrations/slackmessaging/commandHandler
description: Create an event within Cal!
should_escape: false
- command: /today
url: https://YOUR_DOMAIN/api/integrations/slackmessaging/commandHandler
description: View all your bookings for today
should_escape: false
oauth_config:
redirect_urls:
- https://YOUR_DOMAIN/api/integrations/slackmessaging/callback
scopes:
bot:
- chat:write
- commands
- chat:write.public
settings:
interactivity:
is_enabled: true
request_url: https://YOUR_DOMAIN/api/integrations/slackmessaging/interactiveHandler
message_menu_options_url: https://YOUR_DOMAIN/api/integrations/slackmessaging/interactiveHandler
org_deploy_enabled: false
socket_mode_enabled: false
token_rotation_enabled: false
```
</details>
Add the integration as normal - slack app - add. Follow the oauth flow to add it to a server.
Next make sure you have your app running `yarn dx`. Then in the slack chat type one of these commands: `/create-event` or `/today`
> NOTE: Next you will need to setup a proxy server like [ngrok](https://ngrok.com/) to allow your local host machine to be hosted on a public https server.
### Obtaining Zoom Client ID and Secret

View File

@ -20,7 +20,6 @@ export const InstallAppButtonMap = {
office365calendar: dynamic(() => import("./office365calendar/components/InstallAppButton")),
office365video: dynamic(() => import("./office365video/components/InstallAppButton")),
riverside: dynamic(() => import("./riverside/components/InstallAppButton")),
slackmessaging: dynamic(() => import("./slackmessaging/components/InstallAppButton")),
tandemvideo: dynamic(() => import("./tandemvideo/components/InstallAppButton")),
vital: dynamic(() => import("./vital/components/InstallAppButton")),
whereby: dynamic(() => import("./whereby/components/InstallAppButton")),

View File

@ -32,7 +32,6 @@ import { metadata as raycast_meta } from "./raycast/_metadata";
import { metadata as riverside_meta } from "./riverside/_metadata";
import { metadata as sendgridothercalendar_meta } from "./sendgridothercalendar/_metadata";
import { metadata as sirius_video_meta } from "./sirius_video/_metadata";
import { metadata as slackmessaging_meta } from "./slackmessaging/_metadata";
import { metadata as stripepayment_meta } from "./stripepayment/_metadata";
import { metadata as tandemvideo_meta } from "./tandemvideo/_metadata";
import { metadata as typeform_meta } from "./typeform/_metadata";
@ -74,7 +73,6 @@ export const appStoreMetadata = {
riverside: riverside_meta,
sendgridothercalendar: sendgridothercalendar_meta,
sirius_video: sirius_video_meta,
slackmessaging: slackmessaging_meta,
stripepayment: stripepayment_meta,
tandemvideo: tandemvideo_meta,
typeform: typeform_meta,

View File

@ -31,7 +31,6 @@ export const apiHandlers = {
riverside: import("./riverside/api"),
sendgridothercalendar: import("./sendgridothercalendar/api"),
sirius_video: import("./sirius_video/api"),
slackmessaging: import("./slackmessaging/api"),
stripepayment: import("./stripepayment/api"),
tandemvideo: import("./tandemvideo/api"),
typeform: import("./typeform/api"),

View File

@ -16,7 +16,6 @@ import * as larkcalendar from "./larkcalendar";
import * as office365calendar from "./office365calendar";
import * as office365video from "./office365video";
import * as sendgridothercalendar from "./sendgridothercalendar";
import * as slackmessaging from "./slackmessaging";
import * as stripepayment from "./stripepayment";
import * as tandemvideo from "./tandemvideo";
import * as vital from "./vital";
@ -39,7 +38,6 @@ const appStore = {
office365calendar,
office365video,
sendgridothercalendar,
slackmessaging,
stripepayment,
tandemvideo,
vital,

View File

@ -1,9 +0,0 @@
---
items:
- /api/app-store/slackmessaging/1.jpg
- /api/app-store/slackmessaging/2.jpg
- /api/app-store/slackmessaging/3.jpg
---
Slack is a proprietary business communication platform that includes many IRC (internet relay chat) features - these include channels, private groups, direct messaging and more. Users are able to send pictures, and videos as well as even hop on calls with others using the paid version. Slack is available via desktop app, web browser or mobile app. The Cal.com Slack App can be used to display your links, upcoming bookings or to create events. Use it with your team members in a Slack, in a Community Slack or even with external contributors in a Slack Connect Channel.

View File

@ -1,25 +0,0 @@
import type { AppMeta } from "@calcom/types/App";
import _package from "./package.json";
export const metadata = {
name: "Slack App",
description: _package.description,
category: "messaging",
imageSrc: "/apps/slack.svg",
logo: "/apps/slack.svg",
publisher: "Cal.com",
rating: 5,
reviews: 69,
slug: "slack",
title: "Slack App",
trending: true,
// DB has type slack_app. It is an inconsistency
type: "slack_messaging",
url: "https://slack.com/",
variant: "conferencing",
verified: true,
email: "help@cal.com",
} as AppMeta;
export default metadata;

View File

@ -1,40 +0,0 @@
import type { NextApiRequest } from "next";
import { stringify } from "querystring";
import { HttpError } from "@calcom/lib/http-error";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import { getSlackAppKeys } from "../lib/utils";
const scopes = ["commands", "users:read", "users:read.email", "chat:write", "chat:write.public"];
async function handler(req: NextApiRequest) {
if (!req.session?.user?.id) {
throw new HttpError({ statusCode: 401, message: "You must be logged in to do this" });
}
const { client_id } = await getSlackAppKeys();
// Get user
await prisma.user.findFirstOrThrow({
where: {
id: req.session.user.id,
},
select: {
id: true,
},
});
const params = {
client_id,
scope: scopes.join(","),
};
const query = stringify(params);
const url = `https://slack.com/oauth/v2/authorize?${query}&user_`;
// const url =
// "https://slack.com/oauth/v2/authorize?client_id=3194129032064.3178385871204&scope=chat:write,commands&user_scope=";
return { url };
}
export default defaultHandler({
GET: Promise.resolve({ default: defaultResponder(handler) }),
});

View File

@ -1,61 +0,0 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { stringify } from "querystring";
import { z } from "zod";
import { HttpError } from "@calcom/lib/http-error";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
import { getSlackAppKeys } from "../lib/utils";
const callbackQuerySchema = z.object({
code: z.string().min(1),
});
async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!req.session?.user?.id) {
throw new HttpError({ statusCode: 401, message: "You must be logged in to do this" });
}
// Get user
const parsedCallbackQuery = callbackQuerySchema.safeParse(req.query);
if (!parsedCallbackQuery.success) {
return res.redirect("/apps/installed"); // Redirect to where the user was if they cancel the signup or if the oauth fails
}
const { code } = parsedCallbackQuery.data;
const { client_id, client_secret } = await getSlackAppKeys();
const query = {
client_secret,
client_id,
code,
};
const params = stringify(query);
const url = `https://slack.com/api/oauth.v2.access?${params}`;
const result = await fetch(url);
const responseBody = await result.json();
await prisma.user.update({
where: {
id: req.session.user.id,
},
data: {
credentials: {
create: {
type: "slack_app",
key: responseBody,
appId: "slack",
},
},
},
});
res.redirect(getInstalledAppPath({ variant: "conferencing", slug: "slack" }));
}
export default defaultHandler({
GET: Promise.resolve({ default: defaultResponder(handler) }),
});

View File

@ -1,39 +0,0 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import { showCreateEventMessage, showTodayMessage } from "../lib";
import showLinksMessage from "../lib/showLinksMessage";
export enum SlackAppCommands {
CREATE_EVENT = "create-event",
TODAY = "today",
LINKS = "links",
}
const commandHandlerBodySchema = z.object({
command: z.string().min(1),
user_id: z.string(),
trigger_id: z.string(),
channel_id: z.string().optional(),
});
async function handler(req: NextApiRequest, res: NextApiResponse) {
const body = commandHandlerBodySchema.parse(req.body);
const command = body.command.split("/").pop();
switch (command) {
case SlackAppCommands.CREATE_EVENT:
return await showCreateEventMessage(req, res);
case SlackAppCommands.TODAY:
return await showTodayMessage(req, res);
case SlackAppCommands.LINKS:
return await showLinksMessage(req, res);
default:
return res.status(404).json({ message: `Command not found` });
}
}
export default defaultHandler({
POST: Promise.resolve({ default: defaultResponder(handler) }),
});

View File

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

View File

@ -1,21 +0,0 @@
import { NextApiRequest, NextApiResponse } from "next";
import createEvent from "../lib/actions/createEvent";
enum InteractionEvents {
CREATE_EVENT = "cal.event.create",
}
export default async function interactiveHandler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "POST") {
const payload = JSON.parse(req.body.payload);
const actions = payload.view.callback_id;
switch (actions) {
case InteractionEvents.CREATE_EVENT:
await createEvent(req, res);
default:
return res.status(200).end(); // Techincally an invalid request but we don't want to return an throw an error to slack - 200 just does nothing
}
}
return res.status(200).end(); // Send 200 if we dont have a case for the action_id
}

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("slack_messaging");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

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

View File

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

View File

@ -1,9 +0,0 @@
export const WhereCredsEqualsId = (userId: string) => ({
where: {
type: "slack_app",
key: {
path: ["authed_user", "id"],
equals: userId,
},
},
});

View File

@ -1,119 +0,0 @@
import { WebClient } from "@slack/web-api";
import { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
import { DailyLocationType } from "@calcom/app-store/locations";
import dayjs from "@calcom/dayjs";
import { WEBAPP_URL } from "@calcom/lib/constants";
import db from "@calcom/prisma";
import type { BookingCreateBody } from "@calcom/prisma/zod-utils";
import { WhereCredsEqualsId } from "../WhereCredsEqualsID";
import { getUserEmail } from "../utils";
export default async function createEvent(req: NextApiRequest, res: NextApiResponse) {
const {
user,
view: {
state: { values },
id: view_id,
},
response_url,
} = JSON.parse(req.body.payload);
// This is a mess I have no idea why slack makes getting infomation this hard.
const {
eventName: {
event_name: { value: selected_name },
},
eventType: {
"create.event.type": {
selected_option: { value: selected_event_id },
},
},
selectedUsers: {
invite_users: { selected_users },
},
eventDate: {
event_date: { selected_date },
},
eventTime: {
event_start_time: { selected_time },
},
} = values;
// Im sure this query can be made more efficient... The JSON filtering wouldnt work when doing it directly on user.
const foundUser = await db.credential
.findFirstOrThrow({
...WhereCredsEqualsId(user.id),
})
.user({
select: {
username: true,
email: true,
timeZone: true,
locale: true,
eventTypes: {
where: {
id: parseInt(selected_event_id),
},
select: {
id: true,
length: true,
locations: true,
},
},
credentials: {
...WhereCredsEqualsId(user.id),
},
},
});
const SlackCredentialsSchema = z.object({
access_token: z.string(),
});
const slackCredentials = SlackCredentialsSchema.parse(foundUser?.credentials[0].key); // Only one slack credential for user
const access_token = slackCredentials?.access_token;
// https://api.slack.com/authentication/best-practices#verifying since we verify the request is coming from slack we can store the access_token in the DB.
const client = new WebClient(access_token);
// This could get a bit weird as there is a 3 second limit until the post times ou
// Compute all users that have been selected and get their email.
const invitedGuestsEmails = selected_users.map((userId: string) => getUserEmail(client, userId));
const startDate = dayjs(`${selected_date} ${selected_time}`, "YYYY-MM-DD HH:mm");
const PostData: BookingCreateBody = {
start: dayjs(startDate).format(),
end: dayjs(startDate)
.add(foundUser?.eventTypes[0]?.length ?? 0, "minute")
.format(),
eventTypeId: foundUser?.eventTypes[0]?.id ?? 0,
user: foundUser?.username ?? "",
email: foundUser?.email ?? "",
name: foundUser?.username ?? "",
guests: await Promise.all(invitedGuestsEmails),
location: DailyLocationType, // Defaulting to daily video to make this a bit more usefull than in-person
timeZone: foundUser?.timeZone ?? "",
language: foundUser?.locale ?? "en",
customInputs: [{ label: "", value: "" }],
metadata: {},
notes: "This event was created with slack.",
};
const response = await fetch(`${WEBAPP_URL}/api/book/event`, {
method: "POST",
body: JSON.stringify(PostData),
headers: {
"Content-Type": "application/json",
},
});
const body = await response.json();
client.chat.postMessage({
token: access_token,
channel: user.id,
text: body.errorCode ? `Error: ${body.errorCode}` : "Booking has been created.",
});
return res.status(200).send("");
}

View File

@ -1,3 +0,0 @@
export { default as showCreateEventMessage } from "./showCreateEventMessage";
export { default as showTodayMessage } from "./showTodayMessage";
export * as utils from "./utils";

View File

@ -1,41 +0,0 @@
import { Prisma } from "@prisma/client";
import { WebClient } from "@slack/web-api";
import { NextApiRequest, NextApiResponse } from "next";
import prisma from "@calcom/prisma";
import { WhereCredsEqualsId } from "./WhereCredsEqualsID";
import slackVerify from "./slackVerify";
import { CreateEventModal, NoUserMessage } from "./views";
export default async function showCreateEventMessage(req: NextApiRequest, res: NextApiResponse) {
const body = req.body;
await slackVerify(req, res);
const data = await prisma.credential.findFirst({
...WhereCredsEqualsId(body.user_id),
include: {
user: {
select: {
username: true,
eventTypes: {
select: {
id: true,
title: true,
},
},
},
},
},
});
if (!data) return res.status(200).json(NoUserMessage);
const slackCredentials = data?.key; // Only one slack credential for user
const access_token = (slackCredentials as Prisma.JsonObject)?.access_token as string;
const slackClient = new WebClient(access_token);
await slackClient.views.open({
trigger_id: body.trigger_id,
view: CreateEventModal(data),
});
return res.status(200).end();
}

View File

@ -1,49 +0,0 @@
import { Prisma } from "@prisma/client";
import { WebClient } from "@slack/web-api";
import { NextApiRequest, NextApiResponse } from "next";
import prisma from "@calcom/prisma";
import { WhereCredsEqualsId } from "./WhereCredsEqualsID";
import slackVerify from "./slackVerify";
import { NoUserMessage } from "./views";
import ShowLinks from "./views/ShowLinks";
export default async function showLinksMessage(req: NextApiRequest, res: NextApiResponse) {
const body = req.body;
await slackVerify(req, res);
const data = await prisma.credential.findFirst({
...WhereCredsEqualsId(body.user_id),
include: {
user: {
select: {
username: true,
eventTypes: {
where: {
hidden: false,
},
select: {
slug: true,
title: true,
},
},
},
},
},
});
if (!data) return res.status(200).json(NoUserMessage);
const slackCredentials = data?.key; // Only one slack credential for user
const access_token = (slackCredentials as Prisma.JsonObject)?.access_token as string;
const slackClient = new WebClient(access_token);
const blocks = JSON.parse(ShowLinks(data.user?.eventTypes, data.user?.username ?? "")).blocks;
slackClient.chat.postMessage({
channel: body.channel_id,
text: `${data.user?.username}'s Cal.com Links`,
blocks,
});
return res.status(200).end();
}

View File

@ -1,55 +0,0 @@
import { BookingStatus } from "@prisma/client";
import { NextApiRequest, NextApiResponse } from "next";
import dayjs from "@calcom/dayjs";
import prisma from "@calcom/prisma";
import { WhereCredsEqualsId } from "./WhereCredsEqualsID";
import slackVerify from "./slackVerify";
import { NoUserMessage, TodayMessage } from "./views";
export default async function showCreateEventMessage(req: NextApiRequest, res: NextApiResponse) {
const body = req.body;
await slackVerify(req, res);
const foundUser = await prisma.credential.findFirst({
...WhereCredsEqualsId(body.user_id),
include: {
user: {
select: {
id: true,
email: true,
},
},
},
});
if (!foundUser) res.status(200).json(NoUserMessage);
const bookings = await prisma.booking.findMany({
where: {
OR: [
{
userId: foundUser?.userId,
},
{
attendees: {
some: {
email: foundUser?.user?.email,
},
},
},
],
AND: [
{
endTime: { gte: dayjs().startOf("day").toDate(), lte: dayjs().endOf("day").toDate() },
AND: [
{ NOT: { status: { equals: BookingStatus.CANCELLED } } },
{ NOT: { status: { equals: BookingStatus.REJECTED } } },
],
},
],
},
});
return res.status(200).json(TodayMessage(bookings));
}

View File

@ -1,37 +0,0 @@
import { createHmac } from "crypto";
import { NextApiRequest, NextApiResponse } from "next";
import { stringify } from "querystring";
import dayjs from "@calcom/dayjs";
import { getSlackAppKeys } from "./utils";
export default async function slackVerify(req: NextApiRequest, res: NextApiResponse) {
const timeStamp = req.headers["x-slack-request-timestamp"] as string; // Always returns a string and not a string[]
const slackSignature = req.headers["x-slack-signature"] as string;
const currentTime = dayjs().unix();
const { signing_secret: signingSecret } = await getSlackAppKeys();
const [version, hash] = slackSignature.split("=");
if (!timeStamp) {
return res.status(400).json({ message: "Missing X-Slack-Request-Timestamp header" });
}
if (!signingSecret) {
return res.status(400).json({ message: "Missing Slack's signing_secret" });
}
if (Math.abs(currentTime - parseInt(timeStamp)) > 60 * 5) {
return res.status(400).json({ message: "Request is too old" });
}
const hmac = createHmac("sha256", signingSecret);
hmac.update(`${version}:${timeStamp}:${stringify(req.body)}`);
const signed_sig = hmac.digest("hex");
console.log({ signed_sig, hash, match: signed_sig === hash });
if (signed_sig !== hash) {
throw new Error("Hashes do not match ");
}
}

View File

@ -1,18 +0,0 @@
import { WebClient } from "@slack/web-api";
import { z } from "zod";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
export const getUserEmail = async (client: WebClient, userId: string) =>
(await client.users.info({ user: userId })).user?.profile?.email;
const slackAppKeysSchema = z.object({
client_id: z.string(),
client_secret: z.string(),
signing_secret: z.string(),
});
export const getSlackAppKeys = async () => {
const appKeys = await getAppKeysFromSlug("slack");
return slackAppKeysSchema.parse(appKeys);
};

View File

@ -1,9 +0,0 @@
import { Blocks, Message } from "slack-block-builder";
const BookingSuccess = () => {
return Message()
.blocks(Blocks.Section({ text: `Your booking has been created!` }))
.buildToObject();
};
export default BookingSuccess;

View File

@ -1,53 +0,0 @@
import { Credential } from "@prisma/client";
import { Bits, Blocks, Elements, Modal, setIfTruthy } from "slack-block-builder";
const CreateEventModal = (
data:
| (Credential & {
user: {
username: string | null;
eventTypes: {
id: number;
title: string;
}[];
} | null;
})
| null,
invalidInput = false
) => {
return Modal({ title: "Create Booking", submit: "Create", callbackId: "cal.event.create" })
.blocks(
Blocks.Section({ text: `Hey there, *${data?.user?.username}!*` }),
Blocks.Divider(),
Blocks.Input({ label: "Your Name", blockId: "eventName" }).element(
Elements.TextInput({ placeholder: "Event Name" }).actionId("event_name")
),
Blocks.Input({ label: "Which event would you like to create?", blockId: "eventType" }).element(
Elements.StaticSelect({ placeholder: "Which event would you like to create?" })
.actionId("create.event.type")
.options(
data?.user?.eventTypes.map((item: any) =>
Bits.Option({ text: item.title ?? "No Name", value: item.id.toString() })
)
)
), // This doesnt need to reach out to the server when the user changes the selection
Blocks.Input({
label: "Who would you like to invite to your event?",
blockId: "selectedUsers",
}).element(
Elements.UserMultiSelect({ placeholder: "Who would you like to invite to your event?" }).actionId(
"invite_users"
)
),
Blocks.Input({ label: "When would this event be?", blockId: "eventDate" }).element(
Elements.DatePicker({ placeholder: "Select Date" }).actionId("event_date")
),
Blocks.Input({ label: "What time would you like to start?", blockId: "eventTime" }).element(
Elements.TimePicker({ placeholder: "Select Time" }).actionId("event_start_time")
), // TODO: We could in future validate if the time is in the future or if busy at point - Didnt see much point as this gets validated when you submit. Could be better UX
setIfTruthy(invalidInput, [Blocks.Section({ text: "Please fill in all the fields" })])
)
.buildToObject();
};
export default CreateEventModal;

View File

@ -1,20 +0,0 @@
import { Message, Blocks, Elements } from "slack-block-builder";
import { BASE_URL } from "@calcom/lib/constants";
const NoUserMessage = () => {
return Message()
.blocks(
Blocks.Section({ text: "This slack account is not linked with a cal.com account" }),
Blocks.Actions().elements(
Elements.Button({ text: "Cancel", actionId: "cancel" }).danger(),
Elements.Button({
text: "Connect",
actionId: "open.connect.link",
url: `${BASE_URL}/apps/installed`,
}).primary()
)
)
.buildToJSON();
};
export default NoUserMessage;

View File

@ -1,31 +0,0 @@
import { Blocks, Elements, Message } from "slack-block-builder";
import dayjs from "@calcom/dayjs";
import { WEBAPP_URL } from "@calcom/lib/constants";
interface IEventTypes {
slug: string;
title: string;
}
const ShowLinks = (eventLinks: IEventTypes[] | undefined, username: string) => {
if (eventLinks?.length === 0 || !eventLinks) {
return Message()
.blocks(Blocks.Section({ text: "You do not have any links." }))
.asUser()
.buildToJSON();
}
return Message()
.blocks(
Blocks.Section({ text: `${username}'s Cal.com Links` }),
Blocks.Divider(),
eventLinks.map((links) =>
Blocks.Section({
text: `${links.title} | ${WEBAPP_URL}/${username}/${links.slug}`,
}).accessory(Elements.Button({ text: "Open", url: `${WEBAPP_URL}/${username}/${links.slug}` }))
)
)
.buildToJSON();
};
export default ShowLinks;

View File

@ -1,27 +0,0 @@
import { Booking } from "@prisma/client";
import { Blocks, Elements, Message } from "slack-block-builder";
import dayjs from "@calcom/dayjs";
import { WEBAPP_URL } from "@calcom/lib/constants";
const TodayMessage = (bookings: Booking[]) => {
if (bookings.length === 0) {
return Message()
.blocks(Blocks.Section({ text: "You do not have any bookings for today." }))
.asUser()
.buildToObject();
}
return Message()
.blocks(
Blocks.Section({ text: `Todays Bookings.` }),
Blocks.Divider(),
bookings.map((booking) =>
Blocks.Section({
text: `${booking.title} | ${dayjs(booking.startTime).format("HH:mm")}`,
}).accessory(Elements.Button({ text: "Cancel", url: `${WEBAPP_URL}/cancel/${booking.uid}` }))
)
)
.buildToObject();
};
export default TodayMessage;

View File

@ -1,3 +0,0 @@
export { default as CreateEventModal } from "./CreateEventModal";
export { default as TodayMessage } from "./TodayMessage";
export { default as NoUserMessage } from "./NoUser";

View File

@ -1,17 +0,0 @@
{
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"name": "@calcom/slackmessaging",
"version": "0.0.0",
"main": "./index.ts",
"description": "A business communication platform that includes persistent chat rooms (channels), private groups and direct messaging.",
"dependencies": {
"@calcom/prisma": "*",
"@slack/web-api": "^6.7.2",
"slack-block-builder": "^2.6.0",
"zod": "^3.19.1"
},
"devDependencies": {
"@calcom/types": "*"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

View File

@ -1,31 +0,0 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 270 270" style="enable-background:new 0 0 270 270;" xml:space="preserve">
<style type="text/css">
.st0{fill:#E01E5A;}
.st1{fill:#36C5F0;}
.st2{fill:#2EB67D;}
.st3{fill:#ECB22E;}
</style>
<g>
<g>
<path class="st0" d="M99.4,151.2c0,7.1-5.8,12.9-12.9,12.9c-7.1,0-12.9-5.8-12.9-12.9c0-7.1,5.8-12.9,12.9-12.9h12.9V151.2z"/>
<path class="st0" d="M105.9,151.2c0-7.1,5.8-12.9,12.9-12.9s12.9,5.8,12.9,12.9v32.3c0,7.1-5.8,12.9-12.9,12.9
s-12.9-5.8-12.9-12.9V151.2z"/>
</g>
<g>
<path class="st1" d="M118.8,99.4c-7.1,0-12.9-5.8-12.9-12.9c0-7.1,5.8-12.9,12.9-12.9s12.9,5.8,12.9,12.9v12.9H118.8z"/>
<path class="st1" d="M118.8,105.9c7.1,0,12.9,5.8,12.9,12.9s-5.8,12.9-12.9,12.9H86.5c-7.1,0-12.9-5.8-12.9-12.9
s5.8-12.9,12.9-12.9H118.8z"/>
</g>
<g>
<path class="st2" d="M170.6,118.8c0-7.1,5.8-12.9,12.9-12.9c7.1,0,12.9,5.8,12.9,12.9s-5.8,12.9-12.9,12.9h-12.9V118.8z"/>
<path class="st2" d="M164.1,118.8c0,7.1-5.8,12.9-12.9,12.9c-7.1,0-12.9-5.8-12.9-12.9V86.5c0-7.1,5.8-12.9,12.9-12.9
c7.1,0,12.9,5.8,12.9,12.9V118.8z"/>
</g>
<g>
<path class="st3" d="M151.2,170.6c7.1,0,12.9,5.8,12.9,12.9c0,7.1-5.8,12.9-12.9,12.9c-7.1,0-12.9-5.8-12.9-12.9v-12.9H151.2z"/>
<path class="st3" d="M151.2,164.1c-7.1,0-12.9-5.8-12.9-12.9c0-7.1,5.8-12.9,12.9-12.9h32.3c7.1,0,12.9,5.8,12.9,12.9
c0,7.1-5.8,12.9-12.9,12.9H151.2z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -266,14 +266,7 @@ export default async function main() {
// Web3 apps
await createApp("huddle01", "huddle01video", ["web3", "video"], "huddle01_video");
// Messaging apps
if (process.env.SLACK_CLIENT_ID && process.env.SLACK_CLIENT_SECRET && process.env.SLACK_SIGNING_SECRET) {
await createApp("slack", "slackmessaging", ["messaging"], "slack_messaging", {
client_id: process.env.SLACK_CLIENT_ID,
client_secret: process.env.SLACK_CLIENT_SECRET,
signing_secret: process.env.SLACK_SIGNING_SECRET,
});
}
// Payment apps
if (
process.env.STRIPE_CLIENT_ID &&