mirror of
https://github.com/lukevella/rallly.git
synced 2025-05-12 16:36:49 +02:00
Update major dependencies (#408)
This commit is contained in:
parent
6332d6459f
commit
e845d36c51
30 changed files with 1294 additions and 775 deletions
|
@ -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"),
|
||||
};
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
|
|
55
package.json
55
package.json
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
|
@ -0,0 +1,2 @@
|
|||
-- CreateIndex
|
||||
CREATE INDEX "comments_user_id_idx" ON "comments" USING HASH ("user_id");
|
|
@ -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");
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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")}
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">•</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">•</div>
|
||||
<div className="flex items-center space-x-6">
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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() {
|
||||
|
|
42
src/server/routers/_app.ts
Normal file
42
src/server/routers/_app.ts
Normal 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;
|
|
@ -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
19
src/server/trpc.ts
Normal 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;
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue