Skip to content

Commit 4e01b13

Browse files
authoredNov 16, 2021
Feature/cal 677 brand color in settingsprofile (#1158)
* added CSS variable --brand-color * added CustomBranding component * prisma update for brand color * added brandcolor to user context in viewer.me * conflict resolution * added brandColor input and mutation * custom brand color to availability * brandColor added to BookingPage * fixed availability, booking for team and added customBranding to success * brandColor added to cancel/uid * requested changes * lint fix * further changes * lint fix
1 parent 11e7779 commit 4e01b13

File tree

16 files changed

+94
-29
lines changed

16 files changed

+94
-29
lines changed
 

‎components/CustomBranding.tsx

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { useEffect } from "react";
2+
3+
const BrandColor = ({ val = "#292929" }: { val: string | undefined | null }) => {
4+
useEffect(() => {
5+
document.documentElement.style.setProperty("--brand-color", val);
6+
}, [val]);
7+
return null;
8+
};
9+
10+
export default BrandColor;

‎components/Shell.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { useLocale } from "@lib/hooks/useLocale";
2323
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
2424
import { trpc } from "@lib/trpc";
2525

26+
import CustomBranding from "@components/CustomBranding";
2627
import Loader from "@components/Loader";
2728
import { HeadSeo } from "@components/seo/head-seo";
2829
import Avatar from "@components/ui/Avatar";
@@ -166,6 +167,9 @@ export default function Shell(props: {
166167

167168
const pageTitle = typeof props.heading === "string" ? props.heading : props.title;
168169

170+
const query = useMeQuery();
171+
const user = query.data;
172+
169173
const i18n = useViewerI18n();
170174

171175
if (i18n.status === "loading" || isRedirectingToOnboarding || loading) {
@@ -178,6 +182,7 @@ export default function Shell(props: {
178182
}
179183
return (
180184
<>
185+
<CustomBranding val={user?.brandColor} />
181186
<HeadSeo
182187
title={pageTitle ?? "Cal.com"}
183188
description={props.subtitle ? props.subtitle?.toString() : ""}

‎components/booking/pages/AvailabilityPage.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import useTheme from "@lib/hooks/useTheme";
1515
import { isBrandingHidden } from "@lib/isBrandingHidden";
1616
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
1717

18+
import CustomBranding from "@components/CustomBranding";
1819
import AvailableTimes from "@components/booking/AvailableTimes";
1920
import DatePicker from "@components/booking/DatePicker";
2021
import TimeOptions from "@components/booking/TimeOptions";
@@ -95,6 +96,7 @@ const AvailabilityPage = ({ profile, eventType, workingHours }: Props) => {
9596
name={profile.name}
9697
avatar={profile.image}
9798
/>
99+
<CustomBranding val={profile.brandColor} />
98100
<div>
99101
<main
100102
className={
@@ -162,8 +164,8 @@ const AvailabilityPage = ({ profile, eventType, workingHours }: Props) => {
162164
size={10}
163165
truncateAfter={3}
164166
/>
165-
<h2 className="font-medium text-gray-500 dark:text-gray-300 mt-3">{profile.name}</h2>
166-
<h1 className="font-cal mb-4 text-3xl font-semibold text-gray-800 dark:text-white">
167+
<h2 className="mt-3 font-medium text-gray-500 dark:text-gray-300">{profile.name}</h2>
168+
<h1 className="mb-4 text-3xl font-semibold text-gray-800 font-cal dark:text-white">
167169
{eventType.title}
168170
</h1>
169171
<p className="px-2 py-1 mb-1 -ml-2 text-gray-500">

‎components/booking/pages/BookingPage.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { parseZone } from "@lib/parseZone";
2626
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
2727
import { BookingCreateBody } from "@lib/types/booking";
2828

29+
import CustomBranding from "@components/CustomBranding";
2930
import AvatarGroup from "@components/ui/AvatarGroup";
3031
import { Button } from "@components/ui/Button";
3132
import PhoneInput from "@components/ui/form/PhoneInput";
@@ -200,7 +201,7 @@ const BookingPage = (props: BookingPageProps) => {
200201
</title>
201202
<link rel="icon" href="/favicon.ico" />
202203
</Head>
203-
204+
<CustomBranding val={props.profile.brandColor} />
204205
<main className="max-w-3xl mx-auto my-0 rounded-sm sm:my-24 sm:border sm:dark:border-gray-600">
205206
{isReady && (
206207
<div className="overflow-hidden bg-white border border-gray-200 dark:bg-neutral-900 dark:border-0 sm:rounded-sm">

‎pages/[user]/[type].tsx

+2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
7070
weekStart: true,
7171
availability: true,
7272
hideBranding: true,
73+
brandColor: true,
7374
theme: true,
7475
plan: true,
7576
eventTypes: {
@@ -192,6 +193,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
192193
slug: user.username,
193194
theme: user.theme,
194195
weekStart: user.weekStart,
196+
brandColor: user.brandColor,
195197
},
196198
date: dateParam,
197199
eventType: eventTypeObject,

‎pages/[user]/book.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
3434
bio: true,
3535
avatar: true,
3636
theme: true,
37+
brandColor: true,
3738
},
3839
});
3940

@@ -108,6 +109,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
108109
name: user.name,
109110
image: user.avatar,
110111
theme: user.theme,
112+
brandColor: user.brandColor,
111113
},
112114
eventType: eventTypeObject,
113115
booking,

‎pages/cancel/[uid].tsx

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useLocale } from "@lib/hooks/useLocale";
99
import prisma from "@lib/prisma";
1010
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
1111

12+
import CustomBranding from "@components/CustomBranding";
1213
import { HeadSeo } from "@components/seo/head-seo";
1314
import { Button } from "@components/ui/Button";
1415

@@ -64,6 +65,7 @@ export default function Type(props) {
6465
title={`${t("cancel")} ${props.booking && props.booking.title} | ${props.profile.name}`}
6566
description={`${t("cancel")} ${props.booking && props.booking.title} | ${props.profile.name}`}
6667
/>
68+
<CustomBranding val={props.profile.brandColor} />
6769
<main className="max-w-3xl mx-auto my-24">
6870
<div className="fixed inset-0 z-50 overflow-y-auto">
6971
<div className="flex items-end justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
@@ -159,6 +161,7 @@ export async function getServerSideProps(context) {
159161
id: true,
160162
username: true,
161163
name: true,
164+
brandColor: true,
162165
},
163166
},
164167
eventType: {

‎pages/settings/profile.tsx

+22
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
136136
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
137137
const avatarRef = useRef<HTMLInputElement>(null!);
138138
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
139+
const brandColorRef = useRef<HTMLInputElement>(null!);
140+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
139141
const hideBrandingRef = useRef<HTMLInputElement>(null!);
140142
const [selectedTheme, setSelectedTheme] = useState<OptionTypeBase>();
141143
const [selectedTimeZone, setSelectedTimeZone] = useState<ITimezone>(props.user.timeZone);
@@ -166,6 +168,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
166168
const enteredName = nameRef.current.value;
167169
const enteredDescription = descriptionRef.current.value;
168170
const enteredAvatar = avatarRef.current.value;
171+
const enteredBrandColor = brandColorRef.current.value;
169172
const enteredTimeZone = typeof selectedTimeZone === "string" ? selectedTimeZone : selectedTimeZone.value;
170173
const enteredWeekStartDay = selectedWeekStartDay.value;
171174
const enteredHideBranding = hideBrandingRef.current.checked;
@@ -182,6 +185,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
182185
weekStart: asStringOrUndefined(enteredWeekStartDay),
183186
hideBranding: enteredHideBranding,
184187
theme: asStringOrNull(selectedTheme?.value),
188+
brandColor: enteredBrandColor,
185189
locale: enteredLanguage,
186190
});
187191
}
@@ -370,6 +374,23 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
370374
</div>
371375
</div>
372376
</div>
377+
<div>
378+
<label htmlFor="brandColor" className="block text-sm font-medium text-gray-700">
379+
{t("brand_color")}
380+
</label>
381+
<div className="flex mt-1">
382+
<input
383+
ref={brandColorRef}
384+
type="text"
385+
name="brandColor"
386+
id="brandColor"
387+
placeholder="#hex-code"
388+
className="block w-full px-3 py-2 mt-1 border border-gray-300 rounded-sm shadow-sm focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
389+
defaultValue={props.user.brandColor}
390+
/>
391+
</div>
392+
<hr className="mt-6" />
393+
</div>
373394
<div>
374395
<div className="relative flex items-start">
375396
<div className="flex items-center h-5">
@@ -434,6 +455,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
434455
hideBranding: true,
435456
theme: true,
436457
plan: true,
458+
brandColor: true,
437459
},
438460
});
439461

‎pages/success.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { isBrandingHidden } from "@lib/isBrandingHidden";
1818
import prisma from "@lib/prisma";
1919
import { inferSSRProps } from "@lib/types/inferSSRProps";
2020

21+
import CustomBranding from "@components/CustomBranding";
2122
import { HeadSeo } from "@components/seo/head-seo";
2223
import Button from "@components/ui/Button";
2324

@@ -89,6 +90,7 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>)
8990
title={needsConfirmation ? t("booking_submitted") : t("booking_confirmed")}
9091
description={needsConfirmation ? t("booking_submitted") : t("booking_confirmed")}
9192
/>
93+
<CustomBranding val={props.profile.brandColor} />
9294
<main className="max-w-3xl py-24 mx-auto">
9395
<div className="fixed inset-0 z-50 overflow-y-auto">
9496
<div className="flex items-end justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
@@ -303,6 +305,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
303305
hideBranding: true,
304306
plan: true,
305307
theme: true,
308+
brandColor: true,
306309
},
307310
},
308311
team: {
@@ -330,6 +333,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
330333
hideBranding: true,
331334
plan: true,
332335
theme: true,
336+
brandColor: true,
333337
},
334338
});
335339
if (user) {
@@ -346,6 +350,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
346350
const profile = {
347351
name: eventType.team?.name || eventType.users[0]?.name || null,
348352
theme: (!eventType.team?.name && eventType.users[0]?.theme) || null,
353+
brandColor: eventType.team ? null : eventType.users[0].brandColor,
349354
};
350355

351356
return {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-- AlterTable
2+
ALTER TABLE "users" ADD COLUMN "brandColor" TEXT NOT NULL DEFAULT E'#292929';
3+
UPDATE "users" SET "brandColor" = '#292929';

‎prisma/schema.prisma

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ model User {
9494
plan UserPlan @default(PRO)
9595
Schedule Schedule[]
9696
webhooks Webhook[]
97+
brandColor String @default("#292929")
9798
9899
@@map(name: "users")
99100
}

‎public/static/locales/en/common.json

+1
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@
438438
"timezone": "Timezone",
439439
"first_day_of_week": "First Day of Week",
440440
"single_theme": "Single Theme",
441+
"brand_color": "Brand Color",
441442
"file_not_named": "File is not named [idOrSlug]/[user]",
442443
"create_team": "Create Team",
443444
"name": "Name",

‎server/createContext.ts

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ async function getUserFromSession({
4343
hideBranding: true,
4444
avatar: true,
4545
twoFactorEnabled: true,
46+
brandColor: true,
4647
credentials: {
4748
select: {
4849
id: true,

‎server/routers/viewer.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const loggedInViewerRouter = createProtectedRouter()
5555
createdDate,
5656
completedOnboarding,
5757
twoFactorEnabled,
58+
brandColor,
5859
} = ctx.user;
5960
const me = {
6061
id,
@@ -69,6 +70,7 @@ const loggedInViewerRouter = createProtectedRouter()
6970
createdDate,
7071
completedOnboarding,
7172
twoFactorEnabled,
73+
brandColor,
7274
};
7375
return me;
7476
},
@@ -444,6 +446,7 @@ const loggedInViewerRouter = createProtectedRouter()
444446
timeZone: z.string().optional(),
445447
weekStart: z.string().optional(),
446448
hideBranding: z.boolean().optional(),
449+
brandColor: z.string().optional(),
447450
theme: z.string().optional().nullable(),
448451
completedOnboarding: z.boolean().optional(),
449452
locale: z.string().optional(),

1 commit comments

Comments
 (1)

vercel[bot] commented on Nov 16, 2021

@vercel[bot]
Please sign in to comment.