mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-29 06:07:25 +02:00
Use client side fetching for user data (#211)
This commit is contained in:
parent
1d768083ee
commit
368f324865
10 changed files with 93 additions and 63 deletions
15
declarations/iron-session.d.ts
vendored
15
declarations/iron-session.d.ts
vendored
|
@ -2,16 +2,9 @@ import "iron-session";
|
||||||
|
|
||||||
declare module "iron-session" {
|
declare module "iron-session" {
|
||||||
export interface IronSessionData {
|
export interface IronSessionData {
|
||||||
user:
|
user: {
|
||||||
| {
|
id: string;
|
||||||
id: string;
|
isGuest: boolean;
|
||||||
name: string;
|
};
|
||||||
email: string;
|
|
||||||
isGuest: false;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
id: string;
|
|
||||||
isGuest: true;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { formatRelative } from "date-fns";
|
import { formatRelative } from "date-fns";
|
||||||
|
import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import User from "@/components/icons/user.svg";
|
||||||
|
|
||||||
import { trpc } from "../utils/trpc";
|
import { trpc } from "../utils/trpc";
|
||||||
import { EmptyState } from "./empty-state";
|
import { EmptyState } from "./empty-state";
|
||||||
|
import LoginForm from "./login-form";
|
||||||
import { UserDetails } from "./profile/user-details";
|
import { UserDetails } from "./profile/user-details";
|
||||||
import { useSession } from "./session";
|
import { useSession } from "./session";
|
||||||
|
|
||||||
|
@ -19,21 +20,24 @@ export const Profile: React.VoidFunctionComponent = () => {
|
||||||
const { t } = useTranslation("app");
|
const { t } = useTranslation("app");
|
||||||
const { data: userPolls } = trpc.useQuery(["user.getPolls"]);
|
const { data: userPolls } = trpc.useQuery(["user.getPolls"]);
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const createdPolls = userPolls?.polls;
|
const createdPolls = userPolls?.polls;
|
||||||
|
|
||||||
React.useEffect(() => {
|
if (user.isGuest) {
|
||||||
if (!user) {
|
return (
|
||||||
router.replace("/new");
|
<div className="card my-4 p-0">
|
||||||
}
|
<Head>
|
||||||
}, [user, router]);
|
<title>Profile - Login</title>
|
||||||
|
</Head>
|
||||||
if (!user || user.isGuest) {
|
<LoginForm />
|
||||||
return null;
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto max-w-3xl py-4 lg:mx-0">
|
<div className="mx-auto max-w-3xl py-4 lg:mx-0">
|
||||||
|
<Head>
|
||||||
|
<title>Profile - {user.name}</title>
|
||||||
|
</Head>
|
||||||
<div className="mb-4 flex items-center px-4">
|
<div className="mb-4 flex items-center px-4">
|
||||||
<div className="mr-4 inline-flex h-14 w-14 items-center justify-center rounded-lg bg-primary-50">
|
<div className="mr-4 inline-flex h-14 w-14 items-center justify-center rounded-lg bg-primary-50">
|
||||||
<User className="h-7 text-primary-500" />
|
<User className="h-7 text-primary-500" />
|
||||||
|
|
|
@ -11,8 +11,8 @@ import { TextInput } from "../text-input";
|
||||||
|
|
||||||
export interface UserDetailsProps {
|
export interface UserDetailsProps {
|
||||||
userId: string;
|
userId: string;
|
||||||
name: string;
|
name?: string;
|
||||||
email: string;
|
email?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MotionButton = motion(Button);
|
const MotionButton = motion(Button);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import toast from "react-hot-toast";
|
||||||
|
|
||||||
import { trpc } from "@/utils/trpc";
|
import { trpc } from "@/utils/trpc";
|
||||||
|
|
||||||
|
import FullPageLoader from "./full-page-loader";
|
||||||
import { useRequiredContext } from "./use-required-context";
|
import { useRequiredContext } from "./use-required-context";
|
||||||
|
|
||||||
export type UserSessionData = NonNullable<IronSessionData["user"]>;
|
export type UserSessionData = NonNullable<IronSessionData["user"]>;
|
||||||
|
@ -16,13 +17,23 @@ type ParticipantOrComment = {
|
||||||
userId: string | null;
|
userId: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UserSessionDataExtended = UserSessionData & {
|
export type UserSessionDataExtended =
|
||||||
shortName: string;
|
| {
|
||||||
};
|
isGuest: true;
|
||||||
|
id: string;
|
||||||
|
shortName: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
isGuest: false;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
shortName: string;
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
|
||||||
type SessionContextValue = {
|
type SessionContextValue = {
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
user: UserSessionDataExtended | null;
|
user: UserSessionDataExtended;
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
ownsObject: (obj: ParticipantOrComment) => boolean;
|
ownsObject: (obj: ParticipantOrComment) => boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
@ -35,14 +46,9 @@ SessionContext.displayName = "SessionContext";
|
||||||
|
|
||||||
export const SessionProvider: React.VoidFunctionComponent<{
|
export const SessionProvider: React.VoidFunctionComponent<{
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
defaultUser: UserSessionData;
|
}> = ({ children }) => {
|
||||||
}> = ({ children, defaultUser }) => {
|
|
||||||
const queryClient = trpc.useContext();
|
const queryClient = trpc.useContext();
|
||||||
const {
|
const { data: user, refetch, isLoading } = trpc.useQuery(["session.get"]);
|
||||||
data: user = defaultUser,
|
|
||||||
refetch,
|
|
||||||
isLoading,
|
|
||||||
} = trpc.useQuery(["session.get"]);
|
|
||||||
|
|
||||||
const logout = trpc.useMutation(["session.destroy"], {
|
const logout = trpc.useMutation(["session.destroy"], {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
@ -50,6 +56,10 @@ export const SessionProvider: React.VoidFunctionComponent<{
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return <FullPageLoader>Loading user…</FullPageLoader>;
|
||||||
|
}
|
||||||
|
|
||||||
const sessionData: SessionContextValue = {
|
const sessionData: SessionContextValue = {
|
||||||
user: {
|
user: {
|
||||||
...user,
|
...user,
|
||||||
|
@ -95,7 +105,7 @@ export const withSession = <P extends SessionProps>(
|
||||||
const ComposedComponent: React.VoidFunctionComponent<P> = (props: P) => {
|
const ComposedComponent: React.VoidFunctionComponent<P> = (props: P) => {
|
||||||
const Component = component;
|
const Component = component;
|
||||||
return (
|
return (
|
||||||
<SessionProvider defaultUser={props.user}>
|
<SessionProvider>
|
||||||
<Component {...props} />
|
<Component {...props} />
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -100,8 +100,6 @@ export const getServerSideProps: GetServerSideProps = withSessionSsr(
|
||||||
|
|
||||||
req.session.user = {
|
req.session.user = {
|
||||||
isGuest: false,
|
isGuest: false,
|
||||||
name: user.name,
|
|
||||||
email: user.email,
|
|
||||||
id: user.id,
|
id: user.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,40 +1,29 @@
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
import Head from "next/head";
|
|
||||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||||
|
|
||||||
import { withSessionSsr } from "@/utils/auth";
|
import { withSessionSsr } from "@/utils/auth";
|
||||||
|
|
||||||
import { Profile } from "../components/profile";
|
import { Profile } from "../components/profile";
|
||||||
import { SessionProvider, UserSessionData } from "../components/session";
|
import { withSession } from "../components/session";
|
||||||
import StandardLayout from "../components/standard-layout";
|
import StandardLayout from "../components/standard-layout";
|
||||||
|
|
||||||
const Page: NextPage<{ user: UserSessionData }> = ({ user }) => {
|
const Page: NextPage = () => {
|
||||||
const name = user.isGuest ? user.id : user.name;
|
|
||||||
return (
|
return (
|
||||||
<SessionProvider defaultUser={user}>
|
<StandardLayout>
|
||||||
<Head>
|
<Profile />
|
||||||
<title>Profile - {name}</title>
|
</StandardLayout>
|
||||||
</Head>
|
|
||||||
<StandardLayout>
|
|
||||||
<Profile />
|
|
||||||
</StandardLayout>
|
|
||||||
</SessionProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = withSessionSsr(
|
export const getServerSideProps = withSessionSsr(
|
||||||
async ({ locale = "en", query, req }) => {
|
async ({ locale = "en", query }) => {
|
||||||
if (!req.session.user || req.session.user.isGuest) {
|
|
||||||
return { redirect: { destination: "/new" }, props: {} };
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
...(await serverSideTranslations(locale, ["app"])),
|
...(await serverSideTranslations(locale, ["app"])),
|
||||||
...query,
|
...query,
|
||||||
user: req.session.user,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Page;
|
export default withSession(Page);
|
||||||
|
|
|
@ -96,6 +96,19 @@ export const polls = createRouter()
|
||||||
resolve: async ({ ctx, input }): Promise<{ urlId: string }> => {
|
resolve: async ({ ctx, input }): Promise<{ urlId: string }> => {
|
||||||
const adminUrlId = await nanoid();
|
const adminUrlId = await nanoid();
|
||||||
|
|
||||||
|
let verified = false;
|
||||||
|
|
||||||
|
if (ctx.session.user.isGuest === false) {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: ctx.session.user.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
// If user is logged in with the same email address
|
||||||
|
if (user?.email === input.user.email) {
|
||||||
|
verified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const poll = await prisma.poll.create({
|
const poll = await prisma.poll.create({
|
||||||
data: {
|
data: {
|
||||||
id: await nanoid(),
|
id: await nanoid(),
|
||||||
|
@ -106,9 +119,7 @@ export const polls = createRouter()
|
||||||
description: input.description,
|
description: input.description,
|
||||||
authorName: input.user.name,
|
authorName: input.user.name,
|
||||||
demo: input.demo,
|
demo: input.demo,
|
||||||
verified:
|
verified: verified,
|
||||||
ctx.session.user?.isGuest === false &&
|
|
||||||
ctx.session.user.email === input.user.email,
|
|
||||||
adminUrlId,
|
adminUrlId,
|
||||||
participantUrlId: await nanoid(),
|
participantUrlId: await nanoid(),
|
||||||
user: {
|
user: {
|
||||||
|
|
|
@ -49,8 +49,6 @@ export const verification = createRouter()
|
||||||
ctx.session.user = {
|
ctx.session.user = {
|
||||||
id: poll.user.id,
|
id: poll.user.id,
|
||||||
isGuest: false,
|
isGuest: false,
|
||||||
name: poll.user.name,
|
|
||||||
email: poll.user.email,
|
|
||||||
};
|
};
|
||||||
await ctx.session.save();
|
await ctx.session.save();
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,9 +1,36 @@
|
||||||
|
import { prisma } from "~/prisma/db";
|
||||||
|
|
||||||
|
import { createGuestUser } from "../../utils/auth";
|
||||||
import { createRouter } from "../createRouter";
|
import { createRouter } from "../createRouter";
|
||||||
|
|
||||||
export const session = createRouter()
|
export const session = createRouter()
|
||||||
.query("get", {
|
.query("get", {
|
||||||
async resolve({ ctx }) {
|
async resolve({
|
||||||
return ctx.session.user;
|
ctx,
|
||||||
|
}): Promise<
|
||||||
|
| { isGuest: true; id: string }
|
||||||
|
| { isGuest: false; id: string; name: string; email: string }
|
||||||
|
> {
|
||||||
|
if (ctx.session.user.isGuest) {
|
||||||
|
return { isGuest: true, id: ctx.session.user.id };
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: ctx.session.user.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
ctx.session.user = await createGuestUser();
|
||||||
|
await ctx.session.save();
|
||||||
|
return { isGuest: true, id: ctx.session.user.id };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isGuest: false,
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.mutation("destroy", {
|
.mutation("destroy", {
|
||||||
|
|
|
@ -51,7 +51,7 @@ export const createToken = async <T extends Record<string, unknown>>(
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createGuestUser = async (): Promise<{
|
export const createGuestUser = async (): Promise<{
|
||||||
isGuest: true;
|
isGuest: true;
|
||||||
id: string;
|
id: string;
|
||||||
}> => {
|
}> => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue