first commit
This commit is contained in:
129
calcom/packages/features/ee/components/BrandColorsForm.tsx
Normal file
129
calcom/packages/features/ee/components/BrandColorsForm.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import { useState } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
|
||||
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { DEFAULT_LIGHT_BRAND_COLOR, DEFAULT_DARK_BRAND_COLOR } from "@calcom/lib/constants";
|
||||
import { checkWCAGContrastColor } from "@calcom/lib/getBrandColours";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button, ColorPicker, SettingsToggle, Alert } from "@calcom/ui";
|
||||
|
||||
type BrandColorsFormValues = {
|
||||
brandColor: string;
|
||||
darkBrandColor: string;
|
||||
};
|
||||
|
||||
const BrandColorsForm = ({
|
||||
onSubmit,
|
||||
brandColor,
|
||||
darkBrandColor,
|
||||
}: {
|
||||
onSubmit: (values: BrandColorsFormValues) => void;
|
||||
brandColor: string | undefined;
|
||||
darkBrandColor: string | undefined;
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
const brandColorsFormMethods = useFormContext();
|
||||
const {
|
||||
formState: { isSubmitting: isBrandColorsFormSubmitting, isDirty: isBrandColorsFormDirty },
|
||||
} = brandColorsFormMethods;
|
||||
|
||||
const [isCustomBrandColorChecked, setIsCustomBrandColorChecked] = useState(
|
||||
brandColor !== DEFAULT_LIGHT_BRAND_COLOR || darkBrandColor !== DEFAULT_DARK_BRAND_COLOR
|
||||
);
|
||||
const [darkModeError, setDarkModeError] = useState(false);
|
||||
const [lightModeError, setLightModeError] = useState(false);
|
||||
return (
|
||||
<div className="mt-6">
|
||||
<SettingsToggle
|
||||
toggleSwitchAtTheEnd={true}
|
||||
title={t("custom_brand_colors")}
|
||||
description={t("customize_your_brand_colors")}
|
||||
checked={isCustomBrandColorChecked}
|
||||
onCheckedChange={(checked) => {
|
||||
setIsCustomBrandColorChecked(checked);
|
||||
if (!checked) {
|
||||
onSubmit({
|
||||
brandColor: DEFAULT_LIGHT_BRAND_COLOR,
|
||||
darkBrandColor: DEFAULT_DARK_BRAND_COLOR,
|
||||
});
|
||||
}
|
||||
}}
|
||||
childrenClassName="lg:ml-0"
|
||||
switchContainerClassName={classNames(
|
||||
"py-6 px-4 sm:px-6 border-subtle rounded-xl border",
|
||||
isCustomBrandColorChecked && "rounded-b-none"
|
||||
)}>
|
||||
<div className="border-subtle flex flex-col gap-6 border-x p-6">
|
||||
<Controller
|
||||
name="brandColor"
|
||||
control={brandColorsFormMethods.control}
|
||||
defaultValue={brandColor}
|
||||
render={() => (
|
||||
<div>
|
||||
<p className="text-default mb-2 block text-sm font-medium">{t("light_brand_color")}</p>
|
||||
<ColorPicker
|
||||
defaultValue={brandColor || DEFAULT_LIGHT_BRAND_COLOR}
|
||||
resetDefaultValue={DEFAULT_LIGHT_BRAND_COLOR}
|
||||
onChange={(value) => {
|
||||
try {
|
||||
checkWCAGContrastColor("#ffffff", value);
|
||||
setLightModeError(false);
|
||||
brandColorsFormMethods.setValue("brandColor", value, { shouldDirty: true });
|
||||
} catch (err) {
|
||||
setLightModeError(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{lightModeError ? (
|
||||
<div className="mt-4">
|
||||
<Alert severity="warning" message={t("light_theme_contrast_error")} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="darkBrandColor"
|
||||
control={brandColorsFormMethods.control}
|
||||
defaultValue={darkBrandColor}
|
||||
render={() => (
|
||||
<div className="mt-6 sm:mt-0">
|
||||
<p className="text-default mb-2 block text-sm font-medium">{t("dark_brand_color")}</p>
|
||||
<ColorPicker
|
||||
defaultValue={darkBrandColor || DEFAULT_DARK_BRAND_COLOR}
|
||||
resetDefaultValue={DEFAULT_DARK_BRAND_COLOR}
|
||||
onChange={(value) => {
|
||||
try {
|
||||
checkWCAGContrastColor("#101010", value);
|
||||
setDarkModeError(false);
|
||||
brandColorsFormMethods.setValue("darkBrandColor", value, { shouldDirty: true });
|
||||
} catch (err) {
|
||||
setDarkModeError(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{darkModeError ? (
|
||||
<div className="mt-4">
|
||||
<Alert severity="warning" message={t("dark_theme_contrast_error")} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<SectionBottomActions align="end">
|
||||
<Button
|
||||
disabled={isBrandColorsFormSubmitting || !isBrandColorsFormDirty}
|
||||
color="primary"
|
||||
type="submit">
|
||||
{t("update")}
|
||||
</Button>
|
||||
</SectionBottomActions>
|
||||
</SettingsToggle>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BrandColorsForm;
|
||||
@@ -0,0 +1,31 @@
|
||||
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
|
||||
import { Meta, SkeletonButton, SkeletonContainer, SkeletonText } from "@calcom/ui";
|
||||
|
||||
export const AppearanceSkeletonLoader = ({ title, description }: { title: string; description: string }) => {
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<Meta title={title} description={description} borderInShellHeader={false} />
|
||||
<div className="border-subtle mt-6 flex items-center rounded-t-xl border p-6 text-sm">
|
||||
<SkeletonText className="h-8 w-1/3" />
|
||||
</div>
|
||||
<div className="border-subtle space-y-6 border-x px-4 py-6 sm:px-6">
|
||||
<div className="flex w-full items-center justify-center gap-6">
|
||||
<div className="bg-emphasis h-32 flex-1 animate-pulse rounded-md p-5" />
|
||||
<div className="bg-emphasis h-32 flex-1 animate-pulse rounded-md p-5" />
|
||||
<div className="bg-emphasis h-32 flex-1 animate-pulse rounded-md p-5" />
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<SkeletonText className="h-8 w-1/3" />
|
||||
<SkeletonText className="h-8 w-1/3" />
|
||||
</div>
|
||||
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
</div>
|
||||
<div className="rounded-b-xl">
|
||||
<SectionBottomActions align="end">
|
||||
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
|
||||
</SectionBottomActions>
|
||||
</div>
|
||||
</SkeletonContainer>
|
||||
);
|
||||
};
|
||||
34
calcom/packages/features/ee/components/PoweredBy.tsx
Normal file
34
calcom/packages/features/ee/components/PoweredBy.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useSession } from "next-auth/react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||
import { APP_NAME, POWERED_BY_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
const PoweredByCal = ({ logoOnly }: { logoOnly?: boolean }) => {
|
||||
const { t } = useLocale();
|
||||
const session = useSession();
|
||||
const isEmbed = useIsEmbed();
|
||||
const hasValidLicense = session.data ? session.data.hasValidLicense : null;
|
||||
|
||||
return (
|
||||
<div className={`p-2 text-center text-xs sm:text-right${isEmbed ? " max-w-3xl" : ""}`}>
|
||||
<Link href={POWERED_BY_URL} target="_blank" className="text-subtle">
|
||||
{!logoOnly && <>{t("powered_by")} </>}
|
||||
{APP_NAME === "Cal.com" || !hasValidLicense ? (
|
||||
<>
|
||||
<img
|
||||
className="relative -mt-px inline h-[10px] w-auto dark:invert"
|
||||
src="/api/logo"
|
||||
alt="Cal.com Logo"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-emphasis font-semibold opacity-50 hover:opacity-100">{APP_NAME}</span>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PoweredByCal;
|
||||
Reference in New Issue
Block a user