refactor: add next-seo (#531)

* refactor: add next-seo

* refactor: change naming of seo component
This commit is contained in:
Mihai C 2021-08-27 15:35:20 +03:00 committed by GitHub
parent fc50821282
commit a37411b8af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 290 additions and 298 deletions

View file

@ -3,7 +3,7 @@ import React, { Fragment, useEffect, useState } from "react";
import { useRouter } from "next/router";
import { signOut, useSession } from "next-auth/client";
import { Menu, Transition } from "@headlessui/react";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../lib/telemetry";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
import { SelectorIcon } from "@heroicons/react/outline";
import {
CalendarIcon,
@ -20,6 +20,7 @@ import classNames from "@lib/classNames";
import { Toaster } from "react-hot-toast";
import Avatar from "@components/Avatar";
import { User } from "@prisma/client";
import { HeadSeo } from "@components/seo/head-seo";
export default function Shell(props) {
const router = useRouter();
@ -70,8 +71,18 @@ export default function Shell(props) {
router.replace("/auth/login");
}
const pageTitle = typeof props.heading === "string" ? props.heading : props.title;
return session ? (
<>
<HeadSeo
title={pageTitle}
description={props.subtitle}
nextSeoProps={{
nofollow: true,
noindex: true,
}}
/>
<div>
<Toaster position="bottom-right" />
</div>

101
components/seo/head-seo.tsx Normal file
View file

@ -0,0 +1,101 @@
import { NextSeo, NextSeoProps } from "next-seo";
import React from "react";
import { getBrowserInfo } from "@lib/core/browser/browser.utils";
import { getSeoImage, seoConfig } from "@lib/config/next-seo.config";
import merge from "lodash.merge";
export type HeadSeoProps = {
title: string;
description: string;
siteName?: string;
name?: string;
avatar?: string;
url?: string;
canonical?: string;
nextSeoProps?: NextSeoProps;
};
/**
* Build full seo tags from title, desc, canonical and url
*/
const buildSeoMeta = (pageProps: {
title: string;
description: string;
image: string;
siteName?: string;
url?: string;
canonical?: string;
}): NextSeoProps => {
const { title, description, image, canonical, siteName = seoConfig.headSeo.siteName } = pageProps;
return {
title: title,
canonical: canonical,
openGraph: {
site_name: siteName,
type: "website",
title: title,
description: description,
images: [
{
url: image,
//width: 1077,
//height: 565,
//alt: "Alt image"
},
],
},
additionalMetaTags: [
{
property: "name",
content: title,
},
{
property: "description",
content: description,
},
{
name: "description",
content: description,
},
{
property: "image",
content: image,
},
],
};
};
const constructImage = (name: string, avatar: string, description: string): string => {
return (
encodeURIComponent("Meet **" + name + "** <br>" + description).replace(/'/g, "%27") +
".png?md=1&images=https%3A%2F%2Fcalendso.com%2Fcalendso-logo-white.svg&images=" +
encodeURIComponent(avatar)
);
};
export const HeadSeo: React.FC<HeadSeoProps & { children?: never }> = (props) => {
const defaultUrl = getBrowserInfo()?.url;
const image = getSeoImage("default");
const {
title,
description,
name = null,
avatar = null,
siteName,
canonical = defaultUrl,
nextSeoProps = {},
} = props;
const pageTitle = title + " | Calendso";
let seoObject = buildSeoMeta({ title: pageTitle, image, description, canonical, siteName });
if (name && avatar) {
const pageImage = getSeoImage("ogImage") + constructImage(name, avatar, description);
seoObject = buildSeoMeta({ title: pageTitle, description, image: pageImage, canonical, siteName });
}
const seoProps: NextSeoProps = merge(nextSeoProps, seoObject);
return <NextSeo {...seoProps} />;
};

View file

@ -0,0 +1,27 @@
import { DefaultSeoProps } from "next-seo";
import { HeadSeoProps } from "@components/seo/head-seo";
const seoImages = {
default: "https://calendso.com/og-image.png",
ogImage: "https://og-image-one-pi.vercel.app/",
};
export const getSeoImage = (key: keyof typeof seoImages): string => {
return seoImages[key];
};
export const seoConfig: {
headSeo: Required<Pick<HeadSeoProps, "siteName">>;
defaultNextSeo: DefaultSeoProps;
} = {
headSeo: {
siteName: "Calendso",
},
defaultNextSeo: {
twitter: {
handle: "@calendso",
site: "@Calendso",
cardType: "summary_large_image",
},
},
} as const;

View file

@ -0,0 +1,22 @@
export const isBrowser = () => typeof window !== "undefined";
type BrowserInfo = {
url: string;
path: string;
referrer: string;
title: string;
query: string;
};
export const getBrowserInfo = (): Partial<BrowserInfo> => {
if (!isBrowser()) {
return {};
}
return {
url: window.document.location?.href ?? undefined,
path: window.document.location?.pathname ?? undefined,
referrer: window.document?.referrer ?? undefined,
title: window.document.title ?? undefined,
query: window.document.location?.search,
};
};

View file

@ -42,6 +42,7 @@
"lodash.throttle": "^4.1.1",
"next": "^10.2.3",
"next-auth": "^3.28.0",
"next-seo": "^4.26.0",
"next-transpile-modules": "^8.0.0",
"nodemailer": "^6.6.3",
"react": "17.0.2",

View file

@ -3,7 +3,7 @@ import { BookOpenIcon, CheckIcon, CodeIcon, DocumentTextIcon } from "@heroicons/
import { useRouter } from "next/router";
import React from "react";
import Link from "next/link";
import Head from "next/head";
import { HeadSeo } from "@components/seo/head-seo";
const links = [
{
@ -32,9 +32,14 @@ export default function Custom404() {
return (
<>
<Head>
<title>404: This page could not be found.</title>
</Head>
<HeadSeo
title="404: This page could not be found."
description="404: This page could not be found."
nextSeoProps={{
nofollow: true,
noindex: true,
}}
/>
<div className="bg-white min-h-screen px-4">
<main className="max-w-xl mx-auto pb-6 pt-16 sm:pt-24">
<div className="text-center">

View file

@ -1,5 +1,5 @@
import { GetServerSideProps } from "next";
import Head from "next/head";
import { HeadSeo } from "@components/seo/head-seo";
import Link from "next/link";
import prisma, { whereAndSelect } from "@lib/prisma";
import Avatar from "@components/Avatar";
@ -48,59 +48,12 @@ export default function User(props): User {
));
return (
<>
<Head>
<title>{props.user.name || props.user.username} | Calendso</title>
<link rel="icon" href="/favicon.ico" />
<meta name="title" content={"Meet " + (props.user.name || props.user.username) + " via Calendso"} />
<meta name="description" content={"Book a time with " + (props.user.name || props.user.username)} />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://calendso/" />
<meta
property="og:title"
content={"Meet " + (props.user.name || props.user.username) + " via Calendso"}
/>
<meta
property="og:description"
content={"Book a time with " + (props.user.name || props.user.username)}
/>
<meta
property="og:image"
content={
"https://og-image-one-pi.vercel.app/" +
encodeURIComponent("Meet **" + (props.user.name || props.user.username) + "** <br>").replace(
/'/g,
"%27"
) +
".png?md=1&images=https%3A%2F%2Fcalendso.com%2Fcalendso-logo-white.svg&images=" +
encodeURIComponent(props.user.avatar)
}
/>
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://calendso/" />
<meta
property="twitter:title"
content={"Meet " + (props.user.name || props.user.username) + " via Calendso"}
/>
<meta
property="twitter:description"
content={"Book a time with " + (props.user.name || props.user.username)}
/>
<meta
property="twitter:image"
content={
"https://og-image-one-pi.vercel.app/" +
encodeURIComponent("Meet **" + (props.user.name || props.user.username) + "** <br>").replace(
/'/g,
"%27"
) +
".png?md=1&images=https%3A%2F%2Fcalendso.com%2Fcalendso-logo-white.svg&images=" +
encodeURIComponent(props.user.avatar)
}
/>
</Head>
<HeadSeo
title={props.user.name || props.user.username}
description={props.user.name || props.user.username}
name={props.user.name || props.user.username}
avatar={props.user.avatar}
/>
{isReady && (
<div className="bg-neutral-50 dark:bg-black h-screen">
<main className="max-w-3xl mx-auto py-24 px-4">

View file

@ -6,16 +6,16 @@ import prisma from "@lib/prisma";
import * as Collapsible from "@radix-ui/react-collapsible";
import dayjs, { Dayjs } from "dayjs";
import { GetServerSidePropsContext, InferGetServerSidePropsType } from "next";
import Head from "next/head";
import { HeadSeo } from "@components/seo/head-seo";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import Avatar from "@components/Avatar";
import AvailableTimes from "../../components/booking/AvailableTimes";
import DatePicker from "../../components/booking/DatePicker";
import TimeOptions from "../../components/booking/TimeOptions";
import PoweredByCalendso from "../../components/ui/PoweredByCalendso";
import { timeZone } from "../../lib/clock";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry";
import AvailableTimes from "@components/booking/AvailableTimes";
import DatePicker from "@components/booking/DatePicker";
import TimeOptions from "@components/booking/TimeOptions";
import PoweredByCalendso from "@components/ui/PoweredByCalendso";
import { timeZone } from "@lib/clock";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
import { asStringOrNull } from "@lib/asStringOrNull";
export default function Type(props: InferGetServerSidePropsType<typeof getServerSideProps>) {
@ -82,53 +82,14 @@ export default function Type(props: InferGetServerSidePropsType<typeof getServer
return (
<>
<Head>
<title>
{rescheduleUid && "Reschedule"} {props.eventType.title} | {props.user.name || props.user.username} |
Calendso
</title>
<meta name="title" content={"Meet " + (props.user.name || props.user.username) + " via Calendso"} />
<meta name="description" content={props.eventType.description} />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://calendso/" />
<meta
property="og:title"
content={"Meet " + (props.user.name || props.user.username) + " via Calendso"}
/>
<meta property="og:description" content={props.eventType.description} />
<meta
property="og:image"
content={
"https://og-image-one-pi.vercel.app/" +
encodeURIComponent(
"Meet **" + (props.user.name || props.user.username) + "** <br>" + props.eventType.description
).replace(/'/g, "%27") +
".png?md=1&images=https%3A%2F%2Fcalendso.com%2Fcalendso-logo-white.svg&images=" +
encodeURIComponent(props.user.avatar)
}
/>
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://calendso/" />
<meta
property="twitter:title"
content={"Meet " + (props.user.name || props.user.username) + " via Calendso"}
/>
<meta property="twitter:description" content={props.eventType.description} />
<meta
property="twitter:image"
content={
"https://og-image-one-pi.vercel.app/" +
encodeURIComponent(
"Meet **" + (props.user.name || props.user.username) + "** <br>" + props.eventType.description
).replace(/'/g, "%27") +
".png?md=1&images=https%3A%2F%2Fcalendso.com%2Fcalendso-logo-white.svg&images=" +
encodeURIComponent(props.user.avatar)
}
/>
</Head>
<HeadSeo
title={`${rescheduleUid ? "Reschedule" : ""} ${props.eventType.title} | ${
props.user.name || props.user.username
}`}
description={`${rescheduleUid ? "Reschedule" : ""} ${props.eventType.title}`}
name={props.user.name || props.user.username}
avatar={props.user.avatar}
/>
{isReady && (
<div>
<main

View file

@ -1,16 +1,16 @@
import Head from "next/head";
import { HeadSeo } from "@components/seo/head-seo";
import Link from "next/link";
import { useRouter } from "next/router";
import { CalendarIcon, ClockIcon, ExclamationIcon, LocationMarkerIcon } from "@heroicons/react/solid";
import prisma, { whereAndSelect } from "../../lib/prisma";
import prisma, { whereAndSelect } from "@lib/prisma";
import { EventTypeCustomInputType } from "@prisma/client";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
import { useEffect, useState } from "react";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import PhoneInput from "@components/ui/form/PhoneInput";
import { LocationType } from "../../lib/location";
import { LocationType } from "@lib/location";
import Avatar from "@components/Avatar";
import { Button } from "@components/ui/Button";
import Theme from "@components/Theme";
@ -150,13 +150,14 @@ export default function Book(props: any): JSX.Element {
return (
isReady && (
<div>
<Head>
<title>
{rescheduleUid ? "Reschedule" : "Confirm"} your {props.eventType.title} with{" "}
{props.user.name || props.user.username} | Calendso
</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<HeadSeo
title={`${rescheduleUid ? "Reschedule" : "Confirm"} your ${props.eventType.title} with ${
props.user.name || props.user.username
}`}
description={`${rescheduleUid ? "Reschedule" : "Confirm"} your ${props.eventType.title} with ${
props.user.name || props.user.username
}`}
/>
<main className="max-w-3xl mx-auto my-0 sm:my-24">
<div className="dark:bg-neutral-900 bg-white overflow-hidden border border-gray-200 dark:border-0 sm:rounded-sm">

View file

@ -1,7 +1,8 @@
import "../styles/globals.css";
import AppProviders from "@lib/app-providers";
import type { AppProps as NextAppProps } from "next/app";
import Head from "next/head";
import { DefaultSeo } from "next-seo";
import { seoConfig } from "@lib/config/next-seo.config";
// Workaround for https://github.com/zeit/next.js/issues/8592
export type AppProps = NextAppProps & {
@ -12,9 +13,7 @@ export type AppProps = NextAppProps & {
function MyApp({ Component, pageProps, err }: AppProps) {
return (
<AppProviders>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</Head>
<DefaultSeo {...seoConfig.defaultNextSeo} />
<Component {...pageProps} err={err} />
</AppProviders>
);

View file

@ -1,6 +1,6 @@
import { useRouter } from "next/router";
import { XIcon } from "@heroicons/react/outline";
import Head from "next/head";
import { HeadSeo } from "@components/seo/head-seo";
import Link from "next/link";
export default function Error() {
@ -13,10 +13,7 @@ export default function Error() {
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<Head>
<title>{error} - Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<HeadSeo title="Error" description="Error" />
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;

View file

@ -1,7 +1,6 @@
import { getCsrfToken } from "next-auth/client";
import prisma from "../../../lib/prisma";
import Head from "next/head";
import prisma from "@lib/prisma";
import { HeadSeo } from "@components/seo/head-seo";
import React, { useMemo } from "react";
import debounce from "lodash.debounce";
import dayjs from "dayjs";
@ -122,10 +121,7 @@ export default function Page({ resetPasswordRequest, csrfToken }: Props) {
return (
<div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
<Head>
<title>Reset Password</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<HeadSeo title="Reset Password" description="Change your password" />
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white py-8 px-4 mx-2 shadow rounded-lg sm:px-10 space-y-6">
{isRequestExpired && <Expired />}

View file

@ -1,4 +1,4 @@
import Head from "next/head";
import { HeadSeo } from "@components/seo/head-seo";
import Link from "next/link";
import React from "react";
import { getCsrfToken, getSession } from "next-auth/client";
@ -71,11 +71,7 @@ export default function ForgotPassword({ csrfToken }) {
return (
<div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
<Head>
<title>Forgot Password</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<HeadSeo title="Forgot Password" description="Forgot Password" />
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white py-8 px-4 mx-2 shadow rounded-lg sm:px-10 space-y-6">
{success && <Success />}

View file

@ -1,14 +1,11 @@
import Head from "next/head";
import { HeadSeo } from "@components/seo/head-seo";
import Link from "next/link";
import { getCsrfToken, getSession } from "next-auth/client";
export default function Login({ csrfToken }) {
return (
<div className="min-h-screen bg-neutral-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
<Head>
<title>Login</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<HeadSeo title="Login" description="Login" />
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<img className="h-6 mx-auto" src="/calendso-logo-white-word.svg" alt="Calendso Logo" />
<h2 className="mt-6 text-center text-3xl font-bold text-neutral-900">Sign in to your account</h2>

View file

@ -1,4 +1,4 @@
import Head from "next/head";
import { HeadSeo } from "@components/seo/head-seo";
import Link from "next/link";
import { CheckIcon } from "@heroicons/react/outline";
@ -9,10 +9,7 @@ export default function Logout() {
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<Head>
<title>Logged out - Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<HeadSeo title="Logged out" description="Logged out" />
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;

View file

@ -1,10 +1,10 @@
import Head from "next/head";
import { HeadSeo } from "@components/seo/head-seo";
import { useRouter } from "next/router";
import { signIn } from "next-auth/client";
import ErrorAlert from "../../components/ui/alerts/Error";
import ErrorAlert from "@components/ui/alerts/Error";
import { useState } from "react";
import { UsernameInput } from "../../components/ui/UsernameInput";
import prisma from "../../lib/prisma";
import { UsernameInput } from "@components/ui/UsernameInput";
import prisma from "@lib/prisma";
export default function Signup(props) {
const router = useRouter();
@ -54,10 +54,7 @@ export default function Signup(props) {
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<Head>
<title>Sign up</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<HeadSeo title="Sign up" description="Sign up" />
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<h2 className="text-center text-3xl font-extrabold text-gray-900">Create your account</h2>
</div>

View file

@ -1,8 +1,7 @@
import Head from "next/head";
import Link from "next/link";
import prisma from "../../lib/prisma";
import Modal from "../../components/Modal";
import Shell from "../../components/Shell";
import prisma from "@lib/prisma";
import Modal from "@components/Modal";
import Shell from "@components/Shell";
import { useRouter } from "next/router";
import { useRef, useState } from "react";
import { getSession, useSession } from "next-auth/client";
@ -115,15 +114,7 @@ export default function Availability(props) {
return (
<div>
<Head>
<title>Availability | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Shell
heading="Availability"
subtitle="Configure times when you are available for bookings.
">
<Shell heading="Availability" subtitle="Configure times when you are available for bookings.">
<div className="flex">
<div className="w-1/2 mr-2 bg-white border border-gray-200 rounded-sm">
<div className="px-4 py-5 sm:p-6">

View file

@ -4,9 +4,8 @@ import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import { GetServerSideProps } from "next";
import { getSession } from "next-auth/client";
import Head from "next/head";
import { useEffect, useState } from "react";
import Shell from "../../components/Shell";
import Shell from "@components/Shell";
dayjs.extend(utc);
@ -52,10 +51,6 @@ export default function Troubleshoot({ user }) {
return (
<div>
<Head>
<title>Troubleshoot | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Shell
heading="Troubleshoot"
subtitle="Understand why certain times are available and others are blocked.">

View file

@ -1,7 +1,6 @@
import Head from "next/head";
import prisma from "../../lib/prisma";
import prisma from "@lib/prisma";
import { getSession, useSession } from "next-auth/client";
import Shell from "../../components/Shell";
import Shell from "@components/Shell";
import { useRouter } from "next/router";
import dayjs from "dayjs";
import { Fragment } from "react";
@ -36,10 +35,6 @@ export default function Bookings({ bookings }) {
return (
<div>
<Head>
<title>Bookings | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Shell heading="Bookings" subtitle="See upcoming and past events booked through your event type links.">
<div className="-mx-4 sm:mx-auto flex flex-col">
<div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">

View file

@ -4,11 +4,11 @@ import isBetween from "dayjs/plugin/isBetween";
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import Head from "next/head";
import { HeadSeo } from "@components/seo/head-seo";
import { useRouter } from "next/router";
import { useState } from "react";
import prisma from "../../lib/prisma";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry";
import prisma from "@lib/prisma";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
dayjs.extend(isSameOrBefore);
dayjs.extend(isBetween);
@ -55,13 +55,12 @@ export default function Type(props) {
return (
<div>
<Head>
<title>
Cancel {props.booking && `${props.booking.title} | ${props.user.name || props.user.username} `}|
Calendso
</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<HeadSeo
title={`Cancel ${props.booking && props.booking.title} | ${props.user.name || props.user.username}`}
description={`Cancel ${props.booking && props.booking.title} | ${
props.user.name || props.user.username
}`}
/>
<main className="max-w-3xl mx-auto my-24">
<div className="fixed z-50 inset-0 overflow-y-auto">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">

View file

@ -1,4 +1,4 @@
import Head from "next/head";
import { HeadSeo } from "@components/seo/head-seo";
import prisma from "../../lib/prisma";
import { useRouter } from "next/router";
import dayjs from "dayjs";
@ -19,12 +19,10 @@ export default function Type(props) {
return (
<div>
<Head>
<title>
Cancelled {props.title} | {props.user.name || props.user.username} | Calendso
</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<HeadSeo
title={`Cancelled ${props.title} | ${props.user.name || props.user.username}`}
description={`Cancelled ${props.title} | ${props.user.name || props.user.username}`}
/>
<main className="max-w-3xl mx-auto my-24">
<div className="fixed z-50 inset-0 overflow-y-auto">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">

View file

@ -1,7 +1,6 @@
import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import Modal from "../../components/Modal";
import Modal from "@components/Modal";
import React, { useEffect, useRef, useState } from "react";
import Select, { OptionBase } from "react-select";
import prisma from "@lib/prisma";
@ -331,11 +330,8 @@ const EventTypePage = (props: InferGetServerSidePropsType<typeof getServerSidePr
return (
<div>
<Head>
<title>{eventType.title} | Event Type | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Shell
title={`${eventType.title} | Event Type`}
heading={
<input
ref={titleRef}

View file

@ -15,7 +15,6 @@ import {
import classNames from "@lib/classNames";
import showToast from "@lib/notification";
import { getSession, useSession } from "next-auth/client";
import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { Fragment, useRef } from "react";
@ -193,10 +192,6 @@ const EventTypesPage = (props: InferGetServerSidePropsType<typeof getServerSideP
return (
<div>
<Head>
<title>Event Types | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Shell
heading="Event Types"
subtitle="Create events to share for people to book on your calendar."

View file

@ -1,7 +1,6 @@
import Head from "next/head";
import prisma from "../../lib/prisma";
import { getIntegrationName, getIntegrationType } from "../../lib/integrations";
import Shell from "../../components/Shell";
import prisma from "@lib/prisma";
import { getIntegrationName, getIntegrationType } from "@lib/integrations";
import Shell from "@components/Shell";
import { useState } from "react";
import { useRouter } from "next/router";
import { getSession, useSession } from "next-auth/client";
@ -40,12 +39,9 @@ export default function Integration(props) {
return (
<div>
<Head>
<title>{getIntegrationName(props.integration.type)} App | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Shell heading={getIntegrationName(props.integration.type)} subtitle="Manage and delete this app.">
<Shell
heading={`${getIntegrationName(props.integration.type)} App`}
subtitle="Manage and delete this app.">
<div className="block sm:grid grid-cols-3 gap-4">
<div className="col-span-2 bg-white border border-gray-200 mb-6 overflow-hidden rounded-sm">
<div className="px-4 py-5 sm:px-6">

View file

@ -1,7 +1,6 @@
import Head from "next/head";
import Link from "next/link";
import prisma from "../../lib/prisma";
import Shell from "../../components/Shell";
import prisma from "@lib/prisma";
import Shell from "@components/Shell";
import { useEffect, useState, useRef, useCallback } from "react";
import { getSession, useSession } from "next-auth/client";
import { CheckCircleIcon, ChevronRightIcon, PlusIcon, XCircleIcon } from "@heroicons/react/solid";
@ -270,11 +269,6 @@ export default function Home({ integrations }: Props) {
return (
<div>
<Head>
<title>App Store | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Shell heading="App Store" subtitle="Connect your favourite apps." CTA={<ConnectNewAppDialog />}>
<div className="bg-white border border-gray-200 overflow-hidden rounded-sm mb-8">
{integrations.filter((ig) => ig.credential).length !== 0 ? (

View file

@ -1,15 +1,11 @@
import Head from "next/head";
import Shell from "../../components/Shell";
import SettingsShell from "../../components/Settings";
import prisma from "../../lib/prisma";
import Shell from "@components/Shell";
import SettingsShell from "@components/Settings";
import prisma from "@lib/prisma";
import { getSession } from "next-auth/client";
export default function Billing() {
return (
<Shell heading="Billing" subtitle="Manage your billing information and cancel your subscription.">
<Head>
<title>Billing | Calendso</title>
</Head>
<SettingsShell>
<div className="py-6 lg:pb-8 lg:col-span-9">
<div className="my-6">

View file

@ -1,7 +1,6 @@
import Head from "next/head";
import prisma from "../../lib/prisma";
import Shell from "../../components/Shell";
import SettingsShell from "../../components/Settings";
import prisma from "@lib/prisma";
import Shell from "@components/Shell";
import SettingsShell from "@components/Settings";
import { getSession, useSession } from "next-auth/client";
import Loader from "@components/Loader";
@ -15,10 +14,6 @@ export default function Embed(props) {
return (
<Shell heading="Embed" subtitle="Integrate with your website using our embed options.">
<Head>
<title>Embed | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<SettingsShell>
<div className="py-6 lg:pb-8 lg:col-span-9">
<div className="mb-6">

View file

@ -1,9 +1,8 @@
import Head from "next/head";
import { useRef, useState } from "react";
import prisma from "../../lib/prisma";
import Modal from "../../components/Modal";
import Shell from "../../components/Shell";
import SettingsShell from "../../components/Settings";
import prisma from "@lib/prisma";
import Modal from "@components/Modal";
import Shell from "@components/Shell";
import SettingsShell from "@components/Settings";
import { getSession, useSession } from "next-auth/client";
import Loader from "@components/Loader";
@ -45,11 +44,7 @@ export default function Settings() {
}
return (
<Shell heading="Password" subtitle="Change the password that you use to sign in to your account.">
<Head>
<title>Change Password | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Shell heading="Change Password" subtitle="Change the password that you use to sign in to your account.">
<SettingsShell>
<form className="divide-y divide-gray-200 lg:col-span-9" onSubmit={changePasswordHandler}>
<div className="py-6 lg:pb-8">

View file

@ -1,17 +1,16 @@
import { GetServerSideProps } from "next";
import Head from "next/head";
import { useEffect, useRef, useState } from "react";
import prisma from "@lib/prisma";
import Modal from "../../components/Modal";
import Shell from "../../components/Shell";
import SettingsShell from "../../components/Settings";
import Modal from "@components/Modal";
import Shell from "@components/Shell";
import SettingsShell from "@components/Settings";
import Avatar from "@components/Avatar";
import { getSession } from "next-auth/client";
import Select from "react-select";
import TimezoneSelect from "react-timezone-select";
import { UsernameInput } from "../../components/ui/UsernameInput";
import ErrorAlert from "../../components/ui/alerts/Error";
import ImageUploader from "../../components/ImageUploader";
import { UsernameInput } from "@components/ui/UsernameInput";
import ErrorAlert from "@components/ui/alerts/Error";
import ImageUploader from "@components/ImageUploader";
import crypto from "crypto";
const themeOptions = [
@ -107,10 +106,6 @@ export default function Settings(props) {
return (
<Shell heading="Profile" subtitle="Edit your profile information, which shows on your scheduling link.">
<Head>
<title>Profile | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<SettingsShell>
<form className="divide-y divide-gray-200 lg:col-span-9" onSubmit={updateProfileHandler}>
{hasErrors && <ErrorAlert message={errorMessage} />}

View file

@ -1,13 +1,12 @@
import { GetServerSideProps } from "next";
import Head from "next/head";
import Shell from "../../components/Shell";
import SettingsShell from "../../components/Settings";
import Shell from "@components/Shell";
import SettingsShell from "@components/Settings";
import { useEffect, useState } from "react";
import type { Session } from "next-auth";
import { getSession, useSession } from "next-auth/client";
import { UsersIcon } from "@heroicons/react/outline";
import TeamList from "../../components/team/TeamList";
import TeamListItem from "../../components/team/TeamListItem";
import TeamList from "@components/team/TeamList";
import TeamListItem from "@components/team/TeamListItem";
import Loader from "@components/Loader";
export default function Teams() {
@ -59,10 +58,6 @@ export default function Teams() {
return (
<Shell heading="Teams" subtitle="Create and manage teams to use collaborative features.">
<Head>
<title>Teams | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<SettingsShell>
<div className="divide-y divide-gray-200 lg:col-span-9">
<div className="py-6 lg:pb-8">

View file

@ -1,6 +1,6 @@
import Head from "next/head";
import { HeadSeo } from "@components/seo/head-seo";
import Link from "next/link";
import prisma, { whereAndSelect } from "../lib/prisma";
import prisma, { whereAndSelect } from "@lib/prisma";
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { CheckIcon } from "@heroicons/react/outline";
@ -10,7 +10,7 @@ import utc from "dayjs/plugin/utc";
import toArray from "dayjs/plugin/toArray";
import timezone from "dayjs/plugin/timezone";
import { createEvent } from "ics";
import { getEventName } from "../lib/event";
import { getEventName } from "@lib/event";
import Theme from "@components/Theme";
dayjs.extend(utc);
@ -61,13 +61,10 @@ export default function Success(props) {
return (
isReady && (
<div className="bg-neutral-50 dark:bg-neutral-900 h-screen">
<Head>
<title>
Booking {props.eventType.requiresConfirmation ? "Submitted" : "Confirmed"} | {eventName} |
Calendso
</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<HeadSeo
title={`Booking ${props.eventType.requiresConfirmation ? "Submitted" : "Confirmed"}`}
description={`Booking ${props.eventType.requiresConfirmation ? "Submitted" : "Confirmed"}`}
/>
<main className="max-w-3xl mx-auto py-24">
<div className="fixed z-50 inset-0 overflow-y-auto">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">

View file

@ -1,6 +1,5 @@
import { GetServerSideProps } from "next";
import Head from "next/head";
import { HeadSeo } from "@components/seo/head-seo";
import Theme from "@components/Theme";
import { getTeam } from "@lib/teams/getTeam";
import Team from "@components/team/screens/Team";
@ -11,11 +10,7 @@ export default function Page(props) {
return (
isReady && (
<div>
<Head>
<title>{props.team.name} | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<HeadSeo title={props.team.name} description={props.team.name} />
<main className="mx-auto py-24 px-4">
<Team team={props.team} />
</main>

View file

@ -4216,6 +4216,10 @@ next-auth@^3.28.0:
preact-render-to-string "^5.1.14"
querystring "^0.2.0"
next-seo@^4.26.0:
version "4.26.0"
resolved "https://registry.yarnpkg.com/next-seo/-/next-seo-4.26.0.tgz#4218cfae5651fdc2e330dcdf1cc1b34ce199d41c"
next-transpile-modules@^8.0.0:
version "8.0.0"
resolved "https://registry.npmjs.org/next-transpile-modules/-/next-transpile-modules-8.0.0.tgz#56375cdc25ae5d23a834195f277fc2737b26cb97"