Update major dependencies (#408)

This commit is contained in:
Luke Vella 2023-01-20 10:43:48 +00:00 committed by GitHub
parent 6332d6459f
commit e845d36c51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1294 additions and 775 deletions

View file

@ -24,7 +24,7 @@ module.exports = {
"sv",
"zh",
],
localePath: path.resolve("./public/locales"),
reloadOnPrerender: process.env.NODE_ENV === "development",
},
reloadOnPrerender: process.env.NODE_ENV === "development",
localePath: path.resolve("./public/locales"),
};

View file

@ -9,15 +9,13 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
const moduleExports = {
future: {
webpack5: false,
},
const nextConfig = {
i18n: i18n,
productionBrowserSourceMaps: true,
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
issuer: /\.[jt]sx?$/,
use: ["@svgr/webpack"],
});
@ -56,6 +54,9 @@ const moduleExports = {
},
];
},
sentry: {
hideSourceMaps: false,
},
};
const sentryWebpackPluginOptions = {
@ -73,5 +74,5 @@ const sentryWebpackPluginOptions = {
// Make sure adding Sentry options is the last code to run before exporting, to
// ensure that your source maps include changes from all other Webpack plugins
module.exports = withBundleAnalyzer(
withSentryConfig(moduleExports, sentryWebpackPluginOptions),
withSentryConfig(nextConfig, sentryWebpackPluginOptions),
);

View file

@ -15,46 +15,54 @@
},
"dependencies": {
"@floating-ui/react-dom-interactions": "^0.4.0",
"@headlessui/react": "^1.5.0",
"@headlessui/react": "^1.6.6",
"@next/bundle-analyzer": "^12.1.0",
"@prisma/client": "^4.1.0",
"@sentry/nextjs": "^7.7.0",
"@prisma/client": "^4.9.0",
"@radix-ui/react-radio-group": "^1.1.0",
"@radix-ui/react-slider": "^1.0.0",
"@radix-ui/react-slot": "^1.0.1",
"@radix-ui/react-tabs": "^1.0.1",
"@sentry/nextjs": "^7.17.3",
"@svgr/webpack": "^6.2.1",
"@tailwindcss/forms": "^0.4.0",
"@tailwindcss/typography": "^0.5.2",
"@trpc/client": "^9.23.2",
"@trpc/next": "^9.23.2",
"@trpc/react": "^9.23.2",
"@trpc/server": "^9.23.2",
"@tanstack/react-query": "^4.16.1",
"@trpc/client": "^10.0.0-rc.8",
"@trpc/next": "^10.0.0-rc.8",
"@trpc/react-query": "^10.0.0-rc.8",
"@trpc/server": "^10.0.0-rc.8",
"accept-language-parser": "^1.5.0",
"axios": "^0.24.0",
"clsx": "^1.1.1",
"dayjs": "^1.11.3",
"dayjs": "^1.11.7",
"eta": "^1.12.3",
"framer-motion": "^6.3.11",
"i18next": "^22.0.4",
"immer": "^9.0.15",
"iron-session": "^6.1.3",
"jose": "^4.5.1",
"js-cookie": "^3.0.1",
"lodash": "^4.17.21",
"nanoid": "^3.1.30",
"next": "^12.2.2",
"next-i18next": "^10.5.0",
"next-plausible": "^3.1.9",
"next-seo": "^5.14.1",
"next": "^13.1.2",
"next-i18next": "^12.1.0",
"next-plausible": "^3.6.4",
"next-seo": "^5.15.0",
"nodemailer": "^6.7.2",
"prisma": "^4.1.0",
"react": "17.0.2",
"react-big-calendar": "^0.38.9",
"react-dom": "17.0.2",
"react-hook-form": "^7.31.3",
"react-hot-toast": "^2.2.0",
"react-i18next": "^11.16.9",
"prisma": "^4.9.0",
"react": "^18.2.0",
"react-big-calendar": "^1.5.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.39.3",
"react-hot-toast": "^2.4.0",
"react-i18next": "^12.0.0",
"react-linkify": "^1.0.0-alpha",
"react-query": "^3.34.12",
"react-use": "^17.3.2",
"react-textarea-autosize": "^8.3.4",
"react-use": "^17.4.0",
"smoothscroll-polyfill": "^0.4.4",
"spacetime": "^7.1.4",
"superjson": "^1.9.1",
"tailwind-scrollbar": "^2.0.1",
"timezone-soft": "^1.3.1",
"typescript": "^4.5.2",
"zod": "^3.16.0"
@ -73,7 +81,7 @@
"@typescript-eslint/parser": "^5.21.0",
"autoprefixer": "^10.4.2",
"eslint": "^7.26.0",
"eslint-config-next": "^12.2.2",
"eslint-config-next": "^13.0.1",
"eslint-import-resolver-typescript": "^2.7.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-react": "^7.23.2",
@ -83,7 +91,8 @@
"postcss": "^8.4.6",
"prettier": "^2.3.0",
"prettier-plugin-tailwindcss": "^0.1.8",
"tailwindcss": "^3.0.23",
"tailwindcss": "^3.2.4",
"tailwindcss-writing-mode": "^1.0.0",
"wait-on": "^6.0.1"
}
}

