├── .eslintrc.json ├── .gitignore ├── README.md ├── app ├── [id] │ ├── layout.tsx │ ├── page.tsx │ └── replies │ │ └── page.tsx ├── activity │ └── page.tsx ├── api │ └── loadMore │ │ └── route.ts ├── favicon.ico ├── globals.css ├── layout.tsx ├── onboarding │ └── page.tsx ├── page.tsx ├── search │ └── page.tsx ├── sign-in │ └── [[...sign-in]] │ │ └── page.tsx ├── sign-up │ └── [[...sign-up]] │ │ └── page.tsx └── t │ └── [id] │ ├── layout.tsx │ └── page.tsx ├── assets ├── loop.svg ├── threads-screenshot.png ├── threads.png └── threads.svg ├── components.json ├── components ├── activity │ ├── categories.tsx │ ├── follow.tsx │ └── index.tsx ├── onboarding │ ├── card.tsx │ ├── index.tsx │ ├── privacy.tsx │ └── screens.tsx ├── profile │ ├── edit.tsx │ ├── follow.tsx │ ├── info.tsx │ ├── selfShare.tsx │ └── signOut.tsx ├── search │ ├── bar.tsx │ ├── follow.tsx │ └── user.tsx ├── thread │ ├── backButton.tsx │ ├── comment │ │ ├── commentModal.tsx │ │ ├── createComment.tsx │ │ └── index.tsx │ ├── controls │ │ ├── index.tsx │ │ ├── like.tsx │ │ ├── repost.tsx │ │ └── share.tsx │ ├── create │ │ ├── createThread.tsx │ │ ├── index.tsx │ │ └── threadModal.tsx │ ├── homePosts.tsx │ ├── index.tsx │ ├── main.tsx │ ├── moreMenu.tsx │ ├── nameLink.tsx │ ├── others.tsx │ └── timestamp.tsx └── ui │ ├── badge.tsx │ ├── button.tsx │ ├── card.tsx │ ├── dialog.tsx │ ├── dropdown-menu.tsx │ ├── input.tsx │ ├── label.tsx │ ├── modeToggle.tsx │ ├── nav.tsx │ ├── tabs.tsx │ ├── themeProvider.tsx │ ├── toast.tsx │ ├── toaster.tsx │ └── use-toast.ts ├── lib ├── actions │ ├── index.ts │ ├── threadActions.ts │ └── userActions.ts ├── prisma.ts └── utils.ts ├── middleware.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── prisma └── schema.prisma ├── public └── threads.png ├── tailwind.config.js └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | 37 | .vercel 38 | .env*.local 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Threads 2 | 3 | An open-source clone of Threads using Next.js server components, Vercel Postgres, shadcn UI, Clerk, and Prisma. 4 | 5 | 6 | https://github.com/ishaan1013/thr/assets/69771365/f1ca7104-0fa0-4825-ab83-06deeec5cc3f 7 | 8 | 9 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fishaan1013%2Fthr&env=CLERK_SECRET_KEY,NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL,NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL,NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,NEXT_PUBLIC_CLERK_SIGN_IN_URL,NEXT_PUBLIC_CLERK_SIGN_UP_URL&envDescription=Clerk%20is%20recommended%20to%20work%20with%20this%20project.%20Vercel%20Postgres%20is%20optional%2C%20and%20is%20what%20was%20used%20in%20the%20original%20project.&project-name=clone&repository-name=clone&demo-title=Clone&demo-description=A%20Next.js%20clone%20of%20Meta%27s%20new%20app&demo-url=https%3A%2F%2Ft.ishaand.co%2F&demo-image=https%3A%2F%2Fgithub.com%2Fishaan1013%2Fthr%2Fblob%2Fmaster%2Fassets%2Fthreads-screenshot.png%3Fraw%3Dtrue) 10 | 11 | ## Running Locally 12 | 13 | ### Cloning the repository the local machine. 14 | 15 | ```bash 16 | git clone https://github.com/ishaan1013/thr 17 | ``` 18 | 19 | ### Create a Postgres database on Vercel (optional, can use other provider) 20 | 21 | - Add the environment variables in .env 22 | - (This project uses Prisma as an ORM for the database) 23 | 24 | ### Create a project on Clerk 25 | 26 | - Add the environment variables in .env 27 | - Ensure you have the following variables: 28 | ``` 29 | NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL="/" 30 | NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL="/onboarding" 31 | NEXT_PUBLIC_CLERK_SIGN_IN_URL="/sign-in" 32 | NEXT_PUBLIC_CLERK_SIGN_UP_URL="/sign-up" 33 | ``` 34 | 35 | ### Installing the dependencies. 36 | 37 | ```bash 38 | npm install 39 | ``` 40 | 41 | ### Running the application. 42 | 43 | Then, run the application in the command line and it will be available at `http://localhost:3000`. 44 | 45 | ```bash 46 | npm run dev 47 | ``` 48 | -------------------------------------------------------------------------------- /app/[id]/layout.tsx: -------------------------------------------------------------------------------- 1 | import prisma from "@/lib/prisma"; 2 | import { currentUser } from "@clerk/nextjs"; 3 | import Image from "next/image"; 4 | import { redirect } from "next/navigation"; 5 | 6 | import Nav from "@/components/ui/nav"; 7 | import { Badge } from "@/components/ui/badge"; 8 | import { Button } from "@/components/ui/button"; 9 | import { Instagram } from "lucide-react"; 10 | 11 | import logo from "@/assets/threads.svg"; 12 | import { InfoModal } from "@/components/profile/info"; 13 | import SelfShare from "@/components/profile/selfShare"; 14 | import { nFormatter } from "@/lib/utils"; 15 | import SignOut from "@/components/profile/signOut"; 16 | import { EditModal } from "@/components/profile/edit"; 17 | import FollowButton from "@/components/profile/follow"; 18 | 19 | export default async function ProfilePageLayout({ 20 | children, 21 | params, 22 | }: { 23 | children: React.ReactNode; 24 | params: { id: string }; 25 | }) { 26 | const user = await currentUser(); 27 | 28 | if (!user) return null; 29 | 30 | const getSelf = await prisma.user.findUnique({ 31 | where: { 32 | id: user.id, 33 | }, 34 | }); 35 | 36 | if (!getSelf?.onboarded) { 37 | redirect("/onboarding"); 38 | } 39 | 40 | const getUser = await prisma.user.findUnique({ 41 | where: { 42 | username: params.id, 43 | }, 44 | include: { 45 | followedBy: true, 46 | }, 47 | }); 48 | 49 | if (!getUser) { 50 | return ( 51 | <> 52 |