fix i18n flicker on booking pages (#1013)
This commit is contained in:
parent
b8e8319b23
commit
c28d800aa9
8 changed files with 76 additions and 44 deletions
|
@ -1,7 +1,7 @@
|
||||||
import { IdProvider } from "@radix-ui/react-id";
|
import { IdProvider } from "@radix-ui/react-id";
|
||||||
import { Provider } from "next-auth/client";
|
import { Provider } from "next-auth/client";
|
||||||
import { appWithTranslation } from "next-i18next";
|
import { appWithTranslation } from "next-i18next";
|
||||||
import { AppProps } from "next/dist/shared/lib/router/router";
|
import type { AppProps as NextAppProps } from "next/app";
|
||||||
import React, { ComponentProps, ReactNode } from "react";
|
import React, { ComponentProps, ReactNode } from "react";
|
||||||
|
|
||||||
import DynamicIntercomProvider from "@ee/lib/intercom/providerDynamic";
|
import DynamicIntercomProvider from "@ee/lib/intercom/providerDynamic";
|
||||||
|
@ -12,24 +12,38 @@ import { trpc } from "./trpc";
|
||||||
|
|
||||||
const I18nextAdapter = appWithTranslation(({ children }: { children?: ReactNode }) => <>{children}</>);
|
const I18nextAdapter = appWithTranslation(({ children }: { children?: ReactNode }) => <>{children}</>);
|
||||||
|
|
||||||
const CustomI18nextProvider = (props: { children: ReactNode }) => {
|
// Workaround for https://github.com/vercel/next.js/issues/8592
|
||||||
|
export type AppProps = NextAppProps & {
|
||||||
|
/** Will be defined only is there was an error */
|
||||||
|
err?: Error;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AppPropsWithChildren = AppProps & {
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomI18nextProvider = (props: AppPropsWithChildren) => {
|
||||||
const { i18n, locale } = trpc.useQuery(["viewer.i18n"]).data ?? {};
|
const { i18n, locale } = trpc.useQuery(["viewer.i18n"]).data ?? {};
|
||||||
|
|
||||||
const passedProps = {
|
const passedProps = {
|
||||||
...props,
|
...props,
|
||||||
pageProps: { ...i18n },
|
pageProps: {
|
||||||
router: { locale },
|
...props.pageProps,
|
||||||
|
...i18n,
|
||||||
|
},
|
||||||
|
router: locale ? { locale } : props.router,
|
||||||
} as unknown as ComponentProps<typeof I18nextAdapter>;
|
} as unknown as ComponentProps<typeof I18nextAdapter>;
|
||||||
return <I18nextAdapter {...passedProps} />;
|
return <I18nextAdapter {...passedProps} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AppProviders = (props: AppProps) => {
|
const AppProviders = (props: AppPropsWithChildren) => {
|
||||||
const session = trpc.useQuery(["viewer.session"]).data;
|
const session = trpc.useQuery(["viewer.session"]).data;
|
||||||
return (
|
return (
|
||||||
<TelemetryProvider value={createTelemetryClient()}>
|
<TelemetryProvider value={createTelemetryClient()}>
|
||||||
<IdProvider>
|
<IdProvider>
|
||||||
<DynamicIntercomProvider>
|
<DynamicIntercomProvider>
|
||||||
<Provider session={session || undefined}>
|
<Provider session={session || undefined}>
|
||||||
<CustomI18nextProvider>{props.children}</CustomI18nextProvider>
|
<CustomI18nextProvider {...props}>{props.children}</CustomI18nextProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
</DynamicIntercomProvider>
|
</DynamicIntercomProvider>
|
||||||
</IdProvider>
|
</IdProvider>
|
||||||
|
|
|
@ -12,18 +12,22 @@ import EventTypeDescription from "@components/eventtype/EventTypeDescription";
|
||||||
import { HeadSeo } from "@components/seo/head-seo";
|
import { HeadSeo } from "@components/seo/head-seo";
|
||||||
import Avatar from "@components/ui/Avatar";
|
import Avatar from "@components/ui/Avatar";
|
||||||
|
|
||||||
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
export default function User(props: inferSSRProps<typeof getServerSideProps>) {
|
export default function User(props: inferSSRProps<typeof getServerSideProps>) {
|
||||||
const { isReady } = useTheme(props.user.theme);
|
const { isReady } = useTheme(props.user.theme);
|
||||||
const { user, eventTypes } = props;
|
const { user, eventTypes } = props;
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
|
||||||
|
const nameOrUsername = user.name || user.username || "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeadSeo
|
<HeadSeo
|
||||||
title={user.name || user.username}
|
title={nameOrUsername}
|
||||||
description={user.name || user.username}
|
description={nameOrUsername}
|
||||||
name={user.name || user.username}
|
name={nameOrUsername}
|
||||||
avatar={user.avatar}
|
avatar={user.avatar || undefined}
|
||||||
/>
|
/>
|
||||||
{isReady && (
|
{isReady && (
|
||||||
<div className="bg-neutral-50 dark:bg-black h-screen">
|
<div className="bg-neutral-50 dark:bg-black h-screen">
|
||||||
|
@ -31,11 +35,11 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
|
||||||
<div className="mb-8 text-center">
|
<div className="mb-8 text-center">
|
||||||
<Avatar
|
<Avatar
|
||||||
imageSrc={user.avatar}
|
imageSrc={user.avatar}
|
||||||
displayName={user.name}
|
|
||||||
className="mx-auto w-24 h-24 rounded-full mb-4"
|
className="mx-auto w-24 h-24 rounded-full mb-4"
|
||||||
|
alt={nameOrUsername}
|
||||||
/>
|
/>
|
||||||
<h1 className="font-cal text-3xl font-bold text-neutral-900 dark:text-white mb-1">
|
<h1 className="font-cal text-3xl font-bold text-neutral-900 dark:text-white mb-1">
|
||||||
{user.name || user.username}
|
{nameOrUsername}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-neutral-500 dark:text-white">{user.bio}</p>
|
<p className="text-neutral-500 dark:text-white">{user.bio}</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -72,6 +76,8 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||||
|
const ssr = await ssrInit(context);
|
||||||
|
|
||||||
const username = (context.query.user as string).toLowerCase();
|
const username = (context.query.user as string).toLowerCase();
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
|
@ -138,6 +144,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||||
props: {
|
props: {
|
||||||
user,
|
user,
|
||||||
eventTypes,
|
eventTypes,
|
||||||
|
trpcState: ssr.dehydrate(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
|
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
|
||||||
|
|
||||||
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
export type AvailabilityPageProps = inferSSRProps<typeof getServerSideProps>;
|
export type AvailabilityPageProps = inferSSRProps<typeof getServerSideProps>;
|
||||||
|
|
||||||
export default function Type(props: AvailabilityPageProps) {
|
export default function Type(props: AvailabilityPageProps) {
|
||||||
|
@ -14,6 +16,7 @@ export default function Type(props: AvailabilityPageProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||||
|
const ssr = await ssrInit(context);
|
||||||
// get query params and typecast them to string
|
// get query params and typecast them to string
|
||||||
// (would be even better to assert them instead of typecasting)
|
// (would be even better to assert them instead of typecasting)
|
||||||
const userParam = asStringOrNull(context.query.user);
|
const userParam = asStringOrNull(context.query.user);
|
||||||
|
@ -185,6 +188,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||||
date: dateParam,
|
date: dateParam,
|
||||||
eventType: eventTypeObject,
|
eventType: eventTypeObject,
|
||||||
workingHours,
|
workingHours,
|
||||||
|
trpcState: ssr.dehydrate(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
import BookingPage from "@components/booking/pages/BookingPage";
|
import BookingPage from "@components/booking/pages/BookingPage";
|
||||||
|
|
||||||
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
|
|
||||||
|
@ -19,6 +21,8 @@ export default function Book(props: BookPageProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||||
|
const ssr = await ssrInit(context);
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
username: asStringOrThrow(context.query.user),
|
username: asStringOrThrow(context.query.user),
|
||||||
|
@ -107,6 +111,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||||
},
|
},
|
||||||
eventType: eventTypeObject,
|
eventType: eventTypeObject,
|
||||||
booking,
|
booking,
|
||||||
|
trpcState: ssr.dehydrate(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { DefaultSeo } from "next-seo";
|
import { DefaultSeo } from "next-seo";
|
||||||
import type { AppProps as NextAppProps } from "next/app";
|
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
|
|
||||||
import AppProviders from "@lib/app-providers";
|
import AppProviders, { AppProps } from "@lib/app-providers";
|
||||||
import { seoConfig } from "@lib/config/next-seo.config";
|
import { seoConfig } from "@lib/config/next-seo.config";
|
||||||
|
|
||||||
import I18nLanguageHandler from "@components/I18nLanguageHandler";
|
import I18nLanguageHandler from "@components/I18nLanguageHandler";
|
||||||
|
@ -16,12 +15,6 @@ import { Maybe } from "@trpc/server";
|
||||||
|
|
||||||
import "../styles/globals.css";
|
import "../styles/globals.css";
|
||||||
|
|
||||||
// Workaround for https://github.com/vercel/next.js/issues/8592
|
|
||||||
export type AppProps = NextAppProps & {
|
|
||||||
/** Will be defined only is there was an error */
|
|
||||||
err?: Error;
|
|
||||||
};
|
|
||||||
|
|
||||||
function MyApp(props: AppProps) {
|
function MyApp(props: AppProps) {
|
||||||
const { Component, pageProps, err } = props;
|
const { Component, pageProps, err } = props;
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { NextApiRequest } from "next";
|
import { GetServerSidePropsContext, NextApiRequest } from "next";
|
||||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||||
|
|
||||||
import { getSession, Session } from "@lib/auth";
|
import { getSession, Session } from "@lib/auth";
|
||||||
|
@ -11,7 +11,15 @@ import * as trpc from "@trpc/server";
|
||||||
import { Maybe } from "@trpc/server";
|
import { Maybe } from "@trpc/server";
|
||||||
import * as trpcNext from "@trpc/server/adapters/next";
|
import * as trpcNext from "@trpc/server/adapters/next";
|
||||||
|
|
||||||
async function getUserFromSession({ session, req }: { session: Maybe<Session>; req: NextApiRequest }) {
|
type CreateContextOptions = trpcNext.CreateNextContextOptions | GetServerSidePropsContext;
|
||||||
|
|
||||||
|
async function getUserFromSession({
|
||||||
|
session,
|
||||||
|
req,
|
||||||
|
}: {
|
||||||
|
session: Maybe<Session>;
|
||||||
|
req: CreateContextOptions["req"];
|
||||||
|
}) {
|
||||||
if (!session?.user?.id) {
|
if (!session?.user?.id) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -80,7 +88,7 @@ async function getUserFromSession({ session, req }: { session: Maybe<Session>; r
|
||||||
* Creates context for an incoming request
|
* Creates context for an incoming request
|
||||||
* @link https://trpc.io/docs/context
|
* @link https://trpc.io/docs/context
|
||||||
*/
|
*/
|
||||||
export const createContext = async ({ req, res }: trpcNext.CreateNextContextOptions) => {
|
export const createContext = async ({ req, res }: CreateContextOptions) => {
|
||||||
// for API-response caching see https://trpc.io/docs/caching
|
// for API-response caching see https://trpc.io/docs/caching
|
||||||
const session = await getSession({ req });
|
const session = await getSession({ req });
|
||||||
|
|
||||||
|
|
22
server/lib/ssr.ts
Normal file
22
server/lib/ssr.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { GetServerSidePropsContext } from "next";
|
||||||
|
import superjson from "superjson";
|
||||||
|
|
||||||
|
import { createContext } from "@server/createContext";
|
||||||
|
import { createSSGHelpers } from "@trpc/react/ssg";
|
||||||
|
|
||||||
|
import { appRouter } from "../routers/_app";
|
||||||
|
|
||||||
|
export async function ssrInit(context: GetServerSidePropsContext) {
|
||||||
|
const ctx = await createContext(context);
|
||||||
|
|
||||||
|
const ssr = createSSGHelpers({
|
||||||
|
router: appRouter,
|
||||||
|
transformer: superjson,
|
||||||
|
ctx,
|
||||||
|
});
|
||||||
|
|
||||||
|
// always preload "viewer.i18n"
|
||||||
|
await ssr.fetchQuery("viewer.i18n");
|
||||||
|
|
||||||
|
return ssr;
|
||||||
|
}
|
|
@ -1,21 +0,0 @@
|
||||||
import prisma from "@lib/prisma";
|
|
||||||
|
|
||||||
import { createSSGHelpers } from "@trpc/react/ssg";
|
|
||||||
|
|
||||||
import { appRouter } from "./routers/_app";
|
|
||||||
|
|
||||||
export const ssg = createSSGHelpers({
|
|
||||||
router: appRouter,
|
|
||||||
ctx: {
|
|
||||||
prisma,
|
|
||||||
session: null,
|
|
||||||
user: null,
|
|
||||||
i18n: {
|
|
||||||
_nextI18Next: {
|
|
||||||
initialI18nStore: null,
|
|
||||||
userConfig: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
locale: "en",
|
|
||||||
},
|
|
||||||
});
|
|
Loading…
Reference in a new issue