├── .gitignore ├── .vscode └── settings.json ├── README.md ├── app ├── QueryHydrate.tsx ├── api │ ├── add-like │ │ └── route.ts │ ├── clerk-webhooks │ │ └── route.ts │ ├── create-post │ │ └── route.ts │ └── get-feed │ │ └── route.ts ├── dashboard │ └── page.tsx ├── favicon.ico ├── getQueryClient.tsx ├── globals.css ├── header.tsx ├── layout.tsx ├── page.tsx ├── providers.tsx ├── sign-in │ └── page.tsx └── sign-up │ └── page.tsx ├── auth └── SignIn.tsx ├── components ├── Like.tsx ├── Post.tsx ├── PostForm.tsx ├── Posts.tsx └── SubmitButton.tsx ├── hook ├── usePosts.ts ├── useSubmitLike.ts └── useSubmitPost.ts ├── middleware.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── prisma ├── client.ts ├── migrations │ ├── 20230421172749_posts │ │ └── migration.sql │ ├── 20230421191015_added_user │ │ └── migration.sql │ ├── 20230422000105_add_like_and_comment_models │ │ └── migration.sql │ ├── 20230422015102_added_more_user_info │ │ └── migration.sql │ ├── 20230425235700_string_ids │ │ └── migration.sql │ ├── 20230426102757_layoutid │ │ └── migration.sql │ ├── 20230506192917_ │ │ └── migration.sql │ ├── 20230506193555_revert │ │ └── migration.sql │ └── migration_lock.toml └── schema.prisma ├── public ├── heart.json ├── next.svg ├── paperplane.json └── vercel.svg ├── sentry.server.config.js ├── tailwind.config.js ├── tsconfig.json └── types ├── PostSubmit.ts └── PostsType.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | .env 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules\\typescript\\lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 18 | 19 | [http://localhost:3000/api/hello](http://localhost:3000/api/hello) is an endpoint that uses [Route Handlers](https://beta.nextjs.org/docs/routing/route-handlers). This endpoint can be edited in `app/api/hello/route.ts`. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /app/QueryHydrate.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { Hydrate as RQHydrate, HydrateProps } from "@tanstack/react-query" 4 | 5 | function Hydrate(props: HydrateProps) { 6 | return 7 | } 8 | export default Hydrate 9 | -------------------------------------------------------------------------------- /app/api/add-like/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse, NextRequest } from "next/server" 2 | import { auth } from "@clerk/nextjs/app-beta" 3 | import { prisma } from "../../../prisma/client" 4 | 5 | export async function POST(req: NextRequest, res: NextResponse) { 6 | //test speed 7 | 8 | const start = Date.now() 9 | const body = await req.json() 10 | try { 11 | const { userId } = auth() 12 | //Throw error if not logged 13 | if (!userId) { 14 | NextResponse.json({ error: "Please log in to post ❣️" }, { status: 401 }) 15 | return 16 | } 17 | const liked = await prisma.like.findFirst({ 18 | where: { 19 | authorId: userId, 20 | postId: body.postId, 21 | }, 22 | }) 23 | 24 | if (!liked) { 25 | await prisma.$transaction([ 26 | prisma.like.create({ 27 | data: { 28 | authorId: userId, 29 | postId: body.postId, 30 | }, 31 | }), 32 | ]) 33 | } else { 34 | await prisma.$transaction([ 35 | prisma.like.delete({ 36 | where: { 37 | id: liked.id, 38 | }, 39 | }), 40 | ]) 41 | } 42 | const end = Date.now() 43 | console.log(`Time to add like: ${end - start}ms`) 44 | // retrieve data from your database 45 | return NextResponse.json("Liked 👍") 46 | } catch (error) { 47 | return NextResponse.json({ error: "Something went wrong ❣️" }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/api/clerk-webhooks/route.ts: -------------------------------------------------------------------------------- 1 | import type { WebhookEvent } from "@clerk/clerk-sdk-node" 2 | import { NextResponse, NextRequest } from "next/server" 3 | import { prisma } from "../../../prisma/client" 4 | 5 | export async function POST(req: NextRequest, res: NextResponse) { 6 | const { data, object, type }: WebhookEvent = await req.json() 7 | console.log(type) 8 | switch (type) { 9 | case "user.created": 10 | try { 11 | await prisma.user.create({ 12 | data: { 13 | id: data.id, 14 | profile_image_url: data.profile_image_url, 15 | name: data.first_name, 16 | }, 17 | }) 18 | } catch (e) { 19 | console.log(e) 20 | } 21 | case "user.updated": 22 | try { 23 | await prisma.user.update({ 24 | where: { id: data.id }, 25 | data: { 26 | profile_image_url: data.profile_image_url, 27 | name: data.first_name, 28 | }, 29 | }) 30 | } catch (e) { 31 | console.log(e) 32 | } 33 | } 34 | return NextResponse.json({ event: type }) 35 | } 36 | -------------------------------------------------------------------------------- /app/api/create-post/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse, NextRequest } from "next/server" 2 | import { auth } from "@clerk/nextjs/app-beta" 3 | import { prisma } from "../../../prisma/client" 4 | 5 | export async function POST(req: NextRequest, res: NextResponse) { 6 | try { 7 | const { userId, user } = auth() 8 | //Throw error if not logged 9 | if (!userId) { 10 | NextResponse.json({ error: "Please log in to post ❣️" }, { status: 401 }) 11 | return 12 | } 13 | //Get Body Data 14 | const body = await req.json() 15 | 16 | //Check if body is empty 17 | if (body.content.length > 300) { 18 | return NextResponse.json( 19 | { error: "Please post a shorter message 🙏" }, 20 | { status: 403, statusText: "Too short" } 21 | ) 22 | } 23 | 24 | if (body.content.length < 1) { 25 | return NextResponse.json( 26 | { error: "Please post a longer message 🙏" }, 27 | { status: 403 } 28 | ) 29 | } 30 | 31 | const { author } = body 32 | const post = await prisma.post.create({ 33 | data: { 34 | content: body.content, 35 | authorId: userId, 36 | layoutId: body.layoutId, 37 | }, 38 | }) 39 | // retrieve data from your database 40 | return NextResponse.json({ user, post, author }) 41 | } catch (error) { 42 | return NextResponse.json({ error: "Something went wrong ❣️" }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/api/get-feed/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server" 2 | import { prisma } from "../../../prisma/client" 3 | 4 | export const dynamic = "force-dynamic" 5 | 6 | export async function GET(req: Request) { 7 | try { 8 | const posts = await prisma.post.findMany({ 9 | include: { author: true, likes: true }, 10 | orderBy: { createdAt: "asc" }, 11 | }) 12 | 13 | return NextResponse.json(posts) 14 | } catch (error) { 15 | return NextResponse.json({ error: "Something went wrong ❣️" }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Dashboard() { 2 | return ( 3 |
4 |

Dashboard

5 |

Here are your courses ✨

6 |
7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developedbyed/Fancy-Next-SSR/b6b29d31d3d483e12838b2cf9b3e4451d67db0c4/app/favicon.ico -------------------------------------------------------------------------------- /app/getQueryClient.tsx: -------------------------------------------------------------------------------- 1 | import { QueryClient } from "@tanstack/query-core" 2 | import { cache } from "react" 3 | 4 | const getQueryClient = cache(() => new QueryClient()) 5 | export default getQueryClient 6 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/header.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { SignInButton, useUser, UserButton } from "@clerk/nextjs" 3 | import Link from "next/link" 4 | 5 | function Header() { 6 | const { isSignedIn } = useUser() 7 | 8 | return ( 9 |
10 | 23 |
24 | ) 25 | } 26 | 27 | export default Header 28 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css" 2 | import { ClerkProvider } from "@clerk/nextjs" 3 | import Providers from "./providers" 4 | import Header from "./header" 5 | 6 | export const metadata = { 7 | title: "Buzz ⚡", 8 | description: "Built with Next 13", 9 | } 10 | 11 | export default function RootLayout({ 12 | children, 13 | }: { 14 | children: React.ReactNode 15 | }) { 16 | return ( 17 | 18 | 19 | 20 | 21 |
22 | {children} 23 | 24 | 25 | 26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import getQueryClient from "./getQueryClient" 2 | import Hydrate from "./QueryHydrate" 3 | import { dehydrate } from "@tanstack/query-core" 4 | import { prisma } from "../prisma/client" 5 | 6 | import Posts from "@/components/Posts" 7 | import PostForm from "@/components/PostForm" 8 | 9 | export default async function Home() { 10 | const queryClient = getQueryClient() 11 | await queryClient.prefetchQuery(["posts"], async () => { 12 | const posts = await prisma.post.findMany({ 13 | include: { author: true, likes: true }, 14 | orderBy: { createdAt: "asc" }, 15 | }) 16 | return posts 17 | }) 18 | const dehydratedState = dehydrate(queryClient) 19 | 20 | return ( 21 |
22 | 23 | 24 | 25 | 26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /app/providers.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query" 4 | import { useState } from "react" 5 | 6 | const Providers = ({ children }: { children: React.ReactNode }) => { 7 | const [queryClient] = useState(() => new QueryClient()) 8 | 9 | return ( 10 | {children} 11 | ) 12 | } 13 | 14 | export default Providers 15 | -------------------------------------------------------------------------------- /app/sign-in/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignIn } from "@clerk/nextjs/app-beta" 2 | 3 | export default function Page() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /app/sign-up/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignUp } from "@clerk/nextjs/app-beta" 2 | 3 | export default function Page() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /auth/SignIn.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { SignInButton } from "@clerk/nextjs/" 3 | 4 | const SignIn = () => { 5 | return ( 6 | <> 7 | 8 | 11 | 12 | 13 | ) 14 | } 15 | 16 | export default SignIn 17 | -------------------------------------------------------------------------------- /components/Like.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import useSubmitLike from "@/hook/useSubmitLike" 4 | import { PostsType } from "@/types/PostsType" 5 | import { useUser } from "@clerk/nextjs" 6 | import { motion } from "framer-motion" 7 | import UseAnimations from "react-useanimations" 8 | import heart from "react-useanimations/lib/heart" 9 | import { useState } from "react" 10 | 11 | export default function Like({ post }: { post: PostsType }) { 12 | const mutation = useSubmitLike() 13 | const { user } = useUser() 14 | const userHasLiked = post.likes.some((like) => like.authorId === user?.id) 15 | const [checked, setChecked] = useState(true) 16 | return ( 17 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /components/Post.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import Image from "next/image" 3 | import { motion } from "framer-motion" 4 | import { PostsType } from "@/types/PostsType" 5 | import Like from "../components/Like" 6 | 7 | const Post = ({ post }: { post: PostsType }) => { 8 | return ( 9 | 16 |
17 | avatar 24 |
25 |

26 | {post.author.name} 27 |

28 |

{post.content}

29 |
30 |
31 | 32 |
33 | ) 34 | } 35 | 36 | export default Post 37 | -------------------------------------------------------------------------------- /components/PostForm.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { useState, useEffect } from "react" 3 | import useSubmitPost from "@/hook/useSubmitPost" 4 | import { useUser } from "@clerk/nextjs/app-beta/client" 5 | import { motion } from "framer-motion" 6 | import SubmitButton from "./SubmitButton" 7 | 8 | const PostForm = () => { 9 | const [content, setContent] = useState("") 10 | const [postError, setPostError] = useState("") 11 | const { user } = useUser() 12 | const mutation = useSubmitPost() 13 | 14 | const submitPost = async () => { 15 | mutation.mutate({ 16 | content, 17 | author: { 18 | name: user?.fullName, 19 | profile_image_url: user?.profileImageUrl, 20 | id: user?.id, 21 | }, 22 | likes: [], 23 | id: crypto.randomUUID(), 24 | layoutId: crypto.randomUUID(), 25 | }) 26 | } 27 | 28 | useEffect(() => { 29 | if (mutation.isError) { 30 | const error = mutation.error as Error 31 | setPostError(error.message) 32 | } 33 | if (mutation.isSuccess) { 34 | setContent("") 35 | setPostError("") 36 | } 37 | }, [mutation.isError, mutation.isSuccess]) 38 | 39 | return ( 40 | { 43 | e.preventDefault() 44 | submitPost() 45 | }} 46 | > 47 |