mirror of
https://github.com/lukevella/rallly.git
synced 2025-05-03 04:06:06 +02:00
♻️ Handle self-hosting environment with updated authentication (#918)
This commit is contained in:
parent
221ae62d8e
commit
3e90c302d6
12 changed files with 133 additions and 222 deletions
|
@ -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">
|
||||
|
|
|
@ -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,10 +180,8 @@ export const StandardLayout: React.FunctionComponent<{
|
|||
}> = ({ children, hideNav, ...rest }) => {
|
||||
const key = hideNav ? "no-nav" : "nav";
|
||||
return (
|
||||
<UserProvider>
|
||||
<ConnectedDayjsProvider>
|
||||
<Toaster />
|
||||
<ModalProvider>
|
||||
<Toaster />
|
||||
<div className="flex min-h-screen flex-col" {...rest}>
|
||||
<AnimatePresence initial={false}>
|
||||
{!hideNav ? <MainNav /> : null}
|
||||
|
@ -222,8 +219,6 @@ export const StandardLayout: React.FunctionComponent<{
|
|||
</>
|
||||
) : null}
|
||||
</ModalProvider>
|
||||
</ConnectedDayjsProvider>
|
||||
</UserProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,8 +117,6 @@ const Page = ({ id, title, user }: PageProps) => {
|
|||
],
|
||||
}}
|
||||
/>
|
||||
<UserProvider>
|
||||
<ConnectedDayjsProvider>
|
||||
<Prefetch>
|
||||
<LegacyPollContextProvider>
|
||||
<VisibilityProvider>
|
||||
|
@ -172,8 +169,6 @@ const Page = ({ id, title, user }: PageProps) => {
|
|||
</VisibilityProvider>
|
||||
</LegacyPollContextProvider>
|
||||
</Prefetch>
|
||||
</ConnectedDayjsProvider>
|
||||
</UserProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -71,6 +71,7 @@ const Page: NextPageWithLayout = () => {
|
|||
};
|
||||
|
||||
Page.getLayout = getProfileLayout;
|
||||
Page.isAuthRequired = true;
|
||||
|
||||
export const getStaticProps = getStaticTranslations;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,7 +16,6 @@ import {
|
|||
router,
|
||||
} from "../trpc";
|
||||
import { comments } from "./polls/comments";
|
||||
import { demo } from "./polls/demo";
|
||||
import { options } from "./polls/options";
|
||||
import { participants } from "./polls/participants";
|
||||
|
||||
|
@ -42,7 +41,6 @@ const getPollIdFromAdminUrlId = async (urlId: string) => {
|
|||
};
|
||||
|
||||
export const polls = router({
|
||||
demo,
|
||||
participants,
|
||||
comments,
|
||||
options,
|
||||
|
@ -390,11 +388,12 @@ export const polls = router({
|
|||
message: "Poll not found",
|
||||
});
|
||||
}
|
||||
const inviteLink = ctx.shortUrl(`/invite/${res.id}`);
|
||||
|
||||
if (ctx.user.id === res.userId || res.adminUrlId === input.adminToken) {
|
||||
return res;
|
||||
return { ...res, inviteLink };
|
||||
} else {
|
||||
return { ...res, adminUrlId: "" };
|
||||
return { ...res, adminUrlId: "", inviteLink };
|
||||
}
|
||||
}),
|
||||
transfer: possiblyPublicProcedure
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
import { prisma, VoteType } from "@rallly/database";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { nanoid } from "../../../utils/nanoid";
|
||||
import { possiblyPublicProcedure, router } from "../../trpc";
|
||||
|
||||
const participantData: Array<{ name: string; votes: VoteType[] }> = [
|
||||
{
|
||||
name: "Reed",
|
||||
votes: ["yes", "no", "yes", "no"],
|
||||
},
|
||||
{
|
||||
name: "Susan",
|
||||
votes: ["yes", "yes", "yes", "no"],
|
||||
},
|
||||
{
|
||||
name: "Johnny",
|
||||
votes: ["no", "no", "yes", "yes"],
|
||||
},
|
||||
{
|
||||
name: "Ben",
|
||||
votes: ["yes", "yes", "yes", "yes"],
|
||||
},
|
||||
];
|
||||
|
||||
const optionValues = ["2022-12-14", "2022-12-15", "2022-12-16", "2022-12-17"];
|
||||
|
||||
export const demo = router({
|
||||
create: possiblyPublicProcedure.mutation(async () => {
|
||||
const adminUrlId = nanoid();
|
||||
const demoUser = { name: "John Example", email: "noreply@rallly.co" };
|
||||
|
||||
const options: Array<{ start: Date; id: string }> = [];
|
||||
|
||||
for (let i = 0; i < optionValues.length; i++) {
|
||||
options.push({ id: await nanoid(), start: new Date(optionValues[i]) });
|
||||
}
|
||||
|
||||
const participants: Array<{
|
||||
name: string;
|
||||
id: string;
|
||||
userId: string;
|
||||
createdAt: Date;
|
||||
}> = [];
|
||||
|
||||
const votes: Array<{
|
||||
optionId: string;
|
||||
participantId: string;
|
||||
type: VoteType;
|
||||
}> = [];
|
||||
|
||||
for (let i = 0; i < participantData.length; i++) {
|
||||
const { name, votes: participantVotes } = participantData[i];
|
||||
const participantId = await nanoid();
|
||||
participants.push({
|
||||
id: participantId,
|
||||
name,
|
||||
userId: "user-demo",
|
||||
createdAt: dayjs()
|
||||
.add(i * -1, "minutes")
|
||||
.toDate(),
|
||||
});
|
||||
|
||||
options.forEach((option, index) => {
|
||||
votes.push({
|
||||
optionId: option.id,
|
||||
participantId,
|
||||
type: participantVotes[index],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.poll.create({
|
||||
data: {
|
||||
id: nanoid(),
|
||||
title: "Lunch Meeting",
|
||||
location: "Starbucks, 901 New York Avenue",
|
||||
description: `Hey everyone, please choose the dates when you are available to meet for our monthly get together. Looking forward to see you all!`,
|
||||
demo: true,
|
||||
adminUrlId,
|
||||
participantUrlId: nanoid(),
|
||||
user: {
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
email: demoUser.email,
|
||||
},
|
||||
create: demoUser,
|
||||
},
|
||||
},
|
||||
options: {
|
||||
createMany: {
|
||||
data: options,
|
||||
},
|
||||
},
|
||||
participants: {
|
||||
createMany: {
|
||||
data: participants,
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
createMany: {
|
||||
data: votes,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return adminUrlId;
|
||||
}),
|
||||
});
|
Loading…
Add table
Reference in a new issue