Put meaningful data on each page

This commit is contained in:
2023-04-18 20:18:46 -05:00
parent dc1e9c6947
commit 9e8d302c31
15 changed files with 204 additions and 109 deletions

View File

@@ -1,100 +1,102 @@
generator client { generator client {
provider = "prisma-client-js" provider = "prisma-client-js"
} }
datasource db { datasource db {
provider = "mysql" provider = "mysql"
url = env("DATABASE_URL") url = env("DATABASE_URL")
relationMode = "prisma" relationMode = "prisma"
} }
model Account { model Account {
id String @id @default(cuid()) id String @id @default(cuid())
userId String userId String
type String type String
provider String provider String
providerAccountId String providerAccountId String
refresh_token String? @db.Text refresh_token String? @db.Text
access_token String? @db.Text access_token String? @db.Text
expires_at Int? expires_at Int?
token_type String? token_type String?
scope String? scope String?
id_token String? @db.Text id_token String? @db.Text
session_state String? session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId]) @@unique([provider, providerAccountId])
@@index([userId]) @@index([userId])
} }
model Session { model Session {
id String @id @default(cuid()) id String @id @default(cuid())
sessionToken String @unique sessionToken String @unique
userId String userId String
expires DateTime expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId]) @@index([userId])
} }
model VerificationToken { model VerificationToken {
identifier String identifier String
token String @unique token String @unique
expires DateTime expires DateTime
@@unique([identifier, token]) @@unique([identifier, token])
} }
model User { model User {
id String @id @default(cuid()) id String @id @default(cuid())
username String? @unique
email String @unique name String?
emailVerified DateTime? email String @unique
username String? @unique emailVerified DateTime?
name String? image String?
bio String? bio String?
image String? accounts Account[]
sessions Session[]
accounts Account[] authoredProjects Project[] @relation("author")
sessions Session[] projects Project[] @relation("members")
authoredProjects Project[] @relation("author")
projects Project[] @relation("members")
} }
model Project { model Project {
id String @id @default(cuid()) id String @id @default(cuid())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
title String
description String
authorId String
bannerImageUrl String?
state ProjectLifecycle @default(PROPOSAL)
author User @relation("author", fields: [authorId], references: [id])
members User[] @relation("members")
previews ProjectPreview[]
title String @@index([authorId])
description String
bannerImageUrl String?
authorId String
author User @relation("author", fields: [authorId], references: [id])
members User[] @relation("members")
state ProjectLifecycle @default(PROPOSAL)
previews ProjectPreview[]
@@index([authorId])
} }
model ProjectPreview { model ProjectPreview {
projectState ProjectLifecycle projectState ProjectLifecycle
projectId String projectId String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) title String?
description String?
cardImageUrl String?
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
title String? @@unique([projectId, projectState])
description String? }
cardImageUrl String?
@@unique([projectId, projectState]) model members {
A String
B String
@@unique([A, B], map: "_members_AB_unique")
@@index([B], map: "_members_B_index")
@@map("_members")
} }
enum ProjectLifecycle { enum ProjectLifecycle {
PROPOSAL PROPOSAL
IN_PROGRESS IN_PROGRESS
REVISION REVISION
COMPLETE COMPLETE
} }

View File

@@ -0,0 +1,15 @@
import type { Project } from "@prisma/client";
import { formatDate } from "@utils/filters";
export const DisplayProjectCard: React.FC<{ project: Project }> = ({
project,
}) => {
return (
<div key={project.id} className="mb-10">
<h4 className="text-r-2xl font-medium">{project.title}</h4>
<p className="text-r-sm">{formatDate(project.createdAt)}</p>
<p className="text-r-sm">{project.state}</p>
<p className="text-r-lg">{project.description}</p>
</div>
);
};

View File

@@ -0,0 +1,18 @@
import type { Project } from "@prisma/client";
import { formatDate } from "@utils/filters";
import Link from "next/link";
export const EditProjectCard: React.FC<{ project: Project }> = ({
project,
}) => {
return (
<Link href={`/projects/${project.id}`}>
<div className="mb-10 rounded border-[6px] border-fg py-2 px-3">
<h4 className="text-r-2xl font-medium">{project.title}</h4>
<p className="text-r-sm">{formatDate(project.createdAt)}</p>
<p className="text-r-sm">{project.state}</p>
<p className="text-r-lg">{project.description}</p>
</div>
</Link>
);
};

View File

@@ -1,17 +0,0 @@
import Button from "@components/Button";
import type { Project } from "@prisma/client";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
dayjs.extend(relativeTime);
export const ProjectCard: React.FC<{ project: Project }> = ({ project }) => {
return (
<div key={project.id} className="mb-10">
<h4 className="text-r-2xl font-medium">{project.title}</h4>
<p className="text-r-sm">{dayjs(project.createdAt).fromNow()}</p>
<p className="text-r-sm">{project.state}</p>
<p className="text-r-lg">{project.description}</p>
</div>
);
};

View File

@@ -80,7 +80,16 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
const session = await getServerAuthSession(ctx); const session = await getServerAuthSession(ctx);
if (!session) { if (!session) {
const providers = await getProviders(); // const providers = await getProviders();
const providers = {
google: {
id: "google",
name: "Google",
type: "oauth",
signinUrl: "http://localhost:3000/api/auth/signin/google",
callbackUrl: "http://localhost:3000/api/auth/callback/google",
},
};
return { return {
props: { props: {
providers, providers,

View File

@@ -1,7 +1,7 @@
import { DiscoverLayout } from "@components/layouts"; import { DiscoverLayout } from "@components/layouts";
import { api } from "@utils/api"; import { api } from "@utils/api";
import type { InferGetServerSidePropsType, NextPage } from "next"; import type { InferGetServerSidePropsType, NextPage } from "next";
import { ProjectCard } from "@components/projects/ProjectCard"; import { DisplayProjectCard } from "@components/projects/DisplayProjectCard";
import { requireAuth } from "@components/HOC/requireAuth"; import { requireAuth } from "@components/HOC/requireAuth";
const Discover: NextPage< const Discover: NextPage<
@@ -18,7 +18,7 @@ const Discover: NextPage<
<p>Loading ...</p> <p>Loading ...</p>
) : ( ) : (
data?.map((project) => ( data?.map((project) => (
<ProjectCard key={project.id} project={project} /> <DisplayProjectCard key={project.id} project={project} />
)) ))
)} )}
</DiscoverLayout> </DiscoverLayout>

View File

@@ -7,7 +7,7 @@ import type {
InferGetServerSidePropsType, InferGetServerSidePropsType,
NextPage, NextPage,
} from "next"; } from "next";
import { ProjectCard } from "@components/projects/ProjectCard"; import { ProjectCard } from "@components/projects/DisplayProjectCard";
import { requireAuth } from "@components/HOC/requireAuth"; import { requireAuth } from "@components/HOC/requireAuth";
const Discover: NextPage< const Discover: NextPage<

View File

@@ -1,7 +1,7 @@
import { DiscoverLayout } from "@components/layouts"; import { DiscoverLayout } from "@components/layouts";
import { api } from "@utils/api"; import { api } from "@utils/api";
import type { InferGetServerSidePropsType, NextPage } from "next"; import type { InferGetServerSidePropsType, NextPage } from "next";
import { ProjectCard } from "@components/projects/ProjectCard"; import { DisplayProjectCard } from "@components/projects/DisplayProjectCard";
import { requireAuth } from "@components/HOC/requireAuth"; import { requireAuth } from "@components/HOC/requireAuth";
const Discover: NextPage< const Discover: NextPage<
@@ -18,7 +18,7 @@ const Discover: NextPage<
<p>Loading ...</p> <p>Loading ...</p>
) : ( ) : (
data?.map((project) => ( data?.map((project) => (
<ProjectCard key={project.id} project={project} /> <DisplayProjectCard key={project.id} project={project} />
)) ))
)} )}
</DiscoverLayout> </DiscoverLayout>

View File

@@ -1,13 +1,7 @@
import { DiscoverLayout } from "@components/layouts"; import { DiscoverLayout } from "@components/layouts";
import { getServerAuthSession } from "@server/auth";
import { api } from "@utils/api"; import { api } from "@utils/api";
import type { import type { InferGetServerSidePropsType, NextPage } from "next";
GetServerSideProps, import { DisplayProjectCard } from "@components/projects/DisplayProjectCard";
GetServerSidePropsContext,
InferGetServerSidePropsType,
NextPage,
} from "next";
import { ProjectCard } from "@components/projects/ProjectCard";
import { requireAuth } from "@components/HOC/requireAuth"; import { requireAuth } from "@components/HOC/requireAuth";
const Discover: NextPage< const Discover: NextPage<
@@ -24,7 +18,7 @@ const Discover: NextPage<
<p>Loading ...</p> <p>Loading ...</p>
) : ( ) : (
data?.map((project) => ( data?.map((project) => (
<ProjectCard key={project.id} project={project} /> <DisplayProjectCard key={project.id} project={project} />
)) ))
)} )}
</DiscoverLayout> </DiscoverLayout>

View File

