Avatar - Copy this React, Tailwind Component to your project
Please create me a student forum website admin page. I will send you the code for analysis:import defaultAvatar from '../../Assets/images/defaultAvatar'; import './AvatarStyle.scss'; export default function Avatar({link, ...style}) { return ( <div className='Avatar'> <img className={Object.keys(style)} src={link || defaultAvatar} alt="User Avatar" /> </div> ); }.Avatar{ img{ border radius: 50%; width: 100%; object fit: contain; } .small{ width: 38px; height: 38px; } .normal{ width: 46px; height: 46px; } .big{ width: 68px; height: 68px; } .bigger{ width: 148px; height: 148px; } } import { useDispatch, useSelector } from 'react redux'; import './SendMessageStyle.scss'; import { useEffect, useState } from 'react'; import { createComment, getComment } from '../../API/CommentAPI'; import { IoMdClose } from "react icons/io"; import { resetComment, resetCommentId } from '../../Redux/postSlice'; import { IoSend } from "react icons/io5"; const SendMessage = ({...style}) => { const dispatch = useDispatch(); const user = useSelector(state => state.user); const comment = useSelector(state => state.post.comment); const [textComment, setTextComment] = useState(""); const [askComment, setAskComment] = useState(''); const handleChange = (e) => { setTextComment(e.target.value); } const handleCancleAsk = () => { dispatch(resetCommentId()); } const handleSendComment = async () => { console.log({...comment, textComment, userId: user.id}); let res = await createComment({ userId: user.id, postId: comment.postId, commentId: comment.commentId, content: textComment }); if(res.status === 200){ dispatch(resetCommentId()); setTextComment(""); setAskComment(""); } } const fetchApi = async (commentId) => { let res = await getComment(commentId); if(res.status === 200) { setAskComment(res.data); return; } } useEffect(() => { if (comment.commentId !== 0){ fetchApi(comment.commentId); } }, [comment]); return ( <div className={`SendMessage ${Object.keys(style).find(key => style[key]) }`}> { (comment.commentId !== 0 && comment.commentId !== null) && <div className={`ask_comment p 2`}> <p>{askComment?.content}</p> <IoMdClose onClick={handleCancleAsk}/> </div> } <div className='d flex px 3 gap 3'> <input className={`form control`} value={textComment} onChange={(e) => handleChange(e)} /> <div className=' text center'> <button onClick={handleSendComment} className='btn btn primary'> <IoSend/> </button> </div> </div> </div> ) } export default SendMessage@import '../../Scss/colors'; .SendMessage{ .ask_comment{ background color: $no active transparent; color: gray; display: flex; align items: center; justify content: space between; } .small { width: 65%; } .big { width: 100%; } }import React, { Fragment, useState } from 'react' import Avatar from '../Avatar' import { BsThreeDotsVertical } from "react icons/bs"; import { AiOutlineLike } from "react icons/ai"; import { FaRegComment } from "react icons/fa6"; import './PostStyle.scss'; import { Link } from 'react router dom'; import timeFormat from '../../Helpers/timeFormat'; import { useDispatch, useSelector } from 'react redux'; import { createLike, deleteLike } from '../../API/LikeAPI'; import ContaierComment from '../Comment/ContaierComment'; import { FaChevronUp } from "react icons/fa"; import { changeLike, resetComment, setComment } from '../../Redux/postSlice'; import SendMessage from '../SendMessage'; export default function Post( { id, userId, name, createdAt, content, avatar, image, category, like,...style} ) { const user = useSelector(state => state.user); const dispath = useDispatch(); const [isOpenComment, setIsOpenComment] = useState(false); const userLiked = (like?.some(e => e.userId === user.id)); const handleLike = async(postId) => { let res; if (userLiked){ res = await deleteLike({userId: user.id, postId}); }else{ res = await createLike({userId: user.id, postId}); } dispath(changeLike()); } const handleOpenComment = () => { if(isOpenComment){ dispath(resetComment()); }else{ dispath(setComment({ postId: id, commentId: 0 })); } setIsOpenComment(!isOpenComment); } return ( <div className='Post d flex flex column align items center'> <div className={` overflow hidden block_main border p 2 pb 0 rounded 3 ${Object.keys(style)}`}> <header className='d flex justify content between'> <div className='d flex gap 1 align items center'> <div className="d block"> <Link to={`/@${userId}`}> <Avatar normal link={avatar}/> </Link> </div> <div> <Link to={`/@${userId}`} className='fw medium text decoration none text dark' >{name}</Link> <p className='block_time text secondary'>{timeFormat(createdAt)}</p> </div> </div> <Link className='d flex align items center justify content center block_edit' to={`/post/edit?postId=${id}`}> <BsThreeDotsVertical/> </Link> </header> <main className='my 2'> {/* <div className='block_content'>{content}</div> */} <div className='block_content' dangerouslySetInnerHTML={{ __html: content }}></div> <div className='block_category'>#{category?.name}</div> { image && <div className='block_image my 2'> <img src={image} alt="" /> </div> } </main> <footer className='row border top user select none'> <div className={`col btn_active p 1 d flex align items center gap 1 justify content center ${userLiked ? "text primary": ""}` } onClick={()=> handleLike(id)} > <AiOutlineLike/> <div className='d flex '> <p>{like?.length}</p> </div> </div> <div onClick={handleOpenComment} className="col text center btn_active p 1"> <FaRegComment/> </div> </footer> </div> { isOpenComment && <Fragment> <ContaierComment small={Object.keys(style).includes('small')} big={Object.keys(style).includes("big")} postId={id} /> <SendMessage small={Object.keys(style).includes('small')} big={Object.keys(style).includes("big")} /> <div className='text primary' onClick={handleOpenComment}> <FaChevronUp/> </div> </Fragment> } </div> ) } @import '../../Scss/colors'; @import '../../Scss//fonts'; .Post{ .block_main{ margin: 10px auto; } .block_image{ object fit: contain; img{ object fit: cover; width: 100%; } } .block_edit{ padding: 0px 16px; color: $no active; } .block_edit:hover{ background color: $no active transparent; border radius: 50%; } .block_time{ font size: 12px; } .block_category{ @include afacad(500); font style: italic; color: $active; font size: 14px; } .btn_active:hover{ background color: $no active transparent;; } .small{ width: 65%; } .big{ width: 100%; } }import React from 'react' import './NavbarStyle.scss'; import { Link, NavLink } from 'react router dom'; export default function Navbar({ listItems, ...style }) { return ( <div className='Navbar'> <nav className={`${Object.keys(style)}`}> { listItems.map((item, index) => { let Type = "li"; if(item.type !== 'text'){ Type = item.type === 'link' ? Link : NavLink; } return ( <Type to={item?.to} key={`item ${index}`} className='item d flex justify content between gap 2 fs 5 text secondary' onClick={item.onClick ? item.onClick : ()=>{}} > <div className='d flex align items center gap 2'> { item.icon && <div className='icon text white d flex align items center'> {item.icon} </div> } <div className='text fs 6'> <p className='m 0'>{item.title}</p> </div> </div> </Type> ) }) } </nav> </div> ) } @import '../../Scss/colors'; .Navbar{ user select: none; cursor: pointer; .icon{ border radius: 50%; padding: 8px 8px; background color: $no active; } .item{ padding left: 8px; border radius: 4px; text decoration: none; border: 1px solid white; padding: 8px 10px; } .item:hover{ background color: #94929229; // border color: $active; .icon{ background color: $active; } } .horizontal{ display: flex !important; flex direction: row; flex wrap: nowrap; gap: 10px; .item{ padding: 2px 8px; border: 2px solid white; } .item:hover{ border color: white; background color: white; } .active { border color: white; border bottom: 2px solid $active !important; } } }import React, { useState, useRef, useEffect } from 'react'; import { Modal, Button, Form, Alert } from 'react bootstrap'; import { Editor } from '@tinymce/tinymce react'; import { getAll } from '../../../API/CategoryAPI'; import { createPost } from '../../../API/PostAPI'; import { useSelector } from 'react redux'; const ModalCreatePost = ({ show, handleClose }) => { const user = useSelector(state => state.user); const [content, setContent] = useState(''); const [imagePreview, setImagePreview] = useState(null); const [categories, setCategories] = useState([]); const [selectedCategory, setSelectedCategory] = useState(''); const fileInputRef = useRef(null); const [error, setError] = useState(''); //const [images, setImages] = useState(null); const [groupId, setGroupId] = useState(null); const [successMessage, setSuccessMessage] = useState(''); useEffect(() => { const fetchCategories = async () => { try { const response = await getAll(); setCategories(response.data); } catch (error) { console.error('Error fetching categories', error); } }; if (show) { fetchCategories(); } }, [show]); const handleEditorChange = (newContent) => { setContent(newContent); }; const handleImageChange = (e) => { const file = e.target.files[0]; if (file) { const imageUrl = URL.createObjectURL(file); setImagePreview(imageUrl); //setImages(imageUrl); } }; const handleCloseModal = () => { setImagePreview(""); setContent(""); setSelectedCategory(""); setError(""); setGroupId(null); //setImages(null); setSuccessMessage(""); handleClose(); } const handlePostSubmit = async (e) => { e.preventDefault(); setError(null); setSuccessMessage(""); if (!selectedCategory) { setError("Please select a category!"); return; } if (!content) { setError("Please enter content!") return; } try { //const cleanContent = content.replace(/^<p>(.*)<\/p>$/, '$1'); // const formData = new FormData(); // formData.append("userId", userId); // formData.append("groupId", null); // formData.append("content", content); // formData.append("categoryId", selectedCategory); // if (images) // formData.append("image", images); const postData = { userId: user?.id, groupId: groupId, categoryId: selectedCategory, content: content, image: imagePreview, }; const response = await createPost(postData); if (response.status === 200) { setSuccessMessage('Post created successfully!'); handleClose(); } else { setError(response.message || 'Something went wrong!'); } } catch (error) { console.error("Error creating post", error); setError("An error occurred while creating the post!"); } }; return ( <Modal show={show} onHide={handleCloseModal}> <Modal.Header closeButton> <Modal.Title>Tạo bài đăng mới</Modal.Title> </Modal.Header> <Modal.Body> {error && <Alert variant='danger'>{error}</Alert>} {successMessage && <Alert variant='success'>{successMessage}</Alert>} <Editor apiKey='c71zurgnk0wg3iv3upi49j8zotrzy0chhq2evkxb69yca39g' value={content} init={{ plugins: [ // Core editing features //'anchor', 'autolink', 'charmap', 'codesample', 'emoticons', 'image', 'link', 'lists', 'media', 'searchreplace', 'table', 'visualblocks', 'wordcount', // Your account includes a free trial of TinyMCE premium features // Try the most popular premium features until Oct 28, 2024: //'checklist', 'mediaembed', 'casechange', 'export', 'formatpainter', 'pageembed', 'a11ychecker', 'tinymcespellchecker', 'permanentpen', 'powerpaste', 'advtable', 'advcode', 'editimage', 'advtemplate', 'ai', 'mentions', 'tinycomments', 'tableofcontents', 'footnotes', 'mergetags', 'autocorrect', 'typography', 'inlinecss', 'markdown', ], toolbar: 'undo redo | blocks fontfamily fontsize | bold italic underline strikethrough | link image media table mergetags | addcomment showcomments | spellcheckdialog a11ycheck typography | align lineheight | checklist numlist bullist indent outdent | emoticons charmap | removeformat', tinycomments_mode: 'embedded', tinycomments_author: 'Author name', mergetags_list: [ { value: 'First.Name', title: 'First Name' }, { value: 'Email', title: 'Email' }, ], ai_request: (request, respondWith) => respondWith.string(() => Promise.reject('See docs to implement AI Assistant')), }} initialValue="Chào bạn, bạn đang nghĩ gì!" onEditorChange={handleEditorChange} /> {/* Categories */} <Form.Group className="mt 3"> <Form.Label>Danh mục</Form.Label> <Form.Select value={selectedCategory} onChange={(e) => setSelectedCategory(e.target.value)} > <option value="">Chọn danh mục</option> {categories.map((category) => ( <option key={category.id} value={category.id}> {category.name} </option> ))} </Form.Select> </Form.Group> {/* End Categories */} <div className="image uploader mt 3"> <input type="file" ref={fileInputRef} onChange={handleImageChange} accept="image/*" style={{ display: 'none' }} /> <button className="btn btn primary" onClick={() => fileInputRef.current.click()}> Chọn Ảnh </button> {imagePreview && ( <img src={imagePreview} alt="Preview" style={{ marginTop: '10px', width: '100%', height: 'auto' }} /> )} </div> </Modal.Body> <Modal.Footer> <Button variant="secondary" onClick={handleCloseModal}> Hủy </Button> <Button variant="primary" onClick={handlePostSubmit}> Đăng bài </Button> </Modal.Footer> </Modal> ); }; export default ModalCreatePost; .post { .avatar { width: 50px; height: 50px; border radius: 50%; } }import { useState } from 'react'; import './CommentStyle.scss'; import Avatar from '../Avatar'; import { AiOutlineLike } from "react icons/ai"; import { GoComment } from "react icons/go"; import { FaChevronUp, FaChevronDown } from "react icons/fa"; import {Link} from 'react router dom'; import { useDispatch, useSelector } from 'react redux'; import { createLike, deleteLike } from '../../API/LikeAPI'; import { changeLike, setComment } from '../../Redux/postSlice'; const Comment = ({children, userName, userId, message, avatar, likes, commentId, disableCommentButton}) => { const [showMore, setShowMore] = useState(false); const dispatch = useDispatch(); const user = useSelector(state => state.user); const userLiked = (likes?.some(like => like.userId === user.id)); const comment = useSelector(state => state.post.comment) const handleOpenComment = () => { dispatch(setComment({ postId: comment.postId, commentId: commentId })); } const handleLike = async (commentId) => { let res; if (userLiked) { res = await deleteLike({ userId: user.id, commentId: commentId }); } else { res = await createLike({ userId: user.id, commentId: commentId }); } dispatch(changeLike()); } return ( <div className={`Comment mt 1 ps 3 ${showMore && 'border start'}`} > <div className='d flex gap 2'> <Link to={`/@${userId}`}> <Avatar normal link={avatar} /> </Link> <div> <div> <p className='fw medium'>{userName}</p> </div> <div> <p>{message}</p> </div> <div className='d flex gap 4'> <div className={`d flex align items center gap 1 ${userLiked ? "text primary" : ""}`} onClick={() => handleLike(commentId)} > <AiOutlineLike/> <p>{likes?.length}</p> </div> { !disableCommentButton && <div onClick={handleOpenComment}><GoComment /></div> } { children ? <div onClick={()=>setShowMore(!showMore)}> { showMore ? ( <div className='d flex align items center gap 1'> <p>less</p> <FaChevronUp/> </div> ):( <div className='d flex align items center gap 1'> <p>more</p> <FaChevronDown /> </div> ) } </div> : <div></div> } </div> </div> </div> <div className='ps 5 mt 3'> { showMore && children } </div> </div> ) } export default Comment@import '../../Scss/colors'; .ContainerComment{ .small{ width: 65%; } .big{ width: 100%; } } .Comment{ }import { useEffect, useState } from 'react'; import { getAllCommentByPostId } from '../../API/CommentAPI'; import Comment from './Comment'; import './CommentStyle.scss'; import { useSelector } from 'react redux'; const ContaierComment = ({postId, ...style}) => { const [comments, setComments] = useState([]); const fetchApi = async () => { let res = await getAllCommentByPostId(postId); if(res.status === 200){ setComments(res.data); return; } alert(res.message); } const commentRedux = useSelector(state => state.post.comment); console.log(commentRedux); useEffect(() => { fetchApi(); }, [useSelector(state => state.post.like.changeLike), useSelector(state => state.post.comment)]); let level = 5; const renderComments = (comment, level) => { level ; return <Comment key={`comment ${comment.id}`} userName={comment.User.name} userId={comment.User.id} avatar={comment.User.avatar} message={comment.content} likes={comment.Likes} commentId={comment.id} disableCommentButton={level === 0} > { comment.children.length && comment.children.map(e => renderComments(e, level)) } </Comment> } return ( <div className={`ContainerComment ${Object.keys(style).find(key => style[key]) }`}> { comments.map(comment => renderComments(comment, level)) } </div> ) } export default ContaierComment
