️ Use nextjs layout feature

This commit is contained in:
Luke Vella 2023-02-09 19:50:36 +00:00
parent 02ef9000a7
commit c2c000f770
9 changed files with 168 additions and 154 deletions

View file

@ -17,7 +17,6 @@ import {
UserDetailsData, UserDetailsData,
UserDetailsForm, UserDetailsForm,
} from "./forms"; } from "./forms";
import StandardLayout from "./layouts/standard-layout";
import Steps from "./steps"; import Steps from "./steps";
import { useUser } from "./user-provider"; import { useUser } from "./user-provider";
@ -144,82 +143,80 @@ const Page: NextPage<CreatePollPageProps> = ({
}; };
return ( return (
<StandardLayout> <div className="max-w-full px-3 pb-3 sm:p-4">
<div className="max-w-full px-3 pb-3 sm:p-4"> <div className="max-w-full">
<div className="max-w-full"> <div className="max-w-full overflow-hidden rounded-lg border bg-white shadow-sm">
<div className="max-w-full overflow-hidden rounded-lg border bg-white shadow-sm"> <div className="flex justify-between border-b p-4">
<div className="flex justify-between border-b p-4"> <h1 className="m-0 text-xl font-semibold text-slate-800">
<h1 className="m-0 text-xl font-semibold text-slate-800"> {t("createNew")}
{t("createNew")} </h1>
</h1> <Steps current={currentStepIndex} total={steps.length} />
<Steps current={currentStepIndex} total={steps.length} /> </div>
</div> <div className="">
<div className=""> {(() => {
{(() => { switch (currentStepName) {
switch (currentStepName) { case "eventDetails":
case "eventDetails": return (
return ( <PollDetailsForm
<PollDetailsForm className="max-w-full p-3 sm:p-4"
className="max-w-full p-3 sm:p-4" name={currentStepName}
name={currentStepName} defaultValues={formData?.eventDetails}
defaultValues={formData?.eventDetails} onSubmit={handleSubmit}
onSubmit={handleSubmit} onChange={handleChange}
onChange={handleChange} />
/> );
); case "options":
case "options": return (
return ( <PollOptionsForm
<PollOptionsForm className="grow"
className="grow" name={currentStepName}
name={currentStepName} defaultValues={formData?.options}
defaultValues={formData?.options} onSubmit={handleSubmit}
onSubmit={handleSubmit} onChange={handleChange}
onChange={handleChange} title={formData.eventDetails?.title}
title={formData.eventDetails?.title} />
/> );
); case "userDetails":
case "userDetails": return (
return ( <UserDetailsForm
<UserDetailsForm className="grow p-4"
className="grow p-4" name={currentStepName}
name={currentStepName} defaultValues={formData?.userDetails}
defaultValues={formData?.userDetails} onSubmit={handleSubmit}
onSubmit={handleSubmit} onChange={handleChange}
onChange={handleChange} />
/> );
); }
} })()}
})()} <div className="flex w-full justify-end space-x-3 border-t bg-slate-50 px-4 py-3">
<div className="flex w-full justify-end space-x-3 border-t bg-slate-50 px-4 py-3"> {currentStepIndex > 0 ? (
{currentStepIndex > 0 ? (
<Button
disabled={isBusy}
onClick={() => {
setFormData({
...persistedFormData,
currentStep: currentStepIndex - 1,
});
}}
>
{t("back")}
</Button>
) : null}
<Button <Button
form={currentStepName} disabled={isBusy}
loading={isBusy} onClick={() => {
htmlType="submit" setFormData({
type="primary" ...persistedFormData,
currentStep: currentStepIndex - 1,
});
}}
> >
{currentStepIndex < steps.length - 1 {t("back")}
? t("continue")
: t("createPoll")}
</Button> </Button>
</div> ) : null}
<Button
form={currentStepName}
loading={isBusy}
htmlType="submit"
type="primary"
>
{currentStepIndex < steps.length - 1
? t("continue")
: t("createPoll")}
</Button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</StandardLayout> </div>
); );
}; };

View file

@ -1,16 +0,0 @@
import { MobileNavigation } from "./standard-layout/mobile-navigation";
export const ParticipantLayout = ({
children,
}: {
children?: React.ReactNode;
}) => {
return (
<div className="bg-pattern min-h-full sm:space-y-8">
<MobileNavigation />
<div className="mx-auto max-w-4xl space-y-4 px-3 pb-8">
<div>{children}</div>
</div>
</div>
);
};

