Merge pull request 'Initial Commit for like and comment UI' (#1) from dev into main

Reviewed-on: #1
Reviewed-by: Rishab Kumar <rishabh.kumar@digimantra.com>
This commit is contained in:
Rishab Kumar 2025-01-07 12:47:53 +00:00
commit 9e621dead4
19 changed files with 6204 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules

8
README copy.md Normal file
View file

@ -0,0 +1,8 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

38
eslint.config.js Normal file
View file

@ -0,0 +1,38 @@
import js from '@eslint/js'
import globals from 'globals'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
settings: { react: { version: '18.3' } },
plugins: {
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
]

13
index.html Normal file
View file

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

5813
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

32
package.json Normal file
View file

@ -0,0 +1,32 @@
{
"name": "likeandcomments",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"lucide-react": "^0.469.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"eslint": "^9.17.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.14.0",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"vite": "^6.0.5"
}
}

6
postcss.config.js Normal file
View file

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

BIN
public/images/Blog.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
public/images/logo.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

42
src/App.css Normal file
View file

@ -0,0 +1,42 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

62
src/App.jsx Normal file
View file

@ -0,0 +1,62 @@
import React from "react";
import Post from "./components/Post";
const App = () => {
const posts = [
{
id: 1,
user: "Sachin Tendulkar",
time: "2 hours ago",
content: "Hailed as the world's most prolific batsman of all time...",
image: "/images/sachin.webp",
},
{
id: 2,
user: "Vansh Vatsa",
time: "1 hour ago",
content: "Jaspreet Bumrah awesome performance at Australia.",
image: "/images/jaspreet.jpeg",
},
{
id: 3,
user: "Ro-Hit Sharma",
time: "15 mins ago",
content: "Rohit Sharma Score 264 against Sri Lanka at the Eden Gardens.",
image: "/images/rohit-sharma.avif",
},
{
id: 4,
user: "Virat Kohli",
time: "1 year ago",
content: "Kohli has garnered 10 ICC Awards, making him the most awarded...",
image: "/images/kholi.jpg",
},
{
id: 5,
user: "MS Dhoni",
time: "2 year ago",
content: " He has scored 16 centuries and 106 fifties in his international career",
image: "/images/dhoni.avif",
},
{
id: 6,
user: "Mohammad Siraj",
time: "15 mins ago",
content: "His contributions were pivotal as India claimed a famous 2-1 away win against Australia. Siraj started to shine as an ODI bowler as well, and was lethal particularly in the powerplay.",
image: "/images/siraj.webp",
},
];
return (
<div className="container mx-auto p-4 bg-white shadow rounded-lg max-w-2xl">
<div className="space-y-8">
{posts.map((post) => (
<Post key={post.id} {...post} />
))}
</div>
</div>
);
};
export default App;

View file

@ -0,0 +1,23 @@
import React from "react";
import { Heart, MessageCircle } from "lucide-react";
const ActionButtons = ({ handleLike, setShowComments }) => (
<div className="flex mb-4 pb-2 border-b">
<button
onClick={handleLike}
className="flex items-center justify-center w-1/2 gap-2 py-2 hover:bg-gray-100 rounded"
>
<Heart size={20} />
<span>Like</span>
</button>
<button
onClick={() => setShowComments(true)}
className="flex items-center justify-center w-1/2 gap-2 py-2 hover:bg-gray-100 rounded"
>
<MessageCircle size={20} />
<span>Comment</span>
</button>
</div>
);
export default ActionButtons;

View file

@ -0,0 +1,24 @@
import React from "react";
const CommentForm = ({ newComment, setNewComment, handleSubmit }) => (
<form onSubmit={handleSubmit} className="mb-4">
<div className="flex gap-2">
<input
type="text"
value={newComment}
onChange={(e) => setNewComment(e.target.value)}
placeholder="Write a comment..."
className="flex-1 px-4 py-2 border rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
type="submit"
disabled={!newComment.trim()}
className="px-6 py-2 bg-blue-500 text-white rounded-full hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed"
>
Post
</button>
</div>
</form>
);
export default CommentForm;

