diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index f3ceae7afd354a8b1b1fc9301c5b6f25e2eca343..f499915ecddbb268410432807a0eb8c2c9dcca56 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -4,7 +4,7 @@ import Link from 'next/link' export default function LoginPage() { return ( <div className="h-screen w-screen flex justify-center items-center bg-slate-100"> - <div className="sm:shadow-xl px-8 pb-8 pt-12 sm:bg-white rounded-xl space-y-12"> + <div className="sm:shadow-xl px-8 pb-8 pt-12 sm:bg-black rounded-xl space-y-12"> <h1 className="font-semibold text-2xl">Login</h1> <LoginForm /> <p className="text-center"> diff --git a/app/(auth)/signup/page.tsx b/app/(auth)/signup/page.tsx index 8f2d6d9dffb89f50281a46d4e4ac89bbcd05654f..ea1b9f6903ff3760516c72353c6c14f392819529 100644 --- a/app/(auth)/signup/page.tsx +++ b/app/(auth)/signup/page.tsx @@ -4,7 +4,7 @@ import Link from 'next/link' export default function SignupPage() { return ( <div className="h-screen w-screen flex justify-center items-center bg-slate-100"> - <div className="sm:shadow-xl px-8 pb-8 pt-12 sm:bg-white rounded-xl space-y-12"> + <div className="sm:shadow-xl px-8 pb-8 pt-12 sm:bg-black rounded-xl space-y-12"> <h1 className="font-semibold text-2xl">Create your Account</h1> <SignupForm /> <p className="text-center"> diff --git a/app/(content)/(user)/[userid]/page.tsx b/app/(content)/(user)/[userid]/page.tsx index 69fa2065f2ba3c79cce8dd9d19db4264ecadb2f6..86ddc8ad3eadfa841f8e89e7d82f93ea50456d1a 100644 --- a/app/(content)/(user)/[userid]/page.tsx +++ b/app/(content)/(user)/[userid]/page.tsx @@ -1,8 +1,40 @@ +'use client' +import { useSession } from "next-auth/react"; + + export default function User({ params }: { params: { userid: string } }) { + const { data: session } = useSession(); return ( - <> - <h1>User Profile Page WIP</h1> - <p>Unique Page Params: {params.userid}</p> - </> + <div className="mt-8 px-4"> + <div className="flex-shrink-0"> + <title>{`GameUnity User`}</title> + <div className="h-10 w-10 rounded-full bg-gray-300"></div> {/* Profile picture */} + </div> + <div className="flex flex-col"> + <p className="text-white text-2xl font-semibold"> + {session?.user.name} + </p> + <div + className=" + flex + flex-row + items-center + gap-2 + mt-4 + text-neutral-500 + "> + </div> + </div> + <div className="flex flex-row items-center mt-4 gap-6"> + <div className="flex flex-row items-center gap-1"> + <p className="text-neutral-500">Following</p> + </div> + <div className="flex flex-row items-center gap-1"> + <p className="text-white">{}</p> + <p className="text-neutral-500">Followers</p> + </div> + </div> + </div> + ) } \ No newline at end of file diff --git a/app/(content)/(user)/followers/page.tsx b/app/(content)/(user)/followers/page.tsx deleted file mode 100644 index bf352f844956a40af55fe6c3ee73867628a9ab10..0000000000000000000000000000000000000000 --- a/app/(content)/(user)/followers/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function Followers() { - return ( - <div> - <h1>Followers Page WIP</h1> - </div> - ) -} \ No newline at end of file diff --git a/app/(content)/followers/page.tsx b/app/(content)/followers/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7d8778f1536ea8bccdf82875f3b1ccb301ed563b --- /dev/null +++ b/app/(content)/followers/page.tsx @@ -0,0 +1,18 @@ +import { authOptions } from "@/app/api/auth/[...nextauth]/route"; +import FollowersList from "@/components/following-users"; +import { getServerSession } from "next-auth"; + +export default async function Followers() { + const session = await getServerSession(authOptions); + + if (!session) { + return <div>Loading...</div>; + } + + return ( + <div> + <h1>Followers Page WIP</h1> + <FollowersList userId={session.user?.id} /> + </div> + ) +} \ No newline at end of file diff --git a/app/(content)/layout.tsx b/app/(content)/layout.tsx index 0e27a5e2484fc79b7061b26b072adb93f36cda51..b960adc16735c90def990ddce4d44f07074b8255 100644 --- a/app/(content)/layout.tsx +++ b/app/(content)/layout.tsx @@ -15,6 +15,7 @@ export default async function ContentLayout({ <aside className="hidden w-[200px] flex-col md:flex"> <div className="sticky top-0"> <DashboardNav items={dashboardConfig.sidebarNav} /> + <button>Logout</button> </div> </aside> <main className="flex w-full flex-1 flex-col overflow-hidden"> diff --git a/app/(content)/(user)/notifications/page.tsx b/app/(content)/notifications/page.tsx similarity index 100% rename from app/(content)/(user)/notifications/page.tsx rename to app/(content)/notifications/page.tsx diff --git a/app/api/user/[userid].ts b/app/api/user/[userid].ts new file mode 100644 index 0000000000000000000000000000000000000000..5fa1e7c6303bfcd9b8181eaf1680cdb213aa549d --- /dev/null +++ b/app/api/user/[userid].ts @@ -0,0 +1,28 @@ +import { NextApiRequest, NextApiResponse } from "next"; + +import { prisma } from "@/lib/db"; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== 'GET') { + return res.status(405).end(); + } + + try { + const { userId } = req.query; + + if (!userId || typeof userId !== 'string') { + throw new Error('Invalid ID'); + } + + const existingUser = await prisma.user.findUnique({ + where: { + id : +userId + } + }); + + return res.status(200).json({ ...existingUser}); + } catch (error) { + console.log(error); + return res.status(400).end(); + } +}; \ No newline at end of file diff --git a/app/api/user/index.ts b/app/api/user/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..abb35ff5646110d1595419851254f320385d9175 --- /dev/null +++ b/app/api/user/index.ts @@ -0,0 +1,22 @@ +import { NextApiRequest, NextApiResponse } from "next"; + +import { prisma } from "@/lib/db"; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== 'GET') { + return res.status(405).end(); + } + + try { + const users = await prisma.user.findMany({ + orderBy: { + createdAt: 'desc' + } + }); + + return res.status(200).json(users); + } catch(error) { + console.log(error); + return res.status(400).end(); + } +} \ No newline at end of file diff --git a/components/following-button.tsx b/components/following-button.tsx new file mode 100644 index 0000000000000000000000000000000000000000..af3f477154482518863ab563d5ebcb1fa11cf031 --- /dev/null +++ b/components/following-button.tsx @@ -0,0 +1,54 @@ +"use client" + +import { PrismaClient } from '@prisma/client'; +import { useState } from 'react'; +import { Button } from './ui/button'; + +const prisma = new PrismaClient(); + +async function getFollower(userId: number, followerId: number) { + const follower = await prisma.follows.findFirst({ + where: { + followerId: followerId, + followingId: userId, + }, + }); + + return follower; +} + +export default function FollowButton({ userId, followerId }: { userId: number; followerId: number }) { + const [isFollowing, setIsFollowing] = useState(false); + + const handleFollow = async () => { + const follower = await getFollower(userId, followerId); + + if (follower) { + // User is already following, so unfollow + await prisma.follows.delete({ + where: { + followerId_followingId: { + followerId: followerId, + followingId: userId, + }, + }, + }); + setIsFollowing(false); + } else { + // User is not following, so follow + await prisma.follows.create({ + data: { + followerId: followerId, + followingId: userId, + }, + }); + setIsFollowing(true); + } + }; + + return ( + <Button onClick={handleFollow}> + {isFollowing ? 'Unfollow' : 'Follow'} + </Button> + ); +} \ No newline at end of file diff --git a/components/following-users.tsx b/components/following-users.tsx new file mode 100644 index 0000000000000000000000000000000000000000..be7c652973df7a5f4d0e5bb4871cfb8b71184eee --- /dev/null +++ b/components/following-users.tsx @@ -0,0 +1,46 @@ +"use client" + +import { PrismaClient } from '@prisma/client'; +import { useEffect, useState } from 'react'; + +const prisma = new PrismaClient(); + +interface Follower { + id: number; + name: string; + email: string | null; +} + +export default function FollowersList({ userId }: { userId: number }) { + const [followers, setFollowers] = useState<Follower[]>([]); + + useEffect(() => { + async function fetchFollowers() { + const followersList = await prisma.follows.findMany({ + where: { + followingId: userId, + }, + include: { + follower: true, + }, + }); + + const filteredFollowers = followersList.map((follow) => { + const { id, name, email } = follow.follower; + return { id, name: name ?? "", email }; + }); + + setFollowers(filteredFollowers); + } + + fetchFollowers(); + }, [userId]); + + return ( + <ul> + {followers.map((follower) => ( + <li key={follower.id}>{follower.name} ({follower.email})</li> + ))} + </ul> + ); +} \ No newline at end of file diff --git a/components/user-item.tsx b/components/user-item.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0da145d8bdee65dbe40671248f0b64cfcc3106ec --- /dev/null +++ b/components/user-item.tsx @@ -0,0 +1,23 @@ +import Image from "next/image"; +import Link from "next/link"; +import FollowButton from "./following-button"; + +// this is a single user helper-component, only for design purposes +export default function FollowUser({ id, followId, userName, image }: { id: number, followId: number, userName: string, image: { url: string } }) { + return ( + <div> + <Link href={`/user/${id}`}> + <div className=""> + <Image + src={image.url} + alt={userName} + width={50} + height={50} + priority={true} /> + </div> + <p>{userName}</p> + <FollowButton userId={id} followerId={followId} /> + </Link> + </div> + ) +} \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a6b261ad022b8097f26e23533fea0e740a542bfd..dbf586aff3bf9942c1708cfcf403b99eb71a2001 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -12,16 +12,30 @@ datasource db { model User { id Int @id @default(autoincrement()) - userName String? @unique - name String? + userName String? @unique + name String? @default("u ${id}") email String? @unique password String emailVerified DateTime? - image String? + image String? + createdAt DateTime @default(now()) Post Post[] Comment Comment[] Like Like[] + + followers Follows[] @relation("follower") + following Follows[] @relation("following") +} + +model Follows { + follower User @relation("following", fields: [followerId], references: [id]) + followerId Int + following User @relation("follower", fields: [followingId], references: [id]) + followingId Int + createdAt DateTime @default(now()) + + @@id([followerId, followingId]) } model Post {