View file

@ -2,19 +2,28 @@ import React from "react";
import { DayjsProvider } from "@/utils/dayjs"; import { DayjsProvider } from "@/utils/dayjs";
import { NextPageWithLayout } from "../../types";
import { UserProvider } from "../user-provider";
import { MobileNavigation } from "./standard-layout/mobile-navigation"; import { MobileNavigation } from "./standard-layout/mobile-navigation";
const StandardLayout: React.VoidFunctionComponent<{ const StandardLayout: React.VoidFunctionComponent<{
children?: React.ReactNode; children?: React.ReactNode;
}> = ({ children, ...rest }) => { }> = ({ children, ...rest }) => {
return ( return (
<DayjsProvider> <UserProvider>
<div className="bg-pattern relative min-h-full" {...rest}> <DayjsProvider>
<MobileNavigation /> <div className={"bg-pattern relative min-h-full"} {...rest}>
<div className="mx-auto max-w-4xl">{children}</div> <MobileNavigation />
</div> <div className="mx-auto max-w-4xl">{children}</div>
</DayjsProvider> </div>
</DayjsProvider>
</UserProvider>
); );
}; };
export default StandardLayout; export default StandardLayout;
export const getStandardLayout: NextPageWithLayout["getLayout"] =
function getLayout(page) {
return <StandardLayout>{page}</StandardLayout>;
};

View file