View file

@ -0,0 +1,42 @@
import React from "react";
import { X } from "lucide-react";
const CommentsModal = ({ showComments, setShowComments, comments }) => {
if (!showComments) return null;
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-white rounded-lg w-full max-w-lg max-h-[80vh] overflow-hidden">
<div className="p-4 border-b flex justify-between items-center">
<h3 className="text-xl font-semibold">Comments ({comments.length})</h3>
<button
onClick={() => setShowComments(false)}
className="text-gray-500 hover:text-gray-700"
>
<X size={24} />
</button>
</div>
<div className="overflow-y-auto p-4">
{comments.length === 0 ? (
<p className="text-center text-gray-500">No comments yet</p>
) : (
<div className="space-y-4">
{comments.map((comment, index) => (
<div key={index} className="flex gap-3">
<div className="w-8 h-8 rounded-full bg-gray-200"></div>
<div className="flex-1">
<div className="bg-gray-100 rounded-lg p-3">
<p className="text-gray-700">{comment.text}</p>
</div>
</div>
</div>
))}
</div>
)}
</div>
</div>
</div>
);
};
export default CommentsModal;

68
src/components/Post.jsx Normal file
View file

@ -0,0 +1,68 @@
import React, { useState, useEffect } from "react";
import ActionButtons from "./ActionButtons";
import CommentForm from "./CommentForm";
import CommentsModal from "./CommentsModal";
const Post = ({ id, user, time, content, image }) => {
const [likes, setLikes] = useState(() => {
const storedLikes = localStorage.getItem(`post-${id}-likes`);
return storedLikes ? JSON.parse(storedLikes) : 0;
});
const [comments, setComments] = useState(() => {
const storedComments = localStorage.getItem(`post-${id}-comments`);
return storedComments ? JSON.parse(storedComments) : [];
});
const [newComment, setNewComment] = useState("");
const [showComments, setShowComments] = useState(false);
useEffect(() => {
localStorage.setItem(`post-${id}-likes`, JSON.stringify(likes));
}, [id, likes]);
useEffect(() => {
localStorage.setItem(`post-${id}-comments`, JSON.stringify(comments));
}, [id, comments]);
const handleLike = () => setLikes((prev) => prev + 1);
const handleSubmit = (e) => {
e.preventDefault();
if (newComment.trim()) {
setComments((prev) => [...prev, { text: newComment }]);
setNewComment("");
}
};
return (
<div className="mb-6 p-4 bg-white shadow rounded-lg">
<div className="flex items-center mb-4">
<div className="w-10 h-10 rounded-full bg-gray-200">
<img src="/images/logo.webp" alt={`${user}'s Avatar`} />
</div>
<div className="ml-3">
<p className="font-semibold">{user}</p>
<p className="text-gray-500 text-sm">{time}</p>
</div>
</div>
<p className="text-gray-800 mb-4">{content}</p>
{image && <img src={image} alt="Post content" className="w-full rounded-lg mb-4" />}
<div className="flex justify-between mb-4 pb-2 border-b">
<span>{likes} likes</span>
<span>{comments.length} comments</span>
</div>
<ActionButtons handleLike={handleLike} setShowComments={setShowComments} />
<CommentForm
newComment={newComment}
setNewComment={setNewComment}
handleSubmit={handleSubmit}
/>
<CommentsModal
showComments={showComments}
setShowComments={setShowComments}
comments={comments}
/>
</div>
);
};
export default Post;

3
src/index.css Normal file
View file

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

10
src/main.jsx Normal file
View file

@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)

12
tailwind.config.js Normal file
View file

@ -0,0 +1,12 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

7
vite.config.js Normal file
View file

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})