├── .env.example ├── .gitignore ├── LikedIcon.svg ├── README.md ├── components.json ├── database.types.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── prisma ├── migrations │ ├── 20230821055137_added_my_schema │ │ └── migration.sql │ ├── 20230821060240_change_column_type │ │ └── migration.sql │ ├── 20230821060634_change_password_column_length │ │ └── migration.sql │ ├── 20230821181433_add_content_in_notification │ │ └── migration.sql │ ├── 20230822054808_addded_likes_table │ │ └── migration.sql │ ├── 20230823053646_added_user_id_in_likes_table │ │ └── migration.sql │ └── migration_lock.toml └── schema.prisma ├── public ├── favicon.ico └── images │ └── logo.svg ├── src ├── DB │ └── db.config.ts ├── app │ ├── (authPages) │ │ ├── layout.tsx │ │ ├── login │ │ │ └── page.tsx │ │ └── register │ │ │ └── page.tsx │ ├── (front) │ │ ├── explore │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ ├── notifications │ │ │ └── page.tsx │ │ ├── page.tsx │ │ ├── post │ │ │ └── [id] │ │ │ │ └── page.tsx │ │ ├── profile │ │ │ └── page.tsx │ │ └── user │ │ │ └── [id] │ │ │ └── page.tsx │ ├── api │ │ ├── auth │ │ │ ├── [...nextauth] │ │ │ │ ├── options.ts │ │ │ │ └── route.ts │ │ │ ├── login │ │ │ │ └── route.ts │ │ │ └── register │ │ │ │ └── route.ts │ │ ├── comment │ │ │ ├── [id] │ │ │ │ └── route.ts │ │ │ └── route.ts │ │ ├── explore │ │ │ └── route.ts │ │ ├── like │ │ │ └── route.ts │ │ ├── notifications │ │ │ └── route.ts │ │ ├── post │ │ │ ├── [id] │ │ │ │ └── route.ts │ │ │ └── route.ts │ │ └── user │ │ │ ├── [id] │ │ │ └── route.ts │ │ │ ├── comment │ │ │ └── route.ts │ │ │ ├── post │ │ │ └── route.ts │ │ │ └── route.ts │ ├── globals.css │ ├── layout.tsx │ └── providers.tsx ├── components │ ├── base │ │ ├── BaseComponent.tsx │ │ ├── LeftSidebar.tsx │ │ ├── MobileNavBar.tsx │ │ └── RightSidebar.tsx │ ├── common │ │ ├── CommentCard.tsx │ │ ├── DyanmicNavBar.tsx │ │ ├── ImagePreviewCard.tsx │ │ ├── ImageViewer.tsx │ │ ├── PostCard.tsx │ │ ├── PostUserBar.tsx │ │ ├── ShareModal.tsx │ │ ├── SideBarLinks.tsx │ │ ├── SignOutBtn.tsx │ │ ├── ThemeToggleBtn.tsx │ │ ├── UserAvatar.tsx │ │ ├── UserListCard.tsx │ │ ├── UserProfileAvatar.tsx │ │ └── loading.tsx │ ├── explore │ │ └── ExploreSearchBar.tsx │ ├── theme-provider.tsx │ ├── threads │ │ ├── AddComment.tsx │ │ ├── AddThread.tsx │ │ ├── DeleteCommentBtn.tsx │ │ └── DeletePostBtn.tsx │ └── ui │ │ ├── alert-dialog.tsx │ │ ├── avatar.tsx │ │ ├── button.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── scroll-area.tsx │ │ ├── sheet.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ └── use-toast.ts ├── config │ └── env.ts ├── lib │ ├── serverMethods.ts │ └── utils.ts ├── middleware.ts ├── types.ts └── validators │ ├── CustomErrorReporter.ts │ ├── authSchema.ts │ ├── commentSchema.ts │ ├── imageValidator.ts │ └── postSchema.ts ├── tailwind.config.js ├── tailwind.config.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_APP_URL="http://localhost:3000" 2 | 3 | NEXTAUTH_URL="http://localhost:3000" 4 | NEXTAUTH_SECRET="mystrongsecret" 5 | 6 | DATABASE_URL="postgresql://postgres:@localhost:5432/threads_app?schema=public" -------------------------------------------------------------------------------- /.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 | 38 | /public/uploads/ -------------------------------------------------------------------------------- /LikedIcon.svg: -------------------------------------------------------------------------------- 1 | 9 | 15 | -------------------------------------------------------------------------------- /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 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | 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. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /database.types.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TusharVashishth/Threads_clone/cae5a9842419b15a274a5340431fc0fcdf255b42/database.types.ts -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | domains: ["localhost"], 5 | }, 6 | }; 7 | 8 | module.exports = nextConfig; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "threads", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "prisma generate && next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "postinstall": "prisma generate" 11 | }, 12 | "dependencies": { 13 | "@hookform/resolvers": "^3.2.0", 14 | "@prisma/client": "^5.1.1", 15 | "@radix-ui/react-alert-dialog": "^1.0.4", 16 | "@radix-ui/react-avatar": "^1.0.3", 17 | "@radix-ui/react-dialog": "^1.0.4", 18 | "@radix-ui/react-dropdown-menu": "^2.0.5", 19 | "@radix-ui/react-label": "^2.0.2", 20 | "@radix-ui/react-scroll-area": "^1.0.4", 21 | "@radix-ui/react-slot": "^1.0.2", 22 | "@radix-ui/react-tabs": "^1.0.4", 23 | "@radix-ui/react-toast": "^1.1.4", 24 | "@types/node": "20.5.0", 25 | "@types/react": "18.2.20", 26 | "@types/react-dom": "18.2.7", 27 | "@vinejs/vine": "^1.6.0", 28 | "autoprefixer": "10.4.15", 29 | "axios": "^1.4.0", 30 | "bcryptjs": "^2.4.3", 31 | "class-variance-authority": "^0.7.0", 32 | "clsx": "^2.0.0", 33 | "lucide-react": "^0.268.0", 34 | "moment": "^2.29.4", 35 | "next": "13.4.17", 36 | "next-auth": "^4.23.1", 37 | "next-share": "^0.25.0", 38 | "next-themes": "^0.2.1", 39 | "postcss": "8.4.28", 40 | "prisma": "^5.1.1", 41 | "react": "18.2.0", 42 | "react-dom": "18.2.0", 43 | "react-hook-form": "^7.45.4", 44 | "tailwind-merge": "^1.14.0", 45 | "tailwindcss": "3.3.3", 46 | "tailwindcss-animate": "^1.0.6", 47 | "typescript": "5.1.6", 48 | "yup": "^1.2.0" 49 | }, 50 | "devDependencies": { 51 | "@types/bcryptjs": "^2.4.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /prisma/migrations/20230821055137_added_my_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "User" ( 3 | "id" SERIAL NOT NULL, 4 | "name" VARCHAR(100) NOT NULL, 5 | "username" VARCHAR(100) NOT NULL, 6 | "email" TEXT NOT NULL, 7 | "password" VARCHAR(40), 8 | "image" VARCHAR(100), 9 | "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 10 | 11 | CONSTRAINT "User_pkey" PRIMARY KEY ("id") 12 | ); 13 | 14 | -- CreateTable 15 | CREATE TABLE "Post" ( 16 | "id" SERIAL NOT NULL, 17 | "user_id" INTEGER NOT NULL, 18 | "content" TEXT NOT NULL, 19 | "image" VARCHAR(100), 20 | "comment_count" INTEGER NOT NULL DEFAULT 0, 21 | "like_count" INTEGER NOT NULL DEFAULT 0, 22 | "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 23 | 24 | CONSTRAINT "Post_pkey" PRIMARY KEY ("id") 25 | ); 26 | 27 | -- CreateTable 28 | CREATE TABLE "Comment" ( 29 | "id" SERIAL NOT NULL, 30 | "user_id" INTEGER NOT NULL, 31 | "post_id" INTEGER NOT NULL, 32 | "content" TEXT NOT NULL, 33 | "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 34 | 35 | CONSTRAINT "Comment_pkey" PRIMARY KEY ("id") 36 | ); 37 | 38 | -- CreateTable 39 | CREATE TABLE "Notification" ( 40 | "id" SERIAL NOT NULL, 41 | "user_id" INTEGER NOT NULL, 42 | "toUser_id" INTEGER NOT NULL, 43 | "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 44 | 45 | CONSTRAINT "Notification_pkey" PRIMARY KEY ("id") 46 | ); 47 | 48 | -- CreateIndex 49 | CREATE UNIQUE INDEX "User_username_key" ON "User"("username"); 50 | 51 | -- CreateIndex 52 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 53 | 54 | -- AddForeignKey 55 | ALTER TABLE "Post" ADD CONSTRAINT "Post_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 56 | 57 | -- AddForeignKey 58 | ALTER TABLE "Comment" ADD CONSTRAINT "Comment_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 59 | 60 | -- AddForeignKey 61 | ALTER TABLE "Comment" ADD CONSTRAINT "Comment_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "Post"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 62 | 63 | -- AddForeignKey 64 | ALTER TABLE "Notification" ADD CONSTRAINT "Notification_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 65 | -------------------------------------------------------------------------------- /prisma/migrations/20230821060240_change_column_type/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" ALTER COLUMN "name" SET DATA TYPE TEXT, 3 | ALTER COLUMN "username" SET DATA TYPE TEXT, 4 | ALTER COLUMN "image" SET DATA TYPE TEXT; 5 | -------------------------------------------------------------------------------- /prisma/migrations/20230821060634_change_password_column_length/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to alter the column `name` on the `User` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(100)`. 5 | - You are about to alter the column `username` on the `User` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(100)`. 6 | - You are about to alter the column `image` on the `User` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(100)`. 7 | 8 | */ 9 | -- AlterTable 10 | ALTER TABLE "User" ALTER COLUMN "name" SET DATA TYPE VARCHAR(100), 11 | ALTER COLUMN "username" SET DATA TYPE VARCHAR(100), 12 | ALTER COLUMN "password" SET DATA TYPE TEXT, 13 | ALTER COLUMN "image" SET DATA TYPE VARCHAR(100); 14 | -------------------------------------------------------------------------------- /prisma/migrations/20230821181433_add_content_in_notification/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `content` to the `Notification` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Notification" ADD COLUMN "content" TEXT NOT NULL; 9 | -------------------------------------------------------------------------------- /prisma/migrations/20230822054808_addded_likes_table/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Likes" ( 3 | "id" SERIAL NOT NULL, 4 | "post_id" INTEGER NOT NULL, 5 | 6 | CONSTRAINT "Likes_pkey" PRIMARY KEY ("id") 7 | ); 8 | 9 | -- AddForeignKey 10 | ALTER TABLE "Likes" ADD CONSTRAINT "Likes_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "Post"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 11 | -------------------------------------------------------------------------------- /prisma/migrations/20230823053646_added_user_id_in_likes_table/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `user_id` to the `Likes` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Likes" ADD COLUMN "user_id" INTEGER NOT NULL; 9 | 10 | -- AddForeignKey 11 | ALTER TABLE "Likes" ADD CONSTRAINT "Likes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 12 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "postgresql" 10 | url = env("DATABASE_URL") 11 | } 12 | 13 | model User { 14 | id Int @id @default(autoincrement()) 15 | name String @db.VarChar(100) 16 | username String @unique @db.VarChar(100) 17 | email String @unique 18 | password String? 19 | image String? @db.VarChar(100) 20 | created_at DateTime @default(now()) 21 | Post Post[] 22 | Comment Comment[] 23 | Notification Notification[] 24 | Likes Likes[] 25 | } 26 | 27 | model Post { 28 | id Int @id @default(autoincrement()) 29 | user User @relation(fields: [user_id], references: [id]) 30 | user_id Int 31 | content String 32 | image String? @db.VarChar(100) 33 | comment_count Int @default(0) 34 | like_count Int @default(0) 35 | created_at DateTime @default(now()) 36 | Comment Comment[] 37 | Likes Likes[] 38 | } 39 | 40 | model Comment { 41 | id Int @id @default(autoincrement()) 42 | user User @relation(fields: [user_id], references: [id]) 43 | user_id Int 44 | post Post @relation(fields: [post_id], references: [id]) 45 | post_id Int 46 | content String 47 | created_at DateTime @default(now()) 48 | } 49 | 50 | model Notification { 51 | id Int @id @default(autoincrement()) 52 | user User @relation(fields: [user_id], references: [id]) 53 | user_id Int 54 | toUser_id Int 55 | content String 56 | created_at DateTime @default(now()) 57 | } 58 | 59 | model Likes { 60 | id Int @id @default(autoincrement()) 61 | post Post @relation(fields: [post_id], references: [id]) 62 | post_id Int 63 | user User @relation(fields: [user_id], references: [id]) 64 | user_id Int 65 | } 66 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TusharVashishth/Threads_clone/cae5a9842419b15a274a5340431fc0fcdf255b42/public/favicon.ico -------------------------------------------------------------------------------- /public/images/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/DB/db.config.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | const globalForPrisma = global as unknown as { prisma: PrismaClient }; 4 | 5 | export const prisma = globalForPrisma.prisma || new PrismaClient(); 6 | 7 | if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; 8 | 9 | export default prisma; 10 | -------------------------------------------------------------------------------- /src/app/(authPages)/layout.tsx: -------------------------------------------------------------------------------- 1 | import "../globals.css"; 2 | import type { Metadata } from "next"; 3 | import CustomProvider from "../providers"; 4 | 5 | export const metadata: Metadata = { 6 | title: "Auth Page", 7 | description: "The Threads app Auth pages.", 8 | }; 9 | 10 | export default function LoginLayout({ 11 | children, 12 | }: { 13 | children: React.ReactNode; 14 | }) { 15 | return {children}; 16 | } 17 | -------------------------------------------------------------------------------- /src/app/(authPages)/login/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useState, useEffect } from "react"; 3 | import Image from "next/image"; 4 | import { Label } from "@/components/ui/label"; 5 | import { Input } from "@/components/ui/input"; 6 | import Link from "next/link"; 7 | import { Button } from "@/components/ui/button"; 8 | import { useSearchParams } from "next/navigation"; 9 | import axios from "axios"; 10 | import { signIn } from "next-auth/react"; 11 | import ThemeToggleBtn from "@/components/common/ThemeToggleBtn"; 12 | import { useSession } from "next-auth/react"; 13 | import { useRouter } from "next/navigation"; 14 | 15 | export default function Login() { 16 | const params = useSearchParams(); 17 | const router = useRouter(); 18 | const { status } = useSession(); 19 | const [authState, setAuthState] = useState({ 20 | email: "", 21 | password: "", 22 | }); 23 | const [errors, setErrors] = useState({}); 24 | const [loading, setLoading] = useState(false); 25 | 26 | useEffect(() => { 27 | if (status == "authenticated") { 28 | router.push("/"); 29 | } 30 | }, [status]); 31 | 32 | const login = (event: React.FormEvent) => { 33 | event.preventDefault(); 34 | setLoading(true); 35 | axios 36 | .post("/api/auth/login", authState) 37 | .then((res) => { 38 | setLoading(false); 39 | const response = res.data; 40 | if (response.status == 200) { 41 | signIn("credentials", { 42 | email: authState.email, 43 | password: authState.password, 44 | callbackUrl: "/", 45 | redirect: true, 46 | }); 47 | } else if (response.status == 400) { 48 | setErrors(response.errors); 49 | } 50 | }) 51 | .catch((err) => { 52 | setLoading(false); 53 | console.log("the error is", err); 54 | }); 55 | }; 56 | 57 | return ( 58 |
59 |
60 |
61 |
62 | Logo 63 |
64 | {params.get("message") ? ( 65 |
66 | Success! {params.get("message")} 67 |
68 | ) : ( 69 | <> 70 | )} 71 |
72 |
73 |
74 |
75 |

Login

76 |

Welcome back

77 |
78 |
79 | 80 |
81 | 82 | 87 | setAuthState({ ...authState, email: event.target.value }) 88 | } 89 | /> 90 | {errors?.email} 91 |
92 |
93 | 94 | 99 | setAuthState({ ...authState, password: event.target.value }) 100 | } 101 | /> 102 | 103 | {errors?.password} 104 | 105 |
106 |
107 | 110 |
111 |
112 | Don't Have an account ? 113 | 114 | Register 115 | 116 |
117 |
118 |
119 |
120 |
121 |
122 | ); 123 | } 124 | -------------------------------------------------------------------------------- /src/app/(authPages)/register/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState } from "react"; 4 | import Image from "next/image"; 5 | import { Label } from "@/components/ui/label"; 6 | import { Input } from "@/components/ui/input"; 7 | import Link from "next/link"; 8 | import { Button } from "@/components/ui/button"; 9 | import axios from "axios"; 10 | import { useRouter } from "next/navigation"; 11 | import ThemeToggleBtn from "@/components/common/ThemeToggleBtn"; 12 | 13 | export default function Register() { 14 | const router = useRouter(); 15 | const [authState, setAuthState] = useState({ 16 | name: "", 17 | email: "", 18 | password: "", 19 | password_confirmation: "", 20 | username: "", 21 | }); 22 | const [errors, setErrors] = useState({}); 23 | const [loading, setLoading] = useState(false); 24 | 25 | const submit = (event: React.FormEvent) => { 26 | event.preventDefault(); 27 | setLoading(true); 28 | 29 | axios 30 | .post("/api/auth/register", authState) 31 | .then((res) => { 32 | setLoading(false); 33 | const response = res.data; 34 | if (response.status == 400) { 35 | setErrors(response.errors); 36 | } else if (response.status == 200) { 37 | router.push(`/login?message=${response.message}`); 38 | } 39 | }) 40 | .catch((err) => { 41 | setLoading(false); 42 | console.log("The error is", err); 43 | }); 44 | }; 45 | 46 | return ( 47 |
48 |
49 |
50 |
51 | Logo 52 |
53 |
54 |
55 |
56 |
57 |

Register

58 |

Welcome to the threads

59 |
60 |
61 | 62 |
63 | 64 | 69 | setAuthState({ ...authState, name: event.target.value }) 70 | } 71 | /> 72 | {errors.name} 73 |
74 |
75 | 76 | 81 | setAuthState({ ...authState, username: event.target.value }) 82 | } 83 | /> 84 | 85 | {errors.username} 86 | 87 |
88 |
89 | 90 | 95 | setAuthState({ ...authState, email: event.target.value }) 96 | } 97 | /> 98 | {errors.email} 99 |
100 |
101 | 102 | 107 | setAuthState({ ...authState, password: event.target.value }) 108 | } 109 | /> 110 | 111 | {errors.password} 112 | 113 |
114 |
115 | 116 | 121 | setAuthState({ 122 | ...authState, 123 | password_confirmation: event.target.value, 124 | }) 125 | } 126 | /> 127 |
128 |
129 | 132 |
133 |
134 | Already Have an account ? 135 | 136 | Login 137 | 138 |
139 |
140 |
141 |
142 |
143 |
144 | ); 145 | } 146 | -------------------------------------------------------------------------------- /src/app/(front)/explore/page.tsx: -------------------------------------------------------------------------------- 1 | import DyanmicNavBar from "@/components/common/DyanmicNavBar"; 2 | import React, { Suspense } from "react"; 3 | 4 | import ExploreSearchBar from "@/components/explore/ExploreSearchBar"; 5 | import { searchUser } from "@/lib/serverMethods"; 6 | import UserListCard from "@/components/common/UserListCard"; 7 | import { Metadata } from "next"; 8 | 9 | export const metadata: Metadata = { 10 | title: "Explore", 11 | description: "Search users here and show there profile...", 12 | }; 13 | 14 | export default async function Explore({ 15 | searchParams, 16 | }: { 17 | searchParams: { [key: string]: string | undefined }; 18 | }) { 19 | const users: Array | [] = await searchUser(searchParams?.query!); 20 | return ( 21 |
22 | 23 | 24 | 25 |
26 | {users?.length > 0 && 27 | users.map((item) => )} 28 | {users?.length < 1 && searchParams?.query?.length! > 1 && ( 29 |
30 |

No User found

31 |
32 | )} 33 |
34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/app/(front)/layout.tsx: -------------------------------------------------------------------------------- 1 | import "../globals.css"; 2 | import type { Metadata } from "next"; 3 | import { ThemeProvider } from "@/components/theme-provider"; 4 | import { Toaster } from "@/components/ui/toaster"; 5 | import CustomProvider from "../providers"; 6 | import BaseComponent from "@/components/base/BaseComponent"; 7 | 8 | export const metadata: Metadata = { 9 | title: "Threads App", 10 | description: "The Threads app to share your thoughts and much more.", 11 | }; 12 | 13 | export default function RootLayout({ 14 | children, 15 | }: { 16 | children: React.ReactNode; 17 | }) { 18 | return ( 19 | 20 | 21 | {children} 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/app/(front)/loading.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function loading() { 4 | return ( 5 |
6 |
7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/app/(front)/notifications/page.tsx: -------------------------------------------------------------------------------- 1 | import DyanmicNavBar from "@/components/common/DyanmicNavBar"; 2 | import UserAvatar from "@/components/common/UserAvatar"; 3 | import { fetchNotifications } from "@/lib/serverMethods"; 4 | import { formateDate } from "@/lib/utils"; 5 | import { Metadata } from "next"; 6 | import React from "react"; 7 | 8 | export const metadata: Metadata = { 9 | title: "Notification", 10 | description: "Find your all notifications and who commented on your post.", 11 | openGraph: { 12 | title: "Search your notification", 13 | }, 14 | }; 15 | 16 | export default async function Notifications() { 17 | const notifications: Array | [] = 18 | await fetchNotifications(); 19 | return ( 20 |
21 | 22 | 23 |
24 | {notifications && 25 | notifications.length > 0 && 26 | notifications.map((item) => ( 27 |
31 | 32 |
33 |
34 |

{item.user.name}

35 | 36 | {formateDate(item.created_at)} 37 | 38 |
39 |

{item.content}

40 |
41 |
42 | ))} 43 | {notifications && notifications.length < 1 && ( 44 |
45 |

No Notifications found!

46 |
47 | )} 48 |
49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/app/(front)/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { fetchPosts } from "@/lib/serverMethods"; 3 | import AddThread from "@/components/threads/AddThread"; 4 | import PostCard from "@/components/common/PostCard"; 5 | import { Suspense } from "react"; 6 | import Loading from "@/components/common/loading"; 7 | 8 | export default async function Home() { 9 | const posts: Array | [] = await fetchPosts(1); 10 | return ( 11 |
12 |
13 | Logo 20 |
21 | 22 | }> 23 |
24 | {posts.map((item) => ( 25 | 26 | ))} 27 |
28 |
29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/app/(front)/post/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import CommentCard from "@/components/common/CommentCard"; 2 | import PostCard from "@/components/common/PostCard"; 3 | import DynamicNavbar from "@/components/common/DyanmicNavBar"; 4 | import { fetchPost } from "@/lib/serverMethods"; 5 | import React from "react"; 6 | 7 | export default async function ShowPost({ params }: { params: { id: number } }) { 8 | const post = await fetchPost(params.id); 9 | 10 | return ( 11 |
12 | 13 | {post && ( 14 |
15 | 16 |
17 | )} 18 | 19 |
20 |

Comments

21 | 22 | {post?.Comment ? ( 23 |
24 | {post.Comment.map((item: CommentType) => ( 25 | 26 | ))} 27 |
28 | ) : ( 29 |
No Comments Found
30 | )} 31 |
32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/app/(front)/profile/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { getServerSession } from "next-auth"; 4 | import UserProfileAvatar from "@/components/common/UserProfileAvatar"; 5 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 6 | import { fetchUserComments, fetchUserPosts } from "@/lib/serverMethods"; 7 | import PostCard from "@/components/common/PostCard"; 8 | import CommentCard from "@/components/common/CommentCard"; 9 | import DyanmicNavBar from "@/components/common/DyanmicNavBar"; 10 | import { 11 | CustomSession, 12 | authOptions, 13 | } from "@/app/api/auth/[...nextauth]/options"; 14 | 15 | export default async function Profile() { 16 | const session: CustomSession | null = await getServerSession(authOptions); 17 | const posts: Array | [] = await fetchUserPosts(); 18 | const comments: Array | [] = await fetchUserComments(); 19 | return ( 20 |
21 | 22 |
23 |
24 | 28 |
29 |
30 |

{session?.user?.name}

31 |

@{session?.user?.username}

32 |

{session?.user?.email}

33 |
34 |
35 | 36 |
37 | 38 | 39 | 40 | Posts 41 | 42 | 43 | Comments 44 | 45 | 46 | 47 |
48 | {posts && 49 | posts.map((item) => )} 50 | {posts && posts.length < 1 && ( 51 |

No Post found

52 | )} 53 |
54 |
55 | 56 |
57 | {comments && 58 | comments.map((item) => ( 59 | 60 | ))} 61 | 62 | {comments && comments.length < 1 && ( 63 |

No Comment found

64 | )} 65 |
66 |
67 |
68 |
69 |
70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /src/app/(front)/user/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import DyanmicNavBar from "@/components/common/DyanmicNavBar"; 2 | import UserProfileAvatar from "@/components/common/UserProfileAvatar"; 3 | import { fetchUser } from "@/lib/serverMethods"; 4 | import React from "react"; 5 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 6 | import PostCard from "@/components/common/PostCard"; 7 | import CommentCard from "@/components/common/CommentCard"; 8 | 9 | export default async function ShowUser({ params }: { params: { id: number } }) { 10 | const user: ShowUserType | undefined = await fetchUser(params.id); 11 | return ( 12 |
13 | 14 |
15 | {user && ( 16 |
17 |
18 | 19 |
20 |
21 |

{user.name}

22 |

@{user.username}

23 |

{user.email}

24 |
25 |
26 | )} 27 | 28 |
29 | 30 | 31 | 32 | Posts 33 | 34 | 35 | Comments 36 | 37 | 38 | 39 |
40 | {user?.Post && 41 | user.Post.map((item) => )} 42 | 43 | {user?.Post && user.Post.length < 1 && ( 44 |

No Post Found

45 | )} 46 |
47 |
48 | 49 |
50 | {user?.Comment && 51 | user.Comment.map((item) => )} 52 | 53 | {user?.Comment && user.Comment.length < 1 && ( 54 |

No Comment found

55 | )} 56 |
57 |
58 |
59 |
60 |
61 |
62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /src/app/api/auth/[...nextauth]/options.ts: -------------------------------------------------------------------------------- 1 | import prisma from "@/DB/db.config"; 2 | import { AuthOptions, ISODateString, User } from "next-auth"; 3 | import { JWT } from "next-auth/jwt"; 4 | import CredentialsProvider from "next-auth/providers/credentials"; 5 | 6 | export interface CustomSession { 7 | user?: CustomUser; 8 | expires: ISODateString; 9 | } 10 | export interface CustomUser { 11 | id?: string | null; 12 | name?: string | null; 13 | username?: string | null; 14 | email?: string | null; 15 | image?: string | null; 16 | } 17 | export const authOptions: AuthOptions = { 18 | pages: { 19 | signIn: "/login", 20 | }, 21 | callbacks: { 22 | async jwt({ token, user }) { 23 | if (user) { 24 | token.user = user; 25 | } 26 | return token; 27 | }, 28 | 29 | async session({ 30 | session, 31 | token, 32 | user, 33 | }: { 34 | session: CustomSession; 35 | token: JWT; 36 | user: User; 37 | }) { 38 | session.user = token.user as CustomUser; 39 | return session; 40 | }, 41 | }, 42 | 43 | providers: [ 44 | CredentialsProvider({ 45 | name: "Credentials", 46 | credentials: { 47 | email: {}, 48 | password: {}, 49 | }, 50 | async authorize(credentials, req) { 51 | const user = await prisma.user.findUnique({ 52 | where: { 53 | email: credentials?.email, 54 | }, 55 | }); 56 | 57 | if (user) { 58 | return { 59 | ...user, 60 | id: user.id.toString(), 61 | }; 62 | } else { 63 | return null; 64 | } 65 | }, 66 | }), 67 | ], 68 | }; 69 | -------------------------------------------------------------------------------- /src/app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth/next"; 2 | import { authOptions } from "./options"; 3 | 4 | const nextAuth = NextAuth(authOptions); 5 | 6 | export { nextAuth as GET, nextAuth as POST }; 7 | -------------------------------------------------------------------------------- /src/app/api/auth/login/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import vine, { errors } from "@vinejs/vine"; 3 | import { CustomErrorReporter } from "@/validators/CustomErrorReporter"; 4 | import { loginSchema } from "@/validators/authSchema"; 5 | import prisma from "@/DB/db.config"; 6 | import bcrypt from "bcryptjs"; 7 | 8 | export async function POST(request: NextRequest) { 9 | try { 10 | const body = await request.json(); 11 | vine.errorReporter = () => new CustomErrorReporter(); 12 | const validator = vine.compile(loginSchema); 13 | const payload = await validator.validate(body); 14 | 15 | // * Check is there any email or not 16 | const isUserExist = await prisma.user.findUnique({ 17 | where: { 18 | email: payload.email, 19 | }, 20 | }); 21 | 22 | if (isUserExist) { 23 | // * Check is password correct or not 24 | const isPasswordSame = bcrypt.compareSync( 25 | payload.password, 26 | isUserExist.password! 27 | ); 28 | 29 | if (isPasswordSame) { 30 | return NextResponse.json({ 31 | status: 200, 32 | message: "you logged in successfully!", 33 | }); 34 | } 35 | return NextResponse.json({ 36 | status: 400, 37 | errors: { 38 | email: "Invalid credentials.", 39 | }, 40 | }); 41 | } 42 | 43 | return NextResponse.json({ 44 | status: 400, 45 | errors: { 46 | email: "No account found with this email", 47 | }, 48 | }); 49 | } catch (error) { 50 | if (error instanceof errors.E_VALIDATION_ERROR) { 51 | return NextResponse.json({ status: 400, errors: error.messages }); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/app/api/auth/register/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import vine, { errors } from "@vinejs/vine"; 3 | import { CustomErrorReporter } from "@/validators/CustomErrorReporter"; 4 | import { registerSchema } from "@/validators/authSchema"; 5 | import prisma from "@/DB/db.config"; 6 | import bcrypt from "bcryptjs"; 7 | 8 | export async function POST(request: NextRequest) { 9 | try { 10 | const body = await request.json(); 11 | vine.errorReporter = () => new CustomErrorReporter(); 12 | const validator = vine.compile(registerSchema); 13 | const payload = await validator.validate(body); 14 | 15 | // * Check email if it already exist 16 | const isEmailExist = await prisma.user.findUnique({ 17 | where: { 18 | email: payload.email, 19 | }, 20 | select: { 21 | id: true, 22 | }, 23 | }); 24 | 25 | if (isEmailExist) { 26 | return NextResponse.json({ 27 | status: 400, 28 | errors: { 29 | email: "Email already taken. please use another email.", 30 | }, 31 | }); 32 | } 33 | 34 | // * Check username if it already exist 35 | const isUsernameExist = await prisma.user.findUnique({ 36 | where: { 37 | username: payload.username, 38 | }, 39 | select: { 40 | id: true, 41 | }, 42 | }); 43 | 44 | if (isUsernameExist) { 45 | return NextResponse.json({ 46 | status: 400, 47 | errors: { 48 | username: "Username already taken. please use another email.", 49 | }, 50 | }); 51 | } 52 | 53 | // * Encrypt the password 54 | const salt = bcrypt.genSaltSync(10); 55 | payload.password = bcrypt.hashSync(payload.password, salt); 56 | 57 | await prisma.user.create({ data: payload }); 58 | return NextResponse.json({ 59 | status: 200, 60 | message: "Account created successfully.Please login into your account!", 61 | }); 62 | } catch (error) { 63 | if (error instanceof errors.E_VALIDATION_ERROR) { 64 | return NextResponse.json( 65 | { status: 400, errors: error.messages }, 66 | { status: 200 } 67 | ); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/app/api/comment/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { CustomSession, authOptions } from "../../auth/[...nextauth]/options"; 3 | import { getServerSession } from "next-auth"; 4 | import prisma from "@/DB/db.config"; 5 | 6 | export async function DELETE( 7 | request: NextRequest, 8 | { params }: { params: { id: number } } 9 | ) { 10 | const session: CustomSession | null = await getServerSession(authOptions); 11 | 12 | if (!session) { 13 | return NextResponse.json({ status: 401, message: "Un-Authorized" }); 14 | } 15 | 16 | const comment = await prisma.comment.findUnique({ 17 | where: { 18 | id: Number(params.id), 19 | }, 20 | }); 21 | 22 | if (comment == null || comment.user_id != Number(session.user?.id)) { 23 | return NextResponse.json({ status: 401, message: "Un-Authorized" }); 24 | } 25 | 26 | await prisma.comment.delete({ 27 | where: { 28 | id: Number(params.id), 29 | }, 30 | }); 31 | 32 | return NextResponse.json({ 33 | status: 200, 34 | message: "Comment deleted successfully!", 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /src/app/api/comment/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { CustomSession, authOptions } from "../auth/[...nextauth]/options"; 3 | import { getServerSession } from "next-auth"; 4 | import vine, { errors } from "@vinejs/vine"; 5 | import { CustomErrorReporter } from "@/validators/CustomErrorReporter"; 6 | import prisma from "@/DB/db.config"; 7 | import { commentSchema } from "@/validators/commentSchema"; 8 | 9 | export async function GET(request: NextRequest) { 10 | // * Add Notification 11 | try { 12 | await prisma.notification.create({ 13 | data: { 14 | user_id: Number(3), 15 | toUser_id: Number(2), 16 | content: "Commented on your post", 17 | }, 18 | }); 19 | 20 | return NextResponse.json({ status: 200, message: "Notification added" }); 21 | } catch (error) { 22 | console.log("The error is", error); 23 | return NextResponse.json({ status: 400, error }); 24 | } 25 | } 26 | 27 | export async function POST(request: NextRequest) { 28 | try { 29 | const session: CustomSession | null = await getServerSession(authOptions); 30 | if (!session) { 31 | return NextResponse.json({ status: 401, message: "Un-Authorized" }); 32 | } 33 | const data = await request.json(); 34 | 35 | vine.errorReporter = () => new CustomErrorReporter(); 36 | const validator = vine.compile(commentSchema); 37 | const payload = await validator.validate(data); 38 | 39 | // * Increase the post comment count 40 | await prisma.post.update({ 41 | where: { 42 | id: Number(payload.post_id), 43 | }, 44 | data: { 45 | comment_count: { 46 | increment: 1, 47 | }, 48 | }, 49 | }); 50 | 51 | // * Add Notification 52 | try { 53 | await prisma.notification.create({ 54 | data: { 55 | user_id: Number(session?.user?.id), 56 | toUser_id: Number(payload.toUser_id), 57 | content: "Commented on your post", 58 | }, 59 | }); 60 | } catch (error) { 61 | console.log("The error is", error); 62 | } 63 | 64 | await prisma.comment.create({ 65 | data: { 66 | post_id: Number(payload.post_id), 67 | content: payload.content, 68 | user_id: Number(session?.user?.id), 69 | }, 70 | }); 71 | 72 | return NextResponse.json({ 73 | status: 200, 74 | message: "Comment added successfully!", 75 | }); 76 | } catch (error) { 77 | if (error instanceof errors.E_VALIDATION_ERROR) { 78 | return NextResponse.json( 79 | { status: 400, errors: error.messages }, 80 | { status: 200 } 81 | ); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/app/api/explore/route.ts: -------------------------------------------------------------------------------- 1 | import prisma from "@/DB/db.config"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | import { CustomSession, authOptions } from "../auth/[...nextauth]/options"; 4 | import { getServerSession } from "next-auth"; 5 | 6 | export async function GET(request: NextRequest) { 7 | const session: CustomSession | null = await getServerSession(authOptions); 8 | if (!session) { 9 | return NextResponse.json({ status: 401, message: "Un-Authorized" }); 10 | } 11 | const query = request.nextUrl.searchParams.get("query"); 12 | const users = await prisma.user.findMany({ 13 | where: { 14 | OR: [ 15 | { 16 | name: { 17 | contains: query ?? "", 18 | mode: "insensitive", 19 | }, 20 | }, 21 | { 22 | username: { 23 | contains: query ?? "", 24 | mode: "insensitive", 25 | }, 26 | }, 27 | ], 28 | NOT: { 29 | id: Number(session.user?.id), 30 | }, 31 | }, 32 | select: { 33 | id: true, 34 | name: true, 35 | email: true, 36 | username: true, 37 | }, 38 | }); 39 | 40 | return NextResponse.json({ status: 200, data: users }); 41 | } 42 | -------------------------------------------------------------------------------- /src/app/api/like/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { CustomSession, authOptions } from "../auth/[...nextauth]/options"; 3 | import { getServerSession } from "next-auth"; 4 | import prisma from "@/DB/db.config"; 5 | 6 | export async function POST(request: NextRequest) { 7 | const session: CustomSession | null = await getServerSession(authOptions); 8 | 9 | if (!session) { 10 | return NextResponse.json({ status: 401, message: "Un-Authorized" }); 11 | } 12 | const payload: LikeType = await request.json(); 13 | 14 | if (!payload.post_id || !payload.toUserId) { 15 | return NextResponse.json({ 16 | status: 400, 17 | message: "Bad request. Please pass post id", 18 | }); 19 | } 20 | 21 | // * Add Notification 22 | if (payload.status == "1") { 23 | await prisma.notification.create({ 24 | data: { 25 | user_id: Number(session.user?.id), 26 | toUser_id: Number(payload.toUserId), 27 | content: "Liked your post.", 28 | }, 29 | }); 30 | 31 | // * In crease the post like counter 32 | await prisma.post.update({ 33 | where: { 34 | id: Number(payload.post_id), 35 | }, 36 | data: { 37 | like_count: { 38 | increment: 1, 39 | }, 40 | }, 41 | }); 42 | 43 | // * Add Entry in like table 44 | await prisma.likes.create({ 45 | data: { 46 | user_id: Number(session?.user?.id), 47 | post_id: Number(payload.post_id), 48 | }, 49 | }); 50 | } else if (payload.status == "0") { 51 | // * In crease the post like counter 52 | await prisma.post.update({ 53 | where: { 54 | id: Number(payload.post_id), 55 | }, 56 | data: { 57 | like_count: { 58 | decrement: 1, 59 | }, 60 | }, 61 | }); 62 | 63 | // * delete in like table 64 | await prisma.likes.deleteMany({ 65 | where: { 66 | post_id: Number(payload.post_id), 67 | user_id: Number(session?.user?.id), 68 | }, 69 | }); 70 | } 71 | 72 | return NextResponse.json({ 73 | status: 200, 74 | message: 75 | payload.status == "1" 76 | ? "Post Liked successfully!" 77 | : "Post Disliked successfully!", 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /src/app/api/notifications/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { CustomSession, authOptions } from "../auth/[...nextauth]/options"; 3 | import { getServerSession } from "next-auth"; 4 | import prisma from "@/DB/db.config"; 5 | 6 | export async function GET(request: NextRequest) { 7 | const session: CustomSession | null = await getServerSession(authOptions); 8 | 9 | if (!session) { 10 | return NextResponse.json({ status: 401, message: "Un Authorized." }); 11 | } 12 | 13 | const notifications = await prisma.notification.findMany({ 14 | where: { 15 | toUser_id: Number(session?.user?.id), 16 | }, 17 | include: { 18 | user: { 19 | select: { 20 | id: true, 21 | name: true, 22 | username: true, 23 | }, 24 | }, 25 | }, 26 | orderBy: { 27 | id: "desc", 28 | }, 29 | }); 30 | 31 | return NextResponse.json({ status: 200, data: notifications }); 32 | } 33 | -------------------------------------------------------------------------------- /src/app/api/post/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import prisma from "@/DB/db.config"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | import { CustomSession, authOptions } from "../../auth/[...nextauth]/options"; 4 | import { getServerSession } from "next-auth"; 5 | import { join } from "path"; 6 | import { rmSync } from "fs"; 7 | 8 | export async function GET( 9 | request: NextRequest, 10 | { params }: { params: { id: number } } 11 | ) { 12 | const session: CustomSession | null = await getServerSession(authOptions); 13 | if (!session) { 14 | return NextResponse.json({ status: 401, message: "Un-Authorized" }); 15 | } 16 | const post = await prisma.post.findUnique({ 17 | where: { 18 | id: Number(params.id), 19 | }, 20 | include: { 21 | user: { 22 | select: { 23 | id: true, 24 | name: true, 25 | username: true, 26 | image: true, 27 | }, 28 | }, 29 | Comment: { 30 | include: { 31 | user: { 32 | select: { 33 | id: true, 34 | name: true, 35 | username: true, 36 | image: true, 37 | }, 38 | }, 39 | }, 40 | }, 41 | Likes: { 42 | where: { 43 | user_id: Number(session?.user?.id), 44 | }, 45 | }, 46 | }, 47 | }); 48 | 49 | return NextResponse.json({ status: 200, data: post }); 50 | } 51 | 52 | // * Delete Post 53 | 54 | export async function DELETE( 55 | request: NextRequest, 56 | { params }: { params: { id: number } } 57 | ) { 58 | const session: CustomSession | null = await getServerSession(authOptions); 59 | 60 | if (!session) { 61 | return NextResponse.json({ status: 401, message: "Un-Authorized" }); 62 | } 63 | 64 | const post = await prisma.post.findUnique({ 65 | where: { 66 | id: Number(params.id), 67 | }, 68 | }); 69 | 70 | if (post == null || post.user_id != Number(session.user?.id)) { 71 | return NextResponse.json({ status: 401, message: "Un-Authorized" }); 72 | } 73 | 74 | // * remove the file 75 | const dir = join(process.cwd(), "public", "/uploads"); 76 | const path = dir + "/" + post?.image; 77 | rmSync(path, { force: true }); 78 | 79 | await prisma.post.delete({ 80 | where: { 81 | id: Number(params.id), 82 | }, 83 | }); 84 | 85 | return NextResponse.json({ 86 | status: 200, 87 | message: "Post deleted successfully!", 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /src/app/api/post/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import vine, { errors } from "@vinejs/vine"; 3 | import { CustomErrorReporter } from "@/validators/CustomErrorReporter"; 4 | import { postSchema } from "@/validators/postSchema"; 5 | import { getServerSession } from "next-auth"; 6 | import { CustomSession, authOptions } from "../auth/[...nextauth]/options"; 7 | import { imagevalidator } from "@/validators/imageValidator"; 8 | import { join } from "path"; 9 | import { writeFile } from "fs/promises"; 10 | import { getRandomNumber } from "@/lib/utils"; 11 | import prisma from "@/DB/db.config"; 12 | 13 | export async function GET(request: NextRequest) { 14 | const session: CustomSession | null = await getServerSession(authOptions); 15 | if (!session) { 16 | return NextResponse.json({ status: 401, message: "Un-Authorized" }); 17 | } 18 | const posts = await prisma.post.findMany({ 19 | include: { 20 | user: { 21 | select: { 22 | id: true, 23 | name: true, 24 | username: true, 25 | }, 26 | }, 27 | Likes: { 28 | take: 1, 29 | where: { 30 | user_id: Number(session?.user?.id), 31 | }, 32 | }, 33 | }, 34 | orderBy: { 35 | id: "desc", 36 | }, 37 | }); 38 | 39 | return NextResponse.json({ 40 | status: 200, 41 | data: posts, 42 | }); 43 | } 44 | 45 | export async function POST(request: NextRequest) { 46 | try { 47 | const session: CustomSession | null = await getServerSession(authOptions); 48 | if (!session) { 49 | return NextResponse.json({ status: 401, message: "Un-Authorized" }); 50 | } 51 | const formData = await request.formData(); 52 | const data = { 53 | content: formData.get("content"), 54 | image: "", 55 | }; 56 | vine.errorReporter = () => new CustomErrorReporter(); 57 | const validator = vine.compile(postSchema); 58 | const payload = await validator.validate(data); 59 | 60 | const image = formData.get("image") as Blob | null; 61 | // * IF image exist 62 | if (image) { 63 | const isImageNotValid = imagevalidator(image?.name, image?.size); 64 | if (isImageNotValid) { 65 | return NextResponse.json({ 66 | status: 400, 67 | errors: { 68 | content: isImageNotValid, 69 | }, 70 | }); 71 | } 72 | 73 | // * Upload image if all good 74 | try { 75 | const buffer = Buffer.from(await image!.arrayBuffer()); 76 | const uploadDir = join(process.cwd(), "public", "/uploads"); 77 | const uniqueNmae = Date.now() + "_" + getRandomNumber(1, 999999); 78 | const imgExt = image?.name.split("."); 79 | const filename = uniqueNmae + "." + imgExt?.[1]; 80 | await writeFile(`${uploadDir}/${filename}`, buffer); 81 | data.image = filename; 82 | } catch (error) { 83 | return NextResponse.json({ 84 | status: 500, 85 | message: "Something went wrong.Please try again later.", 86 | }); 87 | } 88 | } 89 | 90 | // * create post in DB 91 | await prisma.post.create({ 92 | data: { 93 | content: payload.content, 94 | user_id: Number(session.user?.id), 95 | image: data.image ?? null, 96 | }, 97 | }); 98 | 99 | return NextResponse.json({ 100 | status: 200, 101 | message: "Post created successfully!", 102 | }); 103 | } catch (error) { 104 | if (error instanceof errors.E_VALIDATION_ERROR) { 105 | return NextResponse.json( 106 | { status: 400, errors: error.messages }, 107 | { status: 200 } 108 | ); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/app/api/user/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import prisma from "@/DB/db.config"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | 4 | export async function GET( 5 | request: NextRequest, 6 | { params }: { params: { id: number } } 7 | ) { 8 | const users = await prisma.user.findUnique({ 9 | where: { 10 | id: Number(params.id), 11 | }, 12 | select: { 13 | name: true, 14 | id: true, 15 | email: true, 16 | username: true, 17 | image: true, 18 | 19 | Post: { 20 | include: { 21 | user: { 22 | select: { 23 | id: true, 24 | name: true, 25 | email: true, 26 | }, 27 | }, 28 | Likes: { 29 | where: { 30 | user_id: Number(params.id), 31 | }, 32 | }, 33 | }, 34 | }, 35 | Comment: { 36 | include: { 37 | user: { 38 | select: { 39 | id: true, 40 | name: true, 41 | email: true, 42 | }, 43 | }, 44 | }, 45 | }, 46 | }, 47 | }); 48 | 49 | return NextResponse.json({ status: 200, data: users }); 50 | } 51 | -------------------------------------------------------------------------------- /src/app/api/user/comment/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { getServerSession } from "next-auth"; 3 | import prisma from "@/DB/db.config"; 4 | import { CustomSession, authOptions } from "../../auth/[...nextauth]/options"; 5 | 6 | export async function GET(request: NextRequest) { 7 | const session: CustomSession | null = await getServerSession(authOptions); 8 | if (!session) { 9 | return NextResponse.json({ status: 401, message: "Un-Authorized" }); 10 | } 11 | 12 | const comments = await prisma.comment.findMany({ 13 | where: { 14 | user_id: Number(session.user?.id), 15 | }, 16 | include: { 17 | user: { 18 | select: { 19 | id: true, 20 | name: true, 21 | username: true, 22 | }, 23 | }, 24 | }, 25 | }); 26 | 27 | return NextResponse.json({ status: 200, data: comments }); 28 | } 29 | -------------------------------------------------------------------------------- /src/app/api/user/post/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import prisma from "@/DB/db.config"; 3 | import { CustomSession, authOptions } from "../../auth/[...nextauth]/options"; 4 | import { getServerSession } from "next-auth"; 5 | 6 | export async function GET(request: NextRequest) { 7 | const session: CustomSession | null = await getServerSession(authOptions); 8 | if (!session) { 9 | return NextResponse.json({ status: 401, message: "Un-Authorized" }); 10 | } 11 | 12 | const posts = await prisma.post.findMany({ 13 | include: { 14 | user: { 15 | select: { 16 | id: true, 17 | name: true, 18 | username: true, 19 | }, 20 | }, 21 | Likes: { 22 | where: { 23 | user_id: Number(session?.user?.id), 24 | }, 25 | }, 26 | }, 27 | where: { 28 | user_id: Number(session?.user?.id), 29 | }, 30 | orderBy: { 31 | id: "desc", 32 | }, 33 | }); 34 | 35 | return NextResponse.json({ 36 | status: 200, 37 | data: posts, 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /src/app/api/user/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { CustomSession, authOptions } from "../auth/[...nextauth]/options"; 3 | import { getServerSession } from "next-auth"; 4 | import prisma from "@/DB/db.config"; 5 | 6 | export async function GET(request: NextRequest) { 7 | const session: CustomSession | null = await getServerSession(authOptions); 8 | if (!session) { 9 | return NextResponse.json({ status: 401, message: "Un-Authorized" }); 10 | } 11 | 12 | const users = await prisma.user.findMany({ 13 | where: { 14 | NOT: { 15 | id: Number(session.user?.id), 16 | }, 17 | }, 18 | select: { 19 | id: true, 20 | name: true, 21 | username: true, 22 | image: true, 23 | }, 24 | orderBy: { 25 | id: "desc", 26 | }, 27 | }); 28 | 29 | return NextResponse.json({ status: 200, data: users }); 30 | } 31 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 0 0% 3.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 0 0% 3.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 0 0% 3.9%; 15 | 16 | --primary: 0 0% 9%; 17 | --primary-foreground: 0 0% 98%; 18 | 19 | --secondary: 0 0% 96.1%; 20 | --secondary-foreground: 0 0% 9%; 21 | 22 | --muted: 0 0% 96.1%; 23 | --muted-foreground: 0 0% 45.1%; 24 | 25 | --accent: 0 0% 96.1%; 26 | --accent-foreground: 0 0% 9%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 0 0% 98%; 30 | 31 | --border: 0 0% 89.8%; 32 | --input: 0 0% 89.8%; 33 | --ring: 0 0% 3.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 0 0% 3.9%; 40 | --foreground: 0 0% 98%; 41 | 42 | --card: 0 0% 3.9%; 43 | --card-foreground: 0 0% 98%; 44 | 45 | --popover: 0 0% 3.9%; 46 | --popover-foreground: 0 0% 98%; 47 | 48 | --primary: 0 0% 98%; 49 | --primary-foreground: 0 0% 9%; 50 | 51 | --secondary: 0 0% 14.9%; 52 | --secondary-foreground: 0 0% 98%; 53 | 54 | --muted: 0 0% 14.9%; 55 | --muted-foreground: 0 0% 63.9%; 56 | 57 | --accent: 0 0% 14.9%; 58 | --accent-foreground: 0 0% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 0 0% 98%; 62 | 63 | --border: 0 0% 14.9%; 64 | --input: 0 0% 14.9%; 65 | --ring: 0 0% 83.1%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import type { Metadata } from "next"; 3 | import { Inter } from "next/font/google"; 4 | import { ThemeProvider } from "@/components/theme-provider"; 5 | const inter = Inter({ subsets: ["latin"] }); 6 | import { Toaster } from "@/components/ui/toaster"; 7 | import CustomProvider from "./providers"; 8 | 9 | export const metadata: Metadata = { 10 | title: "Threads App", 11 | description: "The Threads app to share your thoughts and much more.", 12 | icons: "/favicon.ico", 13 | }; 14 | 15 | export default function RootLayout({ 16 | children, 17 | }: { 18 | children: React.ReactNode; 19 | }) { 20 | return ( 21 | 22 | 23 | {children} 24 | 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/app/providers.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { SessionProvider } from "next-auth/react"; 4 | 5 | export default function CustomProvider({ 6 | children, 7 | }: { 8 | children?: React.ReactNode; 9 | }) { 10 | return {children}; 11 | } 12 | -------------------------------------------------------------------------------- /src/components/base/BaseComponent.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react"; 2 | import LeftSidebar from "./LeftSidebar"; 3 | import RightSidebar from "./RightSidebar"; 4 | import { ScrollArea } from "../ui/scroll-area"; 5 | import MobileNavBar from "./MobileNavBar"; 6 | import Loading from "../common/loading"; 7 | 8 | export default async function BaseComponent({ 9 | children, 10 | }: { 11 | children: React.ReactNode; 12 | }) { 13 | return ( 14 |
15 |
16 | 17 | 18 | 19 | {children} 20 | 21 | }> 22 | 23 | 24 |
25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/base/LeftSidebar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import Image from "next/image"; 4 | import SideBarLinks from "../common/SideBarLinks"; 5 | 6 | export default function LeftSidebar() { 7 | return ( 8 |
9 |
10 | logo 11 |

Threads

12 |
13 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/base/MobileNavBar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { 4 | Sheet, 5 | SheetContent, 6 | SheetDescription, 7 | SheetHeader, 8 | SheetTitle, 9 | SheetTrigger, 10 | } from "@/components/ui/sheet"; 11 | 12 | import Image from "next/image"; 13 | import Link from "next/link"; 14 | import SideBarLinks from "../common/SideBarLinks"; 15 | import { HammerIcon, Menu, User2 } from "lucide-react"; 16 | 17 | export default function MobileNavBar() { 18 | return ( 19 | 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /src/components/base/RightSidebar.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | import UserListCard from "../common/UserListCard"; 3 | import { fetchUsers } from "@/lib/serverMethods"; 4 | 5 | export default async function RightSidebar() { 6 | const users: Array | [] = await fetchUsers(); 7 | return ( 8 |
9 |
10 |

Suggestion for you

11 |
12 |
13 | {users.map((item) => ( 14 | 15 | ))} 16 |
17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/common/CommentCard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import UserAvatar from "./UserAvatar"; 3 | import { formateDate } from "@/lib/utils"; 4 | import DeleteCommentBtn from "../threads/DeleteCommentBtn"; 5 | 6 | export default function CommentCard({ 7 | comment, 8 | isAuthCard, 9 | }: { 10 | comment: CommentType; 11 | isAuthCard?: boolean; 12 | }) { 13 | return ( 14 |
15 |
16 | 17 |
18 |
19 |

{comment.user.name}

20 |
21 | 22 | {formateDate(comment.created_at)} 23 | 24 |
25 |
26 |
{comment.content}
27 | 28 | {isAuthCard && ( 29 |
30 | 31 |
32 | )} 33 |
34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/components/common/DyanmicNavBar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { useRouter } from "next/navigation"; 4 | import { Button } from "../ui/button"; 5 | import { MoveLeft } from "lucide-react"; 6 | 7 | export default function DyanmicNavBar({ title }: { title: string }) { 8 | const router = useRouter(); 9 | return ( 10 |
11 |
12 | router.back()} className="cursor-pointer" /> 13 | 14 |

{title}

15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/common/ImagePreviewCard.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { X } from "lucide-react"; 3 | import { Button } from "../ui/button"; 4 | 5 | export default function ImagePreviewCard({ 6 | image, 7 | callback, 8 | }: { 9 | image: string; 10 | callback: () => void; 11 | }) { 12 | return ( 13 |
21 |
22 | 25 |
26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/components/common/ImageViewer.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { 3 | Sheet, 4 | SheetContent, 5 | SheetDescription, 6 | SheetHeader, 7 | SheetTitle, 8 | SheetTrigger, 9 | } from "@/components/ui/sheet"; 10 | import Env from "@/config/env"; 11 | import Image from "next/image"; 12 | 13 | export default function ImageViewer({ image }: { image: string }) { 14 | return ( 15 | 16 | 17 | 1 24 | 25 | 26 | 27 | Show Image 28 | 29 | {`Post_image_${image}`} 37 | 38 | 39 | 40 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/components/common/PostCard.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState } from "react"; 4 | import PostUserBar from "./PostUserBar"; 5 | import Env from "@/config/env"; 6 | import AddComment from "../threads/AddComment"; 7 | import Link from "next/link"; 8 | import { Heart } from "lucide-react"; 9 | import ShareModal from "./ShareModal"; 10 | import ImageViewer from "./ImageViewer"; 11 | import axios from "axios"; 12 | 13 | export default function PostCard({ 14 | post, 15 | noRedirect, 16 | isAuthPost, 17 | }: { 18 | post: PostType; 19 | noRedirect?: boolean; 20 | isAuthPost?: boolean; 21 | }) { 22 | const [isLiked, setIsLiked] = useState(""); 23 | const likeDislike = (status: string) => { 24 | setIsLiked(status); 25 | axios 26 | .post("/api/like", { 27 | status: status, 28 | post_id: post.id, 29 | toUserId: post.user_id, 30 | }) 31 | .then((res) => { 32 | const response = res.data; 33 | console.log("The response is", response); 34 | }) 35 | .catch((err) => { 36 | console.log("The error is", err); 37 | }); 38 | }; 39 | 40 | return ( 41 |
42 | 43 |
44 | 45 | {post.content} 46 | 47 | {post?.image ? : <>} 48 |
49 | {post.Likes.length > 0 || isLiked == "1" ? ( 50 | likeDislike("0")} 58 | > 59 | 65 | 66 | ) : ( 67 | likeDislike("1")} 71 | className="cursor-pointer" 72 | /> 73 | )} 74 | 75 | 76 | 77 |
78 |
79 | {post.comment_count} Replies 80 | {post.like_count} Likes 81 |
82 |
83 |
84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /src/components/common/PostUserBar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import UserAvatar from "./UserAvatar"; 3 | import { formateDate } from "@/lib/utils"; 4 | import { MoreHorizontal } from "lucide-react"; 5 | import DeletePostBtn from "../threads/DeletePostBtn"; 6 | 7 | export default function PostUserBar({ 8 | post, 9 | isAuthPost, 10 | }: { 11 | post: PostType; 12 | isAuthPost?: boolean; 13 | }) { 14 | return ( 15 |
16 |
17 | 18 |
19 |
20 |

{post.user.name}

21 | 22 |
23 | {formateDate(post.created_at)} 24 | 25 | {isAuthPost ? ( 26 | 27 | ) : ( 28 | 29 | )} 30 |
31 |
32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/components/common/ShareModal.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { 4 | Dialog, 5 | DialogContent, 6 | DialogDescription, 7 | DialogHeader, 8 | DialogTitle, 9 | DialogTrigger, 10 | } from "@/components/ui/dialog"; 11 | import { Copy, SendHorizonal } from "lucide-react"; 12 | import { 13 | FacebookShareButton, 14 | FacebookIcon, 15 | WhatsappIcon, 16 | WhatsappShareButton, 17 | LineShareButton, 18 | LinkedinIcon, 19 | EmailShareButton, 20 | EmailIcon, 21 | } from "next-share"; 22 | import { useToast } from "../ui/use-toast"; 23 | 24 | export default function ShareModal({ url }: { url: string }) { 25 | const { toast } = useToast(); 26 | const copyUrl = async () => { 27 | navigator.clipboard.writeText(url); 28 | toast({ 29 | title: "Copied!", 30 | description: "Url Copied successfully!", 31 | className: "bg-green-500", 32 | }); 33 | }; 34 | return ( 35 | 36 | 37 | 38 | 39 | 40 | 41 | Share Post 42 | 43 |
44 | {url} 45 | 46 |
47 |
48 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 |
66 |
67 |
68 |
69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /src/components/common/SideBarLinks.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Link from "next/link"; 4 | import ThemeToggleBtn from "../common/ThemeToggleBtn"; 5 | import { Button } from "../ui/button"; 6 | import { signOut } from "next-auth/react"; 7 | import { usePathname } from "next/navigation"; 8 | import { Bell, Home, Search, User2 } from "lucide-react"; 9 | import SignOutBtn from "./SignOutBtn"; 10 | 11 | export default function SideBarLinks() { 12 | const pathname = usePathname(); 13 | return ( 14 |
    15 |
  • 16 | 22 | 23 |

    Home

    24 | 25 |
  • 26 |
  • 27 | 33 | 34 |

    Explore

    35 | 36 |
  • 37 |
  • 38 | 44 | 45 |

    Notifications

    46 | 47 |
  • 48 |
  • 49 | 55 | 56 |

    Profile

    57 | 58 |
  • 59 | 60 |
  • 61 | 62 | 63 |
  • 64 |
65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /src/components/common/SignOutBtn.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | AlertDialog, 5 | AlertDialogAction, 6 | AlertDialogCancel, 7 | AlertDialogContent, 8 | AlertDialogDescription, 9 | AlertDialogFooter, 10 | AlertDialogHeader, 11 | AlertDialogTitle, 12 | AlertDialogTrigger, 13 | } from "@/components/ui/alert-dialog"; 14 | import { signOut } from "next-auth/react"; 15 | import { Button } from "../ui/button"; 16 | 17 | export default function SignOutBtn() { 18 | return ( 19 | 20 | 21 | 24 | 25 | 26 | 27 | Are you absolutely sure? 28 | 29 | After logout . You have to sign in again to access your account. 30 | 31 | 32 | 33 | Cancel 34 | signOut({ callbackUrl: "/login" })}> 35 | Continue 36 | 37 | 38 | 39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/components/common/ThemeToggleBtn.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { useTheme } from "next-themes"; 5 | 6 | import { Button } from "@/components/ui/button"; 7 | import { 8 | DropdownMenu, 9 | DropdownMenuContent, 10 | DropdownMenuItem, 11 | DropdownMenuTrigger, 12 | } from "@/components/ui/dropdown-menu"; 13 | import { Moon, SunMoon } from "lucide-react"; 14 | 15 | export default function ThemeToggleBtn() { 16 | const { setTheme } = useTheme(); 17 | 18 | return ( 19 | 20 | 21 | 26 | 27 | 28 | setTheme("light")}> 29 | Light 30 | 31 | setTheme("dark")}> 32 | Dark 33 | 34 | setTheme("system")}> 35 | System 36 | 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/components/common/UserAvatar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar"; 3 | 4 | export default function UserAvatar({ 5 | name, 6 | image, 7 | }: { 8 | name: string; 9 | image?: string; 10 | }) { 11 | return ( 12 | 13 | 14 | {name[0].toUpperCase()} 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/common/UserListCard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import UserAvatar from "./UserAvatar"; 3 | import { Button } from "../ui/button"; 4 | import Link from "next/link"; 5 | 6 | export default function UserListCard({ user }: { user: User }) { 7 | return ( 8 |
9 |
10 | 11 |
12 |
13 | {user.name} 14 | @{user.username} 15 |
16 | 17 | 18 | 19 |
20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/components/common/UserProfileAvatar.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar"; 2 | 3 | export default function UserProfileAvatar({ 4 | name, 5 | image, 6 | }: { 7 | name: string; 8 | image?: string; 9 | }) { 10 | return ( 11 | 12 | 13 | 14 | {name[0].toUpperCase()} 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/common/loading.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | export default function Loading() { 3 | return ( 4 |
5 |
6 |
7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /src/components/explore/ExploreSearchBar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState } from "react"; 4 | import { useRouter } from "next/navigation"; 5 | 6 | export default function ExploreSearchBar() { 7 | const [query, setQuery] = useState(""); 8 | const router = useRouter(); 9 | 10 | const submit = (event: React.FormEvent) => { 11 | event.preventDefault(); 12 | router.replace(`/explore?query=${query}`); 13 | }; 14 | return ( 15 |
16 |
17 | setQuery(e.target.value)} 23 | /> 24 |
25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 5 | import { type ThemeProviderProps } from "next-themes/dist/types"; 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children}; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/threads/AddComment.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useState } from "react"; 3 | import { 4 | Dialog, 5 | DialogContent, 6 | DialogDescription, 7 | DialogHeader, 8 | DialogTitle, 9 | DialogTrigger, 10 | } from "@/components/ui/dialog"; 11 | import PostUserBar from "../common/PostUserBar"; 12 | import { useSession } from "next-auth/react"; 13 | import UserAvatar from "../common/UserAvatar"; 14 | import { Button } from "../ui/button"; 15 | import { useToast } from "@/components/ui/use-toast"; 16 | import axios from "axios"; 17 | import { MessageCircle } from "lucide-react"; 18 | 19 | export default function AddComment({ post }: { post: PostType }) { 20 | const { data } = useSession(); 21 | const { toast } = useToast(); 22 | const [content, setContent] = useState(""); 23 | const [loading, setLoading] = useState(false); 24 | const [errors, setErrors] = useState({}); 25 | 26 | const submit = () => { 27 | setLoading(true); 28 | const data = { 29 | content: content, 30 | post_id: post.id.toString(), 31 | toUser_id: post.user_id.toString(), 32 | }; 33 | axios 34 | .post("/api/comment", data) 35 | .then((res) => { 36 | setLoading(false); 37 | const response = res.data; 38 | if (response.status == 200) { 39 | setContent(""); 40 | setErrors({}); 41 | toast({ 42 | title: "Success", 43 | description: response.message, 44 | className: "bg-green-400", 45 | }); 46 | } else if (response.status == 400) { 47 | setErrors(response.errors); 48 | } 49 | }) 50 | .catch((err) => { 51 | setLoading(false); 52 | console.log("The error is", err); 53 | }); 54 | }; 55 | 56 | return ( 57 | 58 | 59 | 60 | 61 | 62 | 63 | Add Comment 64 | 65 |
66 | 67 |
{post.content}
68 |
69 |
70 | 71 |