* update env vars * update middleware * remove pages router and move pages to /app * move to /team * update imports * fix * remove pages router and move org pages to /app * wip * fix orgSlug/user pages * fix orgSlug/user/type pages * remove generateMetadata from embed pages * fix * remove pages router for /user pages * generateMetadata is not needed in embed pages * remove future/org * no layout in [user] page * simplify * fix * fix OG image for [user] * fix OG images for org/user and team/slug * fix OG images for booking page * fix all metadata * use isBrandingHidden * remove BookerSeo and its usages * rename excludeAppNameFromTitle -> hideBranding * remove logic for meeting type in HeadSeo * remove BookerSeo instances from team-view and users-public-view * create generateMeetingMetadata util and use it to reduce duplicate code * remove BookerSeo imports * fix spacing * remove constructMeetingImage mock from head-seo.test * fetch avatarUrl using user id for user page metadata * fix test * remove unused test cases * index and follow must be true by default * invert noindex/nofollow flags * remove HeadSeo for already migrated pages and refactor prepareMetadata * fix organization-settings.e2e.ts * fix order * enable parallel test execution for dynamic-booking e2e test * fix * + could be %2B in app router * refactor handling logic for embeds in app router * fix isEmbed * fix embed-core * remove dead code * move embed pages back to /future * add back embed pages in pages router * revert some changes * fix import type checks * simplify * fix * feat: Implement generateBookingPageMetadata function for improved SEO and metadata handling across user and event pages (#18440) - Added a new utility function `generateBookingPageMetadata` to streamline the generation of metadata for booking and user profile pages. - Updated multiple page components to utilize the new function, enhancing SEO indexing and metadata consistency. - Removed redundant code related to previous metadata generation methods, improving code clarity and maintainability. * fix dirs * Pr-review-fixes-app-router-team-pages (#18450) * Remove unnecessary getPublicEvent call * Remove unused variable * Fix 404 for team page (#18451) * Remove console log --------- Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
156 lines
5.1 KiB
TypeScript
156 lines
5.1 KiB
TypeScript
import { TrpcProvider } from "app/_trpc/trpc-provider";
|
|
import { dir } from "i18next";
|
|
import { Inter } from "next/font/google";
|
|
import localFont from "next/font/local";
|
|
import { headers, cookies } from "next/headers";
|
|
import React from "react";
|
|
|
|
import { getLocale } from "@calcom/features/auth/lib/getLocale";
|
|
import { IconSprites } from "@calcom/ui";
|
|
|
|
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",
|
|
weight: "600",
|
|
});
|
|
|
|
export const generateMetadata = () => prepareRootMetadata();
|
|
|
|
const getInitialProps = async (url: string) => {
|
|
const { pathname, searchParams } = new URL(url);
|
|
|
|
const isEmbedSnippetGeneratorPath = pathname.startsWith("/event-types");
|
|
const isEmbed =
|
|
(pathname.endsWith("/embed") || (searchParams?.get("embedType") ?? null) !== null) &&
|
|
!isEmbedSnippetGeneratorPath;
|
|
const embedColorScheme = searchParams?.get("ui.color-scheme");
|
|
|
|
const req = { headers: headers(), cookies: cookies() };
|
|
const newLocale = await getLocale(req);
|
|
const direction = dir(newLocale);
|
|
|
|
return { isEmbed, embedColorScheme, locale: newLocale, direction };
|
|
};
|
|
|
|
const getFallbackProps = () => ({
|
|
locale: "en",
|
|
direction: "ltr",
|
|
isEmbed: false,
|
|
embedColorScheme: false,
|
|
});
|
|
|
|
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
const h = headers();
|
|
|
|
const fullUrl = h.get("x-url") ?? "";
|
|
const nonce = h.get("x-csp") ?? "";
|
|
|
|
const isSSG = !fullUrl;
|
|
|
|
const { locale, direction, isEmbed, embedColorScheme } = isSSG
|
|
? getFallbackProps()
|
|
: await getInitialProps(fullUrl);
|
|
|
|
return (
|
|
<html
|
|
lang={locale}
|
|
dir={direction}
|
|
style={embedColorScheme ? { colorScheme: embedColorScheme as string } : undefined}
|
|
suppressHydrationWarning
|
|
data-nextjs-router="app">
|
|
<head nonce={nonce}>
|
|
{!!process.env.NEXT_PUBLIC_HEAD_SCRIPTS && (
|
|
<script
|
|
nonce={nonce}
|
|
id="injected-head-scripts"
|
|
// eslint-disable-next-line react/no-danger
|
|
dangerouslySetInnerHTML={{
|
|
__html: process.env.NEXT_PUBLIC_HEAD_SCRIPTS,
|
|
}}
|
|
/>
|
|
)}
|
|
<script
|
|
nonce={nonce}
|
|
id="headScript"
|
|
// eslint-disable-next-line react/no-danger
|
|
dangerouslySetInnerHTML={{
|
|
__html: `
|
|
window.calNewLocale = "${locale}";
|
|
(function applyTheme() {
|
|
try {
|
|
const appTheme = localStorage.getItem('app-theme');
|
|
if (!appTheme) return;
|
|
|
|
let bookingTheme, username;
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
const key = localStorage.key(i);
|
|
if (key.startsWith('booking-theme:')) {
|
|
bookingTheme = localStorage.getItem(key);
|
|
username = key.split("booking-theme:")[1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
const onReady = () => {
|
|
const isBookingPage = username && window.location.pathname.slice(1).startsWith(username);
|
|
|
|
if (document.body) {
|
|
document.body.classList.add(isBookingPage ? bookingTheme : appTheme);
|
|
} else {
|
|
requestAnimationFrame(onReady);
|
|
}
|
|
};
|
|
|
|
requestAnimationFrame(onReady);
|
|
} catch (e) {
|
|
console.error('Error applying theme:', e);
|
|
}
|
|
})();
|
|
`,
|
|
}}
|
|
/>
|
|
<style>{`
|
|
:root {
|
|
--font-inter: ${interFont.style.fontFamily.replace(/\'/g, "")};
|
|
--font-cal: ${calFont.style.fontFamily.replace(/\'/g, "")};
|
|
}
|
|
`}</style>
|
|
<IconSprites />
|
|
</head>
|
|
<body
|
|
className="dark:bg-darkgray-50 bg-subtle antialiased"
|
|
style={
|
|
isEmbed
|
|
? {
|
|
background: "transparent",
|
|
// Keep the embed hidden till parent initializes and
|
|
// - gives it the appropriate styles if UI instruction is there.
|
|
// - gives iframe the appropriate height(equal to document height) which can only be known after loading the page once in browser.
|
|
// - Tells iframe which mode it should be in (dark/light) - if there is a a UI instruction for that
|
|
visibility: "hidden",
|
|
}
|
|
: {}
|
|
}>
|
|
{!!process.env.NEXT_PUBLIC_BODY_SCRIPTS && (
|
|
<script
|
|
nonce={nonce}
|
|
id="injected-head-scripts"
|
|
// eslint-disable-next-line react/no-danger
|
|
dangerouslySetInnerHTML={{
|
|
__html: process.env.NEXT_PUBLIC_BODY_SCRIPTS,
|
|
}}
|
|
/>
|
|
)}
|
|
<TrpcProvider>{children}</TrpcProvider>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|