diff --git a/apps/web/components/App.tsx b/apps/web/components/App.tsx new file mode 100644 index 0000000000..fbf678d0e8 --- /dev/null +++ b/apps/web/components/App.tsx @@ -0,0 +1,231 @@ +import { + BookOpenIcon, + DocumentTextIcon, + ExternalLinkIcon, + FlagIcon, + MailIcon, + ShieldCheckIcon, +} from "@heroicons/react/outline"; +import { ChevronLeftIcon } from "@heroicons/react/solid"; +import Link from "next/link"; +import React from "react"; + +import { InstallAppButton } from "@calcom/app-store/components"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { App as AppType } from "@calcom/types/App"; +import { Button } from "@calcom/ui"; + +//import NavTabs from "@components/NavTabs"; +import Shell from "@components/Shell"; +import Badge from "@components/ui/Badge"; + +export default function App({ + name, + type, + logo, + body, + categories, + author, + price = 0, + commission, + isGlobal = false, + feeType, + docs, + website, + email, + tos, + privacy, +}: { + name: string; + type: AppType["type"]; + isGlobal?: AppType["isGlobal"]; + logo: string; + body: React.ReactNode; + categories: string[]; + author: string; + pro?: boolean; + price?: number; + commission?: number; + feeType?: AppType["feeType"]; + docs?: string; + website?: string; + email: string; // required + tos?: string; + privacy?: string; +}) { + const { t } = useLocale(); + + /*const tabs = [ + { + name: t("description"), + href: "?description", + }, + { + name: t("features"), + href: "?features", + }, + { + name: t("permissions"), + href: "?permissions", + }, + ];*/ + + const priceInDollar = Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + useGrouping: false, + }).format(price); + + return ( + <> + +
+
+ + + {t("browse_apps")} + + +
+
+ +
+

{name}

+

+ {categories[0]} • {t("published_by", { author })} +

+
+
+ +
+ {isGlobal ? ( + + ) : ( + } + /> + )} + {price !== 0 && ( + + {feeType === "usage-based" + ? commission + "% + " + priceInDollar + "/booking" + : priceInDollar} + {feeType === "monthly" && "/" + t("month")} + + )} +
+
+ {/* reintroduce once we show permissions and features + */} +
+ +
+
{body}
+
+

{t("categories")}

+
+ {categories.map((category) => ( + + + {category} + + + ))} +
+

{t("pricing")}

+ + {price === 0 ? ( + "Free" + ) : ( + <> + {Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + useGrouping: false, + }).format(price)} + {feeType === "monthly" && "/" + t("month")} + + )} + +

{t("learn_more")}

