├── assets ├── image ├── GSSoC-Ext.png └── hacktoberfest.png ├── src ├── assets │ ├── favicon.png │ └── contactPageBg.jpg ├── lib │ └── utils.ts ├── componenets │ ├── SparkelIcons.jsx │ ├── container │ │ └── Container.jsx │ ├── Logo.jsx │ ├── Button.jsx │ ├── AiChatbot.jsx │ ├── index.js │ ├── Select.jsx │ ├── Input.jsx │ ├── TrendingTopicHomePage.jsx │ ├── AuthLayout.jsx │ ├── Header │ │ └── LogoutBtn.jsx │ ├── RTE.jsx │ ├── ForgotPasswordModal.jsx │ ├── SearchBar.jsx │ ├── PostCard.jsx │ ├── Login.jsx │ ├── Signup.jsx │ └── GoogleTranslator.jsx ├── store │ ├── store.js │ └── authSlice.js ├── pages │ ├── Signup.jsx │ ├── Login.jsx │ ├── AddPost.jsx │ ├── Error404.jsx │ ├── Home.jsx │ ├── Features.jsx │ ├── Feedback.css │ ├── EditPost.jsx │ ├── FAQ.jsx │ ├── SupportPage.jsx │ ├── Category.jsx │ ├── ContactUsBG.jsx │ ├── Presskit.css │ ├── FeedBackPage.jsx │ ├── AllPost.jsx │ └── Presskit.jsx ├── config │ └── config.js ├── api │ └── chatbot.js ├── components │ └── ui │ │ ├── label.tsx │ │ ├── textarea.tsx │ │ ├── badge.tsx │ │ ├── Chatbot.jsx │ │ ├── popover.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── accordion.tsx │ │ ├── BackToTopButton.tsx │ │ ├── dialog.tsx │ │ ├── alert-dialog.tsx │ │ ├── command.tsx │ │ ├── navigation-menu.tsx │ │ └── select.tsx ├── utils │ └── ai.js ├── App.jsx ├── index.css ├── appwrite │ └── auth.js └── App.css ├── postcss.config.js ├── server ├── .env.example ├── routes │ ├── feedbackRoute.js │ ├── contactRoute.js │ ├── storiesRoutes.js │ ├── newsletterRoute.js │ └── discussionRoutes.js ├── models │ ├── postModel.js │ ├── discussion.js │ ├── Newsletter.js │ ├── contact.js │ └── feedback.js ├── utils │ └── db.js ├── package.json ├── controller │ ├── postsController.js │ ├── newsletterController.js │ ├── feedbackController.js │ ├── contactController.js │ └── discussionController.js ├── app.js ├── generateFromAi.py ├── sendSuscribedMail.js └── sendContactDetailsToAdmin.js ├── vite.config.js ├── .env.sample ├── .gitignore ├── components.json ├── index.html ├── .eslintrc.cjs ├── .github ├── workflows │ ├── greetings.yml │ └── update-readme.yml └── scripts │ └── update_structure.py ├── public ├── service-worker.js └── offline.html ├── tsconfig.json ├── package.json ├── repo_structure.txt ├── PROJECT_STRUCTURE.md ├── tailwind.config.js ├── README.md ├── env.md └── CODE_OF_CONDUCT.md /assets/image: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/GSSoC-Ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anuj123upadhyay/MegaBlog/HEAD/assets/GSSoC-Ext.png -------------------------------------------------------------------------------- /assets/hacktoberfest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anuj123upadhyay/MegaBlog/HEAD/assets/hacktoberfest.png -------------------------------------------------------------------------------- /src/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anuj123upadhyay/MegaBlog/HEAD/src/assets/favicon.png -------------------------------------------------------------------------------- /src/assets/contactPageBg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anuj123upadhyay/MegaBlog/HEAD/src/assets/contactPageBg.jpg -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /server/.env.example: -------------------------------------------------------------------------------- 1 | MONGO_URI=mongodb://localhost:27017/megaBlog 2 | JWT_SECRET=scijyasfy7dsvegdffvbfbfgg435tgrsnbgfgn 3 | PORT=5000 4 | EMAIL_ID= 5 | PASS_KEY= 6 | ADMIN_EMAIL_ID= -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /src/componenets/SparkelIcons.jsx: -------------------------------------------------------------------------------- 1 | // src/components/SparklesIcon.jsx 2 | import React from "react"; 3 | 4 | const SparkelIcons = () => { 5 | return ; // Or your icon design 6 | }; 7 | 8 | export default SparkelIcons; 9 | -------------------------------------------------------------------------------- /src/componenets/container/Container.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function Container({children}) { 4 | return ( 5 |
6 | {children} 7 |
8 | ) 9 | } 10 | 11 | export default Container 12 | -------------------------------------------------------------------------------- /src/store/store.js: -------------------------------------------------------------------------------- 1 | import {configureStore} from '@reduxjs/toolkit' 2 | import authSlice from './authSlice'; 3 | 4 | 5 | const store = configureStore({ 6 | reducer:{ 7 | auth : authSlice 8 | } 9 | }) 10 | export default store; 11 | -------------------------------------------------------------------------------- /server/routes/feedbackRoute.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { submitFeedback } from '../Controller/feedbackController.js'; 3 | const router = express.Router(); 4 | 5 | router.post('/submitFeedback', submitFeedback); 6 | 7 | export default router; 8 | -------------------------------------------------------------------------------- /src/pages/Signup.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Signup as SignUp } from '../componenets' 3 | 4 | function Signup() { 5 | return ( 6 |
7 | 8 |
9 | ) 10 | } 11 | 12 | export default Signup 13 | -------------------------------------------------------------------------------- /src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Login as LoginComponent } from '../componenets' 3 | 4 | function Login() { 5 | return ( 6 |
7 | 8 |
9 | ) 10 | } 11 | 12 | export default Login 13 | -------------------------------------------------------------------------------- /server/routes/contactRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { getContact, saveContact } from "../controller/contactController.js"; 3 | const router = express.Router(); 4 | 5 | router.post("/saveContact", saveContact); 6 | router.get("/saveContact", getContact); 7 | 8 | export default router; 9 | -------------------------------------------------------------------------------- /server/routes/storiesRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { getPosts, savePost } from "../controller/postsController.js"; 3 | 4 | const router = express.Router(); 5 | 6 | router.get("/getposts", getPosts); 7 | router.post("/saveposts", savePost); 8 | 9 | export default router; 10 | -------------------------------------------------------------------------------- /server/routes/newsletterRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | import { getNewsletter, saveNewsletter } from "../controller/newsletterController.js"; 4 | 5 | router.post("/suscribe", saveNewsletter); 6 | router.get("/getNewsletter", getNewsletter); 7 | 8 | export default router; 9 | -------------------------------------------------------------------------------- /src/pages/AddPost.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Container, PostForm } from '../componenets' 3 | 4 | function AddPost() { 5 | return ( 6 |
7 | 8 | 9 | 10 |
11 | ) 12 | } 13 | 14 | export default AddPost -------------------------------------------------------------------------------- /server/models/postModel.js: -------------------------------------------------------------------------------- 1 | // postModel.js 2 | import mongoose from "mongoose"; 3 | 4 | const postSchema = new mongoose.Schema({ 5 | title: String, 6 | content: String, 7 | category: String, 8 | date: { type: Date, default: Date.now }, 9 | }); 10 | 11 | const Post = mongoose.model("Post", postSchema); 12 | 13 | export default Post; 14 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | VITE_APPWRITE_ENDPOINT= 2 | VITE_APPWRITE_PROJECT_ID= 3 | VITE_APPWRITE_DATABASE_ID= 4 | VITE_APPWRITE_COLLECTION_ID= 5 | VITE_APPWRITE_NEWSLETTER_COLLECTION_ID= 6 | VITE_APPWRITE_BUCKET_ID= 7 | VITE_APPWRITE_FUNCTION_ID= 8 | VITE_APPWRITE_URL= 9 | 10 | EMAIL_SERVICE= 11 | EMAIL_FROM= 12 | EMAIL_PASS= 13 | EMAIL_USER= 14 | APPWRITE_API_KEY= 15 | -------------------------------------------------------------------------------- /src/componenets/Logo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Badge } from '../components/ui/badge' 3 | 4 | function Logo({width = '100px'}) { 5 | return ( 6 |
7 | 8 |
9 | MegaBlog 10 |
11 |
12 |
13 | ) 14 | } 15 | 16 | export default Logo 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .env 26 | 27 | .vercel 28 | -------------------------------------------------------------------------------- /server/routes/discussionRoutes.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const router = express.Router(); 3 | 4 | import { getQuestions, saveQuestion, addAnswerToQuestion } from '../controller/discussionController.js'; 5 | 6 | router.get('/getQuestion', getQuestions); 7 | 8 | router.post('/postQuestion', saveQuestion); 9 | 10 | router.put('/:id/answer', addAnswerToQuestion); 11 | 12 | export default router; 13 | -------------------------------------------------------------------------------- /server/utils/db.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import dotenv from "dotenv"; 3 | dotenv.config(); 4 | 5 | const connectDB = async () => { 6 | try { 7 | await mongoose.connect(process.env.MONGO_URI); 8 | console.log("MongoDB Connected"); 9 | } catch (error) { 10 | console.error("Database connection error:", error); 11 | process.exit(1); 12 | } 13 | }; 14 | 15 | export default connectDB; 16 | -------------------------------------------------------------------------------- /src/componenets/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function Button({ 4 | children, 5 | type = 'button', 6 | bgColor = "bg-blue-600", 7 | textColor = 'text-white', 8 | className = '', 9 | ...props 10 | 11 | }) { 12 | return ( 13 | 18 | ) 19 | } 20 | 21 | export default Button 22 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /server/models/discussion.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | // Define the schema for a question 4 | const discussionSchema = new mongoose.Schema({ 5 | content: { 6 | type: String, 7 | required: true, 8 | }, 9 | answered: { 10 | type: Boolean, 11 | default: false, 12 | }, 13 | answer: { 14 | type: String, 15 | default: '', 16 | }, 17 | }, { timestamps: true }); 18 | 19 | const Question = mongoose.model('Question', discussionSchema); 20 | 21 | export default Question; 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | MegaBlog 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "main": "app.js", 5 | "type": "module", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "nodemon app.js" 9 | }, 10 | "keywords": [ 11 | "megablog" 12 | ], 13 | "author": "", 14 | "license": "ISC", 15 | "description": "", 16 | "dependencies": { 17 | "cors": "^2.8.5", 18 | "dotenv": "^16.4.5", 19 | "express": "^4.21.1", 20 | "mongoose": "^8.8.0", 21 | "nodemailer": "^6.9.16", 22 | "nodemon": "^3.1.7" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/models/Newsletter.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const newsletterSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | required: true, 7 | trim: true 8 | }, 9 | email: { 10 | type: String, 11 | required: true, 12 | trim: true, 13 | match: [/^\S+@\S+\.\S+$/, "Please enter a valid email address"] 14 | }, 15 | subscribedAt: { 16 | type: Date, 17 | default: Date.now 18 | } 19 | }); 20 | 21 | const Newsletter = mongoose.model("Newsletter", newsletterSchema); 22 | 23 | export default Newsletter; 24 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react-refresh/only-export-Components': [ 16 | 'warn', 17 | { allowConstantExport: true }, 18 | ], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /src/store/authSlice.js: -------------------------------------------------------------------------------- 1 | import {createSlice} from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | status: false, 5 | userData: null 6 | } 7 | 8 | 9 | const authSlice = createSlice({ 10 | name:"auth", 11 | initialState, 12 | reducers:{ 13 | login: (state,action) =>{ 14 | state.status = true, 15 | state.userData = action.payload.userData; 16 | }, 17 | logout: (state)=>{ 18 | state.status = false; 19 | state.userData = null; 20 | } 21 | } 22 | }) 23 | 24 | export const {login,logout} = authSlice.actions; 25 | 26 | export default authSlice.reducer; 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request_target, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | steps: 12 | - uses: actions/first-interaction@v1 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | issue-message: "👋 Thank you for raising an issue! We appreciate your effort in helping us improve. Our team will review it shortly. Stay tuned!" 16 | pr-message: " 🎉 Thank you for your contribution! Your pull request has been submitted successfully. A maintainer will review it as soon as possible. We appreciate your support in making this project better" 17 | -------------------------------------------------------------------------------- /src/componenets/AiChatbot.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | const AiChatbot = () => { 4 | useEffect(() => { 5 | window.embeddedChatbotConfig = { 6 | chatbotId: "SiX5OMRAbSuoa7OrpiZ0b", 7 | domain: "www.chatbase.co" 8 | }; 9 | 10 | const script = document.createElement('script'); 11 | script.src = "https://www.chatbase.co/embed.min.js"; 12 | script.chatbotId = "SiX5OMRAbSuoa7OrpiZ0b"; 13 | script.domain = "www.chatbase.co"; 14 | script.defer = true; 15 | 16 | document.body.appendChild(script); 17 | 18 | return () => { 19 | document.body.removeChild(script); 20 | }; 21 | }, []); 22 | 23 | return null; 24 | }; 25 | 26 | export default AiChatbot; 27 | -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | const conf = { 2 | appwriteUrl: String(import.meta.env.VITE_APPWRITE_URL), 3 | appwriteProjectId: String(import.meta.env.VITE_APPWRITE_PROJECT_ID), 4 | appwriteDatabaseId: String(import.meta.env.VITE_APPWRITE_DATABASE_ID), 5 | appwriteCollectionId: String(import.meta.env.VITE_APPWRITE_COLLLECTION_ID), 6 | appwriteNewsLetterCollectionId:String(import.meta.env.VITE_APPWRITE_NEWSLETTER_COLLECTION_ID), 7 | appwriteBucketId: String(import.meta.env.VITE_APPWRITE_BUCKET_ID), 8 | appwriteFunctionId:String(import.meta.env.VITE_APPWRITE_FUNCTION_ID), 9 | appwriteTrendinTopicCollectionId:String(import.meta.env.VITE_APPWRITE_TRENDING_TOPIC_COLLECTION_ID), 10 | appwriteHuggingFaceAcessToken:String(import.meta.env.VITE_HUGGINGFACE_ACCESS_TOKEN) 11 | } 12 | 13 | export default conf -------------------------------------------------------------------------------- /src/componenets/index.js: -------------------------------------------------------------------------------- 1 | import Header from "./Header/Header"; 2 | import Footer from "./Footer/Footer"; 3 | import Container from "./container/Container"; 4 | import Logo from './Logo' 5 | import LogoutBtn from "./Header/LogoutBtn"; 6 | import Button from "./Button" 7 | import Input from "./Input"; 8 | import RTE from "./RTE" 9 | import Signup from "./Signup"; 10 | import Login from "./Login" 11 | import PostCard from "./PostCard"; 12 | import PostForm from './postForm/PostForm' 13 | import AuthLayout from "./AuthLayout"; 14 | import Select from "./Select"; 15 | 16 | 17 | export { 18 | Header, 19 | Footer, 20 | Container, 21 | Logo, 22 | LogoutBtn, 23 | Button, 24 | Input, 25 | RTE, 26 | Signup, 27 | Login, 28 | PostCard, 29 | PostForm, 30 | AuthLayout, 31 | Select 32 | } -------------------------------------------------------------------------------- /src/api/chatbot.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export default async function handler(req, res) { 4 | const { message } = req.body; 5 | 6 | try { 7 | const response = await axios.post( 8 | 'https://api.openai.com/v1/chat/completions', 9 | { 10 | model: 'gpt-4', 11 | messages: [{ role: 'user', content: message }], 12 | }, 13 | { 14 | headers: { 15 | 'Content-Type': 'application/json', 16 | Authorization: `Bearer ${process.env.VITE_OPENAI_API_KEY}`, 17 | }, 18 | } 19 | ); 20 | 21 | const reply = response.data.choices[0].message.content; 22 | res.status(200).json({ reply }); 23 | } catch (error) { 24 | console.error('Error communicating with OpenAI:', error); 25 | res.status(500).json({ error: 'Internal Server Error' }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/componenets/Select.jsx: -------------------------------------------------------------------------------- 1 | import React, {useId} from 'react' 2 | 3 | function Select({ 4 | label, 5 | options, 6 | className = '', 7 | ...props 8 | }, ref) { 9 | 10 | const id = useId() 11 | return ( 12 |
13 | { label && } 14 | 26 |
27 | ) 28 | } 29 | 30 | export default React.forwardRef(Select) 31 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "../../lib/utils" 6 | 7 | const labelVariants = cva( 8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 9 | ) 10 | 11 | const Label = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef & 14 | VariantProps 15 | >(({ className, ...props }, ref) => ( 16 | 21 | )) 22 | Label.displayName = LabelPrimitive.Root.displayName 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /server/controller/postsController.js: -------------------------------------------------------------------------------- 1 | // postsController.js 2 | import Post from "../models/postModel.js"; 3 | 4 | // Fetch all posts 5 | export const getPosts = async (req, res) => { 6 | try { 7 | const posts = await Post.find(); 8 | res.status(200).json(posts); 9 | } catch (error) { 10 | res.status(500).json({ message: "Error fetching posts", error }); 11 | } 12 | }; 13 | 14 | // Save a new post 15 | export const savePost = async (req, res) => { 16 | const { title, content, category, date } = req.body; 17 | 18 | try { 19 | const newPost = new Post({ title, content, category, date }); 20 | const savedPost = await newPost.save(); 21 | res.status(201).json(savedPost); 22 | } catch (error) { 23 | res.status(500).json({ message: "Error saving post", error }); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /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 |