├── src
├── utils
│ ├── index.js
│ └── time.js
├── Assets
│ ├── images
│ │ ├── screenshot.png
│ │ ├── icon-delete.svg
│ │ ├── icon-minus.svg
│ │ ├── icon-reply.svg
│ │ ├── icon-edit.svg
│ │ └── icon-plus.svg
│ ├── design
│ │ ├── active-states.jpg
│ │ ├── desktop-modal.jpg
│ │ ├── mobile-design.jpg
│ │ ├── mobile-modal.jpg
│ │ ├── desktop-design.jpg
│ │ └── desktop-preview.jpg
│ └── avatars
│ │ ├── image-amyrobson.png
│ │ ├── image-juliusomo.png
│ │ ├── image-maxblagun.png
│ │ └── image-ramsesmiron.png
├── index.js
├── Components
│ ├── commentParts
│ │ ├── index.js
│ │ ├── CommentFooter.js
│ │ ├── CommentVotes.js
│ │ ├── CommentHeader.js
│ │ └── CommentBtn.js
│ ├── Styles
│ │ ├── _mixins.scss
│ │ ├── App.scss
│ │ ├── _variables.scss
│ │ ├── index.scss
│ │ ├── DeleteModal.scss
│ │ ├── AddComment.scss
│ │ └── Comment.scss
│ ├── ReplyContainer.js
│ ├── DeleteModal.js
│ ├── AddComment.js
│ ├── Reply.js
│ └── Comment.js
└── App.js
├── public
├── robots.txt
├── favicon.ico
├── manifest.json
├── data
│ └── data.json
└── index.html
├── .gitignore
├── package.json
└── README.md
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export { commentPostedTime } from "./time";
2 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/Assets/images/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/images/screenshot.png
--------------------------------------------------------------------------------
/src/Assets/design/active-states.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/design/active-states.jpg
--------------------------------------------------------------------------------
/src/Assets/design/desktop-modal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/design/desktop-modal.jpg
--------------------------------------------------------------------------------
/src/Assets/design/mobile-design.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/design/mobile-design.jpg
--------------------------------------------------------------------------------
/src/Assets/design/mobile-modal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/design/mobile-modal.jpg
--------------------------------------------------------------------------------
/src/Assets/avatars/image-amyrobson.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/avatars/image-amyrobson.png
--------------------------------------------------------------------------------
/src/Assets/avatars/image-juliusomo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/avatars/image-juliusomo.png
--------------------------------------------------------------------------------
/src/Assets/avatars/image-maxblagun.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/avatars/image-maxblagun.png
--------------------------------------------------------------------------------
/src/Assets/design/desktop-design.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/design/desktop-design.jpg
--------------------------------------------------------------------------------
/src/Assets/design/desktop-preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/design/desktop-preview.jpg
--------------------------------------------------------------------------------
/src/Assets/avatars/image-ramsesmiron.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/avatars/image-ramsesmiron.png
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client';
2 |
3 | import './Components/Styles/index.scss';
4 |
5 | import App from './App';
6 |
7 | const container = document.getElementById('root');
8 | const root = createRoot(container);
9 | root.render();
--------------------------------------------------------------------------------
/src/Assets/images/icon-delete.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Components/commentParts/index.js:
--------------------------------------------------------------------------------
1 | import CommentHeader from "./CommentHeader";
2 | import CommentFooter from "./CommentFooter";
3 | import CommentBtn from "./CommentBtn";
4 | import CommentVotes from "./CommentVotes";
5 |
6 | export { CommentHeader, CommentFooter, CommentBtn, CommentVotes };
7 |
--------------------------------------------------------------------------------
/src/Components/Styles/_mixins.scss:
--------------------------------------------------------------------------------
1 | @mixin media-sm() {
2 | @media (min-width: 26.25em) {
3 | @content;
4 | }
5 | }
6 |
7 |
8 | @mixin media-md() {
9 | @media (min-width: 47em) {
10 | @content;
11 | }
12 | }
13 |
14 | @mixin support($value) {
15 | @supports (#{$value}) {
16 | @content;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Assets/images/icon-minus.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Assets/images/icon-reply.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Assets/images/icon-edit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Components/Styles/App.scss:
--------------------------------------------------------------------------------
1 | @use "variables" as *;
2 | @use "mixins" as *;
3 |
4 | .App {
5 | min-height: 100vh;
6 | display: flex;
7 | flex-direction: column;
8 | justify-content: center;
9 | align-items: center;
10 | gap: 15px;
11 | padding: 2rem 0;
12 | background-color: $very-light-gray;
13 |
14 | @include media-md() {
15 | gap: 20px;
16 | padding: 4rem 0;
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 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | # assets
26 | /Assets/design
--------------------------------------------------------------------------------
/src/Assets/images/icon-plus.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.png",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "favicon.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "favicon.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/Components/Styles/_variables.scss:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;700&display=swap");
2 |
3 | /* primary */
4 | $moderate-blue: hsl(238, 40%, 52%);
5 | $soft-red: hsl(358, 79%, 66%);
6 | $light-grayish-blue: hsl(239, 57%, 85%);
7 | $pale-red: hsl(357, 100%, 86%);
8 |
9 | /* neutral */
10 | $dark-blue: hsl(212, 24%, 26%);
11 | $grayish-blue: hsl(211, 10%, 45%);
12 | $light-gray: hsl(223, 19%, 93%);
13 | $very-light-gray: hsl(228, 33%, 97%);
14 | $white: hsl(0, 0%, 100%);
15 |
16 | /* typography */
17 | $ff-rubik: "Rubik", sans-serif;
18 | $fw-regular: 400;
19 | $fw-medium: 500;
20 | $fw-bold: 700;
21 |
--------------------------------------------------------------------------------
/src/Components/ReplyContainer.js:
--------------------------------------------------------------------------------
1 | import Reply from "./Reply";
2 |
3 | const ReplyContainer = ({
4 | commentData,
5 | updateScore,
6 | addReply,
7 | editComment,
8 | deleteComment,
9 | setDeleteModalState,
10 | }) => {
11 | return (
12 |
13 | {commentData.map((data) => (
14 |
23 | ))}
24 |
25 | );
26 | };
27 |
28 | export default ReplyContainer;
29 |
--------------------------------------------------------------------------------
/src/Components/commentParts/CommentFooter.js:
--------------------------------------------------------------------------------
1 | import { CommentVotes, CommentBtn } from "./";
2 |
3 | const CommentFooter = ({
4 | updateScore,
5 | commentData,
6 | setReplying,
7 | setDeleting,
8 | setDeleteModalState,
9 | setEditing,
10 | type,
11 | }) => {
12 | return (
13 |
14 |
19 |
20 |
27 |
28 | );
29 | };
30 |
31 | export default CommentFooter;
32 |
--------------------------------------------------------------------------------
/src/utils/time.js:
--------------------------------------------------------------------------------
1 | const commentPostedTime = (timeInMileSec) => {
2 | const sec = (timeInMileSec / 1000).toFixed(0);
3 | const min = (timeInMileSec / (1000 * 60)).toFixed(0);
4 | const hrs = (timeInMileSec / (1000 * 60 * 60)).toFixed(0);
5 | const days = (timeInMileSec / (1000 * 60 * 60 * 24)).toFixed(0);
6 | const weeks = (timeInMileSec / (1000 * 60 * 60 * 24 * 7)).toFixed(0);
7 | const months = (timeInMileSec / (1000 * 60 * 60 * 24 * 31)).toFixed(0);
8 | const years = (timeInMileSec / (1000 * 60 * 60 * 24 * 12)).toFixed(0);
9 |
10 | if (sec < 60) {
11 | return "seconds";
12 | } else if (min < 60) {
13 | return min + " mins";
14 | } else if (hrs < 24) {
15 | return hrs + " hrs";
16 | } else if (days < 7) {
17 | return days + " days";
18 | } else if (weeks < 4) {
19 | return weeks + " weeks";
20 | } else if (months < 12) {
21 | return months + " months";
22 | } else {
23 | return years + " year";
24 | }
25 | };
26 |
27 | export { commentPostedTime };
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "interactive-comments-section",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.1",
7 | "@testing-library/react": "^12.1.2",
8 | "@testing-library/user-event": "^13.5.0",
9 | "node": "^17.3.0",
10 | "react": "^18.1.0",
11 | "react-dom": "^18.1.0",
12 | "react-scripts": "5.0.0",
13 | "sass": "^1.45.1",
14 | "web-vitals": "^2.1.2"
15 | },
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "test": "react-scripts test",
20 | "eject": "react-scripts eject"
21 | },
22 | "eslintConfig": {
23 | "extends": [
24 | "react-app",
25 | "react-app/jest"
26 | ]
27 | },
28 | "browserslist": {
29 | "production": [
30 | ">0.2%",
31 | "not dead",
32 | "not op_mini all"
33 | ],
34 | "development": [
35 | "last 1 chrome version",
36 | "last 1 firefox version",
37 | "last 1 safari version"
38 | ]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Components/DeleteModal.js:
--------------------------------------------------------------------------------
1 | import "./Styles/DeleteModal.scss";
2 |
3 | const DeleteModal = ({ setDeleting, deleteComment, setDeleteModalState }) => {
4 | const cancelDelete = () => {
5 | setDeleting(false);
6 | setDeleteModalState(false);
7 | };
8 |
9 | const deleteBtnClick = () => {
10 | deleteComment();
11 | setDeleteModalState(false);
12 | };
13 |
14 | return (
15 |
16 |
17 |
Delete comment
18 |
19 | Are you sure you want to delete this comment? This will remove the
20 | comment and can't be undone.
21 |
22 |
23 |
26 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default DeleteModal;
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Interactive comments section
2 |
3 | 
4 |
5 | ## The challenge
6 |
7 | - optimal layout for the app depending on their device's screen size.
8 | - hover states for all interactive elements on the page.
9 | - create, Read, Update, and Delete comments and replies.
10 | - up-vote and down-vote comments.
11 | - tracks the time dynamically since the comment or reply was posted.
12 |
13 | ### Information
14 | - this is a solution to the [Interactive comments section challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/interactive-comments-section-iG1RugEG9).
15 | - by [Frontend Mentor](https://www.frontendmentor.io)
16 |
17 | ## Built with
18 |
19 | - CSS custom properties
20 | - Flex-box
21 | - Mobile-first workflow
22 | - [SCSS](https://sass-lang.com) - CSS Preprocessor
23 | - [React](https://reactjs.org/) - JS library
24 |
25 | ## What I learned
26 |
27 | - I learned how to calculate the time between current time and given time.
28 |
29 | ## Continued development
30 |
31 | - I still want to add the feature :
32 | - which sorts the comments the comment of the basis of their votes
33 |
34 | ## Author
35 |
36 | - Frontend Mentor - [@arshGoyalDev](https://www.frontendmentor.io/profile/arshGoyalDev)
37 | - Twitter - [@arshGoyalDDev](https://twitter.com/arshGoyalDev)
38 |
--------------------------------------------------------------------------------
/src/Components/AddComment.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | import "./Styles/AddComment.scss";
4 |
5 | const AddComment = ({ buttonValue, addComments, replyingTo }) => {
6 | const replyingToUser = replyingTo ? `@${replyingTo}, ` : "";
7 | const [comment, setComment] = useState(replyingToUser);
8 |
9 | const clickHandler = () => {
10 | if (comment === "" || comment === " ") return;
11 |
12 | const newComment = {
13 | id: Math.floor(Math.random() * 100) + 5,
14 | content: replyingToUser + comment.replace(replyingToUser, ""),
15 | createdAt: new Date(),
16 | score: 0,
17 | username: "juliusomo",
18 | currentUser: true,
19 | replies: [],
20 | };
21 |
22 | addComments(newComment);
23 | setComment("");
24 | };
25 |
26 | return (
27 |
44 | );
45 | };
46 |
47 | export default AddComment;
48 |
--------------------------------------------------------------------------------
/src/Components/Styles/index.scss:
--------------------------------------------------------------------------------
1 | @use "variables" as *;
2 | @use "mixins" as *;
3 |
4 | // ------------------- //
5 | // reset //
6 | // ------------------- //
7 |
8 | *,
9 | *::before,
10 | *::after {
11 | padding: 0;
12 | margin: 0;
13 | box-sizing: border-box;
14 | }
15 |
16 | a,
17 | p,
18 | span {
19 | font-weight: $fw-regular;
20 | }
21 |
22 | button,
23 | input,
24 | textarea {
25 | font-family: inherit;
26 | font-size: inherit;
27 | }
28 |
29 | // ------------------- //
30 | // body //
31 | // ------------------- //
32 |
33 | html {
34 | @include support("scrollbar-width: thin") {
35 | scrollbar-width: thin;
36 | scrollbar-color: $grayish-blue $very-light-gray;
37 | }
38 | }
39 |
40 | body {
41 | font-family: $ff-rubik;
42 | -webkit-font-smoothing: antialiased;
43 | -moz-osx-font-smoothing: grayscale;
44 |
45 | &::-webkit-scrollbar {
46 | width: 6px;
47 | }
48 |
49 | &::-webkit-scrollbar-track {
50 | background: $very-light-gray;
51 | }
52 |
53 | &::-webkit-scrollbar-thumb {
54 | border-radius: 50px;
55 | background: $grayish-blue;
56 | }
57 | }
58 |
59 | // ------------------- //
60 | // utility classes //
61 | // ------------------- //
62 |
63 | .display--none {
64 | display: none !important;
65 | }
66 |
67 | .overflow--hidden {
68 | overflow-y: hidden;
69 | }
70 |
71 | ::selection {
72 | color: $white;
73 | background: $grayish-blue;
74 | }
75 |
--------------------------------------------------------------------------------
/src/Components/commentParts/CommentVotes.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | import { ReactComponent as IconPlus } from "../../Assets/images/icon-plus.svg";
4 | import { ReactComponent as IconMinus } from "../../Assets/images/icon-minus.svg";
5 |
6 | const CommentVotes = ({ updateScore, commentData, type }) => {
7 | const [score, setScore] = useState(commentData.score);
8 | const [voted, setVoted] = useState(commentData.voted ?? false);
9 | let upVote = () => {
10 | if (commentData.currentUser) return;
11 | if (voted === false) {
12 | let n = score + 1;
13 | setScore(n);
14 | updateScore(n, commentData.id, type, "upvote");
15 | setVoted(true);
16 | }
17 | };
18 |
19 | let downVote = () => {
20 | if (commentData.currentUser) return;
21 | if (voted === true) {
22 | let n = score - 1;
23 | setScore(n);
24 | updateScore(n, commentData.id, type, "downvote");
25 | setVoted(false);
26 | }
27 | };
28 |
29 | return (
30 |
31 |
34 |
{commentData.score}
35 |
38 |
39 | );
40 | };
41 |
42 | export default CommentVotes;
43 |
--------------------------------------------------------------------------------
/src/Components/commentParts/CommentHeader.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | import { commentPostedTime } from "../../utils";
4 |
5 | import CommentBtn from "./CommentBtn";
6 |
7 | const CommentHeader = ({
8 | commentData,
9 | replying,
10 | setReplying,
11 | setDeleting,
12 | setDeleteModalState,
13 | setEditing,
14 | }) => {
15 | const [time, setTime] = useState("");
16 | const createdAt = new Date(commentData.createdAt);
17 | const today = new Date();
18 |
19 | useEffect(() => {
20 | const timeout = setTimeout(() => {
21 | const differenceInTime = today.getTime() - createdAt.getTime();
22 | setTime(commentPostedTime(differenceInTime));
23 | }, 1000);
24 |
25 | return () => clearTimeout(timeout);
26 | // eslint-disable-next-line
27 | }, []);
28 |
29 | return (
30 |
31 |
32 |
{commentData.username}
33 | {commentData.currentUser ?
you
: ""}
34 |
{`${time} ago`}
35 |
43 |
44 | );
45 | };
46 |
47 | export default CommentHeader;
48 |
--------------------------------------------------------------------------------
/src/Components/commentParts/CommentBtn.js:
--------------------------------------------------------------------------------
1 | import { ReactComponent as IconReply } from "../../Assets/images/icon-reply.svg";
2 | import { ReactComponent as IconDelete } from "../../Assets/images/icon-delete.svg";
3 | import { ReactComponent as IconEdit } from "../../Assets/images/icon-edit.svg";
4 |
5 | const CommentBtn = ({
6 | commentData,
7 | replying,
8 | setReplying,
9 | setDeleting,
10 | setDeleteModalState,
11 | setEditing,
12 | }) => {
13 | const showAddComment = () => {
14 | setReplying(!replying);
15 | };
16 |
17 | // delete comment
18 | const showDeleteModal = () => {
19 | setDeleting(true);
20 | setDeleteModalState(true);
21 | };
22 |
23 | // edit comment
24 | const showEditComment = () => {
25 | setEditing(true);
26 | };
27 |
28 | return (
29 |
30 |
38 |
46 |
52 |
53 | );
54 | };
55 |
56 | export default CommentBtn;
57 |
--------------------------------------------------------------------------------
/src/Components/Styles/DeleteModal.scss:
--------------------------------------------------------------------------------
1 | @use 'variables' as *;
2 | @use 'mixins' as *;
3 |
4 |
5 | .delete-confirmation-wrapper{
6 | position: fixed;
7 | z-index: 100;
8 | top: 0; left: 0;
9 | height: 100vh; width: 100%;
10 | display: flex;
11 | justify-content: center;
12 | align-items: center;
13 | background: rgba($color: #000000, $alpha: 0.3);
14 |
15 | .delete-container{
16 | display: flex;
17 | flex-direction: column;
18 | gap: 15px;
19 | width: 90%;
20 | background: $white;
21 | padding: 20px;
22 | border-radius: 10px;
23 |
24 | .title{
25 | color: $dark-blue;
26 | font-weight: $fw-bold;
27 | font-size: 1.2rem;
28 | }
29 |
30 | .confirmation-message{
31 | line-height: 1.5;
32 | color: $grayish-blue;
33 | }
34 |
35 | .btn-container{
36 | display: flex;
37 | justify-content: space-between;
38 |
39 | button{
40 | color: $white;
41 | text-transform: uppercase;
42 | font-weight: $fw-medium;
43 | padding: 15px 20px;
44 | border: none;
45 | border-radius: 10px;
46 | transition: 0.3s;
47 | cursor: pointer;
48 |
49 | &:hover{
50 | opacity: 0.65;
51 | }
52 | }
53 |
54 | .cancel-btn{
55 | background: $grayish-blue;
56 | }
57 |
58 | .delete-btn{
59 | background: $soft-red;
60 | color: $white;
61 | }
62 | }
63 |
64 | @include media-sm() {
65 | width: 400px;
66 | padding: 30px;
67 |
68 | .btn-container {
69 |
70 | button{
71 | padding: 15px 30px;
72 | }
73 | }
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/public/data/data.json:
--------------------------------------------------------------------------------
1 | {
2 | "currentUser": {
3 | "username": "juliusomo"
4 | },
5 | "comments": [
6 | {
7 | "id": 1,
8 | "content": "Impressive! Though it seems the drag feature could be improved. But overall it looks incredible. You've nailed the design and the responsiveness at various breakpoints works really well.",
9 | "createdAt": "23 November 2021",
10 | "score": 12,
11 | "username": "amyrobson",
12 | "currentUser": false,
13 | "replies": []
14 | },
15 | {
16 | "id": 2,
17 | "content": "Woah, your project looks awesome! How long have you been coding for? I'm still new, but think I want to dive into React as well soon. Perhaps you can give me an insight on where I can learn React? Thanks!",
18 | "createdAt": "5 December 2021",
19 | "score": 5,
20 | "username": "maxblagun",
21 | "currentUser": false,
22 | "replies": [
23 | {
24 | "id": 3,
25 | "content": "@maxblaugn, If you're still new, I'd recommend focusing on the fundamentals of HTML, CSS, and JS before considering React. It's very tempting to jump ahead but lay a solid foundation first.",
26 | "createdAt": "18 December 2021",
27 | "score": 4,
28 | "username": "ramsesmiron",
29 | "currentUser": false,
30 | "replies": []
31 | },
32 | {
33 | "id": 4,
34 | "content": "@ramsesmiron, I couldn't agree more with this. Everything moves so fast and it always seems like everyone knows the newest library/framework. But the fundamentals are what stay constant.",
35 | "createdAt": "30 December 2021",
36 | "score": 2,
37 | "username": "juliusomo",
38 | "currentUser": true,
39 | "replies": []
40 | }
41 | ]
42 | }
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | Interactive comments section
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/Components/Styles/AddComment.scss:
--------------------------------------------------------------------------------
1 | @use "./variables" as *;
2 | @use "./mixins" as *;
3 |
4 | .add-comment {
5 | display: flex;
6 | flex-direction: column;
7 | gap: 20px;
8 | width: 90%;
9 | padding: 20px;
10 | background: $white;
11 | border-radius: 10px;
12 | animation: come-in 1.5s ease-in-out;
13 |
14 | .profile-pic {
15 | width: 30px;
16 | height: 30px;
17 | background: url(../../Assets/avatars/image-juliusomo.png);
18 | background-size: cover;
19 | border-radius: 50%;
20 | margin-right: 10px;
21 | }
22 |
23 | > .profile-pic {
24 | display: none;
25 | }
26 |
27 | .comment-input {
28 | position: relative;
29 | height: 100px;
30 | border: 2px solid $light-gray;
31 | color: $dark-blue;
32 | border-radius: 10px;
33 | padding: 15px 25px;
34 | resize: none;
35 | scrollbar-width: none;
36 |
37 | &::placeholder {
38 | color: $dark-blue;
39 | }
40 |
41 | &::-webkit-scrollbar {
42 | width: 0;
43 | }
44 |
45 | &:focus {
46 | outline: 1.5px solid $grayish-blue;
47 | }
48 | }
49 |
50 | .send-btn-container {
51 | display: flex;
52 | justify-content: space-between;
53 | align-items: center;
54 | }
55 |
56 | .add-btn {
57 | color: $white;
58 | font-weight: $fw-bold;
59 | text-transform: uppercase;
60 | padding: 15px 30px;
61 | background: $moderate-blue;
62 | border: 0;
63 | border-radius: 10px;
64 | transition: 0.3s;
65 | cursor: pointer;
66 |
67 | &:hover,
68 | &:focus{
69 | opacity: 0.65;
70 | }
71 | }
72 |
73 | @include media-md() {
74 | width: 735px;
75 | flex-direction: row;
76 | padding: 25px;
77 | gap: 25px;
78 |
79 | > .profile-pic {
80 | width: 50px;
81 | height: 36px;
82 | display: block;
83 | margin: 0;
84 | }
85 |
86 | .comment-input {
87 | width: 100%;
88 | }
89 |
90 | .send-btn-container {
91 | align-items: flex-start;
92 |
93 | .profile-pic {
94 | display: none;
95 | }
96 | }
97 | }
98 | }
99 |
100 | @keyframes come-in {
101 | 0%{
102 | opacity: 0;
103 | transform: translateY(-100%);
104 | }
105 |
106 | 75%{
107 | opacity: 0;
108 | transform: translateY(-50%);
109 | }
110 |
111 | 100%{
112 | opacity: 1;
113 | transform: translateY(0);
114 | }
115 | }
--------------------------------------------------------------------------------
/src/Components/Reply.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | import "./Styles/Comment.scss";
4 |
5 | import AddComment from "./AddComment";
6 | import DeleteModal from "./DeleteModal";
7 |
8 | import { CommentHeader, CommentFooter, CommentVotes } from "./commentParts";
9 |
10 | const Reply = ({
11 | commentData,
12 | updateScore,
13 | addNewReply,
14 | editComment,
15 | deleteComment,
16 | setDeleteModalState,
17 | }) => {
18 | const [replying, setReplying] = useState(false);
19 | const [editing, setEditing] = useState(false);
20 | const [content, setContent] = useState(commentData.content);
21 | const [deleting, setDeleting] = useState(false);
22 |
23 | // adding reply
24 | const addReply = (newReply) => {
25 | addNewReply(newReply);
26 | setReplying(false);
27 | };
28 |
29 | const commentContent = () => {
30 | const text = commentData.content.trim().split(" ");
31 | const firstWord = text.shift().split(",");
32 |
33 | return !editing ? (
34 |
35 | {firstWord}
36 | {text.join(" ")}
37 |
38 | ) : (
39 |