@@ -16,16 +16,24 @@ const Profile: NextPage<
<h1 className="text-r-5xl font-bold text-primary">Profile</h1> <h1 className="text-r-5xl font-bold text-primary">Profile</h1>
{data && ( {data && (
<> <>
<h2 className="text-r-4xl">{data.name}</h2>
<p>{data.username}</p> <p>{data.username}</p>
<p>{data.name}</p>
<p>{data.bio}</p> <p>{data.bio}</p>
<p>
{data.projects.map((m) => ( <h3 className="text-r-3xl">My Projects</h3>
<> {data.authoredProjects.map((m) => (
<p>{m.title}</p> <>
</> <p>{m.title}</p>
))} <p>{m.description}</p>
</p> </>
))}
<h3 className="text-r-3xl">Projects I{"'"}ve Worked On</h3>
{data.projects.map((m) => (
<>
<p>{m.title}</p>
<p>{m.description}</p>
</>
))}
</> </>
)} )}
</MainLayout> </MainLayout>

View File

@@ -0,0 +1,24 @@
import { requireAuth } from "@components/HOC/requireAuth";
import { MainLayout } from "@components/layouts";
import { api } from "@utils/api";
import type { InferGetServerSidePropsType, NextPage } from "next";
const SpecificProject: NextPage<
InferGetServerSidePropsType<typeof getServerSideProps>
> = ({ projectId }) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { data } = api.projects.getProjectById.useQuery({ projectId });
return (
<MainLayout>
<p>{data?.id}</p>
</MainLayout>
);
};
export default SpecificProject;
// eslint-disable-next-line @typescript-eslint/require-await
export const getServerSideProps = requireAuth(async (ctx) => {
return { props: { projectId: ctx.params?.projectId } };
});

View File

@@ -1,11 +1,28 @@
import type { NextPage } from "next"; import type { NextPage } from "next";
import { MainLayout } from "@components/layouts"; import { MainLayout } from "@components/layouts";
import { requireAuth } from "@components/HOC/requireAuth"; import { requireAuth } from "@components/HOC/requireAuth";
import { api } from "@utils/api";
import { useMemo } from "react";
import { EditProjectCard } from "@components/projects/EditProjectCard";
const Projects: NextPage = () => { const Projects: NextPage = () => {
const { data } = api.projects.getAllByCurrentUser.useQuery();
const sortedProjects = useMemo(() => {
if (!data) return [];
const projects = data.projects.concat(data.authoredProjects);
return projects.sort(
(a, b) => b.createdAt.getTime() - a.createdAt.getTime()
);
}, [data]);
return ( return (
<MainLayout> <MainLayout>
<h1 className="text-r-5xl font-bold text-primary">Projects</h1> <h1 className="text-r-5xl font-bold text-primary">Projects</h1>
{sortedProjects.map((p) => (
<EditProjectCard key={p.id} project={p} />
))}
</MainLayout> </MainLayout>
); );
}; };

View File

@@ -13,6 +13,7 @@ export const profileRouter = createTRPCRouter({
name: true, name: true,
bio: true, bio: true,
image: true, image: true,
authoredProjects: true,
projects: true, projects: true,
}, },
}); });

View File

@@ -1,5 +1,5 @@
import { proposalSchema } from "@utils/constants/schema/project"; import { proposalSchema } from "@utils/constants/schema/project";
import { Project, ProjectLifecycle } from "@prisma/client"; import { ProjectLifecycle } from "@prisma/client";
import { z } from "zod"; import { z } from "zod";
import { createTRPCRouter, publicProcedure, protectedProcedure } from "../trpc"; import { createTRPCRouter, publicProcedure, protectedProcedure } from "../trpc";
@@ -11,6 +11,12 @@ export const projectsRouter = createTRPCRouter({
orderBy: [{ createdAt: "desc" }], orderBy: [{ createdAt: "desc" }],
}); });
}), }),
getAllByCurrentUser: protectedProcedure.query(({ ctx }) => {
return ctx.prisma.user.findUnique({
where: { id: ctx.session.user.id },
select: { authoredProjects: true, projects: true },
});
}),
getAllByUserId: publicProcedure getAllByUserId: publicProcedure
.input( .input(
z.object({ z.object({
@@ -36,6 +42,17 @@ export const projectsRouter = createTRPCRouter({
orderBy: [{ createdAt: "desc" }], orderBy: [{ createdAt: "desc" }],
}); });
}), }),
getProjectById: publicProcedure
.input(
z.object({
projectId: z.string(),
})
)
.query(({ ctx, input }) => {
return ctx.prisma.project.findUnique({
where: { id: input.projectId },
});
}),
createProposal: protectedProcedure createProposal: protectedProcedure
.input(proposalSchema) .input(proposalSchema)
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {

View File

@@ -1,3 +1,10 @@
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
dayjs.extend(relativeTime);
export const formatDate = (date: Date) => dayjs(date).fromNow();
export const getQueryOrDefault = ( export const getQueryOrDefault = (
value: string | string[] | undefined, value: string | string[] | undefined,
fallback: string fallback: string