├── database.types.ts ├── public ├── favicon.ico └── images │ └── logo.svg ├── postcss.config.js ├── src ├── config │ └── env.ts ├── middleware.ts ├── validators │ ├── postSchema.ts │ ├── commentSchema.ts │ ├── authSchema.ts │ ├── imageValidator.ts │ └── CustomErrorReporter.ts ├── app │ ├── api │ │ ├── auth │ │ │ ├── [...nextauth] │ │ │ │ ├── route.ts │ │ │ │ └── options.ts │ │ │ ├── login │ │ │ │ └── route.ts │ │ │ └── register │ │ │ │ └── route.ts │ │ ├── user │ │ │ ├── comment │ │ │ │ └── route.ts │ │ │ ├── route.ts │ │ │ ├── post │ │ │ │ └── route.ts │ │ │ └── [id] │ │ │ │ └── route.ts │ │ ├── notifications │ │ │ └── route.ts │ │ ├── comment │ │ │ ├── [id] │ │ │ │ └── route.ts │ │ │ └── route.ts │ │ ├── explore │ │ │ └── route.ts │ │ ├── like │ │ │ └── route.ts │ │ └── post │ │ │ ├── [id] │ │ │ └── route.ts │ │ │ └── route.ts │ ├── providers.tsx │ ├── (front) │ │ ├── loading.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── post │ │ │ └── [id] │ │ │ │ └── page.tsx │ │ ├── explore │ │ │ └── page.tsx │ │ ├── notifications │ │ │ └── page.tsx │ │ ├── user │ │ │ └── [id] │ │ │ │ └── page.tsx │ │ └── profile │ │ │ └── page.tsx │ ├── (authPages) │ │ ├── layout.tsx │ │ ├── login │ │ │ └── page.tsx │ │ └── register │ │ │ └── page.tsx │ ├── layout.tsx │ └── globals.css ├── components │ ├── common │ │ ├── loading.tsx │ │ ├── UserAvatar.tsx │ │ ├── UserProfileAvatar.tsx │ │ ├── DyanmicNavBar.tsx │ │ ├── ImagePreviewCard.tsx │ │ ├── UserListCard.tsx │ │ ├── PostUserBar.tsx │ │ ├── CommentCard.tsx │ │ ├── ImageViewer.tsx │ │ ├── SignOutBtn.tsx │ │ ├── ThemeToggleBtn.tsx │ │ ├── ShareModal.tsx │ │ ├── SideBarLinks.tsx │ │ └── PostCard.tsx │ ├── theme-provider.tsx │ ├── base │ │ ├── LeftSidebar.tsx │ │ ├── RightSidebar.tsx │ │ ├── BaseComponent.tsx │ │ └── MobileNavBar.tsx │ ├── ui │ │ ├── label.tsx │ │ ├── textarea.tsx │ │ ├── input.tsx │ │ ├── toaster.tsx │ │ ├── avatar.tsx │ │ ├── scroll-area.tsx │ │ ├── button.tsx │ │ ├── tabs.tsx │ │ ├── dialog.tsx │ │ ├── use-toast.ts │ │ ├── sheet.tsx │ │ ├── alert-dialog.tsx │ │ ├── toast.tsx │ │ └── dropdown-menu.tsx │ ├── explore │ │ └── ExploreSearchBar.tsx │ └── threads │ │ ├── DeletePostBtn.tsx │ │ ├── DeleteCommentBtn.tsx │ │ ├── AddComment.tsx │ │ └── AddThread.tsx ├── DB │ └── db.config.ts ├── lib │ ├── utils.ts │ └── serverMethods.ts └── types.ts ├── prisma ├── migrations │ ├── migration_lock.toml │ ├── 20230821060240_change_column_type │ │ └── migration.sql │ ├── 20230821181433_add_content_in_notification │ │ └── migration.sql │ ├── 20230822054808_addded_likes_table │ │ └── migration.sql │ ├── 20230823053646_added_user_id_in_likes_table │ │ └── migration.sql │ ├── 20230821060634_change_password_column_length │ │ └── migration.sql │ └── 20230821055137_added_my_schema │ │ └── migration.sql └── schema.prisma ├── next.config.js ├── .env.example ├── tailwind.config.ts ├── components.json ├── .gitignore ├── tsconfig.json ├── LikedIcon.svg ├── README.md ├── package.json └── tailwind.config.js /database.types.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TusharVashishth/Threads_clone/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/config/env.ts: -------------------------------------------------------------------------------- 1 | class Env { 2 | static APP_URL: string = process.env.NEXT_PUBLIC_APP_URL!; 3 | } 4 | 5 | export default Env; 6 | -------------------------------------------------------------------------------- /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" -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | export { default } from "next-auth/middleware"; 2 | 3 | export const config = { 4 | matcher: ["/", "/profile", "/explore", "/post/:path*"], 5 | }; 6 | -------------------------------------------------------------------------------- /src/validators/postSchema.ts: -------------------------------------------------------------------------------- 1 | import vine from "@vinejs/vine"; 2 | 3 | export const postSchema = vine.object({ 4 | content: vine.string().trim().minLength(2), 5 | }); 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | domains: ["localhost"], 5 | }, 6 | }; 7 | 8 | module.exports = nextConfig; 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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" -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/validators/commentSchema.ts: -------------------------------------------------------------------------------- 1 | import vine from "@vinejs/vine"; 2 | 3 | export const commentSchema = vine.object({ 4 | content: vine.string().trim().minLength(2), 5 | post_id: vine.string().trim(), 6 | toUser_id: vine.string().trim(), 7 | }); 8 | -------------------------------------------------------------------------------- /src/components/common/loading.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | export default function Loading() { 3 | return ( 4 |
5 |
6 |
7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /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/app/(front)/loading.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function loading() { 4 | return ( 5 |
6 |
7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: {}, 11 | }, 12 | plugins: [], 13 | }; 14 | export default config; 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/validators/authSchema.ts: -------------------------------------------------------------------------------- 1 | import vine from "@vinejs/vine"; 2 | 3 | export const registerSchema = vine.object({ 4 | username: vine.string().minLength(2).maxLength(60), 5 | name: vine.string().minLength(5).maxLength(100), 6 | email: vine.string().email(), 7 | password: vine.string().minLength(6).maxLength(32).confirmed(), 8 | }); 9 | 10 | export const loginSchema = vine.object({ 11 | email: vine.string().email(), 12 | password: vine.string(), 13 | }); 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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/ -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /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/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | import moment from "moment"; 4 | 5 | export function cn(...inputs: ClassValue[]) { 6 | return twMerge(clsx(inputs)); 7 | } 8 | 9 | export function bytesToMB(bytes: number): number { 10 | const MB = 1048576; 11 | return bytes / MB; 12 | } 13 | 14 | export function getRandomNumber(min: number, max: number): string { 15 | return Math.floor(Math.random() * (max - min + 1) + min).toString(); 16 | } 17 | 18 | export function formateDate(date: string): string { 19 | return moment(date).fromNow(); 20 | } 21 | 22 | export async function wait(ms: number) { 23 | return new Promise((resolve) => setTimeout(resolve, ms)); 24 | } 25 | -------------------------------------------------------------------------------- /src/validators/imageValidator.ts: -------------------------------------------------------------------------------- 1 | import { bytesToMB } from "@/lib/utils"; 2 | 3 | export function imagevalidator( 4 | name: string | undefined, 5 | size: number | undefined 6 | ): string | null { 7 | let flag: string | null = null; 8 | 9 | if (name) { 10 | const getImgExt = name.split("."); 11 | const imgExtType: Array = ["svg", "png", "jpeg", "jpg"]; 12 | if (!imgExtType.includes(getImgExt[1])) { 13 | flag = "Image must be .png,.jpeg,.jpg,.svg"; 14 | } else { 15 | flag = null; 16 | } 17 | } 18 | if (size) { 19 | const fileInMB = bytesToMB(size!); 20 | if (fileInMB > 2) { 21 | flag = "Image should be less than 2 MB."; 22 | } else { 23 | flag = null; 24 | } 25 | } 26 | 27 | return flag; 28 | } 29 | -------------------------------------------------------------------------------- /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/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |