Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: remove personal branding if in an org and use org branding #14872

Merged
merged 15 commits into from
Jul 4, 2024
Merged
2 changes: 1 addition & 1 deletion apps/web/playwright/settings/upload-avatar.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ test.describe("Organization Logo", async () => {

await page.getByTestId("upload-avatar").click();

await page.getByText("Update").click();
await page.getByTestId("update-org-profile-button").click();
await page.waitForSelector("text=Your organization updated successfully");

const response = await prisma.avatar.findUniqueOrThrow({
Expand Down
203 changes: 71 additions & 132 deletions packages/features/ee/organizations/pages/settings/appearance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,17 @@ import { useRouter } from "next/navigation";
import { useState, useEffect } from "react";
import { useForm } from "react-hook-form";

import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import BrandColorsForm from "@calcom/features/ee/components/BrandColorsForm";
import { AppearanceSkeletonLoader } from "@calcom/features/ee/components/CommonSkeletonLoaders";
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
import ThemeLabel from "@calcom/features/settings/ThemeLabel";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { DEFAULT_LIGHT_BRAND_COLOR, DEFAULT_DARK_BRAND_COLOR } from "@calcom/lib/constants";
import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { MembershipRole } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import type { RouterOutputs } from "@calcom/trpc/react";
import { Button, Form, Meta, showToast, SettingsToggle, Avatar, ImageUploader } from "@calcom/ui";
import { Icon } from "@calcom/ui";
import { Button, Form, showToast, SettingsToggle } from "@calcom/ui";

type BrandColorsFormValues = {
brandColor: string;
Expand All @@ -27,7 +24,6 @@ type BrandColorsFormValues = {

const OrgAppearanceView = ({
currentOrg,
isAdminOrOwner,
anikdhabal marked this conversation as resolved.
Show resolved Hide resolved
}: {
currentOrg: RouterOutputs["viewer"]["organizations"]["listCurrent"];
isAdminOrOwner: boolean;
Expand Down Expand Up @@ -79,134 +75,79 @@ const OrgAppearanceView = ({
};

return (
<LicenseRequired>
<Meta
title={t("appearance")}
description={t("appearance_org_description")}
borderInShellHeader={false}
/>
anikdhabal marked this conversation as resolved.
Show resolved Hide resolved
{isAdminOrOwner ? (
<div>
<div className="my-6">
<div className="flex items-center text-sm">
<Avatar
alt="calVideoLogo"
imageSrc={currentOrg?.calVideoLogo}
fallback={<Icon name="plus" className="text-subtle h-6 w-6" />}
size="lg"
/>
<div className="ms-4">
<div className="flex gap-2">
<ImageUploader
target="avatar"
id="cal-video-logo-upload"
buttonMsg={
currentOrg?.calVideoLogo ? t("update_cal_video_logo") : t("upload_cal_video_logo")
}
handleAvatarChange={(newLogo) => {
mutation.mutate({
calVideoLogo: newLogo,
});
}}
disabled={mutation.isPending}
imageSrc={currentOrg?.calVideoLogo ?? undefined}
uploadInstruction={t("cal_video_logo_upload_instruction")}
triggerButtonColor={currentOrg?.calVideoLogo ? "secondary" : "primary"}
/>
{currentOrg?.calVideoLogo && (
<Button
color="destructive"
disabled={mutation.isPending}
onClick={() => {
mutation.mutate({
calVideoLogo: null,
});
}}>
{t("remove")}
</Button>
)}
</div>
</div>
</div>
<div>
<Form
form={themeForm}
handleSubmit={(value) => {
mutation.mutate({
theme: value.theme ?? null,
});
}}>
<div className="border-subtle mt-6 flex items-center rounded-t-xl border p-6 text-sm">
<div>
<p className="text-default text-base font-semibold">{t("theme")}</p>
<p className="text-default">{t("theme_applies_note")}</p>
</div>
<Form
form={themeForm}
handleSubmit={(value) => {
mutation.mutate({
theme: value.theme ?? null,
});
}}>
<div className="border-subtle mt-6 flex items-center rounded-t-xl border p-6 text-sm">
<div>
<p className="text-default text-base font-semibold">{t("theme")}</p>
<p className="text-default">{t("theme_applies_note")}</p>
</div>
</div>
<div className="border-subtle flex flex-col justify-between border-x px-6 py-8 sm:flex-row">
<ThemeLabel
variant="system"
value={undefined}
label={t("theme_system")}
defaultChecked={currentOrg.theme === null}
register={themeForm.register}
/>
<ThemeLabel
variant="light"
value="light"
label={t("light")}
defaultChecked={currentOrg.theme === "light"}
register={themeForm.register}
/>
<ThemeLabel
variant="dark"
value="dark"
label={t("dark")}
defaultChecked={currentOrg.theme === "dark"}
register={themeForm.register}
/>
</div>
<SectionBottomActions className="mb-6" align="end">
<Button
disabled={isOrgThemeSubmitting || !isOrgThemeDirty}
type="submit"
data-testid="update-org-theme-btn"
color="primary">
{t("update")}
</Button>
</SectionBottomActions>
</Form>

<Form
form={brandColorsFormMethods}
handleSubmit={(values) => {
onBrandColorsFormSubmit(values);
}}>
<BrandColorsForm
onSubmit={onBrandColorsFormSubmit}
brandColor={currentOrg?.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR}
darkBrandColor={currentOrg?.darkBrandColor ?? DEFAULT_DARK_BRAND_COLOR}
/>
</Form>

<SettingsToggle
toggleSwitchAtTheEnd={true}
title={t("disable_cal_branding", { appName: APP_NAME })}
disabled={mutation?.isPending}
description={t("removes_cal_branding", { appName: APP_NAME })}
checked={hideBrandingValue}
onCheckedChange={(checked) => {
setHideBrandingValue(checked);
mutation.mutate({ hideBranding: checked });
}}
switchContainerClassName="mt-6"
/>
</div>
) : (
<div className="py-5">
<span className="text-default text-sm">{t("only_owner_change")}</span>
<div className="border-subtle flex flex-col justify-between border-x px-6 py-8 sm:flex-row">
<ThemeLabel
variant="system"
value={undefined}
label={t("theme_system")}
defaultChecked={currentOrg.theme === null}
register={themeForm.register}
/>
<ThemeLabel
variant="light"
value="light"
label={t("light")}
defaultChecked={currentOrg.theme === "light"}
register={themeForm.register}
/>
<ThemeLabel
variant="dark"
value="dark"
label={t("dark")}
defaultChecked={currentOrg.theme === "dark"}
register={themeForm.register}
/>
</div>
)}
</LicenseRequired>
<SectionBottomActions className="mb-6" align="end">
<Button
disabled={isOrgThemeSubmitting || !isOrgThemeDirty}
type="submit"
data-testid="update-org-theme-btn"
color="primary">
{t("update")}
</Button>
</SectionBottomActions>
</Form>

<Form
form={brandColorsFormMethods}
handleSubmit={(values) => {
onBrandColorsFormSubmit(values);
}}>
<BrandColorsForm
onSubmit={onBrandColorsFormSubmit}
brandColor={currentOrg?.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR}
darkBrandColor={currentOrg?.darkBrandColor ?? DEFAULT_DARK_BRAND_COLOR}
/>
</Form>

<SettingsToggle
toggleSwitchAtTheEnd={true}
title={t("disable_cal_branding", { appName: APP_NAME })}
disabled={mutation?.isPending}
description={t("removes_cal_branding", { appName: APP_NAME })}
checked={hideBrandingValue}
onCheckedChange={(checked) => {
setHideBrandingValue(checked);
mutation.mutate({ hideBranding: checked });
}}
switchContainerClassName="mt-6"
/>
</div>
);
};

Expand Down Expand Up @@ -237,6 +178,4 @@ const OrgAppearanceViewWrapper = () => {
return <OrgAppearanceView currentOrg={currentOrg} isAdminOrOwner={isAdminOrOwner} />;
};

OrgAppearanceViewWrapper.getLayout = getLayout;

export default OrgAppearanceViewWrapper;
57 changes: 55 additions & 2 deletions packages/features/ee/organizations/pages/settings/profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import { z } from "zod";

import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
import OrgAppearanceViewWrapper from "@calcom/features/ee/organizations/pages/settings/appearance";
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { md } from "@calcom/lib/markdownIt";
import turndown from "@calcom/lib/turndownService";
import { MembershipRole } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import {
Avatar,
BannerUploader,
Expand All @@ -41,6 +43,7 @@ const orgProfileFormSchema = z.object({
name: z.string(),
logoUrl: z.string().nullable(),
banner: z.string().nullable(),
calVideoLogo: z.string().nullable(),
bio: z.string(),
});

Expand All @@ -50,6 +53,7 @@ type FormValues = {
banner: string | null;
bio: string;
slug: string;
calVideoLogo: string | null;
};

const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
Expand Down Expand Up @@ -114,6 +118,7 @@ const OrgProfileView = () => {
logoUrl: currentOrganisation?.logoUrl,
banner: currentOrganisation?.bannerUrl || "",
bio: currentOrganisation?.bio || "",
calVideoLogo: currentOrganisation?.calVideoLogo || "",
slug:
currentOrganisation?.slug ||
((currentOrganisation?.metadata as Prisma.JsonObject)?.requestedSlug as string) ||
Expand All @@ -125,7 +130,10 @@ const OrgProfileView = () => {
<Meta title={t("profile")} description={t("profile_org_description")} borderInShellHeader={true} />
<>
{isOrgAdminOrOwner ? (
<OrgProfileForm defaultValues={defaultValues} />
<>
<OrgProfileForm defaultValues={defaultValues} />
<OrgAppearanceViewWrapper />
</>
) : (
<div className="border-subtle flex rounded-b-md border border-t-0 px-4 py-8 sm:px-6">
<div className="flex-grow">
Expand Down Expand Up @@ -182,6 +190,7 @@ const OrgProfileForm = ({ defaultValues }: { defaultValues: FormValues }) => {
bio: (res.data?.bio || "") as string,
slug: defaultValues["slug"],
banner: (res.data?.bannerUrl || "") as string,
calVideoLogo: (res.data?.calVideoLogo || "") as string,
});
await utils.viewer.teams.get.invalidate();
await utils.viewer.organizations.listCurrent.invalidate();
Expand All @@ -206,6 +215,7 @@ const OrgProfileForm = ({ defaultValues }: { defaultValues: FormValues }) => {
slug: values.slug,
bio: values.bio,
banner: values.banner,
calVideoLogo: values.calVideoLogo,
};

mutation.mutate(variables);
Expand Down Expand Up @@ -288,6 +298,44 @@ const OrgProfileForm = ({ defaultValues }: { defaultValues: FormValues }) => {
}}
/>
</div>
<div className="mt-2 flex items-center">
<Controller
control={form.control}
name="calVideoLogo"
render={({ field: { value, onChange } }) => {
const showRemoveLogoButton = !!value;
return (
<>
<Avatar
alt="calVideoLogo"
imageSrc={value}
fallback={<Icon name="plus" className="text-subtle h-6 w-6" />}
size="lg"
/>
<div className="ms-4">
<div className="flex gap-2">
<ImageUploader
target="avatar"
id="cal-video-logo-upload"
buttonMsg={t("upload_cal_video_logo")}
handleAvatarChange={onChange}
imageSrc={value || undefined}
uploadInstruction={t("cal_video_logo_upload_instruction")}
triggerButtonColor={showRemoveLogoButton ? "secondary" : "primary"}
testId="cal-video-logo"
/>
{showRemoveLogoButton && (
<Button color="secondary" onClick={() => onChange(null)}>
{t("remove")}
</Button>
)}
</div>
</div>
</>
);
}}
/>
</div>

<Controller
control={form.control}
Expand Down Expand Up @@ -334,7 +382,12 @@ const OrgProfileForm = ({ defaultValues }: { defaultValues: FormValues }) => {
<p className="text-default mt-2 text-sm">{t("org_description")}</p>
</div>
<SectionBottomActions align="end">
<Button color="primary" type="submit" loading={mutation.isPending} disabled={isDisabled}>
<Button
data-testid="update-org-profile-button"
color="primary"
type="submit"
loading={mutation.isPending}
disabled={isDisabled}>
{t("update")}
</Button>
</SectionBottomActions>
Expand Down
Loading
Loading