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:
commit
9e621dead4
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
node_modules
|
8
README copy.md
Normal file
8
README copy.md
Normal 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
38
eslint.config.js
Normal 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
13
index.html
Normal 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
5813
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
32
package.json
Normal file
32
package.json
Normal 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
6
postcss.config.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
BIN
public/images/Blog.webp
Normal file
BIN
public/images/Blog.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
BIN
public/images/logo.webp
Normal file
BIN
public/images/logo.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
42
src/App.css
Normal file
42
src/App.css
Normal 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
62
src/App.jsx
Normal 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;
|
23
src/components/ActionButtons.jsx
Normal file
23
src/components/ActionButtons.jsx
Normal 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;
|
24
src/components/CommentForm.jsx
Normal file
24
src/components/CommentForm.jsx
Normal 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;
|
42
src/components/CommentsModal.jsx
Normal file
42
src/components/CommentsModal.jsx
Normal 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
68
src/components/Post.jsx
Normal 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
3
src/index.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
10
src/main.jsx
Normal file
10
src/main.jsx
Normal 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
12
tailwind.config.js
Normal 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
7
vite.config.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
Loading…
Reference in a new issue