♻️ Handle self-hosting environment with updated authentication (#918)

This commit is contained in:
Luke Vella 2023-10-30 10:23:43 +00:00 committed by GitHub
parent 221ae62d8e
commit 3e90c302d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 133 additions and 222 deletions

View file

@ -15,8 +15,6 @@ import { useCopyToClipboard } from "react-use";
import { useParticipants } from "@/components/participants-provider";
import { Trans } from "@/components/trans";
import { usePoll } from "@/contexts/poll";
import { shortUrl } from "@/utils/absolute-url";
import { isSelfHosted } from "@/utils/constants";
export const InviteDialog = () => {
const { participants } = useParticipants();
@ -31,10 +29,6 @@ export const InviteDialog = () => {
}
}, [state]);
const inviteLink = isSelfHosted
? window.location.origin + `/invite/${poll?.id}`
: shortUrl(`/invite/${poll?.id}`);
const [didCopy, setDidCopy] = React.useState(false);
return (
@ -72,7 +66,7 @@ export const InviteDialog = () => {
<Button
className="w-full min-w-0 bg-gray-50 px-2.5"
onClick={() => {
copyToClipboard(inviteLink);
copyToClipboard(poll.inviteLink);
setDidCopy(true);
setTimeout(() => {
setDidCopy(false);
@ -82,7 +76,7 @@ export const InviteDialog = () => {
{didCopy ? (
<Trans i18nKey="copied" />
) : (
<span className="flex truncate">{inviteLink}</span>
<span className="flex truncate">{poll.inviteLink}</span>
)}
</Button>
<div className="shrink-0">

View file

@ -23,11 +23,10 @@ import { UserDropdown } from "@/components/user-dropdown";
import { IfCloudHosted } from "@/contexts/environment";
import { IfFreeUser } from "@/contexts/plan";
import { appVersion, isFeedbackEnabled } from "@/utils/constants";
import { ConnectedDayjsProvider } from "@/utils/dayjs";
import { IconComponent, NextPageWithLayout } from "../../types";
import ModalProvider from "../modal/modal-provider";
import { IfGuest, UserProvider } from "../user-provider";
import { IfGuest } from "../user-provider";
const NavMenuItem = ({
href,
@ -181,49 +180,45 @@ export const StandardLayout: React.FunctionComponent<{
}> = ({ children, hideNav, ...rest }) => {
const key = hideNav ? "no-nav" : "nav";
return (
<UserProvider>
<ConnectedDayjsProvider>
<Toaster />
<ModalProvider>
<div className="flex min-h-screen flex-col" {...rest}>
<AnimatePresence initial={false}>
{!hideNav ? <MainNav /> : null}
</AnimatePresence>
<AnimatePresence mode="wait" initial={false}>
<m.div
key={key}
variants={{
hidden: { opacity: 0, y: -56 },
visible: { opacity: 1, y: 0 },
}}
initial="hidden"
animate="visible"
exit={{ opacity: 0, y: 56 }}
>
{children}
</m.div>
</AnimatePresence>
{appVersion ? (
<div className="fixed bottom-0 right-0 z-50 rounded-tl-md bg-gray-200/90">
<Link
className="px-2 py-1 text-xs tabular-nums tracking-tight"
target="_blank"
href={`https://github.com/lukevella/rallly/releases/${appVersion}`}
>
{`${appVersion}`}
</Link>
</div>
) : null}
<ModalProvider>
<Toaster />
<div className="flex min-h-screen flex-col" {...rest}>
<AnimatePresence initial={false}>
{!hideNav ? <MainNav /> : null}
</AnimatePresence>
<AnimatePresence mode="wait" initial={false}>
<m.div
key={key}
variants={{
hidden: { opacity: 0, y: -56 },
visible: { opacity: 1, y: 0 },
}}
initial="hidden"
animate="visible"
exit={{ opacity: 0, y: 56 }}
>
{children}
</m.div>
</AnimatePresence>
{appVersion ? (
<div className="fixed bottom-0 right-0 z-50 rounded-tl-md bg-gray-200/90">
<Link
className="px-2 py-1 text-xs tabular-nums tracking-tight"
target="_blank"
href={`https://github.com/lukevella/rallly/releases/${appVersion}`}
>
{`${appVersion}`}
</Link>
</div>
{isFeedbackEnabled ? (
<>
<FeaturebaseIdentify />
<FeedbackButton />
</>
) : null}
</ModalProvider>
</ConnectedDayjsProvider>
</UserProvider>
) : null}
</div>
{isFeedbackEnabled ? (
<>
<FeaturebaseIdentify />
<FeedbackButton />
</>
) : null}
</ModalProvider>
);
};

View file

@ -9,11 +9,14 @@ import { AppProps } from "next/app";
import { Inter } from "next/font/google";
import Head from "next/head";
import Script from "next/script";
import { SessionProvider } from "next-auth/react";
import { SessionProvider, signIn, useSession } from "next-auth/react";
import { appWithTranslation } from "next-i18next";
import { DefaultSeo } from "next-seo";
import React from "react";
import Maintenance from "@/components/maintenance";
import { UserProvider } from "@/components/user-provider";
import { ConnectedDayjsProvider } from "@/utils/dayjs";
import { trpc } from "@/utils/trpc/client";
import * as nextI18nNextConfig from "../../next-i18next.config.js";
@ -29,12 +32,30 @@ type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
};
const Auth = ({ children }: { children: React.ReactNode }) => {
const session = useSession();
const isAuthenticated = !!session.data?.user.email;
React.useEffect(() => {
if (!isAuthenticated) {
signIn();
}
}, [isAuthenticated]);
if (isAuthenticated) {
return <>{children}</>;
}
return null;
};
const MyApp: NextPage<AppPropsWithLayout> = ({ Component, pageProps }) => {
if (process.env.NEXT_PUBLIC_MAINTENANCE_MODE === "1") {
return <Maintenance />;
}
const getLayout = Component.getLayout ?? ((page) => page);
const children = <Component {...pageProps} />;
return (
<SessionProvider>
@ -83,7 +104,15 @@ const MyApp: NextPage<AppPropsWithLayout> = ({ Component, pageProps }) => {
}
`}</style>
<TooltipProvider delayDuration={200}>
{getLayout(<Component {...pageProps} />)}
<UserProvider>
<ConnectedDayjsProvider>
{Component.isAuthRequired ? (
<Auth>{getLayout(children)}</Auth>
) : (
getLayout(children)
)}
</ConnectedDayjsProvider>
</UserProvider>
</TooltipProvider>
</LazyMotion>
</SessionProvider>

View file

@ -13,12 +13,11 @@ import { Poll } from "@/components/poll";
import { LegacyPollContextProvider } from "@/components/poll/poll-context-provider";
import { Trans } from "@/components/trans";
import { UserDropdown } from "@/components/user-dropdown";
import { UserProvider, useUser } from "@/components/user-provider";
import { useUser } from "@/components/user-provider";
import { VisibilityProvider } from "@/components/visibility";
import { PermissionsContext } from "@/contexts/permissions";
import { usePoll } from "@/contexts/poll";
import { absoluteUrl } from "@/utils/absolute-url";
import { ConnectedDayjsProvider } from "@/utils/dayjs";
import { trpc } from "@/utils/trpc/client";
import { getStaticTranslations } from "@/utils/with-page-translations";
@ -118,62 +117,58 @@ const Page = ({ id, title, user }: PageProps) => {
],
}}
/>
<UserProvider>
<ConnectedDayjsProvider>
<Prefetch>
<LegacyPollContextProvider>
<VisibilityProvider>
<div>
<svg
className="absolute inset-x-0 top-0 -z-10 hidden h-[64rem] w-full stroke-gray-300/75 [mask-image:radial-gradient(800px_800px_at_center,white,transparent)] sm:block"
aria-hidden="true"
<Prefetch>
<LegacyPollContextProvider>
<VisibilityProvider>
<div>
<svg
className="absolute inset-x-0 top-0 -z-10 hidden h-[64rem] w-full stroke-gray-300/75 [mask-image:radial-gradient(800px_800px_at_center,white,transparent)] sm:block"
aria-hidden="true"
>
<defs>
<pattern
id="1f932ae7-37de-4c0a-a8b0-a6e3b4d44b84"
width={240}
height={240}
x="50%"
y={-1}
patternUnits="userSpaceOnUse"
>
<defs>
<pattern
id="1f932ae7-37de-4c0a-a8b0-a6e3b4d44b84"
width={240}
height={240}
x="50%"
y={-1}
patternUnits="userSpaceOnUse"
>
<path d="M.5 240V.5H240" fill="none" />
</pattern>
</defs>
<rect
width="100%"
height="100%"
strokeWidth={0}
fill="url(#1f932ae7-37de-4c0a-a8b0-a6e3b4d44b84)"
<path d="M.5 240V.5H240" fill="none" />
</pattern>
</defs>
<rect
width="100%"
height="100%"
strokeWidth={0}
fill="url(#1f932ae7-37de-4c0a-a8b0-a6e3b4d44b84)"
/>
</svg>
<GoToApp />
<div className="mx-auto max-w-4xl space-y-4 px-3 sm:py-8">
<Poll />
<div className="mt-4 space-y-4 text-center text-gray-500">
<div className="py-8">
<Trans
defaults="Powered by <a>{name}</a>"
i18nKey="poweredByRallly"
values={{ name: "rallly.co" }}
components={{
a: (
<Link
className="hover:text-primary-600 rounded-none border-b border-b-gray-500 font-semibold"
href="https://rallly.co"
/>
),
}}
/>
</svg>
<GoToApp />
<div className="mx-auto max-w-4xl space-y-4 px-3 sm:py-8">
<Poll />
<div className="mt-4 space-y-4 text-center text-gray-500">
<div className="py-8">
<Trans
defaults="Powered by <a>{name}</a>"
i18nKey="poweredByRallly"
values={{ name: "rallly.co" }}
components={{
a: (
<Link
className="hover:text-primary-600 rounded-none border-b border-b-gray-500 font-semibold"
href="https://rallly.co"
/>
),
}}
/>
</div>
</div>
</div>
</div>
</VisibilityProvider>
</LegacyPollContextProvider>
</Prefetch>
</ConnectedDayjsProvider>
</UserProvider>
</div>
</div>
</VisibilityProvider>
</LegacyPollContextProvider>
</Prefetch>
</>
);
};

View file

@ -3,6 +3,7 @@ import { useTranslation } from "next-i18next";
import { CreatePoll } from "@/components/create-poll";
import { getStandardLayout } from "@/components/layouts/standard-layout";
import { isSelfHosted } from "@/utils/constants";
import { NextPageWithLayout } from "../types";
import { getStaticTranslations } from "../utils/with-page-translations";
@ -95,6 +96,7 @@ const Page: NextPageWithLayout = () => {
};
Page.getLayout = getStandardLayout;
Page.isAuthRequired = isSelfHosted;
export default Page;

View file

@ -10,6 +10,7 @@ import { RegisterLink } from "@/components/register-link";
import { useUser } from "@/components/user-provider";
import { usePoll } from "@/contexts/poll";
import { NextPageWithLayout } from "@/types";
import { isSelfHosted } from "@/utils/constants";
import { getStaticTranslations } from "@/utils/with-page-translations";
const GuestPollAlert = () => {
@ -58,6 +59,7 @@ const Page: NextPageWithLayout = () => {
};
Page.getLayout = getPollLayout;
Page.isAuthRequired = isSelfHosted;
export const getStaticPaths = async () => {
return {

View file

@ -23,6 +23,7 @@ import { PollStatusBadge } from "@/components/poll-status";
import { Skeleton } from "@/components/skeleton";
import { Trans } from "@/components/trans";
import { NextPageWithLayout } from "@/types";
import { isSelfHosted } from "@/utils/constants";
import { useDayjs } from "@/utils/dayjs";
import { trpc } from "@/utils/trpc/client";
import { getStaticTranslations } from "@/utils/with-page-translations";
@ -187,6 +188,7 @@ const Page: NextPageWithLayout = () => {
};
Page.getLayout = getStandardLayout;
Page.isAuthRequired = isSelfHosted;
export default Page;

View file

@ -282,6 +282,7 @@ const Page: NextPageWithLayout = () => {
Page.getLayout = getProfileLayout;
export const getStaticProps: GetStaticProps = async (ctx) => {
// This page is only available on the hosted version
if (isSelfHosted) {
return {
notFound: true,

View file

@ -71,6 +71,7 @@ const Page: NextPageWithLayout = () => {
};
Page.getLayout = getProfileLayout;
Page.isAuthRequired = true;
export const getStaticProps = getStaticTranslations;

View file

@ -10,6 +10,7 @@ export type PropsOf<TTag extends ReactTag> = TTag extends React.ElementType
// eslint-disable-next-line @typescript-eslint/ban-types
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: React.ReactElement) => React.ReactNode;
isAuthRequired?: boolean;
};
// eslint-disable-next-line @typescript-eslint/ban-types