fix: Embed `theme` not working using Embed API (#10163)
## What does this PR do? Fixes #10187 See [Tests Done](https://www.loom.com/share/f03e0191b60143d8b45a505042dbfa11) ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How should this be tested? - [x] Configure embed to use `dark` theme and verify that dark theme is shown on event booking page(when user has light theme set). This is failing in main - Additional Tests for embed to avoid any new regression - [x] - Configure "auto" theme using embed API and see it reacts to system theme - [x] - Don't configure any theme and see that "light" theme is shown even when we switch system theme(Because User has configured light theme in App) - Tests outside embed to avoid any new regression - [x] - See that light theme is shown even after switching system theme - [x] - Now, switch the user theme to dark and see that it reflects the change. ## Mandatory Tasks [x] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
This commit is contained in:
parent
2db4998eaa
commit
6dfc19247e
|
@ -3,7 +3,7 @@ import type { GroupBase, Props, InputProps, SingleValue, MultiValue } from "reac
|
||||||
import ReactSelect, { components } from "react-select";
|
import ReactSelect, { components } from "react-select";
|
||||||
|
|
||||||
import classNames from "@calcom/lib/classNames";
|
import classNames from "@calcom/lib/classNames";
|
||||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
import { useGetTheme } from "@calcom/lib/hooks/useTheme";
|
||||||
|
|
||||||
export type SelectProps<
|
export type SelectProps<
|
||||||
Option,
|
Option,
|
||||||
|
@ -30,7 +30,7 @@ function Select<
|
||||||
Group extends GroupBase<Option> = GroupBase<Option>
|
Group extends GroupBase<Option> = GroupBase<Option>
|
||||||
>({ className, ...props }: SelectProps<Option, IsMulti, Group>) {
|
>({ className, ...props }: SelectProps<Option, IsMulti, Group>) {
|
||||||
const [mounted, setMounted] = useState<boolean>(false);
|
const [mounted, setMounted] = useState<boolean>(false);
|
||||||
const { resolvedTheme, forcedTheme } = useTheme();
|
const { resolvedTheme, forcedTheme } = useGetTheme();
|
||||||
const hasDarkTheme = !forcedTheme && resolvedTheme === "dark";
|
const hasDarkTheme = !forcedTheme && resolvedTheme === "dark";
|
||||||
const darkThemeColors = {
|
const darkThemeColors = {
|
||||||
/** Dark Theme starts */
|
/** Dark Theme starts */
|
||||||
|
|
|
@ -48,6 +48,12 @@ type AppPropsWithChildren = AppProps & {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getEmbedNamespace = (query: ReturnType<typeof useRouter>["query"]) => {
|
||||||
|
// Mostly embed query param should be available on server. Use that there.
|
||||||
|
// Use the most reliable detection on client
|
||||||
|
return typeof window !== "undefined" ? window.getEmbedNamespace() : (query.embed as string) || null;
|
||||||
|
};
|
||||||
|
|
||||||
const CustomI18nextProvider = (props: AppPropsWithChildren) => {
|
const CustomI18nextProvider = (props: AppPropsWithChildren) => {
|
||||||
/**
|
/**
|
||||||
* i18n should never be clubbed with other queries, so that it's caching can be managed independently.
|
* i18n should never be clubbed with other queries, so that it's caching can be managed independently.
|
||||||
|
@ -87,7 +93,7 @@ const CalcomThemeProvider = (props: CalcomThemeProps) => {
|
||||||
// Use namespace of embed to ensure same namespaced embed are displayed with same theme. This allows different embeds on the same website to be themed differently
|
// Use namespace of embed to ensure same namespaced embed are displayed with same theme. This allows different embeds on the same website to be themed differently
|
||||||
// One such example is our Embeds Demo and Testing page at http://localhost:3100
|
// One such example is our Embeds Demo and Testing page at http://localhost:3100
|
||||||
// Having `getEmbedNamespace` defined on window before react initializes the app, ensures that embedNamespace is available on the first mount and can be used as part of storageKey
|
// Having `getEmbedNamespace` defined on window before react initializes the app, ensures that embedNamespace is available on the first mount and can be used as part of storageKey
|
||||||
const embedNamespace = typeof window !== "undefined" ? window.getEmbedNamespace() : null;
|
const embedNamespace = getEmbedNamespace(router.query);
|
||||||
const isEmbedMode = typeof embedNamespace === "string";
|
const isEmbedMode = typeof embedNamespace === "string";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -158,10 +164,10 @@ function getThemeProviderProps({
|
||||||
? ThemeSupport.None
|
? ThemeSupport.None
|
||||||
: ThemeSupport.App;
|
: ThemeSupport.App;
|
||||||
|
|
||||||
const isBookingPageThemSupportRequired = themeSupport === ThemeSupport.Booking;
|
const isBookingPageThemeSupportRequired = themeSupport === ThemeSupport.Booking;
|
||||||
const themeBasis = props.themeBasis;
|
const themeBasis = props.themeBasis;
|
||||||
|
|
||||||
if ((isBookingPageThemSupportRequired || isEmbedMode) && !themeBasis) {
|
if ((isBookingPageThemeSupportRequired || isEmbedMode) && !themeBasis) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"`themeBasis` is required for booking page theme support. Not providing it will cause theme flicker."
|
"`themeBasis` is required for booking page theme support. Not providing it will cause theme flicker."
|
||||||
);
|
);
|
||||||
|
@ -184,7 +190,7 @@ function getThemeProviderProps({
|
||||||
`embed-theme-${embedNamespace}${appearanceIdSuffix}${embedExplicitlySetThemeSuffix}`
|
`embed-theme-${embedNamespace}${appearanceIdSuffix}${embedExplicitlySetThemeSuffix}`
|
||||||
: themeSupport === ThemeSupport.App
|
: themeSupport === ThemeSupport.App
|
||||||
? "app-theme"
|
? "app-theme"
|
||||||
: isBookingPageThemSupportRequired
|
: isBookingPageThemeSupportRequired
|
||||||
? `booking-theme${appearanceIdSuffix}`
|
? `booking-theme${appearanceIdSuffix}`
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden, resc
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Type.isBookingPage = true;
|
||||||
Type.PageWrapper = PageWrapper;
|
Type.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
||||||
|
|
|
@ -278,7 +278,7 @@ export default function Success(props: SuccessProps) {
|
||||||
|
|
||||||
// This is a weird case where the same route can be opened in booking flow as a success page or as a booking detail page from the app
|
// This is a weird case where the same route can be opened in booking flow as a success page or as a booking detail page from the app
|
||||||
// As Booking Page it has to support configured theme, but as booking detail page it should not do any change. Let Shell.tsx handle it.
|
// As Booking Page it has to support configured theme, but as booking detail page it should not do any change. Let Shell.tsx handle it.
|
||||||
useTheme(isSuccessBookingPage ? props.profile.theme : undefined);
|
useTheme(isSuccessBookingPage ? props.profile.theme : "system");
|
||||||
useBrandColors({
|
useBrandColors({
|
||||||
brandColor: props.profile.brandColor,
|
brandColor: props.profile.brandColor,
|
||||||
darkBrandColor: props.profile.darkBrandColor,
|
darkBrandColor: props.profile.darkBrandColor,
|
||||||
|
|
|
@ -39,6 +39,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden, isTe
|
||||||
}
|
}
|
||||||
|
|
||||||
Type.PageWrapper = PageWrapper;
|
Type.PageWrapper = PageWrapper;
|
||||||
|
Type.isBookingPage = true;
|
||||||
|
|
||||||
async function getUserPageProps(context: GetServerSidePropsContext) {
|
async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||||
const { link, slug } = paramsSchema.parse(context.params);
|
const { link, slug } = paramsSchema.parse(context.params);
|
||||||
|
|
|
@ -60,3 +60,4 @@ export default function Page(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Page.PageWrapper = PageWrapper;
|
Page.PageWrapper = PageWrapper;
|
||||||
|
Page.isBookingPage = true;
|
||||||
|
|
|
@ -37,4 +37,5 @@ export default function Page(props: Props) {
|
||||||
return <UserPage {...(props as UserPageProps)} />;
|
return <UserPage {...(props as UserPageProps)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Page.isBookingPage = true;
|
||||||
Page.PageWrapper = PageWrapper;
|
Page.PageWrapper = PageWrapper;
|
||||||
|
|
|
@ -42,6 +42,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden, org
|
||||||
}
|
}
|
||||||
|
|
||||||
Type.PageWrapper = PageWrapper;
|
Type.PageWrapper = PageWrapper;
|
||||||
|
Type.isBookingPage = true;
|
||||||
|
|
||||||
const paramsSchema = z.object({
|
const paramsSchema = z.object({
|
||||||
type: z.string().transform((s) => slugify(s)),
|
type: z.string().transform((s) => slugify(s)),
|
||||||
|
|
|
@ -8,6 +8,16 @@ const callback = function (e) {
|
||||||
|
|
||||||
const searchParams = new URL(document.URL).searchParams;
|
const searchParams = new URL(document.URL).searchParams;
|
||||||
const only = searchParams.get("only");
|
const only = searchParams.get("only");
|
||||||
|
const themeInParam = searchParams.get("theme");
|
||||||
|
const validThemes = ["light", "dark", "auto"] as const;
|
||||||
|
const theme = validThemes.includes((themeInParam as (typeof validThemes)[number]) || "")
|
||||||
|
? (themeInParam as (typeof validThemes)[number])
|
||||||
|
: null;
|
||||||
|
if (themeInParam && !theme) {
|
||||||
|
throw new Error(`Invalid theme: ${themeInParam}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const calLink = searchParams.get("cal-link");
|
||||||
|
|
||||||
if (only === "all" || only === "ns:default") {
|
if (only === "all" || only === "ns:default") {
|
||||||
Cal("init", {
|
Cal("init", {
|
||||||
|
@ -331,7 +341,7 @@ Cal("init", "routingFormDark", {
|
||||||
|
|
||||||
if (only === "all" || only == "ns:floatingButton") {
|
if (only === "all" || only == "ns:floatingButton") {
|
||||||
Cal.ns.floatingButton("floatingButton", {
|
Cal.ns.floatingButton("floatingButton", {
|
||||||
calLink: "pro",
|
calLink: calLink || "pro",
|
||||||
config: {
|
config: {
|
||||||
iframeAttrs: {
|
iframeAttrs: {
|
||||||
id: "floatingtest",
|
id: "floatingtest",
|
||||||
|
@ -340,7 +350,7 @@ if (only === "all" || only == "ns:floatingButton") {
|
||||||
email: "johndoe@gmail.com",
|
email: "johndoe@gmail.com",
|
||||||
notes: "Test Meeting",
|
notes: "Test Meeting",
|
||||||
guests: ["janedoe@example.com", "test@example.com"],
|
guests: ["janedoe@example.com", "test@example.com"],
|
||||||
theme: "dark",
|
...(theme ? { theme } : {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ async function bookFirstFreeUserEventThroughEmbed({
|
||||||
return booking;
|
return booking;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: Change these tests to use a user/eventType per embed type atleast. This is so that we can test different themes,layouts configured in App or per EventType
|
||||||
test.describe("Popup Tests", () => {
|
test.describe("Popup Tests", () => {
|
||||||
test.afterEach(async () => {
|
test.afterEach(async () => {
|
||||||
await deleteAllBookingsByEmail("embed-user@example.com");
|
await deleteAllBookingsByEmail("embed-user@example.com");
|
||||||
|
@ -102,56 +103,6 @@ test.describe("Popup Tests", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should open embed iframe on floating button clicked", async ({
|
|
||||||
page,
|
|
||||||
addEmbedListeners,
|
|
||||||
getActionFiredDetails,
|
|
||||||
}) => {
|
|
||||||
const calNamespace = "floatingButton";
|
|
||||||
await addEmbedListeners(calNamespace);
|
|
||||||
await page.goto("/?only=ns:floatingButton");
|
|
||||||
|
|
||||||
await page.click('[data-cal-namespace="floatingButton"] > button');
|
|
||||||
|
|
||||||
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro" });
|
|
||||||
await expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, {
|
|
||||||
pathname: "/pro",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!embedIframe) {
|
|
||||||
throw new Error("Embed iframe not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const { uid: bookingId } = await bookFirstEvent("pro", embedIframe, page);
|
|
||||||
const booking = await getBooking(bookingId);
|
|
||||||
|
|
||||||
expect(booking.attendees.length).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should open embed iframe with dark theme on floating button clicked", async ({
|
|
||||||
page,
|
|
||||||
addEmbedListeners,
|
|
||||||
getActionFiredDetails,
|
|
||||||
}) => {
|
|
||||||
const calNamespace = "floatingButton";
|
|
||||||
await addEmbedListeners(calNamespace);
|
|
||||||
await page.goto("/?only=ns:floatingButton");
|
|
||||||
|
|
||||||
await page.click('[data-cal-namespace="floatingButton"] > button');
|
|
||||||
|
|
||||||
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro" });
|
|
||||||
await expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, {
|
|
||||||
pathname: "/pro",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!embedIframe) {
|
|
||||||
throw new Error("Embed iframe not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = embedIframe.locator("html");
|
|
||||||
await expect(html).toHaveAttribute("class", "dark");
|
|
||||||
});
|
|
||||||
|
|
||||||
todo("Add snapshot test for embed iframe");
|
todo("Add snapshot test for embed iframe");
|
||||||
|
|
||||||
test("should open Routing Forms embed on click", async ({
|
test("should open Routing Forms embed on click", async ({
|
||||||
|
@ -186,4 +137,112 @@ test.describe("Popup Tests", () => {
|
||||||
});
|
});
|
||||||
await expect(embedIframe.locator("text=Seeded Form - Pro")).toBeVisible();
|
await expect(embedIframe.locator("text=Seeded Form - Pro")).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe("Floating Button Popup", () => {
|
||||||
|
test.describe("Pro User - Configured in App with default setting of system theme", () => {
|
||||||
|
test("should open embed iframe according to system theme when no theme is configured through Embed API", async ({
|
||||||
|
page,
|
||||||
|
addEmbedListeners,
|
||||||
|
getActionFiredDetails,
|
||||||
|
}) => {
|
||||||
|
const calNamespace = "floatingButton";
|
||||||
|
await addEmbedListeners(calNamespace);
|
||||||
|
await page.goto("/?only=ns:floatingButton");
|
||||||
|
|
||||||
|
await page.click('[data-cal-namespace="floatingButton"] > button');
|
||||||
|
|
||||||
|
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro" });
|
||||||
|
await expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, {
|
||||||
|
pathname: "/pro",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!embedIframe) {
|
||||||
|
throw new Error("Embed iframe not found");
|
||||||
|
}
|
||||||
|
const html = embedIframe.locator("html");
|
||||||
|
// Expect "light" theme as configured in App for pro user.
|
||||||
|
await expect(html).toHaveAttribute("class", "light");
|
||||||
|
const { uid: bookingId } = await bookFirstEvent("pro", embedIframe, page);
|
||||||
|
const booking = await getBooking(bookingId);
|
||||||
|
|
||||||
|
expect(booking.attendees.length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should open embed iframe according to system theme when configured with 'auto' theme using Embed API", async ({
|
||||||
|
page,
|
||||||
|
addEmbedListeners,
|
||||||
|
getActionFiredDetails,
|
||||||
|
}) => {
|
||||||
|
const calNamespace = "floatingButton";
|
||||||
|
await addEmbedListeners(calNamespace);
|
||||||
|
await page.goto("/?only=ns:floatingButton");
|
||||||
|
|
||||||
|
await page.click('[data-cal-namespace="floatingButton"] > button');
|
||||||
|
|
||||||
|
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro" });
|
||||||
|
await expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, {
|
||||||
|
pathname: "/pro",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!embedIframe) {
|
||||||
|
throw new Error("Embed iframe not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = embedIframe.locator("html");
|
||||||
|
const prefersDarkScheme = await page.evaluate(() => {
|
||||||
|
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||||
|
});
|
||||||
|
// Detect browser preference and expect accordingly
|
||||||
|
await expect(html).toHaveAttribute("class", prefersDarkScheme ? "dark" : "light");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should open embed iframe(Booker Profile Page) with dark theme when configured with dark theme using Embed API", async ({
|
||||||
|
page,
|
||||||
|
addEmbedListeners,
|
||||||
|
getActionFiredDetails,
|
||||||
|
}) => {
|
||||||
|
const calNamespace = "floatingButton";
|
||||||
|
await addEmbedListeners(calNamespace);
|
||||||
|
await page.goto("/?only=ns:floatingButton&theme=dark");
|
||||||
|
|
||||||
|
await page.click('[data-cal-namespace="floatingButton"] > button');
|
||||||
|
|
||||||
|
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro" });
|
||||||
|
await expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, {
|
||||||
|
pathname: "/pro",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!embedIframe) {
|
||||||
|
throw new Error("Embed iframe not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = embedIframe.locator("html");
|
||||||
|
await expect(html).toHaveAttribute("class", "dark");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should open embed iframe(Event Booking Page) with dark theme when configured with dark theme using Embed API", async ({
|
||||||
|
page,
|
||||||
|
addEmbedListeners,
|
||||||
|
getActionFiredDetails,
|
||||||
|
}) => {
|
||||||
|
const calNamespace = "floatingButton";
|
||||||
|
await addEmbedListeners(calNamespace);
|
||||||
|
await page.goto("/?only=ns:floatingButton&cal-link=pro/30min&theme=dark");
|
||||||
|
|
||||||
|
await page.click('[data-cal-namespace="floatingButton"] > button');
|
||||||
|
|
||||||
|
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro/30min" });
|
||||||
|
await expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, {
|
||||||
|
pathname: "/pro/30min",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!embedIframe) {
|
||||||
|
throw new Error("Embed iframe not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = embedIframe.locator("html");
|
||||||
|
await expect(html).toHaveAttribute("class", "dark");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
import useGetBrandingColours from "@calcom/lib/getBrandColours";
|
import useGetBrandingColours from "@calcom/lib/getBrandColours";
|
||||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||||
import { useCalcomTheme } from "@calcom/ui";
|
import { useCalcomTheme } from "@calcom/ui";
|
||||||
|
@ -17,10 +15,7 @@ export const useBrandColors = ({
|
||||||
lightVal: brandColor,
|
lightVal: brandColor,
|
||||||
darkVal: darkBrandColor,
|
darkVal: darkBrandColor,
|
||||||
});
|
});
|
||||||
useCalcomTheme(brandTheme);
|
|
||||||
const { setTheme } = useTheme(theme);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useCalcomTheme(brandTheme);
|
||||||
if (theme) setTheme(theme);
|
useTheme(theme);
|
||||||
}, [setTheme, theme]);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,29 +2,28 @@ import { useTheme as useNextTheme } from "next-themes";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
import { useEmbedTheme } from "@calcom/embed-core/embed-iframe";
|
import { useEmbedTheme } from "@calcom/embed-core/embed-iframe";
|
||||||
import type { Maybe } from "@calcom/trpc/server";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It should be called once per route and only if you want to use app configured theme. System only theme works automatically by using ThemeProvider
|
* It should be called once per route if you intend to use a theme different from `system` theme. `system` theme is automatically supported using <ThemeProvider />
|
||||||
* Calling it without a theme will just returns the current theme.
|
* If needed you can also set system theme by passing 'system' as `themeToSet`
|
||||||
* It handles embed configured theme as well.
|
* It handles embed configured theme automatically
|
||||||
|
* To just read the values pass `getOnly` as `true` and `themeToSet` as `null`
|
||||||
*/
|
*/
|
||||||
export default function useTheme(themeToSet?: Maybe<string>) {
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
export default function useTheme(themeToSet: "system" | (string & {}) | undefined | null, getOnly = false) {
|
||||||
const { resolvedTheme, setTheme, forcedTheme, theme: activeTheme } = useNextTheme();
|
const { resolvedTheme, setTheme, forcedTheme, theme: activeTheme } = useNextTheme();
|
||||||
const embedTheme = useEmbedTheme();
|
const embedTheme = useEmbedTheme();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// If themeToSet is not provided the app intends to not apply a specific theme
|
// Undefined themeToSet allow the hook to be used where the theme is fetched after calling useTheme hook
|
||||||
if (!themeToSet) {
|
if (getOnly || themeToSet === undefined) {
|
||||||
// But if embedTheme is set then the Embed API intends to apply that theme or it wants "system" theme which is the default
|
|
||||||
setTheme(embedTheme || "system");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Embed theme takes precedence over theme configured in app.
|
// Embed theme takes precedence over theme configured in app.
|
||||||
// If embedTheme isn't set i.e. it's not explicitly configured with a theme, then it would use the theme configured in appearance.
|
// If embedTheme isn't set i.e. it's not explicitly configured with a theme, then it would use the theme configured in appearance.
|
||||||
// If embedTheme is set to "auto" then we consider it as null which then uses system theme.
|
// If embedTheme is set to "auto" then we consider it as null which then uses system theme.
|
||||||
const finalThemeToSet = embedTheme ? (embedTheme === "auto" ? null : embedTheme) : themeToSet;
|
const finalThemeToSet = embedTheme ? (embedTheme === "auto" ? "system" : embedTheme) : themeToSet;
|
||||||
|
|
||||||
if (!finalThemeToSet || finalThemeToSet === activeTheme) return;
|
if (!finalThemeToSet || finalThemeToSet === activeTheme) return;
|
||||||
|
|
||||||
|
@ -33,10 +32,25 @@ export default function useTheme(themeToSet?: Maybe<string>) {
|
||||||
// because there might be another booking page with conflicting theme.
|
// because there might be another booking page with conflicting theme.
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [themeToSet, setTheme, embedTheme]);
|
}, [themeToSet, setTheme, embedTheme]);
|
||||||
return {
|
|
||||||
resolvedTheme,
|
if (getOnly) {
|
||||||
setTheme,
|
return {
|
||||||
forcedTheme,
|
resolvedTheme,
|
||||||
activeTheme,
|
forcedTheme,
|
||||||
};
|
activeTheme,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the currently set theme values.
|
||||||
|
*/
|
||||||
|
export function useGetTheme() {
|
||||||
|
const theme = useTheme(null, true);
|
||||||
|
if (!theme) {
|
||||||
|
throw new Error("useTheme must have a return value here");
|
||||||
|
}
|
||||||
|
return theme;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ async function createUserAndEventType(opts: {
|
||||||
completedOnboarding?: boolean;
|
completedOnboarding?: boolean;
|
||||||
timeZone?: string;
|
timeZone?: string;
|
||||||
role?: UserPermissionRole;
|
role?: UserPermissionRole;
|
||||||
|
theme?: "dark" | "light";
|
||||||
};
|
};
|
||||||
eventTypes: Array<
|
eventTypes: Array<
|
||||||
Prisma.EventTypeCreateInput & {
|
Prisma.EventTypeCreateInput & {
|
||||||
|
@ -218,6 +219,7 @@ async function main() {
|
||||||
name: "Pro Example",
|
name: "Pro Example",
|
||||||
password: "pro",
|
password: "pro",
|
||||||
username: "pro",
|
username: "pro",
|
||||||
|
theme: "light",
|
||||||
},
|
},
|
||||||
eventTypes: [
|
eventTypes: [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue
Block a user