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 { Provider } from "next-auth/client";
|
||||
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 DynamicIntercomProvider from "@ee/lib/intercom/providerDynamic";
|
||||
|
@ -12,24 +12,38 @@ import { trpc } from "./trpc";
|
|||
|
||||
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 passedProps = {
|
||||
...props,
|
||||
pageProps: { ...i18n },
|
||||
router: { locale },
|
||||
pageProps: {
|
||||
...props.pageProps,
|
||||
...i18n,
|
||||
},
|
||||
router: locale ? { locale } : props.router,
|
||||
} as unknown as ComponentProps<typeof I18nextAdapter>;
|
||||
return <I18nextAdapter {...passedProps} />;
|
||||
};
|
||||
|
||||
const AppProviders = (props: AppProps) => {
|
||||
const AppProviders = (props: AppPropsWithChildren) => {
|
||||
const session = trpc.useQuery(["viewer.session"]).data;
|
||||
return (
|
||||
<TelemetryProvider value={createTelemetryClient()}>
|
||||
<IdProvider>
|
||||
<DynamicIntercomProvider>
|
||||
<Provider session={session || undefined}>
|
||||
<CustomI18nextProvider>{props.children}</CustomI18nextProvider>
|
||||
<CustomI18nextProvider {...props}>{props.children}</CustomI18nextProvider>
|
||||
</Provider>
|
||||
</DynamicIntercomProvider>
|
||||
</IdProvider>
|
||||
|
|
|
@ -12,18 +12,22 @@ import EventTypeDescription from "@components/eventtype/EventTypeDescription";
|
|||
import { HeadSeo } from "@components/seo/head-seo";
|
||||
import Avatar from "@components/ui/Avatar";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
export default function User(props: inferSSRProps<typeof getServerSideProps>) {
|
||||
const { isReady } = useTheme(props.user.theme);
|
||||
const { user, eventTypes } = props;
|
||||
const { t } = useLocale();
|
||||
|
||||
const nameOrUsername = user.name || user.username || "";
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeadSeo
|
||||
title={user.name || user.username}
|
||||
description={user.name || user.username}
|
||||
name={user.name || user.username}
|
||||
avatar={user.avatar}
|
||||
title={nameOrUsername}
|
||||
description={nameOrUsername}
|
||||
name={nameOrUsername}
|
||||
avatar={user.avatar || undefined}
|
||||
/>
|
||||
{isReady && (
|
||||
<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">
|
||||
<Avatar
|
||||
imageSrc={user.avatar}
|
||||
displayName={user.name}
|
||||
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">
|
||||
{user.name || user.username}
|
||||
{nameOrUsername}
|
||||
</h1>
|
||||
<p className="text-neutral-500 dark:text-white">{user.bio}</p>
|
||||
</div>
|
||||
|
@ -72,6 +76,8 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
|
|||
}
|
||||
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
const username = (context.query.user as string).toLowerCase();
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
|
@ -138,6 +144,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
props: {
|
||||
user,
|
||||
eventTypes,
|
||||
trpcState: ssr.dehydrate(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -7,6 +7,8 @@ import { inferSSRProps } from "@lib/types/inferSSRProps";
|
|||
|
||||
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
export type AvailabilityPageProps = inferSSRProps<typeof getServerSideProps>;
|
||||
|
||||
export default function Type(props: AvailabilityPageProps) {
|
||||
|
@ -14,6 +16,7 @@ export default function Type(props: AvailabilityPageProps) {
|
|||
}
|
||||
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const ssr = await ssrInit(context);
|
||||
// get query params and typecast them to string
|
||||
// (would be even better to assert them instead of typecasting)
|
||||
const userParam = asStringOrNull(context.query.user);
|
||||
|
@ -185,6 +188,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
date: dateParam,
|
||||
eventType: eventTypeObject,
|
||||
workingHours,
|
||||
trpcState: ssr.dehydrate(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -9,6 +9,8 @@ import { inferSSRProps } from "@lib/types/inferSSRProps";
|
|||
|
||||
import BookingPage from "@components/booking/pages/BookingPage";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
|
@ -19,6 +21,8 @@ export default function Book(props: BookPageProps) {
|
|||
}
|
||||
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
username: asStringOrThrow(context.query.user),
|
||||
|
@ -107,6 +111,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
},
|
||||
eventType: eventTypeObject,
|
||||
booking,
|
||||
trpcState: ssr.dehydrate(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { DefaultSeo } from "next-seo";
|
||||
import type { AppProps as NextAppProps } from "next/app";
|
||||
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 I18nLanguageHandler from "@components/I18nLanguageHandler";
|
||||
|
@ -16,12 +15,6 @@ import { Maybe } from "@trpc/server";
|
|||
|
||||
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) {
|
||||
const { Component, pageProps, err } = props;
|
||||
return (
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { NextApiRequest } from "next";
|
||||
import { GetServerSidePropsContext, NextApiRequest } from "next";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
|
||||
import { getSession, Session } from "@lib/auth";
|
||||
|
@ -11,7 +11,15 @@ import * as trpc from "@trpc/server";
|
|||
import { Maybe } from "@trpc/server";
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
|
@ -80,7 +88,7 @@ async function getUserFromSession({ session, req }: { session: Maybe<Session>; r
|
|||
* Creates context for an incoming request
|
||||
* @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
|
||||
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