Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: v2 app store category/categories #4126

Merged
merged 8 commits into from
Sep 5, 2022
Merged
1 change: 1 addition & 0 deletions apps/web/components/apps/TrendingAppsSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ const TrendingAppsSlider = <T extends App>({ items }: { items: T[] }) => {
);
};

/** @deprecated use v2 instead, located at packages/ui/v2/core/apps */
export default TrendingAppsSlider;
6 changes: 2 additions & 4 deletions apps/web/components/v2/apps/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,7 @@ const Component = ({
isGlobal || (installedAppCount > 0 && allowedMultipleInstalls) ? (
<div className="flex space-x-3">
<Button StartIcon={Icon.FiCheck} color="secondary" disabled>
{installedAppCount > 0
? t("active_install", { count: installedAppCount })
: t("globally_install")}
{installedAppCount > 0 ? t("active_install", { count: installedAppCount }) : t("default")}
</Button>
{!isGlobal && (
<InstallAppButton
Expand All @@ -158,7 +156,7 @@ const Component = ({
color="primary"
size="base"
data-testid="install-app-button">
{t("add_another")}
{t("install_another")}
</Button>
);
}}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/pages/v2/apps/[slug]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
isGlobal={data.isGlobal}
type={data.type}
logo={data.logo}
categories={[data.category]}
categories={data.categories ?? [data.category]}
author={data.publisher}
feeType={data.feeType || "usage-based"}
price={data.price || 0}
Expand Down
86 changes: 86 additions & 0 deletions apps/web/pages/v2/apps/categories/[category].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { AppCategories } from "@prisma/client";
import { GetStaticPropsContext, InferGetStaticPropsType } from "next";
import Link from "next/link";
import { useRouter } from "next/router";

import { getAppRegistry } from "@calcom/app-store/_appRegistry";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { Icon } from "@calcom/ui/Icon";
import Shell from "@calcom/ui/v2/core/Shell";
import AppCard from "@calcom/ui/v2/core/apps/AppCard";

export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticProps>) {
const { t } = useLocale();
const router = useRouter();
const { category } = router.query;

return (
<>
<Shell isPublic large>
<div className="text-md flex items-center gap-1 px-4 pb-3 pt-3 font-normal md:px-8 lg:px-0 lg:pt-0">
<Link href="/apps">
<a className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900">
<Icon.FiArrowLeft className="h-4 w-4" />
{t("app_store")}{" "}
</a>
</Link>
{category && (
<span className="flex gap-1 text-gray-600">
<span>&nbsp;/&nbsp;</span>
{t("category_apps", { category: category[0].toUpperCase() + category?.slice(1) })}
</span>
)}
</div>
<div className="mb-16">
<div className="grid-col-1 grid grid-cols-1 gap-3 md:grid-cols-3">
{apps.map((app) => {
return <AppCard key={app.slug} app={app} />;
})}
</div>
</div>
</Shell>
</>
);
}

export const getStaticPaths = async () => {
const appStore = await getAppRegistry();
const paths = appStore.reduce((categories, app) => {
if (!categories.includes(app.category)) {
categories.push(app.category);
}
return categories;
}, [] as string[]);

return {
paths: paths.map((category) => ({ params: { category } })),
fallback: false,
};
};

export const getStaticProps = async (context: GetStaticPropsContext) => {
const category = context.params?.category as AppCategories;

const appQuery = await prisma.app.findMany({
where: {
categories: {
has: category,
},
},
select: {
slug: true,
},
});

const dbAppsSlugs = appQuery.map((category) => category.slug);

const appStore = await getAppRegistry();

const apps = appStore.filter((app) => dbAppsSlugs.includes(app.slug));
return {
props: {
apps,
},
};
};
57 changes: 57 additions & 0 deletions apps/web/pages/v2/apps/categories/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { InferGetStaticPropsType } from "next";
import Link from "next/link";

import { getAppRegistry } from "@calcom/app-store/_appRegistry";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui/Icon";
import Shell from "@calcom/ui/Shell";

export default function Apps({ categories }: InferGetStaticPropsType<typeof getStaticProps>) {
const { t } = useLocale();

return (
<Shell isPublic large>
<div className="text-md flex items-center gap-1 px-4 pb-3 pt-3 font-normal md:px-8 lg:px-0 lg:pt-0">
<Link href="/apps">
<a className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900">
<Icon.FiArrowLeft className="h-4 w-4" />
{t("app_store")}{" "}
</a>
</Link>
</div>
<div className="mb-16">
<div className="grid h-auto w-full grid-cols-5 gap-3">
{categories.map((category) => (
<Link key={category.name} href={"/apps/categories/" + category.name}>
<a
data-testid={`app-store-category-${category.name}`}
className="relative flex rounded-sm bg-gray-100 px-6 py-4 sm:block">
<div className="self-center">
<h3 className="font-medium capitalize">{category.name}</h3>
<p className="text-sm text-gray-500">
{t("number_apps", { count: category.count })}{" "}
<Icon.FiArrowRight className="inline-block h-4 w-4" />
</p>
</div>
</a>
</Link>
))}
</div>
</div>
</Shell>
);
}

export const getStaticProps = async () => {
const appStore = await getAppRegistry();
const categories = appStore.reduce((c, app) => {
c[app.category] = c[app.category] ? c[app.category] + 1 : 1;
return c;
}, {} as Record<string, number>);

return {
props: {
categories: Object.entries(categories).map(([name, count]) => ({ name, count })),
},
};
};
7 changes: 4 additions & 3 deletions apps/web/pages/v2/apps/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import prisma from "@calcom/prisma";
import type { AppCategories } from "@calcom/prisma/client";
import AllApps from "@calcom/ui/v2/core/apps/AllApps";
import AppStoreCategories from "@calcom/ui/v2/core/apps/Categories";
import TrendingAppsSlider from "@calcom/ui/v2/core/apps/TrendingAppsSlider";
import AppsLayout from "@calcom/ui/v2/core/layouts/AppsLayout";

export default function Apps({ appStore, categories }: InferGetStaticPropsType<typeof getServerSideProps>) {
const { t } = useLocale();

return (
<AppsLayout isPublic heading={t("app_store")} subtitle={t("app_store_description")}>
{/*<AppStoreCategories categories={categories} />*/}
<AppStoreCategories categories={categories} />
<TrendingAppsSlider items={appStore} />
<AllApps apps={appStore} />
</AppsLayout>
);
Expand Down Expand Up @@ -50,8 +52,7 @@ export const getServerSideProps = async (context: NextPageContext) => {
}))
.sort(function (a, b) {
return b.count - a.count;
})
.slice(0, 3),
}),
appStore,
},
};
Expand Down
16 changes: 9 additions & 7 deletions apps/web/public/static/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"view_public_page": "View public page",
"sign_out": "Sign out",
"add_another": "Add another",
"install_another": "Install another",
"until": "until",
"powered_by": "powered by",
"unavailable": "Unavailable",
Expand Down Expand Up @@ -228,6 +229,7 @@
"connect_your_calendar_instructions": "Connect your calendar to automatically check for busy times and new events as they’re scheduled.",
"set_up_later": "Set up later",
"current_time": "Current time",
"details": "Details",
"welcome": "Welcome",
"welcome_back": "Welcome back",
"welcome_to_calcom": "Welcome to Cal.com",
Expand Down Expand Up @@ -699,14 +701,15 @@
"delete_account_confirmation_message": "Are you sure you want to delete your Cal.com account? Anyone who you've shared your account link with will no longer be able to book using it and any preferences you have saved will be lost.",
"integrations": "Integrations",
"apps": "Apps",
"category_apps": "{{category}} apps",
"app_store": "App Store",
"app_store_description": "Connecting people, technology and the workplace.",
"settings": "Settings",
"event_type_moved_successfully": "Event type has been moved successfully",
"next_step": "Skip step",
"prev_step": "Prev step",
"install": "Install",
"installed": "Installed",
"details": "Details",
"active_install_one": "{{count}} active install",
"active_install_other": "{{count}} active installs",
"globally_install": "Globally installed",
Expand Down Expand Up @@ -757,10 +760,10 @@
"visit_roadmap": "Roadmap",
"featured_categories": "Featured Categories",
"popular_categories": "Popular Categories",
"number_apps_one": "{{count}} app",
"number_apps_other": "{{count}} apps",
"number_apps_one": "{{count}} App",
"number_apps_other": "{{count}} Apps",
"trending_apps": "Trending Apps",
"explore_apps": "Explore {{category}} apps",
"explore_apps": "{{category}} apps",
"installed_apps": "Installed Apps",
"empty_installed_apps_headline": "No apps installed",
"empty_installed_apps_description": "Apps enable you to enhance your workflow and improve your scheduling life significantly.",
Expand All @@ -781,9 +784,8 @@
"terms_of_service": "Terms of Service",
"remove": "Remove",
"add": "Add",
"added_one": "Added",
"added_other": "{{count}} added",
"added_globally": "Added globally",
"installed_one": "Installed",
"installed_other": "{{count}} installed",
"verify_wallet": "Verify Wallet",
"connect_metamask": "Connect Metamask",
"create_events_on": "Create events on:",
Expand Down
6 changes: 5 additions & 1 deletion packages/ui/v2/core/apps/AllApps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ export default function AllApps({ apps }: AllAppsPropsType) {
<div className="mb-16">
<div className="mb-4 flex flex-col justify-between lg:flex-row lg:items-center">
<h2 className="text-lg font-semibold text-gray-900 ">
{t("explore_apps", { category: selectedCategory || t("all_apps").toLowerCase() })}
{t("explore_apps", {
category:
(selectedCategory && selectedCategory[0].toUpperCase() + selectedCategory.slice(1)) ||
t("all_apps"),
})}
</h2>
{leftVisible && (
<div className="absolute left-1/2 flex">
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/v2/core/apps/AppCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export default function AppCard({ app, credentials }: AppCardProps) {
}
return (
<Button color="secondary" StartIcon={Icon.FiPlus} {...props}>
{t("add")}
{t("install")}
</Button>
);
}}
Expand All @@ -98,7 +98,7 @@ export default function AppCard({ app, credentials }: AppCardProps) {
color="secondary"
data-testid="install-app-button"
{...props}>
{t("add")}
{t("install")}
</Button>
);
}}
Expand All @@ -108,7 +108,7 @@ export default function AppCard({ app, credentials }: AppCardProps) {
<div className="max-w-44 absolute right-0 mr-4 flex flex-wrap justify-end gap-1">
{appAdded > 0 && (
<span className="rounded-md bg-green-100 px-2 py-1 text-sm font-normal text-green-800">
{t("added", { count: appAdded })}
{t("installed", { count: appAdded })}
</span>
)}
{app.isGlobal && (
Expand Down
47 changes: 24 additions & 23 deletions packages/ui/v2/core/apps/Categories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,49 @@ import Link from "next/link";
import { ArrowRight } from "react-feather";

import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { AppCategories } from "@calcom/prisma/client";

// TODO: See if it is worth it to move this to the database or not
const categoriesDescription: { [key in AppCategories]: string } = {
calendar: "Ut ea id veniam commodo sit ad consequat elit ea labore dolore ex tempor nulla.",
messaging: "Ut ea id veniam commodo sit ad consequat elit ea labore dolore ex tempor nulla.",
other: "Ut ea id veniam commodo sit ad consequat elit ea labore dolore ex tempor nulla.",
payment: "Ut ea id veniam commodo sit ad consequat elit ea labore dolore ex tempor nulla.",
video: "Ut ea id veniam commodo sit ad consequat elit ea labore dolore ex tempor nulla.",
web3: "Ut ea id veniam commodo sit ad consequat elit ea labore dolore ex tempor nulla.",
};
import Slider from "./Slider";

export default function AppStoreCategories({
categories,
}: {
categories: {
name: AppCategories;
name: string;
count: number;
}[];
}) {
const { t } = useLocale();
return (
<div className="mb-16 mt-14">
<h2 className="mb-2 text-base font-semibold text-gray-900">{t("featured_categories")}</h2>
<div className="grid-col-1 grid w-full gap-3 overflow-scroll sm:grid-flow-col">
{categories.map((category) => (
<div className="mb-16">
<Slider
className="mb-16"
title={t("featured_categories")}
items={categories}
itemKey={(category) => category.name}
options={{
perView: 5,
breakpoints: {
768 /* and below */: {
perView: 2,
},
},
}}
renderItem={(category) => (
<Link key={category.name} href={"/apps/categories/" + category.name}>
<a
data-testid={`app-store-category-${category.name}`}
className="relative flex rounded-sm bg-gray-100 p-6 sm:block">
className="relative flex rounded-sm bg-gray-100 px-6 py-4 sm:block">
<div className="self-center">
<h3 className="mb-4 font-medium capitalize">{category.name}</h3>
<p className="mb-4 text-sm font-normal text-gray-600">
{categoriesDescription[category.name]}
<h3 className="font-medium capitalize">{category.name}</h3>
<p className="text-sm text-gray-500">
{t("number_apps", { count: category.count })}{" "}
<ArrowRight className="inline-block h-4 w-4" />
</p>
<p className="inline-block text-sm">{t("number_apps", { count: category.count })}</p>
<ArrowRight className="ml-[11.33px] inline-block h-4 w-4" />
</div>
</a>
</Link>
))}
</div>
)}
/>
</div>
);
}
Loading