chore: [app-router-migration-4]: apps/categories page (#12619)
This commit is contained in:
parent
7a67331d96
commit
add6ffdfc4
|
@ -297,3 +297,7 @@ APP_ROUTER_EVENT_TYPES_ENABLED=1
|
|||
APP_ROUTER_SETTINGS_ADMIN_ENABLED=1
|
||||
APP_ROUTER_APPS_SLUG_ENABLED=1
|
||||
APP_ROUTER_APPS_SLUG_SETUP_ENABLED=1
|
||||
# whether we redirect to the future/apps/categories from /apps/categories or not
|
||||
APP_ROUTER_APPS_CATEGORIES_ENABLED=1
|
||||
# whether we redirect to the future/apps/categories/[category] from /apps/categories/[category] or not
|
||||
APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED=1
|
||||
|
|
|
@ -6,8 +6,10 @@ import z from "zod";
|
|||
const ROUTES: [URLPattern, boolean][] = [
|
||||
["/event-types", process.env.APP_ROUTER_EVENT_TYPES_ENABLED === "1"] as const,
|
||||
["/settings/admin/:path*", process.env.APP_ROUTER_SETTINGS_ADMIN_ENABLED === "1"] as const,
|
||||
["/apps/:slug", Boolean(process.env.APP_ROUTER_APPS_SLUG_ENABLED)] as const,
|
||||
["/apps/:slug/setup", Boolean(process.env.APP_ROUTER_APPS_SLUG_SETUP_ENABLED)] as const,
|
||||
["/apps/:slug", process.env.APP_ROUTER_APPS_SLUG_ENABLED === "1"] as const,
|
||||
["/apps/:slug/setup", process.env.APP_ROUTER_APPS_SLUG_SETUP_ENABLED === "1"] as const,
|
||||
["/apps/categories", process.env.APP_ROUTER_APPS_CATEGORIES_ENABLED === "1"] as const,
|
||||
["/apps/categories/:category", process.env.APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED === "1"] as const,
|
||||
].map(([pathname, enabled]) => [
|
||||
new URLPattern({
|
||||
pathname,
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
import CategoryPage from "@pages/apps/categories/[category]";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { notFound } from "next/navigation";
|
||||
import z from "zod";
|
||||
|
||||
import { getAppRegistry } from "@calcom/app-store/_appRegistry";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { AppCategories } from "@calcom/prisma/enums";
|
||||
|
||||
import PageWrapper from "@components/PageWrapperAppDir";
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
return await _generateMetadata(
|
||||
() => `${APP_NAME} | ${APP_NAME}`,
|
||||
() => ""
|
||||
);
|
||||
};
|
||||
|
||||
export const generateStaticParams = async () => {
|
||||
const paths = Object.keys(AppCategories);
|
||||
|
||||
try {
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Prisma.PrismaClientInitializationError) {
|
||||
// Database is not available at build time. Make sure we fall back to building these pages on demand
|
||||
return [];
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return paths.map((category) => ({ category }));
|
||||
};
|
||||
|
||||
const querySchema = z.object({
|
||||
category: z.nativeEnum(AppCategories),
|
||||
});
|
||||
|
||||
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
|
||||
const p = querySchema.safeParse(params);
|
||||
|
||||
if (!p.success) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const appQuery = await prisma.app.findMany({
|
||||
where: {
|
||||
categories: {
|
||||
has: p.data.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 {
|
||||
apps,
|
||||
};
|
||||
};
|
||||
|
||||
export default async function Page({ params }: { params: Record<string, string | string[]> }) {
|
||||
const { apps } = await getPageProps({ params });
|
||||
return (
|
||||
<PageWrapper getLayout={null} requiresLicense={false} nonce={undefined} themeBasis={null}>
|
||||
<CategoryPage apps={apps} />
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export const dynamic = "force-static";
|
|
@ -0,0 +1,56 @@
|
|||
import LegacyPage from "@pages/apps/categories/index";
|
||||
import { ssrInit } from "app/_trpc/ssrInit";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import PageWrapper from "@components/PageWrapperAppDir";
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
return await _generateMetadata(
|
||||
() => `Categories | ${APP_NAME}`,
|
||||
() => ""
|
||||
);
|
||||
};
|
||||
|
||||
async function getPageProps() {
|
||||
const ssr = await ssrInit();
|
||||
const req = { headers: headers(), cookies: cookies() };
|
||||
|
||||
// @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest | IncomingMessage
|
||||
const session = await getServerSession({ req });
|
||||
|
||||
let appStore;
|
||||
if (session?.user?.id) {
|
||||
appStore = await getAppRegistryWithCredentials(session.user.id);
|
||||
} else {
|
||||
appStore = await getAppRegistry();
|
||||
}
|
||||
|
||||
const categories = appStore.reduce((c, app) => {
|
||||
for (const category of app.categories) {
|
||||
c[category] = c[category] ? c[category] + 1 : 1;
|
||||
}
|
||||
return c;
|
||||
}, {} as Record<string, number>);
|
||||
|
||||
return {
|
||||
categories: Object.entries(categories).map(([name, count]) => ({ name, count })),
|
||||
dehydratedState: await ssr.dehydrate(),
|
||||
};
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const props = await getPageProps();
|
||||
const h = headers();
|
||||
const nonce = h.get("x-nonce") ?? undefined;
|
||||
|
||||
return (
|
||||
<PageWrapper getLayout={null} requiresLicense={false} nonce={nonce} themeBasis={null} {...props}>
|
||||
<LegacyPage {...props} />
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
import { dir } from "i18next";
|
||||
import { Inter } from "next/font/google";
|
||||
import localFont from "next/font/local";
|
||||
import { headers, cookies } from "next/headers";
|
||||
import Script from "next/script";
|
||||
import React from "react";
|
||||
|
@ -10,6 +12,14 @@ import { prepareRootMetadata } from "@lib/metadata";
|
|||
|
||||
import "../styles/globals.css";
|
||||
|
||||
const interFont = Inter({ subsets: ["latin"], variable: "--font-inter", preload: true, display: "swap" });
|
||||
const calFont = localFont({
|
||||
src: "../fonts/CalSans-SemiBold.woff2",
|
||||
variable: "--font-cal",
|
||||
preload: true,
|
||||
display: "block",
|
||||
});
|
||||
|
||||
export const generateMetadata = () =>
|
||||
prepareRootMetadata({
|
||||
twitterCreator: "@calcom",
|
||||
|
@ -66,6 +76,12 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|||
src="https://snippet.meticulous.ai/v1/stagingMeticulousSnippet.js"
|
||||
/>
|
||||
)}
|
||||
<style>{`
|
||||
:root {
|
||||
--font-inter: ${interFont.style.fontFamily.replace(/\'/g, "")};
|
||||
--font-cal: ${calFont.style.fontFamily.replace(/\'/g, "")};
|
||||
}
|
||||
`}</style>
|
||||
</head>
|
||||
<body
|
||||
className="dark:bg-darkgray-50 desktop-transparent bg-subtle antialiased"
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
import { type DehydratedState } from "@tanstack/react-query";
|
||||
import type { SSRConfig } from "next-i18next";
|
||||
import { Inter } from "next/font/google";
|
||||
import localFont from "next/font/local";
|
||||
// import I18nLanguageHandler from "@components/I18nLanguageHandler";
|
||||
import { usePathname } from "next/navigation";
|
||||
import Script from "next/script";
|
||||
|
@ -20,14 +18,6 @@ export interface CalPageWrapper {
|
|||
PageWrapper?: AppProps["Component"]["PageWrapper"];
|
||||
}
|
||||
|
||||
const interFont = Inter({ subsets: ["latin"], variable: "--font-inter", preload: true, display: "swap" });
|
||||
const calFont = localFont({
|
||||
src: "../fonts/CalSans-SemiBold.woff2",
|
||||
variable: "--font-cal",
|
||||
preload: true,
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
export type PageWrapperProps = Readonly<{
|
||||
getLayout: ((page: React.ReactElement) => ReactNode) | null;
|
||||
children: React.ReactElement;
|
||||
|
@ -71,13 +61,6 @@ function PageWrapper(props: PageWrapperProps) {
|
|||
id="page-status"
|
||||
dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}
|
||||
/>
|
||||
<style jsx global>{`
|
||||
:root {
|
||||
--font-inter: ${interFont.style.fontFamily};
|
||||
--font-cal: ${calFont.style.fontFamily};
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{getLayout(
|
||||
props.requiresLicense ? <LicenseRequired>{props.children}</LicenseRequired> : props.children
|
||||
)}
|
||||
|
|
|
@ -97,13 +97,9 @@ const CustomI18nextProvider = (props: { children: React.ReactElement; i18n?: SSR
|
|||
const clientViewerI18n = useViewerI18n(locale);
|
||||
const i18n = clientViewerI18n.data?.i18n ?? props.i18n;
|
||||
|
||||
if (!i18n || !i18n._nextI18Next) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
// @ts-expect-error AppWithTranslationHoc expects AppProps
|
||||
<AppWithTranslationHoc pageProps={{ _nextI18Next: i18n._nextI18Next }}>
|
||||
<AppWithTranslationHoc pageProps={{ _nextI18Next: i18n?._nextI18Next }}>
|
||||
{props.children}
|
||||
</AppWithTranslationHoc>
|
||||
);
|
||||
|
|
|
@ -103,12 +103,14 @@ export const config = {
|
|||
"/future/event-types/",
|
||||
"/settings/admin/:path*",
|
||||
"/future/settings/admin/:path*",
|
||||
|
||||
"/apps/:slug/",
|
||||
"/future/apps/:slug/",
|
||||
|
||||
"/apps/:slug/setup/",
|
||||
"/future/apps/:slug/setup/",
|
||||
"/apps/categories/",
|
||||
"/future/apps/categories/",
|
||||
"/apps/categories/:category/",
|
||||
"/future/apps/categories/:category/",
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { Prisma } from "@prisma/client";
|
||||
import type { GetStaticPropsContext, InferGetStaticPropsType } from "next";
|
||||
import Link from "next/link";
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
|
||||
|
@ -13,7 +15,7 @@ import PageWrapper from "@components/PageWrapper";
|
|||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
export default function Apps({ categories }: inferSSRProps<typeof getServerSideProps>) {
|
||||
export default function Apps({ categories }: Omit<inferSSRProps<typeof getServerSideProps>, "trpcState">) {
|
||||
const { t, isLocaleReady } = useLocale();
|
||||
|
||||
return (
|
||||
|
|
|
@ -58,4 +58,58 @@ test.describe("apps/ A/B tests", () => {
|
|||
|
||||
await expect(locator).toBeVisible();
|
||||
});
|
||||
|
||||
test("should point to the /future/apps/categories", async ({ page, users, context }) => {
|
||||
await context.addCookies([
|
||||
{
|
||||
name: "x-calcom-future-routes-override",
|
||||
value: "1",
|
||||
url: "http://localhost:3000",
|
||||
},
|
||||
]);
|
||||
const user = await users.create();
|
||||
|
||||
await user.apiLogin();
|
||||
|
||||
await page.goto("/apps/categories");
|
||||
|
||||
await page.waitForLoadState();
|
||||
|
||||
const dataNextJsRouter = await page.evaluate(() =>
|
||||
window.document.documentElement.getAttribute("data-nextjs-router")
|
||||
);
|
||||
|
||||
expect(dataNextJsRouter).toEqual("app");
|
||||
|
||||
const locator = page.getByTestId("app-store-category-messaging");
|
||||
|
||||
await expect(locator).toBeVisible();
|
||||
});
|
||||
|
||||
test("should point to the /future/apps/categories/[category]", async ({ page, users, context }) => {
|
||||
await context.addCookies([
|
||||
{
|
||||
name: "x-calcom-future-routes-override",
|
||||
value: "1",
|
||||
url: "http://localhost:3000",
|
||||
},
|
||||
]);
|
||||
const user = await users.create();
|
||||
|
||||
await user.apiLogin();
|
||||
|
||||
await page.goto("/apps/categories/messaging");
|
||||
|
||||
await page.waitForLoadState();
|
||||
|
||||
const dataNextJsRouter = await page.evaluate(() =>
|
||||
window.document.documentElement.getAttribute("data-nextjs-router")
|
||||
);
|
||||
|
||||
expect(dataNextJsRouter).toEqual("app");
|
||||
|
||||
const locator = page.getByText(/messaging apps/i);
|
||||
|
||||
await expect(locator).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -198,6 +198,8 @@
|
|||
"ALLOWED_HOSTNAMES",
|
||||
"ANALYZE",
|
||||
"API_KEY_PREFIX",
|
||||
"APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED",
|
||||
"APP_ROUTER_APPS_CATEGORIES_ENABLED",
|
||||
"APP_ROUTER_APPS_SLUG_ENABLED",
|
||||
"APP_ROUTER_APPS_SLUG_SETUP_ENABLED",
|
||||
"APP_ROUTER_EVENT_TYPES_ENABLED",
|
||||
|
|
Loading…
Reference in New Issue
Block a user