From 0dddc2224a63877ee3b81165b591efd81b4fb683 Mon Sep 17 00:00:00 2001 From: DmytroHryshyn <125881252+DmytroHryshyn@users.noreply.github.com> Date: Thu, 4 Jan 2024 23:36:31 +0200 Subject: [PATCH] chore: [app-router-migration-9] Migrate apps/index page (#12775) Co-authored-by: zomars --- .../future/apps/installed/[category]/page.tsx | 2 +- apps/web/app/future/apps/page.tsx | 81 +++++++++++++++++++ apps/web/pages/apps/index.tsx | 4 +- apps/web/playwright/app-store.e2e.ts | 27 +++++++ apps/web/playwright/teams.e2e.ts | 13 ++- 5 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 apps/web/app/future/apps/page.tsx diff --git a/apps/web/app/future/apps/installed/[category]/page.tsx b/apps/web/app/future/apps/installed/[category]/page.tsx index 203ad830b5..44b511f0f8 100644 --- a/apps/web/app/future/apps/installed/[category]/page.tsx +++ b/apps/web/app/future/apps/installed/[category]/page.tsx @@ -30,7 +30,7 @@ const getPageProps = async ({ params }: { params: Record }) { - const { category } = await getPageProps({ params }); + await getPageProps({ params }); return ; } diff --git a/apps/web/app/future/apps/page.tsx b/apps/web/app/future/apps/page.tsx new file mode 100644 index 0000000000..80c04c5491 --- /dev/null +++ b/apps/web/app/future/apps/page.tsx @@ -0,0 +1,81 @@ +import AppsPage from "@pages/apps"; +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 { getLayout } from "@calcom/features/MainLayoutAppDir"; +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams"; +import getUserAdminTeams from "@calcom/features/ee/teams/lib/getUserAdminTeams"; +import { APP_NAME } from "@calcom/lib/constants"; +import type { AppCategories } from "@calcom/prisma/enums"; + +import PageWrapper from "@components/PageWrapperAppDir"; + +export const generateMetadata = async () => { + return await _generateMetadata( + () => `Apps | ${APP_NAME}`, + () => "" + ); +}; + +const getPageProps = async () => { + const ssr = await ssrInit(); + const req = { headers: headers(), cookies: cookies() }; + + // @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest + const session = await getServerSession({ req }); + + let appStore, userAdminTeams: UserAdminTeams; + if (session?.user?.id) { + userAdminTeams = await getUserAdminTeams({ userId: session.user.id, getUserInfo: true }); + appStore = await getAppRegistryWithCredentials(session.user.id, userAdminTeams); + } else { + appStore = await getAppRegistry(); + userAdminTeams = []; + } + + const categoryQuery = appStore.map(({ categories }) => ({ + categories: categories || [], + })); + + const categories = categoryQuery.reduce((c, app) => { + for (const category of app.categories) { + c[category] = c[category] ? c[category] + 1 : 1; + } + return c; + }, {} as Record); + + return { + categories: Object.entries(categories) + .map(([name, count]): { name: AppCategories; count: number } => ({ + name: name as AppCategories, + count, + })) + .sort(function (a, b) { + return b.count - a.count; + }), + appStore, + userAdminTeams, + dehydratedState: await ssr.dehydrate(), + }; +}; + +export default async function AppPageAppDir() { + const { categories, appStore, userAdminTeams, dehydratedState } = await getPageProps(); + + const h = headers(); + const nonce = h.get("x-nonce") ?? undefined; + + return ( + + + + ); +} diff --git a/apps/web/pages/apps/index.tsx b/apps/web/pages/apps/index.tsx index 679957c923..8315bdc4e3 100644 --- a/apps/web/pages/apps/index.tsx +++ b/apps/web/pages/apps/index.tsx @@ -1,3 +1,5 @@ +"use client"; + import type { GetServerSidePropsContext } from "next"; import type { ChangeEventHandler } from "react"; import { useState } from "react"; @@ -64,7 +66,7 @@ export default function Apps({ categories, appStore, userAdminTeams, -}: inferSSRProps) { +}: Omit, "trpcState">) { const { t } = useLocale(); const [searchText, setSearchText] = useState(undefined); diff --git a/apps/web/playwright/app-store.e2e.ts b/apps/web/playwright/app-store.e2e.ts index bf6ada8561..ba1ea470b6 100644 --- a/apps/web/playwright/app-store.e2e.ts +++ b/apps/web/playwright/app-store.e2e.ts @@ -8,6 +8,33 @@ test.describe.configure({ mode: "parallel" }); test.afterEach(({ users }) => users.deleteAll()); test.describe("App Store - Authed", () => { + test("should point to the /future/apps/", 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/"); + + await page.waitForLoadState(); + + const dataNextJsRouter = await page.evaluate(() => + window.document.documentElement.getAttribute("data-nextjs-router") + ); + + expect(dataNextJsRouter).toEqual("app"); + + const locator = page.getByRole("heading", { name: "App Store" }); + + await expect(locator).toBeVisible(); + }); + test("Browse apple-calendar and try to install", async ({ page, users }) => { const pro = await users.create(); await pro.apiLogin(); diff --git a/apps/web/playwright/teams.e2e.ts b/apps/web/playwright/teams.e2e.ts index 51f1976a43..dce93d42b9 100644 --- a/apps/web/playwright/teams.e2e.ts +++ b/apps/web/playwright/teams.e2e.ts @@ -270,6 +270,9 @@ test.describe("Teams - NonOrg", () => { await page.goto(`/settings/teams/${team.id}/members`); await page.click("[data-testid=make-team-private-check]"); await expect(page.locator(`[data-testid=make-team-private-check][data-state="checked"]`)).toBeVisible(); + // according to switch implementation, checked state can be set before mutation is resolved + // so we need to await for req to resolve + await page.waitForResponse((res) => res.url().includes("/api/trpc/teams/update")); // Go to Team's page await page.goto(`/team/${team.slug}`); @@ -325,15 +328,19 @@ test.describe("Teams - Org", () => { await page.locator('[placeholder="email\\@example\\.com"]').fill(inviteeEmail); await page.getByTestId("invite-new-member-button").click(); await expect(page.locator(`li:has-text("${inviteeEmail}")`)).toBeVisible(); - expect(await page.getByTestId("pending-member-item").count()).toBe(2); + + // locator.count() does not await for the expected number of elements + // https://github.com/microsoft/playwright/issues/14278 + // using toHaveCount() is more reliable + await expect(page.getByTestId("pending-member-item")).toHaveCount(2); }); await test.step("Can remove members", async () => { - expect(await page.getByTestId("pending-member-item").count()).toBe(2); + await expect(page.getByTestId("pending-member-item")).toHaveCount(2); const lastRemoveMemberButton = page.getByTestId("remove-member-button").last(); await lastRemoveMemberButton.click(); await page.waitForLoadState("networkidle"); - expect(await page.getByTestId("pending-member-item").count()).toBe(1); + await expect(page.getByTestId("pending-member-item")).toHaveCount(1); // Cleanup here since this user is created without our fixtures. await prisma.user.delete({ where: { email: inviteeEmail } });