feat: Allow / disallow SEO indexing on booking pages (#10566)
Co-authored-by: Omar López <zomars@me.com>pull/10764/head
parent
e148ee7823
commit
b0d1720d92
|
@ -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);
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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;
|
|
@ -212,6 +212,10 @@ model User {
|
|||
away Boolean @default(false)
|
||||
// participate in dynamic group booking or not
|
||||
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(),
|
||||
|
|
Loading…
Reference in New Issue