{user.shortName}
@@ -195,6 +193,9 @@ const UserDropdown: React.VoidFunctionComponent<
}}
/>
) : null}
+ {!user.isGuest ? (
+
+ ) : null}
{user.isGuest ? (
) : null}
@@ -300,10 +301,7 @@ const StandardLayout: React.VoidFunctionComponent<{
>
- {user.isGuest ? (
-
- ) : null}
-
+
diff --git a/src/components/text-input.tsx b/src/components/text-input.tsx
new file mode 100644
index 000000000..a204fd2d2
--- /dev/null
+++ b/src/components/text-input.tsx
@@ -0,0 +1,26 @@
+import clsx from "clsx";
+import * as React from "react";
+
+export interface TextInputProps
+ extends React.DetailedHTMLProps<
+ React.InputHTMLAttributes
,
+ HTMLInputElement
+ > {
+ error?: boolean;
+}
+
+export const TextInput = React.forwardRef(
+ function TextInput({ className, error, ...forwardProps }, ref) {
+ return (
+
+ );
+ },
+);
diff --git a/src/pages/api/trpc/[trpc].ts b/src/pages/api/trpc/[trpc].ts
index 15a717b20..512e0937e 100644
--- a/src/pages/api/trpc/[trpc].ts
+++ b/src/pages/api/trpc/[trpc].ts
@@ -6,13 +6,15 @@ 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 { withSessionRoute } from "../../../utils/auth";
export const appRouter = createRouter()
.transformer(superjson)
.merge("session.", session)
.merge("polls.", polls)
- .merge(login);
+ .merge(login)
+ .merge("user.", user);
// export type definition of API
export type AppRouter = typeof appRouter;
diff --git a/src/pages/profile.tsx b/src/pages/profile.tsx
new file mode 100644
index 000000000..689e28541
--- /dev/null
+++ b/src/pages/profile.tsx
@@ -0,0 +1,40 @@
+import { NextPage } from "next";
+import Head from "next/head";
+import { serverSideTranslations } from "next-i18next/serverSideTranslations";
+
+import { withSessionSsr } from "@/utils/auth";
+
+import { Profile } from "../components/profile";
+import { SessionProvider, UserSessionData } from "../components/session";
+import StandardLayout from "../components/standard-layout";
+
+const Page: NextPage<{ user: UserSessionData }> = ({ user }) => {
+ const name = user.isGuest ? user.id : user.name;
+ return (
+
+
+ Profile - {name}
+
+
+
+
+
+ );
+};
+
+export const getServerSideProps = withSessionSsr(
+ async ({ locale = "en", query, req }) => {
+ if (!req.session.user || req.session.user.isGuest) {
+ return { redirect: { destination: "/new" }, props: {} };
+ }
+ return {
+ props: {
+ ...(await serverSideTranslations(locale, ["app"])),
+ ...query,
+ user: req.session.user,
+ },
+ };
+ },
+);
+
+export default Page;
diff --git a/src/server/routers/session.ts b/src/server/routers/session.ts
index d6210e59f..9de43fc03 100644
--- a/src/server/routers/session.ts
+++ b/src/server/routers/session.ts
@@ -15,12 +15,14 @@ export const session = createRouter()
return null;
}
- return {
+ ctx.session.user = {
id: user.id,
name: user.name,
email: user.email,
isGuest: false,
};
+
+ await ctx.session.save();
}
return ctx.session.user;
diff --git a/src/server/routers/user.ts b/src/server/routers/user.ts
new file mode 100644
index 000000000..3012d7f21
--- /dev/null
+++ b/src/server/routers/user.ts
@@ -0,0 +1,68 @@
+import { TRPCError } from "@trpc/server";
+import { IronSessionData } from "iron-session";
+import { z } from "zod";
+
+import { prisma } from "~/prisma/db";
+
+import { createRouter } from "../createRouter";
+
+const requireUser = (user: IronSessionData["user"]) => {
+ if (!user) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "Tried to access user route without a session",
+ });
+ }
+ return user;
+};
+
+export const user = createRouter()
+ .query("getPolls", {
+ resolve: async ({ ctx }) => {
+ const user = requireUser(ctx.session.user);
+ const userPolls = await prisma.user.findUnique({
+ where: {
+ id: user.id,
+ },
+ select: {
+ polls: {
+ where: {
+ deleted: false,
+ },
+ select: {
+ title: true,
+ closed: true,
+ verified: true,
+ createdAt: true,
+ links: {
+ where: {
+ role: "admin",
+ },
+ },
+ },
+ take: 5,
+ orderBy: {
+ createdAt: "desc",
+ },
+ },
+ },
+ });
+ return userPolls;
+ },
+ })
+ .mutation("changeName", {
+ input: z.object({
+ userId: z.string(),
+ name: z.string().min(1).max(100),
+ }),
+ resolve: async ({ input }) => {
+ await prisma.user.update({
+ where: {
+ id: input.userId,
+ },
+ data: {
+ name: input.name,
+ },
+ });
+ },
+ });
diff --git a/style.css b/style.css
index d76c558d2..00a87927a 100644
--- a/style.css
+++ b/style.css
@@ -69,12 +69,16 @@
@apply inline-flex h-9 cursor-default select-none items-center justify-center whitespace-nowrap rounded-md border px-3 font-medium shadow-sm transition-all active:scale-95;
}
a.btn {
- @apply hover:no-underline;
+ @apply cursor-pointer hover:no-underline;
}
.btn-default {
@apply btn border-slate-300 bg-white text-slate-700 hover:bg-indigo-50/10 active:bg-slate-100;
}
+
+ a.btn-default {
+ @apply hover:text-indigo-500;
+ }
.btn-danger {
@apply btn border-rose-600 bg-rose-500 text-white hover:bg-rose-600 focus-visible:ring-rose-500;
}
@@ -123,8 +127,7 @@
}
.card {
- @apply rounded-lg border bg-white p-6
- shadow-sm;
+ @apply border-t border-b bg-white p-6 shadow-sm sm:rounded-lg sm:border-l sm:border-r;
}
}
diff --git a/tests/create-delete-poll.spec.ts b/tests/create-delete-poll.spec.ts
index f480d2345..de04465a4 100644
--- a/tests/create-delete-poll.spec.ts
+++ b/tests/create-delete-poll.spec.ts
@@ -23,8 +23,11 @@ test("should be able to create a new poll and delete it", async ({ page }) => {
await page.click('text="Continue"');
- await page.type('[placeholder="John Doe"]', "John");
- await page.type('[placeholder="john.doe@email.com"]', "john.doe@email.com");
+ await page.type('[placeholder="Jessie Smith"]', "John");
+ await page.type(
+ '[placeholder="jessie.smith@email.com"]',
+ "john.doe@email.com",
+ );
await page.click('text="Create poll"');
diff --git a/yarn.lock b/yarn.lock
index 0e6af4f06..d841f5750 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1826,11 +1826,6 @@
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
-"@types/mixpanel-browser@^2.38.0":
- version "2.38.0"
- resolved "https://registry.yarnpkg.com/@types/mixpanel-browser/-/mixpanel-browser-2.38.0.tgz#b3e28e1ba06c10a9f88510b88f1ac9d1b2adfc42"
- integrity sha512-TR8rvsILnqXA7oiiGOxuMGXwvDeCoQDonXJB5UR+TYvEAFpiK8ReFj5LhZT+Xhm3NpI9aPoju30jB2ssorSUww==
-
"@types/node@*":
version "17.0.21"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.21.tgz#864b987c0c68d07b4345845c3e63b75edd143644"
@@ -5073,10 +5068,10 @@ react-github-btn@^1.2.2:
dependencies:
github-buttons "^2.21.1"
-react-hook-form@^7.27.0:
- version "7.27.0"
- resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.27.0.tgz#2c05e54ca557f71c55f645311ff612ec936c6c7c"
- integrity sha512-NEh3Qbz1Rg3w95SRZv0kHorHN3frtMKasplznMBr8RkFrE4pVxjd/zo3clnFXpD0FppUVHBMfsTMtTsa6wyQrA==
+react-hook-form@^7.31.2:
+ version "7.31.2"
+ resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.31.2.tgz#efb7ac469810954488b7cf40be4e5017122c6e5e"
+ integrity sha512-oPudn3YuyzWg//IsT9z2cMEjWocAgHWX/bmueDT8cmsYQnGY5h7/njjvMDfLVv3mbdhYBjslTRnII2MIT7eNCA==
react-hot-toast@^2.2.0:
version "2.2.0"