@ -16,7 +16,9 @@ import Maintenance from "@/components/maintenance";
import { useCrispChat } from "../components/crisp-chat"; import { useCrispChat } from "../components/crisp-chat";
import ModalProvider from "../components/modal/modal-provider"; import ModalProvider from "../components/modal/modal-provider";
import { NextPageWithLayout } from "../types";
import { absoluteUrl } from "../utils/absolute-url"; import { absoluteUrl } from "../utils/absolute-url";
import { UserSession } from "../utils/auth";
import { trpcNext } from "../utils/trpc"; import { trpcNext } from "../utils/trpc";
const inter = Inter({ const inter = Inter({
@ -29,7 +31,15 @@ const noto = Noto_Sans_Mono({
display: "swap", display: "swap",
}); });
const MyApp: NextPage<AppProps> = ({ Component, pageProps }) => { type PageProps = {
user: UserSession;
};
type AppPropsWithLayout = AppProps<PageProps> & {
Component: NextPageWithLayout<PageProps>;
};
const MyApp: NextPage<AppPropsWithLayout> = ({ Component, pageProps }) => {
useCrispChat(); useCrispChat();
React.useEffect(() => { React.useEffect(() => {
@ -43,6 +53,8 @@ const MyApp: NextPage<AppProps> = ({ Component, pageProps }) => {
return <Maintenance />; return <Maintenance />;
} }
const getLayout = Component.getLayout ?? ((page) => page);
return ( return (
<> <>
<DefaultSeo <DefaultSeo
@ -77,9 +89,7 @@ const MyApp: NextPage<AppProps> = ({ Component, pageProps }) => {
--font-noto: ${noto.style.fontFamily}; --font-noto: ${noto.style.fontFamily};
} }
`}</style> `}</style>
<ModalProvider> <ModalProvider>{getLayout(<Component {...pageProps} />)}</ModalProvider>
<Component {...pageProps} />
</ModalProvider>
</> </>
); );
}; };

View file

@ -1,23 +1,22 @@
import { GetServerSideProps, NextPage } from "next"; import { GetServerSideProps } from "next";
import Head from "next/head"; import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import React from "react";
import FullPageLoader from "@/components/full-page-loader"; import FullPageLoader from "@/components/full-page-loader";
import StandardLayout from "@/components/layouts/standard-layout"; import { getStandardLayout } from "@/components/layouts/standard-layout";
import { ParticipantsProvider } from "@/components/participants-provider"; import { ParticipantsProvider } from "@/components/participants-provider";
import { Poll } from "@/components/poll"; import { Poll } from "@/components/poll";
import { PollContextProvider } from "@/components/poll-context"; import { PollContextProvider } from "@/components/poll-context";
import { withSession } from "@/components/user-provider";
import { withSessionSsr } from "@/utils/auth"; import { withSessionSsr } from "@/utils/auth";
import { trpcNext } from "@/utils/trpc"; import { trpcNext } from "@/utils/trpc";
import { withPageTranslations } from "@/utils/with-page-translations"; import { withPageTranslations } from "@/utils/with-page-translations";
import { AdminControls } from "../../components/admin-control"; import { AdminControls } from "../../components/admin-control";
import ModalProvider from "../../components/modal/modal-provider"; import ModalProvider from "../../components/modal/modal-provider";
import { NextPageWithLayout } from "../../types";
const PollPageLoader: NextPage = () => { const Page: NextPageWithLayout = () => {
const { query } = useRouter(); const { query } = useRouter();
const { t } = useTranslation("app"); const { t } = useTranslation("app");
const urlId = query.urlId as string; const urlId = query.urlId as string;
@ -34,17 +33,15 @@ const PollPageLoader: NextPage = () => {
<meta name="robots" content="noindex,nofollow" /> <meta name="robots" content="noindex,nofollow" />
</Head> </Head>
<ParticipantsProvider pollId={poll.id}> <ParticipantsProvider pollId={poll.id}>
<StandardLayout> <PollContextProvider poll={poll} urlId={urlId} admin={true}>
<PollContextProvider poll={poll} urlId={urlId} admin={true}> <ModalProvider>
<ModalProvider> <div className="flex flex-col space-y-3 p-3 sm:space-y-4 sm:p-4">
<div className="flex flex-col space-y-3 p-3 sm:space-y-4 sm:p-4"> <AdminControls>
<AdminControls> <Poll />
<Poll /> </AdminControls>
</AdminControls> </div>
</div> </ModalProvider>
</ModalProvider> </PollContextProvider>
</PollContextProvider>
</StandardLayout>
</ParticipantsProvider> </ParticipantsProvider>
</> </>
); );
@ -53,6 +50,8 @@ const PollPageLoader: NextPage = () => {
return <FullPageLoader>{t("loading")}</FullPageLoader>; return <FullPageLoader>{t("loading")}</FullPageLoader>;
}; };
Page.getLayout = getStandardLayout;
export const getServerSideProps: GetServerSideProps = withSessionSsr( export const getServerSideProps: GetServerSideProps = withSessionSsr(
withPageTranslations(["common", "app", "errors"]), withPageTranslations(["common", "app", "errors"]),
{ {
@ -64,4 +63,4 @@ export const getServerSideProps: GetServerSideProps = withSessionSsr(
}, },
); );
export default withSession(PollPageLoader); export default Page;

View file

@ -4,11 +4,12 @@ import { useTranslation } from "react-i18next";
import CreatePoll from "@/components/create-poll"; import CreatePoll from "@/components/create-poll";
import { withSession } from "../components/user-provider"; import StandardLayout from "../components/layouts/standard-layout";
import { NextPageWithLayout } from "../types";
import { withSessionSsr } from "../utils/auth"; import { withSessionSsr } from "../utils/auth";
import { withPageTranslations } from "../utils/with-page-translations"; import { withPageTranslations } from "../utils/with-page-translations";
const Page = () => { const Page: NextPageWithLayout = () => {
const { t } = useTranslation("app"); const { t } = useTranslation("app");
return ( return (
<> <>
@ -20,7 +21,12 @@ const Page = () => {
</> </>
); );
}; };
export default withSession(Page);
Page.getLayout = function getLayout(page) {
return <StandardLayout>{page}</StandardLayout>;
};
export default Page;
export const getServerSideProps: GetServerSideProps = withSessionSsr( export const getServerSideProps: GetServerSideProps = withSessionSsr(
withPageTranslations(["common", "app"]), withPageTranslations(["common", "app"]),

View file

@ -1,4 +1,4 @@
import { GetServerSideProps, NextPage } from "next"; import { GetServerSideProps } from "next";
import Head from "next/head"; import Head from "next/head";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
@ -7,16 +7,16 @@ import { useTranslation } from "next-i18next";
import { ParticipantsProvider } from "@/components/participants-provider"; import { ParticipantsProvider } from "@/components/participants-provider";
import { Poll } from "@/components/poll"; import { Poll } from "@/components/poll";
import { PollContextProvider } from "@/components/poll-context"; import { PollContextProvider } from "@/components/poll-context";
import { useUser, withSession } from "@/components/user-provider"; import { useUser } from "@/components/user-provider";
import { withSessionSsr } from "@/utils/auth"; import { withSessionSsr } from "@/utils/auth";
import { trpcNext } from "@/utils/trpc"; import { trpcNext } from "@/utils/trpc";
import { withPageTranslations } from "@/utils/with-page-translations"; import { withPageTranslations } from "@/utils/with-page-translations";
import { ParticipantLayout } from "../../components/layouts/participant-layout"; import StandardLayout from "../../components/layouts/standard-layout";
import ModalProvider from "../../components/modal/modal-provider"; import ModalProvider from "../../components/modal/modal-provider";
import { DayjsProvider } from "../../utils/dayjs"; import { NextPageWithLayout } from "../../types";
const Page: NextPage = () => { const Page: NextPageWithLayout = () => {
const { query } = useRouter(); const { query } = useRouter();
const urlId = query.urlId as string; const urlId = query.urlId as string;
@ -33,27 +33,23 @@ const Page: NextPage = () => {
<title>{poll.title}</title> <title>{poll.title}</title>
<meta name="robots" content="noindex,nofollow" /> <meta name="robots" content="noindex,nofollow" />
</Head> </Head>
<DayjsProvider> <ParticipantsProvider pollId={poll.id}>
<ParticipantsProvider pollId={poll.id}> <PollContextProvider poll={poll} urlId={urlId} admin={false}>
<ParticipantLayout> <ModalProvider>
<PollContextProvider poll={poll} urlId={urlId} admin={false}> <div className="space-y-3 sm:space-y-4">
<ModalProvider> {user.id === poll.user.id ? (
<div className="space-y-3 sm:space-y-4"> <Link
{user.id === poll.user.id ? ( className="btn-default"
<Link href={`/admin/${poll.adminUrlId}`}
className="btn-default" >
href={`/admin/${poll.adminUrlId}`} &larr; {t("goToAdmin")}
> </Link>
&larr; {t("goToAdmin")} ) : null}
</Link> <Poll />
) : null} </div>
<Poll /> </ModalProvider>
</div> </PollContextProvider>
</ModalProvider> </ParticipantsProvider>
</PollContextProvider>
</ParticipantLayout>
</ParticipantsProvider>
</DayjsProvider>
</> </>
); );
} }
@ -61,6 +57,10 @@ const Page: NextPage = () => {
return null; return null;
}; };
Page.getLayout = function getLayout(page) {
return <StandardLayout>{page}</StandardLayout>;
};
export const getServerSideProps: GetServerSideProps = withSessionSsr( export const getServerSideProps: GetServerSideProps = withSessionSsr(
withPageTranslations(["common", "app", "errors"]), withPageTranslations(["common", "app", "errors"]),
{ {
@ -72,4 +72,4 @@ export const getServerSideProps: GetServerSideProps = withSessionSsr(
}, },
); );
export default withSession(Page); export default Page;

View file

@ -1,20 +1,16 @@
import { NextPage } from "next";
import { withSessionSsr } from "@/utils/auth"; import { withSessionSsr } from "@/utils/auth";
import StandardLayout from "../components/layouts/standard-layout"; import { getStandardLayout } from "../components/layouts/standard-layout";
import { Profile } from "../components/profile"; import { Profile } from "../components/profile";
import { withSession } from "../components/user-provider"; import { NextPageWithLayout } from "../types";
import { withPageTranslations } from "../utils/with-page-translations"; import { withPageTranslations } from "../utils/with-page-translations";
const Page: NextPage = () => { const Page: NextPageWithLayout = () => {
return ( return <Profile />;
<StandardLayout>
<Profile />
</StandardLayout>
);
}; };
Page.getLayout = getStandardLayout;
export const getServerSideProps = withSessionSsr(async (ctx) => { export const getServerSideProps = withSessionSsr(async (ctx) => {
if (ctx.req.session.user.isGuest !== false) { if (ctx.req.session.user.isGuest !== false) {
return { return {
@ -27,4 +23,4 @@ export const getServerSideProps = withSessionSsr(async (ctx) => {
return withPageTranslations(["common", "app"])(ctx); return withPageTranslations(["common", "app"])(ctx);
}); });
export default withSession(Page); export default Page;

13
src/types.ts Normal file
View file

@ -0,0 +1,13 @@
import { NextPage } from "next";
import React from "react";
export type ReactTag = keyof JSX.IntrinsicElements;
export type PropsOf<TTag extends ReactTag> = TTag extends React.ElementType
? React.ComponentProps<TTag>
: never;
// eslint-disable-next-line @typescript-eslint/ban-types
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: React.ReactElement) => React.ReactNode;
};