Add Meta Mask to app store (#2650)

* Adds available apps

* Adds App Model

* WIP

* Create meta mask app folder

* Add description and images

* Remove credential from installed apps page

* Updates seeder script

* Seeder fixes

* lowercase categories

* Upgrades prisma

* WIP

* WIP

* Hopefully fixes circular deps

* Type fixes

* Fixes seeder

* Adds migration to connect Credentials to Apps

* Updates app store callbacks

* Updates google credentials

* Uses dirName from DB

* Type fixes

* Update reschedule.ts

* Seeder fixes

* Fixes categories listing

* Update index.ts

* Update schema.prisma

* Updates dependencies

* Renames giphy app

* Uses dynamic imports for app metadata

* Fixes credentials error

* Uses dynamic import for api handlers

* Dynamic import fixes

* Allows for simple folder names in app store

* Remove video adaptor

* Squashes app migrations

* seeder fixes

* Renames to metamask

* Updates metamask metadata

* Fixes dyamic imports

* Remove comments

* Create migration.sql

Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
This commit is contained in:
Joe Au-Yeung 2022-05-02 17:44:37 -04:00 committed by Omar López
parent 2e6bc5e5b4
commit 000785c29f
18 changed files with 190 additions and 6 deletions

View File

@ -532,15 +532,10 @@ const loggedInViewerRouter = createProtectedRouter()
});
if (web3Credential) {
return ctx.prisma.credential.update({
return ctx.prisma.credential.delete({
where: {
id: web3Credential.id,
},
data: {
key: {
isWeb3Active: !(web3Credential.key as JSONObject).isWeb3Active,
},
},
});
} else {
return ctx.prisma.credential.create({

View File

@ -13,6 +13,7 @@ export const apiHandlers = {
wipemycalother: import("./wipemycalother/api"),
jitsivideo: import("./jitsivideo/api"),
huddle01video: import("./huddle01video/api"),
metamask: import("./metamask/api"),
giphy: import("./giphy/api"),
};

View File

@ -23,6 +23,7 @@ export const InstallAppButtonMap = {
wipemycalother: dynamic(() => import("./wipemycalother/components/InstallAppButton")),
jitsivideo: dynamic(() => import("./jitsivideo/components/InstallAppButton")),
huddle01video: dynamic(() => import("./huddle01video/components/InstallAppButton")),
metamask: dynamic(() => import("./metamask/components/InstallAppButton")),
giphy: dynamic(() => import("./giphy/components/InstallAppButton")),
};

View File

@ -8,6 +8,7 @@ import * as googlevideo from "./googlevideo";
import * as hubspotothercalendar from "./hubspotothercalendar";
import * as huddle01video from "./huddle01video";
import * as jitsivideo from "./jitsivideo";
import * as metamask from "./metamask";
import * as office365calendar from "./office365calendar";
import * as office365video from "./office365video";
import * as slackmessaging from "./slackmessaging";
@ -33,6 +34,7 @@ const appStore = {
tandemvideo,
zoomvideo,
wipemycalother,
metamask,
giphy,
};

View File

@ -7,6 +7,7 @@ import { metadata as googlevideo } from "./googlevideo/_metadata";
import { metadata as hubspotothercalendar } from "./hubspotothercalendar/_metadata";
import { metadata as huddle01video } from "./huddle01video/_metadata";
import { metadata as jitsivideo } from "./jitsivideo/_metadata";
import { metadata as metamask } from "./metamask/_metadata";
import { metadata as office365calendar } from "./office365calendar/_metadata";
import { metadata as office365video } from "./office365video/_metadata";
import { metadata as slackmessaging } from "./slackmessaging/_metadata";
@ -31,6 +32,7 @@ export const appStoreMetadata = {
tandemvideo,
zoomvideo,
wipemycalother,
metamask,
giphy,
};

View File

@ -0,0 +1,17 @@
---
items:
- /api/app-store/metamask/example1.png
- /api/app-store/metamask/example2.png
---
<Slider items={items} />
Only book and allow bookings from people who share the same tokens, DAOs, or NFTs.
Send a group scheduling link that only members of a DAO or token-based community can join.
Share your booking link and be sure to only receive bookings from owners of your chosen tokens or coins.
Provide office hours for other DAO members or outsiders who want to learn more from you.
[Click here to learn more](https://cal.com/web3)

View File

@ -0,0 +1,26 @@
import type { App } from "@calcom/types/App";
import _package from "./package.json";
export const metadata = {
name: "MetaMask",
description: _package.description,
installed: true,
category: "web3",
// If using static next public folder, can then be referenced from the base URL (/).
imageSrc: "/api/app-store/metamask/icon.svg",
logo: "/api/app-store/metamask/icon.svg",
publisher: "Cal.com",
rating: 5,
reviews: 69,
slug: "metamask",
title: "Meta Mask",
trending: true,
type: "metamask_web3",
url: "https://cal.com/",
variant: "other",
verified: true,
email: "help@cal.com",
} as App;
export default metadata;

View File

@ -0,0 +1,39 @@
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@calcom/prisma";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!req.session?.user?.id) {
return res.status(401).json({ message: "You must be logged in to do this" });
}
const appType = "metamask_web3";
try {
const alreadyInstalled = await prisma.credential.findFirst({
where: {
type: appType,
userId: req.session.user.id,
},
});
if (alreadyInstalled) {
throw new Error("Already installed");
}
const installation = await prisma.credential.create({
data: {
type: appType,
key: { isWeb3Active: true },
userId: req.session.user.id,
appId: "metamask",
},
});
if (!installation) {
throw new Error("Unable to create user credential for metamask");
}
} catch (error: unknown) {
if (error instanceof Error) {
return res.status(500).json({ message: error.message });
}
return res.status(500);
}
return res.redirect("/apps/installed");
}

View File

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

View File

@ -0,0 +1,18 @@
import useAddAppMutation from "../../_utils/useAddAppMutation";
import { InstallAppButtonProps } from "../../types";
export default function InstallAppButton(props: InstallAppButtonProps) {
// @ts-ignore TODO: deprecate App types in favor of DB slugs
const mutation = useAddAppMutation("metamask");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

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

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/metamask",
"version": "0.0.0",
"main": "./index.ts",
"description": "Only book and allow bookings from people who share the same tokens, DAOs, or NFTs.",
"dependencies": {
"@calcom/prisma": "*"
},
"devDependencies": {
"@calcom/types": "*"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 318.6 318.6"
style="enable-background:new 0 0 318.6 318.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#E2761B;stroke:#E2761B;stroke-linecap:round;stroke-linejoin:round;}
.st1{fill:#E4761B;stroke:#E4761B;stroke-linecap:round;stroke-linejoin:round;}
.st2{fill:#D7C1B3;stroke:#D7C1B3;stroke-linecap:round;stroke-linejoin:round;}
.st3{fill:#233447;stroke:#233447;stroke-linecap:round;stroke-linejoin:round;}
.st4{fill:#CD6116;stroke:#CD6116;stroke-linecap:round;stroke-linejoin:round;}
.st5{fill:#E4751F;stroke:#E4751F;stroke-linecap:round;stroke-linejoin:round;}
.st6{fill:#F6851B;stroke:#F6851B;stroke-linecap:round;stroke-linejoin:round;}
.st7{fill:#C0AD9E;stroke:#C0AD9E;stroke-linecap:round;stroke-linejoin:round;}
.st8{fill:#161616;stroke:#161616;stroke-linecap:round;stroke-linejoin:round;}
.st9{fill:#763D16;stroke:#763D16;stroke-linecap:round;stroke-linejoin:round;}
</style>
<polygon class="st0" points="274.1,35.5 174.6,109.4 193,65.8 "/>
<g>
<polygon class="st1" points="44.4,35.5 143.1,110.1 125.6,65.8 "/>
<polygon class="st1" points="238.3,206.8 211.8,247.4 268.5,263 284.8,207.7 "/>
<polygon class="st1" points="33.9,207.7 50.1,263 106.8,247.4 80.3,206.8 "/>
<polygon class="st1" points="103.6,138.2 87.8,162.1 144.1,164.6 142.1,104.1 "/>
<polygon class="st1" points="214.9,138.2 175.9,103.4 174.6,164.6 230.8,162.1 "/>
<polygon class="st1" points="106.8,247.4 140.6,230.9 111.4,208.1 "/>
<polygon class="st1" points="177.9,230.9 211.8,247.4 207.1,208.1 "/>
</g>
<g>
<polygon class="st2" points="211.8,247.4 177.9,230.9 180.6,253 180.3,262.3 "/>
<polygon class="st2" points="106.8,247.4 138.3,262.3 138.1,253 140.6,230.9 "/>
</g>
<polygon class="st3" points="138.8,193.5 110.6,185.2 130.5,176.1 "/>
<polygon class="st3" points="179.7,193.5 188,176.1 208,185.2 "/>
<g>
<polygon class="st4" points="106.8,247.4 111.6,206.8 80.3,207.7 "/>
<polygon class="st4" points="207,206.8 211.8,247.4 238.3,207.7 "/>
<polygon class="st4" points="230.8,162.1 174.6,164.6 179.8,193.5 188.1,176.1 208.1,185.2 "/>
<polygon class="st4" points="110.6,185.2 130.6,176.1 138.8,193.5 144.1,164.6 87.8,162.1 "/>
</g>
<g>
<polygon class="st5" points="87.8,162.1 111.4,208.1 110.6,185.2 "/>
<polygon class="st5" points="208.1,185.2 207.1,208.1 230.8,162.1 "/>
<polygon class="st5" points="144.1,164.6 138.8,193.5 145.4,227.6 146.9,182.7 "/>
<polygon class="st5" points="174.6,164.6 171.9,182.6 173.1,227.6 179.8,193.5 "/>
</g>
<polygon class="st6" points="179.8,193.5 173.1,227.6 177.9,230.9 207.1,208.1 208.1,185.2 "/>
<polygon class="st6" points="110.6,185.2 111.4,208.1 140.6,230.9 145.4,227.6 138.8,193.5 "/>
<polygon class="st7" points="180.3,262.3 180.6,253 178.1,250.8 140.4,250.8 138.1,253 138.3,262.3 106.8,247.4 117.8,256.4
140.1,271.9 178.4,271.9 200.8,256.4 211.8,247.4 "/>
<polygon class="st8" points="177.9,230.9 173.1,227.6 145.4,227.6 140.6,230.9 138.1,253 140.4,250.8 178.1,250.8 180.6,253 "/>
<g>
<polygon class="st9" points="278.3,114.2 286.8,73.4 274.1,35.5 177.9,106.9 214.9,138.2 267.2,153.5 278.8,140 273.8,136.4
281.8,129.1 275.6,124.3 283.6,118.2 "/>
<polygon class="st9" points="31.8,73.4 40.3,114.2 34.9,118.2 42.9,124.3 36.8,129.1 44.8,136.4 39.8,140 51.3,153.5 103.6,138.2
140.6,106.9 44.4,35.5 "/>
</g>
<polygon class="st6" points="267.2,153.5 214.9,138.2 230.8,162.1 207.1,208.1 238.3,207.7 284.8,207.7 "/>
<polygon class="st6" points="103.6,138.2 51.3,153.5 33.9,207.7 80.3,207.7 111.4,208.1 87.8,162.1 "/>
<polygon class="st6" points="174.6,164.6 177.9,106.9 193.1,65.8 125.6,65.8 140.6,106.9 144.1,164.6 145.3,182.8 145.4,227.6
173.1,227.6 173.3,182.8 "/>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,3 @@
-- Connects each saved Credential to their respective App
UPDATE "Credential" SET "appId" = 'metamask' WHERE "type" = 'metamask_web3';
UPDATE "Credential" SET "appId" = 'giphy' WHERE "type" = 'giphy_other';

View File

@ -77,6 +77,7 @@ async function main() {
}
// Web3 apps
await createApp("huddle01", "huddle01video", ["web3", "video"]);
await createApp("metamask", "metamask", ["web3"]);
// Messaging apps
if (process.env.SLACK_CLIENT_ID && process.env.SLACK_CLIENT_SECRET && process.env.SLACK_SIGNING_SECRET) {
await createApp("slack", "slackmessaging", ["messaging"], {