Skip to content
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "gray",
"cssVariables": false
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
6,730 changes: 6,730 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@hookform/resolvers": "^3.1.0",
"@mantine/hooks": "^6.0.13",
"@next-auth/prisma-adapter": "^1.0.6",
"@prisma/client": "^4.14.1",
"@prisma/client": "^5.2.0",
"@radix-ui/react-avatar": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-label": "^2.0.2",
Expand Down
105 changes: 97 additions & 8 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,101 @@ model Session {
}

model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?

image String?
accounts Account[]
sessions Session[]
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
createdSubreddits Subreddit[] @relation("CreatedBy")

username String? @unique

image String?
accounts Account[]
sessions Session[]
Post Post[]
Comment Comment[]
CommentVote CommentVote[]
Vote Vote[]
Subscription Subscription[]
}

model Subreddit {
id String @id @default(cuid())
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posts Post[]

creatorId String?
Creator User? @relation("CreatedBy", fields: [creatorId], references: [id])
subscribers Subscription[]

@@index([name])
}

model Subscription {
user User @relation(fields: [userId], references: [id])
userId String
subreddit Subreddit @relation(fields: [subredditId], references: [id])
subredditId String

@@id([userId, subredditId])
}

model Post {
id String @id @default(cuid())
title String
content Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
subreddit Subreddit @relation(fields: [subredittId], references: [id])
subredittId String

author User @relation(fields: [authorId], references: [id])
authorId String

comments Comment[]
votes Vote[]
}

model Comment {
id String @id @default(cuid())
text String
createdAt DateTime @default(now())
author User @relation(fields: [authorId], references: [id])
authorId String
post Post @relation(fields: [postId], references: [id])
postId String

replyToId String?
replyTo Comment? @relation("ReplyTo", fields: [replyToId], references: [id], onDelete: NoAction, onUpdate: NoAction)
replies Comment[] @relation("ReplyTo")

votes CommentVote[]
commentId String?
}

enum VoteType {
UP
DOWN
}

model Vote {
user User @relation(fields: [userId], references: [id])
userId String
post Post @relation(fields: [postId], references: [id])
postId String
type VoteType

@@id([userId, postId])
}

model CommentVote {
user User @relation(fields: [userId], references: [id])
userId String
comment Comment @relation(fields: [commentId], references: [id])
commentId String
type VoteType

@@id([userId, commentId])
}
26 changes: 26 additions & 0 deletions src/app/(auth)/sign-in/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { buttonVariants } from '@/components/ui/Button'
import { FC } from 'react'
import { cn } from '@/lib/utils'
import Link from 'next/link'
import SignIn from '@/components/SignIn'
import { ChevronLeft } from 'lucide-react'


const page: FC = () => {
return (<div className='absolute inset-0'>
<div className='h-full max-w-2xl mx-auto flex flex-col items-center justify-center gap-20'>
<Link
href='/'
className={cn(buttonVariants({ variant: 'ghost' }),
'self-start -mt-20')}>
<ChevronLeft className='mr-2 h-4 w-4' />
Home
</Link>

<SignIn />
</div>
</div>
)
}

export default page
26 changes: 26 additions & 0 deletions src/app/(auth)/sign-up/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { buttonVariants } from '@/components/ui/Button'
import { FC } from 'react'
import { cn } from '@/lib/utils'
import Link from 'next/link'
import SignUp from '@/components/SignUp'
import { ChevronLeft } from 'lucide-react'


const page = () => {
return (<div className='absolute inset-0'>
<div className='h-full max-w-2xl mx-auto flex flex-col items-center justify-center gap-20'>
<Link
href='/'
className={cn(buttonVariants({ variant: 'ghost' }),
'self-start -mt-20')}>
<ChevronLeft className='mr-2 h-4 w-4' />
Home
</Link>

<SignUp />
</div>
</div>
)
}

export default page
21 changes: 21 additions & 0 deletions src/app/@authModal/(.)sign-in/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import CloseModal from '@/components/CloseModal'
import SignIn from '@/components/SignIn'