+ +
+ + Every app published on the Cal.com App Store is open source and thoroughly tested via peer + reviews. Nevertheless, Cal.com, Inc. does not endorse or certify these apps unless they are + published by Cal.com. If you encounter inappropriate content or behaviour please report it. + + + Report App + +
+
+
+
+ + ); +} diff --git a/apps/web/components/AppsShell.tsx b/apps/web/components/AppsShell.tsx new file mode 100644 index 0000000000..16817268e4 --- /dev/null +++ b/apps/web/components/AppsShell.tsx @@ -0,0 +1,30 @@ +import { useSession } from "next-auth/react"; +import React from "react"; + +import { useLocale } from "@lib/hooks/useLocale"; + +import NavTabs from "./NavTabs"; + +export default function AppsShell({ children }: { children: React.ReactNode }) { + const { t } = useLocale(); + const { status } = useSession(); + const tabs = [ + { + name: t("app_store"), + href: "/apps", + }, + { + name: t("installed_apps"), + href: "/apps/installed", + }, + ]; + + return ( + <> +
+ {status === "authenticated" && } +
+
{children}
+ + ); +} diff --git a/apps/web/components/DestinationCalendarSelector.tsx b/apps/web/components/DestinationCalendarSelector.tsx index f2462dd9d0..7d829bdbbb 100644 --- a/apps/web/components/DestinationCalendarSelector.tsx +++ b/apps/web/components/DestinationCalendarSelector.tsx @@ -67,7 +67,7 @@ const DestinationCalendarSelector = ({ placeholder={!hidePlaceholder ? `${t("select_destination_calendar")}:` : undefined} options={options} isSearchable={false} - className="focus:border-primary-500 focus:ring-primary-500 mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-md border-gray-300 sm:text-sm" + className="focus:ring-primary-500 focus:border-primary-500 mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-md border-gray-300 sm:text-sm" onChange={(option) => { setSelectedOption(option); if (!option) { diff --git a/apps/web/components/Shell.tsx b/apps/web/components/Shell.tsx index 130ebbd814..4b67070f5e 100644 --- a/apps/web/components/Shell.tsx +++ b/apps/web/components/Shell.tsx @@ -1,20 +1,20 @@ import { SelectorIcon } from "@heroicons/react/outline"; import { CalendarIcon, - ArrowLeftIcon, ClockIcon, CogIcon, ExternalLinkIcon, LinkIcon, LogoutIcon, - PuzzleIcon, + ViewGridIcon, MoonIcon, MapIcon, + ArrowLeftIcon, } from "@heroicons/react/solid"; import { signOut, useSession } from "next-auth/react"; import Link from "next/link"; import { useRouter } from "next/router"; -import React, { ReactNode, useEffect, useState } from "react"; +import React, { Fragment, ReactNode, useEffect, useState } from "react"; import { Toaster } from "react-hot-toast"; import Button from "@calcom/ui/Button"; @@ -59,6 +59,10 @@ function useRedirectToLoginIfUnauthenticated() { const router = useRouter(); useEffect(() => { + if (router.pathname.startsWith("/apps")) { + return; + } + if (!loading && !session) { router.replace({ pathname: "/auth/login", @@ -121,10 +125,11 @@ export function ShellSubHeading(props: { export default function Shell(props: { centered?: boolean; title?: string; - heading: ReactNode; + heading?: ReactNode; subtitle?: ReactNode; children: ReactNode; CTA?: ReactNode; + large?: boolean; HeadingLeftIcon?: ReactNode; backPath?: string; // renders back button to specified path // use when content needs to expand with flex @@ -157,10 +162,22 @@ export default function Shell(props: { current: router.asPath.startsWith("/availability"), }, { - name: t("integrations"), - href: "/integrations", - icon: PuzzleIcon, - current: router.asPath.startsWith("/integrations"), + name: t("apps"), + href: "/apps", + icon: ViewGridIcon, + current: router.asPath.startsWith("/apps"), + child: [ + { + name: t("app_store"), + href: "/apps", + current: router.asPath === "/apps", + }, + { + name: t("installed_apps"), + href: "/apps/installed", + current: router.asPath === "/apps/installed", + }, + ], }, { name: t("settings"), @@ -182,6 +199,7 @@ export default function Shell(props: { const user = query.data; const i18n = useViewerI18n(); + const { status } = useSession(); if (i18n.status === "loading" || isRedirectingToOnboarding || loading) { // show spinner whilst i18n is loading to avoid language flicker @@ -206,95 +224,121 @@ export default function Shell(props: { -
-
-
-
-
- - - - - - {/* logo icon for tablet */} - - - - - - +
+ {status === "authenticated" && ( +
+
+
+
+ + + + + + {/* logo icon for tablet */} + + + + + + +
+ +
+ + + + + + +
+ + © {new Date().getFullYear()} Cal.com, Inc. v.{pkg.version + "-"} + {process.env.NEXT_PUBLIC_APP_URL === "https://cal.com" ? "h" : "sh"} + -{user && user.plan} +
- -
- - - - - - -
- - © {new Date().getFullYear()} Cal.com, Inc. v.{pkg.version + "-"} - {process.env.NEXT_PUBLIC_APP_URL === "https://cal.com" ? "h" : "sh"} - -{user && user.plan} -
-
+ )}
{/* show top navigation for md and smaller (tablet and phones) */} - + {status === "authenticated" && ( + + )}
{!!props.backPath && (
@@ -306,14 +350,22 @@ export default function Shell(props: {
)} -
- {props.HeadingLeftIcon &&
{props.HeadingLeftIcon}
} -
-

{props.heading}

-

{props.subtitle}

+ {props.heading && props.subtitle && ( +
+ {props.HeadingLeftIcon &&
{props.HeadingLeftIcon}
} +
+

+ {props.heading} +

+

{props.subtitle}

+
+ {props.CTA &&
{props.CTA}
}
-
{props.CTA}
-
+ )}
{/* show bottom navigation for md and smaller (tablet and phones) */} - + aria-current={item.current ? "page" : undefined}> +