Add AppStore General Settings support (#4998)

* added weather in calendar wrapper

* added textfield to weather app, needs GeneralSettings abstraction

* add automation cateogry for n8n and zapier

* fix border radius for seperated apps

* create app specific settings in installed apps

* Fixed design issues caused by app settings

* add first version of template cards with logos

* Add link to zapier template

* Add missing translations

* fix mobile view

* better readable app description in mobile view

* code clean up

* fix issue that lisItem was always expanded

* code clean up

* code clean up

* fix import

* fix typo

* code clean up

* Add AppStore General Settings support

* add missing alt to img

* use FiShare2 icon instead of FiZap

* Remove duplicate entries

* Add border to AppSettings consistently

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
Hariom Balhara
2022-10-15 10:05:14 +00:00
committed by GitHub
co-authored by Peer Richelsen Peer Richelsen CarinaWolli Carina Wollendorfer Alex van Andel
parent 6ee9de6dd8
commit cf423dda70
20 changed files with 141 additions and 13 deletions
@@ -141,6 +141,7 @@ function CalendarList(props: Props) {
);
}
// todo: @hariom extract this into packages/apps-store as "GeneralAppSettings"
function ConnectedCalendarsList(props: Props) {
const { t } = useLocale();
const query = trpc.useQuery(["viewer.connectedCalendars"], { suspense: true });
@@ -96,19 +96,18 @@ interface IntegrationsListProps {
data: inferQueryOutput<"viewer.integrations">;
}
const IntegrationsList = ({ data, variant }: IntegrationsListProps) => {
const separateItems = variant === "automation";
const IntegrationsList = ({ data }: IntegrationsListProps) => {
return (
<List className={classNames(separateItems ? "flex flex-col gap-6" : "")} noBorderTreatment>
<List className="flex flex-col gap-6" noBorderTreatment>
{data.items.map((item) => (
<IntegrationListItem
name={item.name}
slug={item.slug}
key={item.name}
key={item.title}
title={item.title}
logo={item.logo}
description={item.description}
separate={separateItems}
separate={true}
actions={
<div className="flex w-16 justify-end">
<ConnectOrDisconnectIntegrationButton
@@ -119,7 +118,9 @@ const IntegrationsList = ({ data, variant }: IntegrationsListProps) => {
/>
</div>
}>
{separateItems && <AppSettings slug={item.slug} />}
<div className="border-t border-gray-200">
<AppSettings slug={item.slug} />
</div>
</IntegrationListItem>
))}
</List>
+9
View File
@@ -198,6 +198,15 @@ function generateFiles() {
})
);
// TODO: Make a component map creator that accepts ComponentName and does the rest.
// TODO: dailyvideo has a slug of daily-video, so that mapping needs to be taken care of. But it is an old app, so it doesn't need AppSettings
browserOutput.push(
...getObjectExporter("AppSettingsComponentsMap", {
fileToBeImported: "components/AppSettings.tsx",
entryBuilder: (app) => ` ${app.name}: dynamic(() =>import("./${app.path}/components/AppSettings")),`,
})
);
browserOutput.push(
...getObjectExporter("EventTypeAddonMap", {
fileToBeImported: path.join("extensions", "EventTypeAppCard.tsx"),
@@ -1,11 +1,9 @@
import dynamic from "next/dynamic";
import { AppSettingsComponentsMap } from "@calcom/app-store/apps.browser.generated";
import { DynamicComponent } from "./DynamicComponent";
export const AppSettingsMap = {
zapier: dynamic(() => import("../zapier/components/AppSettings")),
};
export const AppSettings = (props: { slug: string }) => {
return <DynamicComponent<typeof AppSettingsMap> componentMap={AppSettingsMap} {...props} />;
return (
<DynamicComponent<typeof AppSettingsComponentsMap> componentMap={AppSettingsComponentsMap} {...props} />
);
};
@@ -29,6 +29,10 @@ export const InstallAppButtonMap = {
zapier: dynamic(() => import("./zapier/components/InstallAppButton")),
zoomvideo: dynamic(() => import("./zoomvideo/components/InstallAppButton")),
};
export const AppSettingsComponentsMap = {
weather_in_your_calendar: dynamic(() => import("./weather_in_your_calendar/components/AppSettings")),
zapier: dynamic(() => import("./zapier/components/AppSettings")),
};
export const EventTypeAddonMap = {
fathom: dynamic(() => import("./fathom/extensions/EventTypeAppCard")),
giphy: dynamic(() => import("./giphy/extensions/EventTypeAppCard")),
@@ -32,6 +32,7 @@ import { metadata as stripepayment_meta } from "./stripepayment/_metadata";
import { metadata as tandemvideo_meta } from "./tandemvideo/_metadata";
import { metadata as typeform_meta } from "./typeform/_metadata";
import { metadata as vital_meta } from "./vital/_metadata";
import { metadata as weather_in_your_calendar_meta } from "./weather_in_your_calendar/_metadata";
import { metadata as whereby_meta } from "./whereby/_metadata";
import { metadata as wipemycalother_meta } from "./wipemycalother/_metadata";
import { metadata as zapier_meta } from "./zapier/_metadata";
@@ -68,6 +69,7 @@ export const appStoreMetadata = {
tandemvideo: tandemvideo_meta,
typeform: typeform_meta,
vital: vital_meta,
weather_in_your_calendar: weather_in_your_calendar_meta,
whereby: whereby_meta,
wipemycalother: wipemycalother_meta,
zapier: zapier_meta,
@@ -31,6 +31,7 @@ export const apiHandlers = {
tandemvideo: import("./tandemvideo/api"),
typeform: import("./typeform/api"),
vital: import("./vital/api"),
weather_in_your_calendar: import("./weather_in_your_calendar/api"),
whereby: import("./whereby/api"),
wipemycalother: import("./wipemycalother/api"),
zapier: import("./zapier/api"),
@@ -0,0 +1,9 @@
---
items:
- /api/app-store/weather_in_your_calendar/1.jpeg
- /api/app-store/weather_in_your_calendar/2.jpeg
---
<Slider items={items} />
You can now get the weather forecast directly into your calendar. This local weather calendar uses emojis ⛅️ 🌧️ ☀️ 🌨️ to display a 16 days forecast from OpenWeatherMap. Enter your city, adjust according to your preferences and subscribe to your calendar.
@@ -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;
@@ -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;
@@ -0,0 +1 @@
export { default as add } from "./add";
@@ -0,0 +1,38 @@
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/v2/core/Button";
import { TextField } from "@calcom/ui/v2/core/form/fields";
/*
import * as Radio from "@calcom/ui/v2/core/form/radio-area/Radio";
import { RadioField } from "@calcom/ui/v2/core/form/radio-area/Radio";
*/
export default function AppSettings() {
const { t } = useLocale();
//const [units, setUnits] = useState<"metric" | "imperial">("metric");
const unit = "metric";
const [location, setLocation] = useState("");
return (
<div className="space-y-4 px-4 pt-4 pb-4 text-sm">
<TextField
placeholder="San Francisco"
value={location}
name="Enter City"
onChange={async (e) => {
setLocation(e.target.value);
}}
/>
{/* <Radio.Group onChange={(val) => alert(val)} aria-label="Set unit" defaultValue="metric">
<RadioField label="°C Celsius" id="metric" value="metric" />
<RadioField label="°F Fahrenheit" id="imperial" value="imperial" />
</Radio.Group> */}
<Button
href={`webcal://weather-in-calendar.com/cal/weather-cal.php?city=${location}&units=${unit}&temperature=day`}>
{t("add_to_calendar")}
</Button>
</div>
);
}
@@ -0,0 +1,15 @@
{
"/*": "Don't modify slug - If required, do it using cli edit command",
"name": "Weather in your Calendar",
"slug": "weather_in_your_calendar",
"type": "weather_in_your_calendar_other",
"imageSrc": "/api/app-store/weather_in_your_calendar/icon.gif",
"logo": "/api/app-store/weather_in_your_calendar/icon.gif",
"url": "https://weather-in-calendar.com",
"variant": "other",
"categories": ["other"],
"publisher": "vejnoe",
"email": "info@vejnoe.dk",
"description": "Get the local weather forecast with icons in your calendar\r\r",
"__createdUsingCli": true
}
@@ -0,0 +1,2 @@
export * as api from "./api";
export { metadata } from "./_metadata";
@@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"name": "@calcom/weather_in_your_calendar",
"version": "0.0.0",
"main": "./index.ts",
"description": "Get the local weather forecast with icons in your calendar\r\r",
"dependencies": {
"@calcom/lib": "*"
},
"devDependencies": {
"@calcom/types": "*"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 KiB

@@ -60,7 +60,7 @@ const templates: Template[] = [
export default function AppSettings() {
return (
<div className="grid gap-4 border-t border-gray-200 p-4 md:grid-cols-2">
<div className="grid gap-4 p-4 md:grid-cols-2">
{templates.map((template, index) => (
<TemplateCard key={index} template={template} />
))}
@@ -88,6 +88,12 @@
"slug": "exchange",
"type": "exchange_calendar"
},
{
"dirName": "weather_in_your_calendar",
"categories": ["other"],
"slug": "weather_in_your_calendar",
"type": "weather_in_your_calendar_other"
},
{
"dirName": "fathom",
"categories": ["analytics"],