const page = () => {
return <div className='fixed inset-0 bg-zinc-900/20 z-10'>
<div className='container flex items-center h-full max-w-lg mx-auto'>
<div className='relative bg-white w-full h-fit py-20 px-2 rounded-lg'>
<div className='absolute top-4 right-4'>
<CloseModal />
</div>

<SignIn />
</div>
</div>
</div>
}

export default page
19 changes: 19 additions & 0 deletions src/app/@authModal/(.)sign-up/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import CloseModal from '@/components/CloseModal'
import SignUp from '@/components/SignUp'


const page = ({ }) => {
return <div className='fixed inset-0 bg-zinc-900/20 z-10'>
<div className='container flex items-center h-full max-w-lg mx-auto'>
<div className='relative bg-white w-full h-fit py-20 px-2 rounded-lg'>
<div className='absolute top-4 right-4'>
<CloseModal />
</div>

<SignUp />
</div>
</div>
</div>
}

export default page
3 changes: 3 additions & 0 deletions src/app/@authModal/default.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Default() {
return null;
}
6 changes: 6 additions & 0 deletions src/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { authOptions } from "@/lib/auth";
import NextAuth from "next-auth";

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST }
50 changes: 50 additions & 0 deletions src/app/api/subreddit/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { getAuthSession } from "@/lib/auth";
import { db } from "@/lib/db";
import { SubredditValidator } from "@/lib/validators/subreddit";
// import z from "zod/lib";
import { z } from "zod";

export async function POST(req: Request) {
try {
const session = await getAuthSession()

if (!session?.user) {
return new Response('Unauthorized', { status: 401 })
}

const body = await req.json()
const { name } = SubredditValidator.parse(body)

const subredditExists = await db.subreddit.findFirst({
where: {
name,
},
})

if (subredditExists) {
return new Response('Subreddit already exists', { status: 409 })
}

const subreddit = await db.subreddit.create({
data: {
name,
creatorId: session.user.id,
},
})

await db.subscription.create({
data: {
userId: session.user.id,
subredditId: subreddit.id,
},
})

return new Response(subreddit.name)
} catch (error) {
if (error instanceof z.ZodError) {
return new Response(error.message, { status: 422 })
}

return new Response('Could not create subreddit', { status: 500 })
}
}
44 changes: 44 additions & 0 deletions src/app/api/subscribe/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getAuthSession } from "@/lib/auth";
import { db } from "@/lib/db";
import { SubredditValidatorSubscription } from "@/lib/validators/subreddit";
import { z } from "zod";

export async function Post(req: Request) {
try {
const session = await getAuthSession()

if (!session?.user) {
return new Response('Unauthorized', { status: 401 })
}

const body = await req.json()

const { subredditId } = SubredditValidatorSubscription.parse(body)

const subscriptionExists = await db.subscription.findFirst({
where: {
subredditId,
userId: session.user.id
}
})

if (subscriptionExists) {
return new Response('You are already subscribed to this subreddit', { status: 400 })
}

await db.subscription.create({
data: {
subredditId,
userId: session.user.id,
},
})

return new Response(subredditId)
} catch (error) {
if (error instanceof z.ZodError) {
return new Response('Invalid request data passed', { status: 422 })
}

return new Response('Could not subscribe, please try again later', { status: 500 })
}
}
29 changes: 27 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
import { cn } from '@/lib/utils'
import '@/styles/globals.css'
import { Inter } from 'next/font/google'
import Navbar from '@/components/Navbar'
import { Toaster } from '@/components/ui/Toaster'
import Providers from '@/components/Providers'


export const metadata = {
title: 'Breadit',
description: 'A Reddit clone built with Next.js and TypeScript.',
}

const inter = Inter({ subsets: ['latin'] })

export default function RootLayout({
children,
authModal
}: {
children: React.ReactNode
authModal: React.ReactNode
}) {
return (
<html lang='en'>
<body>{children}</body>
<html lang='en'
className={cn('bg-white text-slate-900 antialiased light',
inter.className
)}>
<body className='min-h-screen pt-12 bg-slate-50 antialiased'>
<Providers>
{/* @ts-expect-error server component */}
<Navbar />

{authModal}

<div className='container max-w-7xl mx-auto h-full pt-12'>
{children}
</div>
</Providers>
<Toaster />
</body>
</html>
)
}
Loading