View file

@ -0,0 +1,17 @@
-- DropIndex
DROP INDEX "Participant_id_pollId_key";
-- DropIndex
DROP INDEX "participants_id_poll_id_key";
-- CreateIndex
CREATE INDEX "comments_poll_id_idx" ON "comments" USING HASH ("poll_id");
-- CreateIndex
CREATE INDEX "options_poll_id_idx" ON "options" USING HASH ("poll_id");
-- CreateIndex
CREATE INDEX "polls_user_id_idx" ON "polls" USING HASH ("user_id");
-- CreateIndex
CREATE INDEX "votes_poll_id_idx" ON "votes" USING HASH ("poll_id");

View file

@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX "comments_user_id_idx" ON "comments" USING HASH ("user_id");

View file

@ -0,0 +1,8 @@
/*
Warnings:
- A unique constraint covering the columns `[id,poll_id]` on the table `participants` will be added. If there are existing duplicate values, this will fail.
*/
-- CreateIndex
CREATE UNIQUE INDEX "participants_id_poll_id_key" ON "participants"("id", "poll_id");

View file

@ -1,12 +1,11 @@
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
referentialIntegrity = "prisma"
relationMode = "prisma"
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
binaryTargets = ["native"]
}
@ -16,6 +15,7 @@ model User {
email String @unique() @db.Citext
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime? @updatedAt @map("updated_at")
comments Comment[]
polls Poll[]
@@map("users")
@ -55,6 +55,7 @@ model Poll {
participantUrlId String @unique @map("participant_url_id")
adminUrlId String @unique @map("admin_url_id")
@@index([userId], type: Hash)
@@map("polls")
}
@ -68,7 +69,6 @@ model Participant {
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime? @updatedAt @map("updated_at")
@@index([pollId], type: Hash)
@@unique([id, pollId])
@@map("participants")
@ -81,8 +81,8 @@ model Option {
poll Poll @relation(fields: [pollId], references: [id])
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime? @updatedAt @map("updated_at")
votes Vote[]
@@index([pollId], type: Hash)
@@map("options")
}
@ -98,7 +98,6 @@ model Vote {
id String @id @default(cuid())
participant Participant @relation(fields: [participantId], references: [id], onDelete: Cascade)
participantId String @map("participant_id")
option Option @relation(fields: [optionId], references: [id], onDelete: Cascade)
optionId String @map("option_id")
poll Poll @relation(fields: [pollId], references: [id])
pollId String @map("poll_id")
@ -107,6 +106,7 @@ model Vote {
updatedAt DateTime? @updatedAt @map("updated_at")
@@index([participantId], type: Hash)
@@index([pollId], type: Hash)
@@map("votes")
}
@ -116,10 +116,13 @@ model Comment {
poll Poll @relation(fields: [pollId], references: [id])
pollId String @map("poll_id")
authorName String @map("author_name")
user User? @relation(fields:[userId], references: [id])
userId String? @map("user_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime? @updatedAt @map("updated_at")
@@unique([id, pollId])
@@index([userId], type: Hash)
@@index([pollId], type: Hash)
@@map("comments")
}

View file

@ -38,17 +38,18 @@ const CookieConsentPopover: React.VoidFunctionComponent = () => {
browsing experience on this website.
</div>
<div className="flex items-center space-x-6">
<Link href="/privacy-policy">
<a className="hover:text-primary-500 text-slate-400">
<Link
href="/privacy-policy"
className="text-slate-400 hover:text-primary-500"
>
Privacy Policy
</a>
</Link>
<button
onClick={() => {
Cookies.set("rallly_cookie_consent", "1", { expires: 365 });
setVisible(false);
}}
className="bg-primary-500 hover:bg-primary-500/90 focus:ring-primary-200 active:bg-primary-600/90 grow rounded-md px-5 py-1 font-semibold text-white shadow-sm transition-all focus:ring-2"
className="grow rounded-md bg-primary-500 px-5 py-1 font-semibold text-white shadow-sm transition-all hover:bg-primary-500/90 focus:ring-2 focus:ring-primary-200 active:bg-primary-600/90"
>
OK
</button>

View file

@ -89,8 +89,9 @@ const AnchorLink: React.VoidFunctionComponent<{
className?: string;
}> = ({ href = "", className, children, ...forwardProps }) => {
return (
<Link href={href} passHref>
<a
<Link
href={href}
passHref
className={clsx(
"font-normal hover:text-white hover:no-underline",
className,
@ -98,7 +99,6 @@ const AnchorLink: React.VoidFunctionComponent<{
{...forwardProps}
>
{children}
</a>
</Link>
);
};

View file

@ -22,7 +22,7 @@ const ErrorPage: React.VoidFunctionComponent<ComponentProps> = ({
}) => {
const { t } = useTranslation("errors");
return (
<div className="mx-auto flex h-full max-w-full items-center justify-center bg-gray-50 px-4 py-8 lg:w-[1024px]">
<div className="mx-auto flex h-full max-w-full items-center justify-center px-4 py-8 lg:w-[1024px]">
<Head>
<title>{title}</title>
<meta name="robots" content="noindex,nofollow" />
@ -35,8 +35,13 @@ const ErrorPage: React.VoidFunctionComponent<ComponentProps> = ({
</div>
<p>{description}</p>
<div className="flex justify-center space-x-3">
<Link href="/" passHref={true}>
<a className="btn-default">{t("goToHome")}</a>
<Link
href="/"
passHref={true}
className="btn-default"
legacyBehavior
>
{t("goToHome")}
</Link>
<Button icon={<Chat />} onClick={showCrispChat}>
{t("startChat")}

View file

@ -1,24 +1,20 @@
import clsx from "clsx";
import dynamic from "next/dynamic";
import Link from "next/link";
import { useRouter } from "next/router";
import { Trans, useTranslation } from "next-i18next";
import * as React from "react";
import { createBreakpoint } from "react-use";
import DotsVertical from "@/components/icons/dots-vertical.svg";
import Github from "@/components/icons/github.svg";
import Logo from "~/public/logo.svg";
import Footer from "./page-layout/footer";
import Popover from "./popover";
const Popover = dynamic(() => import("./popover"), { ssr: false });
export interface PageLayoutProps {
children?: React.ReactNode;
}
const useBreakpoint = createBreakpoint({ sm: 640, md: 768, lg: 1024 });
const Menu: React.VoidFunctionComponent<{ className: string }> = ({
className,
}) => {
@ -26,27 +22,24 @@ const Menu: React.VoidFunctionComponent<{ className: string }> = ({
const { t } = useTranslation("common");
return (
<nav className={className}>
<Link href="/">
<a
<Link
href="/"
className={clsx(
"text-gray-400 transition-colors hover:text-primary-500 hover:no-underline hover:underline-offset-2",
{
"pointer-events-none font-bold text-gray-600":
pathname === "/home",
"pointer-events-none font-bold text-gray-600": pathname === "/home",
},
)}
>
{t("home")}
</a>
</Link>
<Link href="https://blog.rallly.co">
<a
<Link
href="https://blog.rallly.co"
className={clsx(
"text-gray-400 transition-colors hover:text-primary-500 hover:no-underline hover:underline-offset-2",
)}
>
{t("blog")}
</a>
</Link>
<a
href="https://support.rallly.co"
@ -54,10 +47,11 @@ const Menu: React.VoidFunctionComponent<{ className: string }> = ({
>
{t("support")}
</a>
<Link href="https://github.com/lukevella/rallly">
<a className="text-gray-400 transition-colors hover:text-primary-500 hover:no-underline hover:underline-offset-2">
<Link
href="https://github.com/lukevella/rallly"
className="text-gray-400 transition-colors hover:text-primary-500 hover:no-underline hover:underline-offset-2"
>
<Github className="w-6" />
</a>
</Link>
</nav>
);
@ -66,7 +60,6 @@ const Menu: React.VoidFunctionComponent<{ className: string }> = ({
const PageLayout: React.VoidFunctionComponent<PageLayoutProps> = ({
children,
}) => {
const breakpoint = useBreakpoint();
const { t } = useTranslation("homepage");
return (
<div className="bg-pattern min-h-full overflow-x-hidden">
@ -74,9 +67,7 @@ const PageLayout: React.VoidFunctionComponent<PageLayoutProps> = ({
<div className="grow">
<div className="relative inline-block">
<Link href="/">
<a>
<Logo className="w-40 text-primary-500" alt="Rallly" />
</a>
</Link>
<span className="absolute -bottom-6 right-0 text-sm text-slate-400 transition-colors">
<Trans t={t} i18nKey="3Ls" components={{ e: <em /> }} />
@ -84,18 +75,16 @@ const PageLayout: React.VoidFunctionComponent<PageLayoutProps> = ({
</div>
</div>
<Menu className="hidden items-center space-x-8 md:flex" />
{breakpoint === "sm" ? (
<Popover
placement="left-start"
trigger={
<button className="text-gray-400 transition-colors hover:text-primary-500 hover:no-underline hover:underline-offset-2">
<button className="text-gray-400 transition-colors hover:text-primary-500 hover:no-underline hover:underline-offset-2 sm:hidden">
<DotsVertical className="w-5" />
</button>
}
>
<Menu className="flex flex-col space-y-2" />
</Popover>
) : null}
</div>
<div className="md:min-h-[calc(100vh-460px)]">{children}</div>
<Footer />

View file

@ -86,10 +86,11 @@ const Footer: React.VoidFunctionComponent = () => {
</a>
</li>
<li>
<Link href="https://blog.rallly.co">
<a className="inline-block font-normal text-slate-400 hover:text-slate-800 hover:no-underline">
<Link
href="https://blog.rallly.co"
className="inline-block font-normal text-slate-400 hover:text-slate-800 hover:no-underline"
>
{t("blog")}
</a>
</Link>
</li>
<li>
@ -101,10 +102,11 @@ const Footer: React.VoidFunctionComponent = () => {
</a>
</li>
<li>
<Link href="/privacy-policy">
<a className="inline-block font-normal text-slate-400 hover:text-slate-800 hover:no-underline">
<Link
href="/privacy-policy"
className="inline-block font-normal text-slate-400 hover:text-slate-800 hover:no-underline"
>
{t("privacyPolicy")}
</a>
</Link>
</li>
</ul>

View file

@ -204,7 +204,7 @@ const PollPage: NextPage = () => {
</div>
</div>
) : null}
<div className="md:card mb-4 border-t bg-white md:overflow-hidden md:p-0">
<div className="mb-4 border border-t bg-white md:overflow-hidden md:rounded-md">
<div className="p-4 md:border-b md:p-6">
<div className="space-y-4">
<div>

View file

@ -65,11 +65,9 @@ export const Profile: React.VoidFunctionComponent = () => {
<div className="card p-0">
<div className="flex items-center justify-between border-b p-4 shadow-sm">
<div className="text-lg text-slate-700">{t("yourPolls")}</div>
<Link href="/new">
<a className="btn-default">
<Link href="/new" className="btn-default">
<Pencil className="mr-1 h-5" />
{t("newPoll")}
</a>
</Link>
</div>
{createdPolls.length > 0 ? (
@ -81,10 +79,11 @@ export const Profile: React.VoidFunctionComponent = () => {
<div>
<div className="flex">
<Calendar className="mr-2 mt-[1px] h-5 text-primary-500" />
<Link href={`/admin/${poll.adminUrlId}`}>
<a className="text-slate-700 hover:text-primary-500 hover:no-underline">
<Link
href={`/admin/${poll.adminUrlId}`}
className="text-slate-700 hover:text-primary-500 hover:no-underline"
>
<div>{poll.title}</div>
</a>
</Link>
</div>
<div className="ml-7 text-sm text-slate-500">

View file

@ -32,9 +32,7 @@ import { useSession } from "./session";
const HomeLink = () => {
return (
<Link href="/">
<a>
<Logo className="inline-block w-28 text-primary-500 transition-colors active:text-primary-600 lg:w-32" />
</a>
</Link>
);
};
@ -128,13 +126,15 @@ const AppMenu: React.VoidFunctionComponent<{ className?: string }> = ({
className,
}) => {
const { t } = useTranslation(["common", "app"]);
console.log("logo", Logo);
return (
<div className={clsx("space-y-1", className)}>
<Link href="/new">
<a className="flex cursor-pointer items-center space-x-2 whitespace-nowrap rounded-md px-2 py-1 pr-4 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300">
<Link
href="/new"
className="flex cursor-pointer items-center space-x-2 whitespace-nowrap rounded-md px-2 py-1 pr-4 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300"
>
<Pencil className="h-5 opacity-75 " />
<span className="inline-block">{t("app:newPoll")}</span>
</a>
</Link>
<a
target="_blank"
@ -267,11 +267,12 @@ const StandardLayout: React.VoidFunctionComponent<{
<HomeLink />
</div>
<div className="mb-4">
<Link href="/new">
<a className="group mb-1 flex items-center space-x-3 whitespace-nowrap rounded-md px-3 py-1 font-medium text-slate-600 transition-colors hover:bg-slate-500/10 hover:text-slate-600 hover:no-underline active:bg-slate-500/20">
<Link
href="/new"
className="group mb-1 flex items-center space-x-3 whitespace-nowrap rounded-md px-3 py-1 font-medium text-slate-600 transition-colors hover:bg-slate-500/10 hover:text-slate-600 hover:no-underline active:bg-slate-500/20"
>
<Pencil className="h-5 opacity-75 group-hover:text-primary-500 group-hover:opacity-100" />
<span className="grow text-left">{t("app:newPoll")}</span>
</a>
</Link>
<a
target="_blank"
@ -350,10 +351,11 @@ const StandardLayout: React.VoidFunctionComponent<{
</div>
<div className="flex flex-col items-center space-y-4 px-6 pt-3 pb-6 text-slate-400 lg:h-16 lg:flex-row lg:space-y-0 lg:space-x-6 lg:py-0 lg:px-8 lg:pb-3">
<div>
<Link href="https://rallly.co">
<a className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline">
<Link
href="https://rallly.co"
className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline"
>
<Logo className="h-5" />
</a>
</Link>
</div>
<div className="hidden text-slate-300 lg:block">&bull;</div>
@ -366,15 +368,17 @@ const StandardLayout: React.VoidFunctionComponent<{
>
{t("common:support")}
</a>
<Link href="https://github.com/lukevella/rallly/discussions">
<a className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline">
<Link
href="https://github.com/lukevella/rallly/discussions"
className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline"
>
{t("common:discussions")}
</a>
</Link>
<Link href="https://blog.rallly.co">
<a className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline">
<Link
href="https://blog.rallly.co"
className="text-sm text-slate-400 transition-colors hover:text-primary-500 hover:no-underline"
>
{t("common:blog")}
</a>
</Link>
<div className="hidden text-slate-300 lg:block">&bull;</div>
<div className="flex items-center space-x-6">

View file

@ -28,9 +28,8 @@ export function middleware({ headers, cookies, nextUrl }: NextRequest) {
// Check if locale is specified in cookie
const localeCookie = cookies.get("NEXT_LOCALE");
if (localeCookie && supportedLocales.includes(localeCookie)) {
newUrl.pathname = `/${localeCookie}${newUrl.pathname}`;
if (localeCookie && supportedLocales.includes(localeCookie.value)) {
newUrl.pathname = `/${localeCookie.value}${newUrl.pathname}`;
return NextResponse.rewrite(newUrl);
} else {
// Check if locale is specified in header
@ -53,5 +52,13 @@ export function middleware({ headers, cookies, nextUrl }: NextRequest) {
}
export const config = {
matcher: ["/admin/:id", "/demo", "/p/:id", "/profile", "/new", "/login"],
matcher: [
"/admin/:id",
"/demo",
"/p/:id",
"/profile",
"/new",
"/login",
"/polls",
],
};

View file

@ -2,7 +2,6 @@ import "react-big-calendar/lib/css/react-big-calendar.css";
import "tailwindcss/tailwind.css";
import "~/style.css";
import { withTRPC } from "@trpc/next";
import { NextPage } from "next";
import { AppProps } from "next/app";
import dynamic from "next/dynamic";
@ -10,14 +9,12 @@ import Head from "next/head";
import { appWithTranslation } from "next-i18next";
import PlausibleProvider from "next-plausible";
import { DefaultSeo } from "next-seo";
import toast, { Toaster } from "react-hot-toast";
import { MutationCache } from "react-query";
import superjson from "superjson";
import { Toaster } from "react-hot-toast";
import Maintenance from "@/components/maintenance";
import { absoluteUrl } from "../utils/absolute-url";
import { AppRouter } from "./api/trpc/[trpc]";
import { trpcNext } from "../utils/trpc";
const CrispChat = dynamic(() => import("@/components/crisp-chat"), {
ssr: false,
@ -67,26 +64,4 @@ const MyApp: NextPage<AppProps> = ({ Component, pageProps }) => {
);
};
export default withTRPC<AppRouter>({
config() {
const url = "/api/trpc";
return {
transformer: superjson,
url,
/**
* @link https://react-query.tanstack.com/reference/QueryClient
*/
queryClientConfig: {
mutationCache: new MutationCache({
onError: () => {
toast.error(
"Uh oh! Something went wrong. The issue has been logged and we'll fix it as soon as possible. Please try again later.",
);
},
}),
},
};
},
ssr: false, // doesn't play well with how we're fetching legacy endpoints. consider switching it on when we don't need to get legacy polls
})(appWithTranslation(MyApp));
export default trpcNext.withTRPC(appWithTranslation(MyApp));

View file

@ -1,24 +1,9 @@
import * as trpcNext from "@trpc/server/adapters/next";
import superjson from "superjson";
import { createContext } from "../../../server/context";
import { createRouter } from "../../../server/createRouter";
import { login } from "../../../server/routers/login";
import { polls } from "../../../server/routers/polls";
import { session } from "../../../server/routers/session";
import { user } from "../../../server/routers/user";
import { appRouter } from "../../../server/routers/_app";
import { withSessionRoute } from "../../../utils/auth";
export const appRouter = createRouter()
.transformer(superjson)
.merge("session.", session)
.merge("polls.", polls)
.merge(login)
.merge("user.", user);
// export type definition of API
export type AppRouter = typeof appRouter;
export const config = {
api: {
externalResolver: true,

View file

@ -3,9 +3,9 @@ import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import Home from "@/components/home";
const Page = () => <Home />;
export default Page;
export default function Page() {
return <Home />;
}
export const getServerSideProps: GetServerSideProps = async ({
locale = "en",

View file

@ -1,13 +1,12 @@
import { GetServerSideProps } from "next";
import dynamic from "next/dynamic";
import CreatePoll from "@/components/create-poll";
import { withSessionSsr } from "../utils/auth";
import { withPageTranslations } from "../utils/with-page-translations";
export default CreatePoll;
export const getServerSideProps: GetServerSideProps = withSessionSsr(
withPageTranslations(["common", "app"]),
);
export default dynamic(() => import("@/components/create-poll"), {
ssr: false,
});

View file

@ -1,10 +1,10 @@
import { GetServerSideProps, NextPage } from "next";
import dynamic from "next/dynamic";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import React from "react";
import FullPageLoader from "@/components/full-page-loader";
import PollPage from "@/components/poll";
import { PollContextProvider } from "@/components/poll-context";
import { withSession } from "@/components/session";
@ -15,8 +15,6 @@ import { trpc } from "../utils/trpc";
import { withPageTranslations } from "../utils/with-page-translations";
import Custom404 from "./404";
const PollPage = dynamic(() => import("@/components/poll"), { ssr: false });
const PollPageLoader: NextPage = () => {
const { query, asPath } = useRouter();
const { t } = useTranslation("app");

View file

@ -1,9 +1,27 @@
import { createProxySSGHelpers } from "@trpc/react-query/ssg";
import * as trpc from "@trpc/server";
import * as trpcNext from "@trpc/server/adapters/next";
import { GetServerSidePropsContext } from "next";
import superjson from "superjson";
export async function createContext(opts: trpcNext.CreateNextContextOptions) {
const session = opts.req.session;
import { getCurrentUser } from "../utils/auth";
import { appRouter } from "./routers/_app";
return {
session,
};
export async function createContext(
opts: trpcNext.CreateNextContextOptions | GetServerSidePropsContext,
) {
const user = await getCurrentUser(opts.req.session);
return { user, session: opts.req.session };
}
export type Context = trpc.inferAsyncReturnType<typeof createContext>;
export const createSSGHelperFromContext = async (
ctx: GetServerSidePropsContext,
) =>
createProxySSGHelpers({
router: appRouter,
ctx: await createContext(ctx),
transformer: superjson,
});

View file

@ -1,9 +1,6 @@
import { inferAsyncReturnType } from "@trpc/server";
import * as trpc from "@trpc/server";
import { createContext } from "./context";
type Context = inferAsyncReturnType<typeof createContext>;
import { Context } from "./context";
// Helper function to create a router with your app's context
export function createRouter() {

View file

@ -0,0 +1,42 @@
import { z } from "zod";
import { prisma } from "~/prisma/db";
import { createRouter } from "../createRouter";
import { mergeRouters, publicProcedure, router } from "../trpc";
import { login } from "./login";
import { polls } from "./polls";
import { session } from "./session";
import { user } from "./user";
const legacyRouter = createRouter()
.merge("user.", user)
.merge(login)
.merge("polls.", polls)
.merge("session.", session);
export const appRouter = mergeRouters(
legacyRouter.interop(),
router({
p: router({
touch: publicProcedure
.input(
z.object({
pollId: z.string(),
}),
)
.mutation(async ({ input }) => {
await prisma.poll.update({
where: {
id: input.pollId,
},
data: {
touchedAt: new Date(),
},
});
}),
}),
}),
);
export type AppRouter = typeof appRouter;

View file

@ -33,13 +33,10 @@ export const participants = createRouter()
pollId: z.string(),
participantId: z.string(),
}),
resolve: async ({ input: { participantId, pollId } }) => {
resolve: async ({ input: { participantId } }) => {
await prisma.participant.delete({
where: {
id_pollId: {
id: participantId,
pollId: pollId,
},
},
});
},
@ -100,10 +97,7 @@ export const participants = createRouter()
resolve: async ({ input: { pollId, participantId, votes, name } }) => {
const participant = await prisma.participant.update({
where: {
id_pollId: {
id: participantId,
pollId: pollId,
},
},
data: {
votes: {

19
src/server/trpc.ts Normal file
View file

@ -0,0 +1,19 @@
import { initTRPC } from "@trpc/server";
import superjson from "superjson";
import { Context } from "./context";
const t = initTRPC.context<Context>().create({
transformer: superjson,
errorFormatter({ shape }) {
return shape;
},
});
export const router = t.router;
export const publicProcedure = t.procedure;
export const middleware = t.middleware;
export const mergeRouters = t.mergeRouters;

View file

@ -1,4 +1,9 @@
import { IronSessionOptions, sealData, unsealData } from "iron-session";
import {
IronSession,
IronSessionOptions,
sealData,
unsealData,
} from "iron-session";
import { withIronSessionApiRoute, withIronSessionSsr } from "iron-session/next";
import { GetServerSideProps, NextApiHandler } from "next";
@ -96,3 +101,15 @@ export const mergeGuestsIntoUser = async (
},
});
};
export const getCurrentUser = async (
session: IronSession,
): Promise<{ isGuest: boolean; id: string }> => {
const user = session.user;
if (!user) {
throw new Error("Tried to get user but no user found.");
}
return user;
};

View file

@ -1,5 +1,52 @@
import { createReactQueryHooks } from "@trpc/react";
import { MutationCache } from "@tanstack/react-query";
import { httpBatchLink } from "@trpc/client";
import { createTRPCNext } from "@trpc/next";
import { createReactQueryHooks } from "@trpc/react-query";
import toast from "react-hot-toast";
import superjson from "superjson";
import type { AppRouter } from "../pages/api/trpc/[trpc]";
import { AppRouter } from "../server/routers/_app";
export const trpc = createReactQueryHooks<AppRouter>();
export const trpcNext = createTRPCNext<AppRouter>({
unstable_overrides: {
useMutation: {
async onSuccess(opts) {
/**
* @note that order here matters:
* The order here allows route changes in `onSuccess` without
* having a flash of content change whilst redirecting.
**/
await opts.originalFn();
await opts.queryClient.invalidateQueries();
},
},
},
config() {
return {
links: [
httpBatchLink({
url: `/api/trpc`,
}),
],
transformer: superjson,
queryClientConfig: {
defaultOptions: {
queries: {
cacheTime: Infinity,
staleTime: 30 * 1000, // 30 seconds
},
},
mutationCache: new MutationCache({
onError: () => {
toast.error(
"Uh oh! Something went wrong. The issue has been logged and we'll fix it as soon as possible. Please try again later.",
);
},
}),
},
};
},
ssr: false,
});

View file

@ -25,8 +25,13 @@
"paths": {
"@/*": ["src/*"],
"~/*": ["./*"]
},
"plugins": [
{
"name": "next"
}
]
},
"exclude": ["node_modules", "**/.*/", "**/*.js"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"]
}

1472
yarn.lock

File diff suppressed because it is too large Load diff