diff --git a/apps/web/components/ui/AuthContainer.tsx b/apps/web/components/ui/AuthContainer.tsx index 12d356a851..9da5b38c0f 100644 --- a/apps/web/components/ui/AuthContainer.tsx +++ b/apps/web/components/ui/AuthContainer.tsx @@ -1,7 +1,6 @@ import classNames from "classnames"; -import { APP_NAME, LOGO } from "@calcom/lib/constants"; -import { HeadSeo } from "@calcom/ui"; +import { HeadSeo, Logo } from "@calcom/ui"; import Loader from "@components/Loader"; @@ -18,10 +17,7 @@ export default function AuthContainer(props: React.PropsWithChildren) { return (
- {props.showLogo && ( - // eslint-disable-next-line @next/next/no-img-element - {`${APP_NAME} - )} + {props.showLogo && }
{props.heading &&

{props.heading}

} diff --git a/apps/web/lib/app-providers.tsx b/apps/web/lib/app-providers.tsx index 4c55778a42..dad9e15d33 100644 --- a/apps/web/lib/app-providers.tsx +++ b/apps/web/lib/app-providers.tsx @@ -155,6 +155,7 @@ const AppProviders = (props: AppPropsWithChildren) => { + {/* color-scheme makes background:transparent not work which is required by embed. We need to ensure next-theme adds color-scheme to `body` instead of `html`(https://github.com/pacocoursey/next-themes/blob/main/src/index.tsx#L74). Once that's done we can enable color-scheme support */} 2) { + const subdomain = hostParts.slice(0, hostParts.length - 2).join("."); + const domain = hostParts.slice(hostParts.length - 2).join("."); + return [subdomain, removePort(domain)]; + } else if (hostParts.length === 2) { + const subdomain = ""; + const domain = hostParts.join("."); + return [subdomain, removePort(domain)]; + } else { + return null; + } + } catch (error) { + return null; + } +} + +const logoApiSchema = z.object({ + icon: z.coerce.boolean().optional(), +}); + +function handleDefaultLogo( + req: NextApiRequest, + res: NextApiResponse, + parsedQuery?: z.infer +) { + let fileNameParts = LOGO.split("."); + + if (parsedQuery?.icon) { + fileNameParts = LOGO_ICON.split("."); + } + + const { [fileNameParts.length - 1]: fileExtension } = fileNameParts; + const STATIC_PATH = path.join(process.cwd(), "public" + LOGO); + const imageBuffer = fs.readFileSync(STATIC_PATH); + const mimeType = mime.lookup(fileExtension); + if (mimeType) res.setHeader("Content-Type", mimeType); + res.setHeader("Cache-Control", "s-maxage=86400"); + res.send(imageBuffer); +} + +/** + * This API endpoint is used to serve the logo associated with a team if no logo is found we serve our default logo + */ +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + const { query } = req; + const parsedQuery = logoApiSchema.parse(query); + + const hostname = req?.headers["host"]; + if (!hostname) throw new Error("No hostname"); + const domains = extractSubdomainAndDomain(hostname); + if (!domains) throw new Error("No domains"); + const [subdomain, domain] = domains; + // Only supported on cal.com and cal.dev + if (!["cal.com", "cal.dev"].includes(domain)) return handleDefaultLogo(req, res, parsedQuery); + // Skip if no subdomain + if (!subdomain) throw new Error("No subdomain"); + // Omit system subdomains + if (["console", "app", "www"].includes(subdomain)) return handleDefaultLogo(req, res, parsedQuery); + + const { default: prisma } = await import("@calcom/prisma"); + + const team = await prisma.team.findUnique({ + where: { + slug: subdomain, + }, + select: { + appLogo: true, + appIconLogo: true, + }, + }); + + const filteredLogo = parsedQuery?.icon ? team?.appIconLogo : team?.appLogo; + + if (!filteredLogo) throw new Error("No team appLogo"); + + const response = await fetch(filteredLogo); + const arrayBuffer = await response.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + res.setHeader("Content-Type", response.headers.get("content-type") as string); + res.setHeader("Cache-Control", "s-maxage=86400"); + res.send(buffer); + } catch (e) { + if (e instanceof Error) log.debug(e.message); + handleDefaultLogo(req, res); + } +} diff --git a/packages/prisma/migrations/20230417102118_app_logo_subdomain/migration.sql b/packages/prisma/migrations/20230417102118_app_logo_subdomain/migration.sql new file mode 100644 index 0000000000..5190f7fbb4 --- /dev/null +++ b/packages/prisma/migrations/20230417102118_app_logo_subdomain/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "Team" ADD COLUMN "appLogo" TEXT; +ALTER TABLE "Team" ADD COLUMN "appIconLogo" TEXT; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 054a5999b3..c77af23439 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -235,6 +235,8 @@ model Team { /// @zod.min(1) slug String? @unique logo String? + appLogo String? + appIconLogo String? bio String? hideBranding Boolean @default(false) hideBookATeamMember Boolean @default(false) diff --git a/packages/ui/components/logo/Logo.tsx b/packages/ui/components/logo/Logo.tsx index f22d80f9c9..31e7e224cd 100644 --- a/packages/ui/components/logo/Logo.tsx +++ b/packages/ui/components/logo/Logo.tsx @@ -1,18 +1,27 @@ import classNames from "@calcom/lib/classNames"; -import { LOGO_ICON, LOGO } from "@calcom/lib/constants"; -export default function Logo({ small, icon }: { small?: boolean; icon?: boolean }) { +export default function Logo({ + small, + icon, + inline = true, + className, +}: { + small?: boolean; + icon?: boolean; + inline?: boolean; + className?: string; +}) { return ( -

+

{icon ? ( - Cal + Cal ) : ( Cal )}