feat: Allow / disallow SEO indexing on booking pages (#10566)

Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
Anshuman PandeyandOmar López authored and GitHub committed 2023-08-15 00:44:09 +00:00
1 parent e148ee7823
commit b0d1720d92
11 files changed
+88 -42

No files matched your search

+8
View File
@@ -36,6 +36,7 @@ import { ssrInit } from "@server/lib/ssr";
export function UserPage(props: InferGetServerSidePropsType<typeof getServerSideProps>) {
const { users, profile, eventTypes, markdownStrippedBio, entity } = props;
const [user] = users; //To be used when we only have a single user, not dynamic group
useTheme(profile.theme);
const { t } = useLocale();
@@ -82,6 +83,10 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
profile: { name: `${profile.name}`, image: null },
users: [{ username: `${user.username}`, name: `${user.name}` }],
}}
nextSeoProps={{
noindex: !profile.allowSEOIndexing,
nofollow: !profile.allowSEOIndexing,
}}
/>
<div className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "max-w-3xl" : "")}>
@@ -214,6 +219,7 @@ export type UserPageProps = {
theme: string | null;
brandColor: string;
darkBrandColor: string;
allowSEOIndexing: boolean;
};
users: Pick<User, "away" | "name" | "username" | "bio" | "verified">[];
themeBasis: string | null;
@@ -276,6 +282,7 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
away: true,
verified: true,
allowDynamicBooking: true,
allowSEOIndexing: true,
},
});
@@ -315,6 +322,7 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
theme: user.theme,
brandColor: user.brandColor,
darkBrandColor: user.darkBrandColor,
allowSEOIndexing: user.allowSEOIndexing ?? true,
};
const eventTypesWithHidden = await getEventTypesWithHiddenFromDB(user.id);
+5
View File
@@ -30,6 +30,7 @@ export default function Type({
booking,
away,
isBrandingHidden,
isSEOIndexable,
rescheduleUid,
entity,
duration,
@@ -41,6 +42,7 @@ export default function Type({
eventSlug={slug}
rescheduleUid={rescheduleUid ?? undefined}
hideBranding={isBrandingHidden}
isSEOIndexable={isSEOIndexable ?? true}
entity={entity}
/>
<Booker
@@ -128,6 +130,7 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
away: false,
trpcState: ssr.dehydrate(),
isBrandingHidden: false,
isSEOIndexable: true,
themeBasis: null,
bookingUid: bookingUid ? `${bookingUid}` : null,
rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null,
@@ -154,6 +157,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
select: {
away: true,
hideBranding: true,
allowSEOIndexing: true,
},
});
@@ -199,6 +203,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
entity: eventData.entity,
trpcState: ssr.dehydrate(),
isBrandingHidden: user?.hideBranding,
isSEOIndexable: user?.allowSEOIndexing,
themeBasis: username,
bookingUid: bookingUid ? `${bookingUid}` : null,
rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null,
@@ -116,6 +116,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
label: nameOfDay(localeProp, user.weekStart === "Sunday" ? 0 : 1),
},
allowDynamicBooking: user.allowDynamicBooking ?? true,
allowSEOIndexing: user.allowSEOIndexing ?? true,
},
});
const {
@@ -124,6 +125,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
getValues,
} = formMethods;
const isDisabled = isSubmitting || !isDirty;
return (
<Form
form={formMethods}
@@ -225,6 +227,24 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
)}
/>
</div>
<div className="mt-8">
<Controller
name="allowSEOIndexing"
control={formMethods.control}
render={() => (
<SettingsToggle
title={t("seo_indexing")}
description={t("allow_seo_indexing")}
checked={formMethods.getValues("allowSEOIndexing")}
onCheckedChange={(checked) => {
formMethods.setValue("allowSEOIndexing", checked, { shouldDirty: true });
}}
/>
)}
/>
</div>
<Button
loading={mutation.isLoading}
disabled={isDisabled}
@@ -407,6 +407,8 @@
"allow_dynamic_booking_tooltip": "Group booking links that can be created dynamically by adding multiple usernames with a '+'. example: '{{appName}}/bailey+peer'",
"allow_dynamic_booking": "Allow attendees to book you through dynamic group bookings",
"dynamic_booking": "Dynamic group links",
"allow_seo_indexing": "Allow search engines to access your public content",
"seo_indexing": "Allow SEO Indexing",
"email": "Email",
"email_placeholder": "jdoe@example.com",
"full_name": "Full name",
@@ -7,6 +7,7 @@ interface BookerSeoProps {
eventSlug: string;
rescheduleUid: string | undefined;
hideBranding?: boolean;
isSEOIndexable?: boolean;
isTeamEvent?: boolean;
entity: {
orgSlug?: string | null;
@@ -16,7 +17,7 @@ interface BookerSeoProps {
}
export const BookerSeo = (props: BookerSeoProps) => {
const { eventSlug, username, rescheduleUid, hideBranding, isTeamEvent, entity } = props;
const { eventSlug, username, rescheduleUid, hideBranding, isTeamEvent, entity, isSEOIndexable } = props;
const { t } = useLocale();
const { data: event } = trpc.viewer.public.event.useQuery(
{ username, eventSlug, isTeamEvent, org: entity.orgSlug ?? null },
@@ -41,8 +42,8 @@ export const BookerSeo = (props: BookerSeoProps) => {
],
}}
nextSeoProps={{
nofollow: event?.hidden,
noindex: event?.hidden,
nofollow: event?.hidden || !isSEOIndexable,
noindex: event?.hidden || !isSEOIndexable,
}}
isBrandingHidden={hideBranding}
/>
+1
View File
@@ -220,6 +220,7 @@ export const buildUser = <T extends Partial<UserPayload>>(user?: T): UserPayload
verified: false,
weekStart: "",
organizationId: null,
allowSEOIndexing: null,
...user,
};
};
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "allowSEOIndexing" BOOLEAN DEFAULT true;
+43 -39
View File
@@ -168,50 +168,54 @@ enum UserPermissionRole {
}
model User {
id Int @id @default(autoincrement())
username String?
name String?
id Int @id @default(autoincrement())
username String?
name String?
/// @zod.email()
email String
emailVerified DateTime?
password String?
bio String?
avatar String?
timeZone String @default("Europe/London")
weekStart String @default("Sunday")
email String
emailVerified DateTime?
password String?
bio String?
avatar String?
timeZone String @default("Europe/London")
weekStart String @default("Sunday")
// DEPRECATED - TO BE REMOVED
startTime Int @default(0)
endTime Int @default(1440)
startTime Int @default(0)
endTime Int @default(1440)
// </DEPRECATED>
bufferTime Int @default(0)
hideBranding Boolean @default(false)
theme String?
createdDate DateTime @default(now()) @map(name: "created")
trialEndsAt DateTime?
eventTypes EventType[] @relation("user_eventtype")
credentials Credential[]
teams Membership[]
bookings Booking[]
schedules Schedule[]
defaultScheduleId Int?
selectedCalendars SelectedCalendar[]
completedOnboarding Boolean @default(false)
locale String?
timeFormat Int? @default(12)
twoFactorSecret String?
twoFactorEnabled Boolean @default(false)
identityProvider IdentityProvider @default(CAL)
identityProviderId String?
availability Availability[]
invitedTo Int?
webhooks Webhook[]
brandColor String @default("#292929")
darkBrandColor String @default("#fafafa")
bufferTime Int @default(0)
hideBranding Boolean @default(false)
theme String?
createdDate DateTime @default(now()) @map(name: "created")
trialEndsAt DateTime?
eventTypes EventType[] @relation("user_eventtype")
credentials Credential[]
teams Membership[]
bookings Booking[]
schedules Schedule[]
defaultScheduleId Int?
selectedCalendars SelectedCalendar[]
completedOnboarding Boolean @default(false)
locale String?
timeFormat Int? @default(12)
twoFactorSecret String?
twoFactorEnabled Boolean @default(false)
identityProvider IdentityProvider @default(CAL)
identityProviderId String?
availability Availability[]
invitedTo Int?
webhooks Webhook[]
brandColor String @default("#292929")
darkBrandColor String @default("#fafafa")
// the location where the events will end up
destinationCalendar DestinationCalendar?
away Boolean @default(false)
destinationCalendar DestinationCalendar?
away Boolean @default(false)
// participate in dynamic group booking or not
allowDynamicBooking Boolean? @default(true)
allowDynamicBooking Boolean? @default(true)
// participate in SEO indexing or not
allowSEOIndexing Boolean? @default(true)
/// @zod.custom(imports.userMetadata)
metadata Json?
verified Boolean? @default(false)
@@ -57,6 +57,7 @@ export async function getUserFromSession(ctx: TRPCContextInner, session: Maybe<S
role: true,
organizationId: true,
allowDynamicBooking: true,
allowSEOIndexing: true,
organization: {
select: {
id: true,
@@ -42,6 +42,7 @@ export const meHandler = async ({ ctx }: MeOptions) => {
metadata: user.metadata,
defaultBookerLayouts: user.defaultBookerLayouts,
allowDynamicBooking: user.allowDynamicBooking,
allowSEOIndexing: user.allowSEOIndexing,
organizationId: user.organizationId,
organization: user.organization,
};
@@ -13,6 +13,7 @@ export const ZUpdateProfileInputSchema = z.object({
weekStart: z.string().optional(),
hideBranding: z.boolean().optional(),
allowDynamicBooking: z.boolean().optional(),
allowSEOIndexing: z.boolean().optional(),
brandColor: z.string().optional(),
darkBrandColor: z.string().optional(),
theme: z.string().optional().nullable(),