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
8 changes: 7 additions & 1 deletion apps/web/pages/settings/my-account/appearance.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"use client";

import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import type { z } from "zod";
Expand Down Expand Up @@ -430,7 +432,11 @@ const AppearanceView = ({
const AppearanceViewWrapper = () => {
const { data: user, isPending } = trpc.viewer.me.useQuery();
const { isPending: isTeamPlanStatusLoading, hasPaidPlan } = useHasPaidPlan();

const session = useSession();
const router = useRouter();
if (!!session.data?.user?.org?.id) {
router.replace("/settings/organizations/profile");
}
anikdhabal marked this conversation as resolved.
Show resolved Hide resolved
const { t } = useLocale();

if (isPending || isTeamPlanStatusLoading || !user)
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;
46 changes: 45 additions & 1 deletion 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 Down Expand Up @@ -125,7 +127,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 @@ -165,6 +170,7 @@ const OrgProfileForm = ({ defaultValues }: { defaultValues: FormValues }) => {
const utils = trpc.useUtils();
const { t } = useLocale();
const [firstRender, setFirstRender] = useState(true);
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {});

const form = useForm({
defaultValues,
Expand Down Expand Up @@ -288,6 +294,44 @@ const OrgProfileForm = ({ defaultValues }: { defaultValues: FormValues }) => {
}}
/>
</div>
<div className="mt-2 flex items-center">
<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>

<Controller
control={form.control}
Expand Down
9 changes: 4 additions & 5 deletions packages/features/settings/layouts/SettingsLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,6 @@ const tabs: VerticalTabItemProps[] = [
name: "privacy",
href: "/settings/organizations/privacy",
},
{
name: "appearance",
href: "/settings/organizations/appearance",
},
{
name: "billing",
href: "/settings/organizations/billing",
Expand Down Expand Up @@ -148,7 +144,7 @@ tabs.find((tab) => {
// The following keys are assigned to admin only
const adminRequiredKeys = ["admin"];
const organizationRequiredKeys = ["organization"];
const organizationAdminKeys = ["privacy", "appearance", "billing", "OAuth Clients", "SSO", "directory_sync"];
const organizationAdminKeys = ["privacy", "billing", "OAuth Clients", "SSO", "directory_sync"];

const useTabs = () => {
const session = useSession();
Expand All @@ -161,6 +157,9 @@ const useTabs = () => {
const processTabsMemod = useMemo(() => {
const processedTabs = tabs.map((tab) => {
if (tab.href === "/settings/my-account") {
if (!!session.data?.user?.org?.id) {
tab.children = tab?.children?.filter((child) => child.href !== "/settings/my-account/appearance");
}
return {
...tab,
name: user?.name || "my_account",
Expand Down
Loading