Social networking platforms are an important part of our lives, connecting people from all over the world. Building such platforms requires a robust technology stack that can handle a large number of users and real-time interactions. This article will guide you through the process of building a social networking platform using the MERN stack.
The social networking platform will allow users to register, log in, create posts, like and comment on posts, and follow other users. The platform will have a user-friendly interface and real-time updates
Project Preview: Social Networking Platform with MERN Stack PrerequisitesFunctionalities- User Authentication: Registration, login, and logout
- User Profiles: View and edit profile details
- Posts: Create, edit, delete posts
- Interactions: Like and comment on posts
- Follow System: Follow and unfollow users
- Real-Time Updates: Real-time notifications for new likes, comments, and follows
Steps to Create Social Networking Platform Step 1: Create a new directory for the project.mkdir backend
cd backend Step 2 : Initialize a new Node.js project.
npm init -y Step 3 : Install required dependencies
npm install bcryptjs cors dotenv express jsonwebtoken mongoose multer Step 4: Create and Open your .env file and paste below code
MONGO_URI=mongodb://127.0.0.1:27017/socialnetwork
JWT_SECRET=gfg@socialNetworkProject
PORT=5000 Dependencies"dependencies": {
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.4.4",
"multer": "^1.4.5-lts.1"
} Project Structure (Backend) project structure (backend) Example: Backend code for Social Networking Platform
JavaScript
//backend/server.js
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
const cors = require('cors');
const path = require('path');
const authMiddleware = require('./middleware/authMiddleware');
const authRoutes = require('./routes/auth');
const userRoutes = require('./routes/users');
const postRoutes = require('./routes/posts');
const commentRoutes = require('./routes/comments');
dotenv.config();
const app = express();
app.use(express.json());
app.use(cors({
origin: '*'
}))
mongoose.connect(process.env.MONGO_URI, {
}).then(() => console.log('MongoDB connected'))
.catch(err => console.error(err));
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
app.use('/api/auth', authRoutes);
app.use('/api/users', authMiddleware, userRoutes);
app.use('/api/posts', authMiddleware, postRoutes);
app.use('/api/post/comments', authMiddleware, commentRoutes);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
JavaScript
//backend/controllers/authController.js
const User = require('../models/User');
const jwt = require('jsonwebtoken');
const generateToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '30d' });
};
exports.registerUser = async (req, res) => {
const { username, email, password } = req.body;
try {
const user = new User({ username, email, password });
await user.save();
res.status(201).json({ message: 'Register successfully...please log-in ' });
} catch (error) {
res.status(400).json({ message: error.message });
}
};
exports.loginUser = async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (user && await user.matchPassword(password)) {
res.json({ token: generateToken(user._id), user_id: user._id });
} else {
res.status(401).json({ message: 'Invalid email or password' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
};
JavaScript
//backend/controllers/commentController.js
const Comment = require("../models/Comment");
const Post = require("../models/Post");
const User = require("../models/User");
exports.createComment = async (req, res) => {
try {
const { postId, content } = req.body;
const user_data = await User.findById(req.user.id);
const comment = new Comment({
user: req.user.id,
username: user_data.username,
post: postId,
content,
});
await comment.save();
const post = await Post.findById(postId);
post.comments.push(comment._id);
await post.save();
res.status(201).json(comment);
} catch (error) {
res.status(400).json({ message: error.message });
}
};
exports.getComments = async (req, res) => {
try {
const comments = await Comment.find({ post: req.params.postId })
.populate("user", "username")
.sort({ createdAt: -1 });
res.json(comments);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.deleteComment = async (req, res) => {
try {
const comment = await Comment.findById(req.params.id);
if (comment.user.toString() === req.user.id) {
await comment.remove();
res.json({ message: "Comment removed" });
} else {
res.status(401).json({ message: "Not authorized" });
}
} catch (error) {
res.status(404).json({ message: "Comment not found" });
}
};
JavaScript
//backend/controllers/postController.js
const Post = require('../models/Post');
const User = require('../models/User');
const multer = require('multer');
const path = require('path');
// Set up multer for file uploads
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/');
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname));
}
});
const upload = multer({ storage: storage }).single('image');
exports.createPost = async (req, res) => {
upload(req, res, async function (err) {
if (err instanceof multer.MulterError) {
return res.status(500).json({ message: err.message });
} else if (err) {
return res.status(500).json({ message: err.message });
}
let Userdata = await User.findById(req.user.id);
try {
const post = new Post({
user: req.user.id,
username: Userdata.username,
content: req.body.content,
image: req.file ? req.file.filename : null
});
await post.save();
res.status(201).json(post);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
};
exports.getPosts = async (req, res) => {
try {
const posts = await Post.find()
.populate('user', 'username')
.populate('comments')
.sort({ createdAt: -1 });
res.json(posts);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.getUserPost = async (req, res) => {
const userId = req.user.id;
try {
const posts = await Post.find({ user: userId })
.populate('user', 'username')
.populate('comments')
.sort({ createdAt: -1 });
res.json(posts);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.updatePost = async (req, res) => {
try {
const post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({ message: 'Post not found' });
}
if (post.user.toString() !== req.user.id) {
return res.status(401).json({ message: 'Not authorized' });
}
post.content = req.body.content;
await post.save();
res.json(post);
} catch (error) {
res.status(500).json({ message: 'Internal Server Error' });
}
};
exports.deletePost = async (req, res) => {
try {
const post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({ message: 'Post not found' });
}
if (post.user.toString() !== req.user.id) {
return res.status(401).json({ message: 'Not authorized' });
}
await post.deleteOne();
res.json({ message: 'Post removed' });
} catch (error) {
res.status(500).json({ message: 'Internal Server Error' });
}
};
exports.likePost = async (req, res) => {
const postId = req.params.id;
const userId = req.user.id;
try {
const post = await Post.findById(postId);
if (!post) {
return res.status(404).json({ message: 'Post not found' });
}
if (post.likedBy.includes(userId)) {
return res.status(400).json({ message: 'You have already liked this post' });
}
post.likes++;
post.likedBy.push(userId);
await post.save();
res.status(200).json({ message: 'Post liked successfully', post });
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Internal Server Error' });
}
};
JavaScript
//backend/controllers/userController.js
const User = require("../models/User");
exports.getUserProfile = async (req, res) => {
try {
const user = await User.findById(req.user.id).select("-password");
console.log(user);
res.json(user);
} catch (error) {
res.status(404).json({ message: "User not found" });
}
};
exports.getAllUserProfile = async (req, res) => {
try {
const users = await User.find({ _id: { $ne: req.user.id } }).select(
"-password"
);
console.log("users");
res.json(users);
} catch (error) {
res.status(500).json({ message: "Server error" });
}
};
exports.updateUserProfile = async (req, res) => {
const { username, email } = req.body;
try {
const user = await User.findById(req.user.id);
if (user) {
user.username = username || user.username;
user.email = email || user.email;
await user.save();
res.json(user);
} else {
res.status(404).json({ message: "User not found" });
}
} catch (error) {
res.status(400).json({ message: error.message });
}
};
JavaScript
//backend/middleware/authMiddleware.js
const jwt = require("jsonwebtoken");
const User = require("../models/User");
const authMiddleware = async (req, res, next) => {
const token = req.header("Authorization")?.replace("Bearer ", "");
if (!token) {
return res.status(401).json({ message: "No token, authorization denied" });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select("-password");
next();
} catch (error) {
res.status(401).json({ message: "Token is not valid" });
}
};
module.exports = authMiddleware;
JavaScript
//backend/models/Comment.js
const mongoose = require("mongoose");
const CommentSchema = new mongoose.Schema(
{
user: { type: mongoose.Schema.Types.ObjectId, required: true },
post: { type: mongoose.Schema.Types.ObjectId, required: true },
username: { type: String, required: true },
content: { type: String, required: true },
},
{ timestamps: true }
);
const Comment = mongoose.model("Comment", CommentSchema);
module.exports = Comment;
JavaScript
//backend/models/Post.js
const mongoose = require("mongoose");
const postSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
username: {
type: String,
required: true,
},
content: {
type: String,
required: true,
},
likes: {
type: Number,
default: 0,
},
likedBy: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
],
image: { type: String },
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment",
},
],
createdAt: {
type: Date,
default: Date.now,
},
});
const Post = mongoose.model("Post", postSchema);
module.exports = Post;
JavaScript
//backend/models/User.js
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const UserSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
token: { type: String, required: true },
});
UserSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
return next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
UserSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};
const User = mongoose.model("User", UserSchema);
module.exports = User;
JavaScript
//backend/routes/auth.js
const express = require('express');
const { registerUser, loginUser } = require('../controllers/authController');
const router = express.Router();
router.post('/register', registerUser);
router.post('/login', loginUser);
module.exports = router;
JavaScript
//backend/routes/comments.js
const express = require("express");
const {
createComment,
getComments,
deleteComment,
} = require("../controllers/commentController");
const authMiddleware = require("../middleware/authMiddleware");
const router = express.Router();
router.post("/:postId/comment", authMiddleware, createComment);
router.get("/:postId", getComments);
router.delete("/:id", authMiddleware, deleteComment);
module.exports = router;
JavaScript
//backend/routes/posts.js
const express = require("express");
const {
createPost,
getPosts,
getUserPost,
updatePost,
deletePost,
likePost,
} = require("../controllers/postController");
const authMiddleware = require("../middleware/authMiddleware");
const router = express.Router();
router.post("/", authMiddleware, createPost);
router.get("/", getPosts);
router.get("/userpost", getUserPost);
router.put("/:id", authMiddleware, updatePost);
router.delete("/:id", authMiddleware, deletePost);
router.post("/:id/like", authMiddleware, likePost);
module.exports = router;
JavaScript
//backend/routes/users.js
const express = require("express");
const {
getUserProfile,
updateUserProfile,
getAllUserProfile,
} = require("../controllers/userController");
const authMiddleware = require("../middleware/authMiddleware");
const router = express.Router();
router.get("/profile", authMiddleware, getUserProfile);
router.get("/allusers/profile", authMiddleware, getAllUserProfile);
router.put("/profile", authMiddleware, updateUserProfile);
module.exports = router;
Frontend Development
- Initialize a new React project
- Set up Redux for state management
- Create components for registration, login, user profile, post feed, and post details
- Implement API calls using Axios
Step 1 : Initialize a new React project
npx create-react-app frontend
cd frontend Step 2 : Install required dependencies
npm install react-redux redux redux-thunk redux-devtools-extension @headlessui/react @heroicons/react axios date-fns –legacy-peer-deps npm install -D tailwindcss npx tailwindcss init
Dependencies"dependencies": {
"@headlessui/react": "^2.1.1",
"@heroicons/react": "^2.1.4",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.7.2",
"date-fns": "^3.6.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.1.2",
"react-router-dom": "^6.23.1",
"react-scripts": "5.0.1",
"redux": "^5.0.1",
"redux-devtools-extension": "^2.13.9",
"redux-thunk": "^3.1.0",
"web-vitals": "^2.1.4"
} Updated tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
Project Structure (Frontend) project structure (frontend) Integrate Frontend and Backend- Ensure the frontend communicates with the backend using REST APIs
- Handle user authentication and protected routes in React
Styling and UI Enhancements- Use CSS, Bootstrap, Tailwind and Material-UI for styling
- Create a responsive design for different screen sizes
Example: Complete code for frontend
CSS
/* frontend/src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
JavaScript
//frontend/src/App.js
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Login from "./components/Login";
import Register from "./components/Register";
import Profile from "./components/Profile";
import Feed from "./components/Feed";
import PrivateRoute from "./components/PrivateRoute";
import Nav from "./components/Nav";
import Sidebarprofile from "./components/Sidebarprofile";
import AllProfileShow from "./components/AllProfileShow";
import ErrorNotification from "./components/ErrorNotification";
const FeedPage = () => {
return (
<>
<Nav />
<div className=" grid grid-cols-1 sm:flex flex-row p-4 justify-between ">
<Sidebarprofile />
<Feed />
<AllProfileShow />
</div>
</>
);
};
export default function App() {
return (
<>
<ErrorNotification />
<div className="bg-[#f4f2ee]">
<Router>
<Routes>
{/* Public routes */}
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
{/* Private routes */}
<Route path="/" element={<PrivateRoute />}>
<Route path="/profile" element={<Profile />} />
<Route path="/feed" element={<FeedPage />} />
</Route>
</Routes>
</Router>
</div>
</>
);
}
JavaScript
//frontend/src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './redux/store';
import App from './App';
import './index.css';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
JavaScript
//frontend/src/redux/actions/authActions.js
import axios from "axios";
import { setError, clearError } from "./errorActions";
export const registerUser = (userData) => async (dispatch) => {
try {
dispatch({ type: "USER_REGISTER_REQUEST" });
const config = {
headers: { "Content-Type": "application/json" },
};
const { data } = await axios.post(
"http://localhost:5000/api/auth/register",
userData,
config
);
dispatch(setError(data.message));
} catch (error) {
dispatch({
type: "USER_REGISTER_FAIL",
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
export const loginUser = (userData) => async (dispatch) => {
try {
dispatch({ type: "USER_LOGIN_REQUEST" });
const config = {
headers: { "Content-Type": "application/json" },
};
const { data } = await axios.post(
"http://localhost:5000/api/auth/login",
userData,
config
);
dispatch({ type: "USER_LOGIN_SUCCESS", payload: data });
localStorage.setItem("userInfo", JSON.stringify(data));
dispatch(clearError());
} catch (error) {
dispatch({
type: "USER_LOGIN_FAIL",
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
dispatch(setError(error.response?.data?.message || error.message));
}
};
export const logoutUser = () => (dispatch) => {
localStorage.removeItem("userInfo");
dispatch({ type: "USER_LOGOUT" });
};
JavaScript
//frontend/src/redux/actions/errorActions.js
import { SET_ERROR, CLEAR_ERROR } from './types';
export const setError = (error) => ({
type: SET_ERROR,
payload: error,
});
export const clearError = () => ({
type: CLEAR_ERROR,
});
JavaScript
//frontend/src/redux/actions/postActions.js
import axios from "axios";
import { setError, clearError } from "./errorActions";
// Action Types
export const GET_POSTS = "GET_POSTS";
export const CREATE_POST = "CREATE_POST";
export const DELETE_POST = "DELETE_POST";
export const LIKE_POST = "LIKE_POST";
export const COMMENT_ON_POST = "COMMENT_ON_POST";
const getTokenConfig = () => {
const tokenObj = localStorage.getItem("userInfo");
return {
headers: {
Authorization: `Bearer ${JSON.parse(tokenObj).token}`,
},
};
};
export const getPosts = () => async (dispatch) => {
try {
const response = await axios.get(
"http://localhost:5000/api/posts",
getTokenConfig()
);
dispatch({ type: GET_POSTS, payload: response.data });
dispatch(clearError());
} catch (error) {
console.error("Error fetching posts:", error);
dispatch(setError(error.response?.data?.message || error.message));
}
};
export const getUserPost = () => async (dispatch) => {
try {
const response = await axios.get(
"http://localhost:5000/api/posts/userpost",
getTokenConfig()
);
dispatch({ type: GET_POSTS, payload: response.data });
dispatch(clearError());
} catch (error) {
console.error("Error fetching posts:", error);
dispatch(setError(error.response?.data?.message || error.message));
}
};
export const createPost = (formData) => async (dispatch) => {
try {
const config = {
headers: {
"Content-Type": "multipart/form-data",
Authorization: `Bearer ${JSON.parse(localStorage.getItem("userInfo")).token
}`,
},
};
const response = await axios.post(
"http://localhost:5000/api/posts",
formData,
config
);
dispatch({ type: CREATE_POST, payload: response.data });
dispatch(setError("Post added!!!!"));
} catch (error) {
console.error("Error creating post:", error);
dispatch(setError(error.response?.data?.message || error.message));
}
};
export const deletePost = (id) => async (dispatch) => {
try {
const response = await axios.delete(
`http://localhost:5000/api/posts/${id}`,
getTokenConfig()
);
dispatch({ type: DELETE_POST, payload: id });
dispatch(setError(response.data.message));
} catch (error) {
console.error("Error deleting post:", error);
dispatch(setError(error.response?.data?.message || error.message));
}
};
export const likePost = (id) => async (dispatch) => {
try {
const response = await axios.post(
`http://localhost:5000/api/posts/${id}/like`,
{},
getTokenConfig()
);
dispatch({ type: LIKE_POST, payload: response.data });
console.log(response.data);
dispatch(setError(response.data.message));
} catch (error) {
console.error("Error liking post:", error);
dispatch(setError(error.response?.data?.message || error.message));
}
};
export const commentOnPost = (postId, content) => async (dispatch) => {
try {
const response = await axios.post(
`http://localhost:5000/api/post/comments/${postId}/comment`,
{ postId, content },
getTokenConfig()
);
dispatch({ type: COMMENT_ON_POST, payload: response.data });
dispatch(setError("Comment added!!!"));
} catch (error) {
console.error("Error commenting on post:", error);
dispatch(setError(error.response?.data?.message || error.message));
}
};
JavaScript
//frontend/src/redux/actions/types.js
// Post action types
export const GET_POSTS = 'GET_POSTS';
export const CREATE_POST = 'CREATE_POST';
export const DELETE_POST = 'DELETE_POST';
export const LIKE_POST = 'LIKE_POST';
export const COMMENT_ON_POST = 'COMMENT_ON_POST';
// User action types
export const FETCHING_USER = 'FETCHING_USER';
export const FETCH_USER_PROFILE_FAIL='FETCH_USER_PROFILE_FAIL';
export const FETCH_USER_PROFILE_SUCCESS='FETCH_USER_PROFILE_SUCCESS';
export const FETCH_USER_PROFILE_REQUEST='FETCH_USER_PROFILE_REQUEST';
export const UPDATE_USER_PROFILE = 'UPDATE_USER_PROFILE';
export const UPDATE_USER_PROFILE_SUCCESS='UPDATE_USER_PROFILE_SUCCESS';
export const USER_REGISTER_FAIL='USER_REGISTER_FAIL';
export const USER_REGISTER_SUCCESS='USER_REGISTER_SUCCESS';
export const USER_REGISTER_REQUEST='USER_REGISTER_REQUEST';
export const USER_LOGIN_SUCCESS='USER_LOGIN_SUCCESS';
export const USER_LOGIN_FAIL='USER_LOGIN_FAIL';
export const USER_LOGIN_REQUEST='USER_LOGIN_REQUEST';
export const FETCHING_ALL_PROFILE_REQUEST='FETCHING_ALL_PROFILE_REQUEST';
export const FETCHING_ALL_PROFILE_FAIL='FETCHING_ALL_PROFILE_FAIL';
export const FETCHING_ALL_PROFILE_SUCCESS='FETCHING_ALL_PROFILE_SUCCESS';
export const FETCHING_ALL_PROFILE='FETCHING_ALL_PROFILE';
export const SET_ERROR = 'SET_ERROR';
export const CLEAR_ERROR = 'CLEAR_ERROR';
JavaScript
//frontend/src/redux/actions/userActions.js
import axios from "axios";
import {
FETCH_USER_PROFILE_REQUEST,
FETCH_USER_PROFILE_SUCCESS,
FETCH_USER_PROFILE_FAIL,
UPDATE_USER_PROFILE_SUCCESS,
FETCHING_ALL_PROFILE_REQUEST,
FETCHING_ALL_PROFILE_FAIL,
FETCHING_ALL_PROFILE_SUCCESS,
} from "./types";
// Action to fetch user profile
export const fetchUserProfile = () => async (dispatch) => {
const token = JSON.parse(localStorage.getItem("userInfo"))?.token;
try {
dispatch({ type: FETCH_USER_PROFILE_REQUEST });
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const res = await axios.get(
"http://localhost:5000/api/users/profile",
config
);
dispatch({
type: FETCH_USER_PROFILE_SUCCESS,
payload: res.data,
});
} catch (err) {
dispatch({
type: FETCH_USER_PROFILE_FAIL,
payload: err.response?.data?.message || err.message,
});
}
};
// Action to update user profile
export const updateUserProfile = (userData) => async (dispatch) => {
const token = JSON.parse(localStorage.getItem("userInfo"))?.token;
try {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const res = await axios.put(
"http://localhost:5000/api/users/profile",
userData,
config
);
dispatch({
type: UPDATE_USER_PROFILE_SUCCESS,
payload: res.data,
});
} catch (err) {
console.error(err);
}
};
//Action to fetch all user profile
export const fetchAllUserProfile = () => async (dispatch) => {
const token = JSON.parse(localStorage.getItem("userInfo"))?.token;
try {
dispatch({ type: FETCHING_ALL_PROFILE_REQUEST });
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const res = await axios.get(
"http://localhost:5000/api/users/allusers/profile",
config
);
console.log(res);
dispatch({
type: FETCHING_ALL_PROFILE_SUCCESS,
payload: res.data,
});
} catch (err) {
dispatch({
type: FETCHING_ALL_PROFILE_FAIL,
payload: err.response?.data?.message || err.message,
});
}
};
JavaScript
//frontend/src/redux/reducers/allProfileReducer.js
import {
FETCHING_ALL_PROFILE_REQUEST,
FETCHING_ALL_PROFILE_SUCCESS,
FETCHING_ALL_PROFILE_FAIL,
} from "../actions/types";
const initialState = {
profile: null,
loading: false,
error: null,
};
const userProfileReducer = (state = initialState, action) => {
switch (action.type) {
case FETCHING_ALL_PROFILE_REQUEST:
return { ...state, loading: true, error: null };
case FETCHING_ALL_PROFILE_SUCCESS:
console.log(action.payload);
return { ...state, loading: false, profile: action.payload };
case FETCHING_ALL_PROFILE_FAIL:
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
export default userProfileReducer;
JavaScript
//frontend/src/redux/reducers/authReducer.js
import {
USER_REGISTER_REQUEST,
USER_REGISTER_SUCCESS,
USER_REGISTER_FAIL,
FETCH_USER_PROFILE_REQUEST,
FETCH_USER_PROFILE_SUCCESS,
FETCH_USER_PROFILE_FAIL,
UPDATE_USER_PROFILE_SUCCESS,
USER_LOGIN_REQUEST,
USER_LOGIN_SUCCESS,
} from "../actions/types";
const initialState = {
userInfo: JSON.parse(localStorage.getItem("userInfo")) || null,
loading: false,
error: null,
};
const authReducer = (state = initialState, action) => {
switch (action.type) {
case USER_REGISTER_REQUEST:
case FETCH_USER_PROFILE_REQUEST:
return { ...state, loading: true };
case FETCH_USER_PROFILE_SUCCESS:
return { ...state, loading: false, userInfo: action.payload };
case USER_REGISTER_FAIL:
case FETCH_USER_PROFILE_FAIL:
return { ...state, loading: false, error: action.payload };
case UPDATE_USER_PROFILE_SUCCESS:
return { ...state, userInfo: action.payload };
case USER_LOGIN_SUCCESS:
return { ...state, userInfo: action.payload };
case "USER_LOGOUT":
return {};
default:
return state;
}
};
export default authReducer;
JavaScript
//frontend/src/redux/reducers/errorReducer.js
import { SET_ERROR, CLEAR_ERROR } from "../actions/types";
const initialState = null;
const errorReducer = (state = initialState, action) => {
switch (action.type) {
case SET_ERROR:
return action.payload;
case CLEAR_ERROR:
return null;
default:
return state;
}
};
export default errorReducer;
JavaScript
// frontend/src/redux/reducers/index.js
import { combineReducers } from "redux";
import postReducer from "./postReducer";
import authReducer from "./authReducer";
import allProfileReducer from "./allProfileReducer";
import errorReducer from "./errorReducer";
const rootReducer = combineReducers({
posts: postReducer,
auth: authReducer,
allUserProfile: allProfileReducer,
error: errorReducer,
});
export default rootReducer;
JavaScript
// frontend/src/redux/reducers/postReducer.js
import {
GET_POSTS,
CREATE_POST,
DELETE_POST,
LIKE_POST,
COMMENT_ON_POST,
} from "../actions/postActions";
const initialState = [];
const postReducer = (state = initialState, action) => {
switch (action.type) {
case GET_POSTS:
return action.payload;
case CREATE_POST:
return [action.payload, ...state];
case DELETE_POST:
return state.filter((post) => post._id !== action.payload);
case LIKE_POST:
return state.map((post) =>
post._id === action.payload._id ? action.payload : post
);
case COMMENT_ON_POST:
return state.map((post) =>
post._id === action.payload._id ? action.payload : post
);
default:
return state;
}
};
export default postReducer;
JavaScript
//frontend/src/redux/store.js
import { createStore, applyMiddleware, compose } from "redux";
import { thunk } from "redux-thunk";
import rootReducer from "./reducers";
const middleware = [thunk];
const composeEnhancers =
(typeof window !== "undefined" &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
compose;
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(...middleware))
);
export default store;
JavaScript
//frontend/src/components/AllProfileShow.js
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchAllUserProfile } from "../redux/actions/userActions";
const AllProfileShow = () => {
const dispatch = useDispatch();
const userProfile = useSelector((state) => state.allUserProfile.profile);
useEffect(() => {
dispatch(fetchAllUserProfile());
}, [dispatch]);
if (!Array.isArray(userProfile)) {
return <div className="mt-20">No connection available</div>;
}
return (
<div className="bg-white my-20 pb-6 rounded-lg shadow-sm grid
grid-cols-2 w-[30%] h-[15%] overflow-y-scroll items-baseline ">
{userProfile.map((profiles) => (
<div>
<UsersCard key={profiles._id} userName={profiles.username} />
</div>
))}
</div>
);
};
const UsersCard = ({ key, userName }) => {
return (
<div className="mt-4">
<div className=" shadow h-24 w-24 mx-auto border-white
rounded-full overflow-hidden border-4">
<img
className="object-cover w-full h-full"
src="https://images.unsplash.com/photo-1438761681033-
6461ffad8d80?ixlib=rb-1.2.
1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=200&q=80"
/>
</div>
<div className="flex flex-col justify-center gap-3">
<h1 className="text-lg text-center font-semibold">
<p className="uppercase ">{userName}</p>
</h1>
<p className="text-sm text-gray-600 text-center">
<a
class="px-6 py-2 min-w-[120px] text-center text-white
bg-violet-600 border border-violet-600
rounded active:text-violet-500 hover:bg-transparent
hover:text-violet-600 focus:outline-none focus:ring"
href="#"
>
Follow
</a>
</p>
</div>
</div>
);
};
export default AllProfileShow;
JavaScript
//frontend/src/components/ErrorNotification.js
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { clearError } from "../redux/actions/errorActions";
const ErrorNotification = () => {
const error = useSelector((state) => state.error);
const dispatch = useDispatch();
if (!error) return null;
return (
<>
<div class="bg-indigo-900 text-center py-4 lg:px-4 fixed bottom-10 z-30 right-0">
<div
class="p-2 bg-indigo-800 items-center text-indigo-100 leading-none
lg:rounded-full flex lg:inline-flex"
role="alert"
>
<span class="flex rounded-full bg-indigo-500 uppercase
px-2 py-1 text-xs font-bold mr-3">
New
</span>
<span class="font-semibold mr-2 text-left flex-auto">{error}</span>
<svg
onClick={() => dispatch(clearError())}
class="fill-current opacity-75 h-4 w-4 cursor-pointer"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path
d="M12.95 10.707l.707-.707L8 4.343 6.586 5.757
10.828 10l-4.242 4.243L8 15.657l4.95-4.95z"
/>
</svg>
</div>
</div>
</>
);
};
export default ErrorNotification;
JavaScript
//frontend/src/components/Feed.js
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
import { formatDistanceToNow } from "date-fns";
import {
getPosts,
createPost,
deletePost,
likePost,
commentOnPost,
} from "../redux/actions/postActions";
const Feed = () => {
const dispatch = useDispatch();
const posts = useSelector((state) => state.posts) || [];
useEffect(() => {
dispatch(getPosts());
}, [dispatch]);
const handleCreatePost = (formData) => {
dispatch(createPost(formData));
};
const handleDeletePost = (id) => {
dispatch(deletePost(id));
};
const handleLikePost = (id) => {
dispatch(likePost(id));
};
const handleCommentOnPost = (postId, content) => {
dispatch(commentOnPost(postId, content));
};
return (
<div>
<PostForm onSubmit={handleCreatePost} />
<PostList
posts={posts}
onDeletePost={handleDeletePost}
onLikePost={handleLikePost}
onCommentOnPost={handleCommentOnPost}
/>
</div>
);
};
const PostForm = ({ onSubmit }) => {
const [content, setContent] = useState("");
const [image, setImage] = useState(null);
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("content", content);
if (image) {
formData.append("image", image);
}
onSubmit(formData);
setContent("");
setImage(null);
};
return (
<>
<div className="flex flex-col justify-center font-[sans-serif] p-4 mt-14">
<div class="min-w-[40rem] w-full mx-auto border border-gray-300
rounded-2xl p-4 bg-white text-black shadow-lg">
<form onSubmit={handleSubmit}>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="What's on your mind?"
style={{ resize: "none" }}
required
class="resize-none rounded-md w-full p-2"
></textarea>
<div className="flex flex-row justify-between">
<img
onClick={() => {
document.getElementById("post_media").click();
}}
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCA
YAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFTElEQVR4nO1YaVOTVx
TmR1jrvleJC4Iii4ILiIIoVlAQAQEXUEjasYvaTqtCsVrDvgQChDXsJLhXrd1EaxfwQ/
ux34SxmuTN8r7wRZ15OufesIxMKQYl2ObMnHlzz53MfZ4nzzn3nbi5ucIVrvjvxmZ5V3
iwvKs3WNGN0TJI3oWN6b9gw9GfEJjaiXUpP8D/0LfwO3gLPslfwzvxKlbtvwyv+A6sjG
vHitgWLItpxNLoerjvrsHiKA0W7SrHwp2lmB9RjLnbCzE7PA+zwrJ7poWcD3OYQLC8q+
ffwG9K/xUb0u5j/ZG7CEj9EWsPfwe/g9/A98BNrEm6jtWJV+CVcBGecTp47GvF8r1NWBq
jhWxPLZZEVeGdyAosfFeNBREqzNtRhDnh+Zi1LQczQy9g+pZzmLo566HjBEZV/TdsTPsZ64/
eQ2DqnUHVfQ8MV/0SPOP1WLmvDStim7EspgGy6DqueiRXfcGg6gWYvS0XM8OUmLH1PKZtOY
upmzMxJegMXjGBLmxidrmPQFI9ZUh1n+QbWJN0DavJLgncLh6xLVz1aC3cB1TfRaqXYX5E
CebtKMQcbhfMsKv+dkgW3grOwJSg0yxfGYHhqgeQ6oe/h/+h24N28U68glUJXHUPUn2vX
fU9dViyuxqLIyuxiOyyk9tl7oDqoUpMJ9VDBlTnwB0m4K4zhMv0T3plHQZMhnTveNI
j0xvG3szsC5MAuGw4Cf2TsTezs8HK/iEdIvBAeIqB6DY9fe0gH4xynkMEXozXTeDF
cBHoNg39pF0TYKHuUc77fzaxbBKlm4tAx+tTd1mHAR56Azx1Rqwall46Izz0RrY/
Ke+BlToj1rQb4d9mQmCrCX+Yng2e97vxGTa08jrtO0RgtLk8XuC+7UYGLqjFhC3NA
sKahBHnbW8UsK1JwNZmYXIQICt4txsR0GpCcIuJgY5oFBClFRCtNY84L7bejJh6M9t3+
j2wXG+AT7uR2YIUjWgUsEdrxr46MxJrzThYY8afj4csRJ9Tqi2snlRrdu4UIvBkmU0t
JmaJyAaBqUvACGR6lQXvayw4prHiw8qh/EBjZXV5lcV5BJZ2GFijbrSDJzvE15lxqI
YDP6ax4ONKKz6psOKzcitOldsGk9afVlhxvNLqPAI0Dte3cr8T+IQ6rvp7Ggs+quQA
T6ttyFLbcK7Mhq9KKUX2pHVWmQ1n1DbnEFihN2BtG58yuxoExNWZcdgO/nilFZ+Xc+
AEOFclokAlorBERFEJfxaU8PqFUtE594C33fc0DvfW80ZVVHHwp8pt+LLMhpxSDlZVLOL
xo+eD5/316DnURbxO+xM+RpcPU5+adn+tGWlVFtac5O2zBF7F1S4rEqEplEacV1MgsToRmX
AC9BpAIzO8UWCznJqWJgo1a6baBmUpB0/gqgslaPNHEmjO4/XqAmni7wGaPHRZ7WzgU+dI
NVeffE/Nma8SUVYsoqpQQkO+hNY8CabeIQvRZ11uH6vTvkMExpN+bUaENJuYfeiiktu9n2
FXv7hEREWRiPoCCS15EvS5fbic04er2f24nt2Pa9n9bK3P4SQm9G8VemUg/9ONu1srILnW
zCYP2ecLtY1NFpVd/cZ8Ce25Ei7l9DHgN5VDeV3Zz+ptudJL/K2iN4SNlwQ18Lo2E0Kb+Os
CTR/yP818at48lYjSYpF5uylPYla5kt2HG8p+3FL24/YF/qT1VaX0UJfTFzpmAq5wxThDcS
LzrvxkRqfbm4pHcTITlG6TJBQvi+fFL8hPZtwbrsBErxWvgECn4kTmHWetFeMl4OxQvDSBN
72JXeEKtzHH3yinOXsdJo8UAAAAAElFTkSuQmCC"
></img>
<input
type="file"
accept="image/*"
onChange={(e) => setImage(e.target.files[0])}
style={{ display: "none" }}
id="post_media"
/>
<button
type="submit"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full"
>
Post
</button>
</div>
</form>
</div>
</div>
</>
);
};
const PostList = ({ posts, onDeletePost, onLikePost, onCommentOnPost }) => {
if (!Array.isArray(posts)) {
return <div>No posts available</div>;
}
return (
<div>
{posts.map((post) => (
<Post
key={post._id}
post={post}
username={post.username}
onDelete={() => onDeletePost(post._id)}
onLike={() => onLikePost(post._id)}
onComment={(content) => onCommentOnPost(post._id, content)}
/>
))}
</div>
);
};
const Post = ({ post, username, onDelete, onLike, onComment }) => {
const [commentContent, setCommentContent] = React.useState("");
const [isViewerOpen, setIsViewerOpen] = useState(false);
const [isShowCommentBox, setShowCommentBox] = useState(false);
const [comments, setComments] = useState(post.comments || []);
const handleCommentSubmit = (e) => {
e.preventDefault();
if (!commentContent.trim()) return;
// Update the local state immediately
const newComment = {
createdAt: new Date(),
content: commentContent,
username,
user: {
username: JSON.parse(localStorage.getItem("userInfo")).user_id,
},
};
setComments([...comments, newComment]);
// Dispatch the action to update the Redux store
onComment(commentContent);
setCommentContent("");
};
const handlePostClick = () => {
setIsViewerOpen(true);
};
const handleCloseViewer = () => {
setIsViewerOpen(false);
};
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
return (
<div>
<div className="flex flex-col justify-center font-[sans-serif] p-4">
<div class="min-w-[40rem] w-full mx-auto border border-gray-300 rounded-2xl p-4 bg-white text-black shadow-lg">
<div className="grid grid-cols-2 gap-4 ">
<div className="flex flex-row justify-stat gap-4 ">
<div className="rounded-lg h-12 w-12 flex flex-row justify-center font-bold">
<img
className="object-cover w-full h-full"
src="https://media.geeksforgeeks.org/wp-content/
uploads/20240725110755/user-profile_5675059.png"
/>
</div>
<div>
<p className="uppercase ">{post.user.username || username}</p>
<p>
{formatDistanceToNow(new Date(post.createdAt), {
addSuffix: true,
})}
</p>
</div>
</div>
<div className="cursor-pointer flex flex-row justify-end">
<Menu as="div" className="relative ml-3">
<div>
<MenuButton className="relative flex rounded-full text-sm focus:outline-none focus:ring-2
focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800">
<span className="absolute -inset-1.5" />
<span className="sr-only">Open user menu</span>
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAA
ADgdz34AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAQElEQVR4nGNgGAVUBEcZGBj+Q/Fh
BhqA/2h46FlwGMnwQ7SwYBQQBEdHkykhcHg0mQ44ODqaTAmBw6PJlIEWAACAfy0E9A9
56gAAAABJRU5ErkJggg==" />
</MenuButton>
</div>
<MenuItems
transition
className="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md
bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 transition
focus:outline-none data-[closed]:scale-95 data-[closed]:transform
data-[closed]:opacity-0 data-[enter]:duration-100 data-[leave]:duration-75
data-[enter]:ease-out data-[leave]:ease-in"
>
{JSON.parse(localStorage.getItem("userInfo"))?.user_id ===
post.user._id ? (
<MenuItem>
{({ focus }) => (
<a
href="#"
className={classNames(
focus ? "bg-gray-100" : "",
"block px-4 py-2 text-sm"
)}
onClick={onDelete}
>
Delete
</a>
)}
</MenuItem>
) : null}
</MenuItems>
</Menu>
</div>
</div>
<div className="mt-2">
<hr></hr>
</div>
<p className=" p-2 overflow-hidden text-nowrap text-ellipsis
whitespace-break-spaces max-w-[100%]">
{post.content}
</p>
<div className="flex justify-center">
{post.image ? (
<img
onClick={handlePostClick}
src={`http://localhost:5000/uploads/${post.image}`}
alt="Post"
className="object-cover h-[220px] w-[220px]"
/>
) : null}
</div>
<div className="mt-4 mb-4">
<hr></hr>
</div>
<div
className="flex flex-row justify-between p-4 bg-gray-100 rounded-md"
style={{ cursor: "pointer" }}
>
<div className="flex flex-row justify-between items-center ">
<div>
<img
onClick={onLike}
src="https://media.geeksforgeeks.org/wp-content/uploads/
20240725145948/like_10950198.png"
className="h-6 w-6" />
</div>
<div>
<p className="font-bold">{post.likes}</p>
</div>
</div>
<div className="flex flex-row justify-between items-center">
<div
onClick={() => {
setShowCommentBox(!isShowCommentBox);
}}
>
<img src="https://media.geeksforgeeks.org/wp-content/uploads/20240725150621
/bubble-chat_8345612.png" className="h-6 w-6" />
</div>
<div>
<p className="font-bold">{post.comments.length}</p>
</div>
</div>
<div>
<img src="https://media.geeksforgeeks.org/wp-content/uploads/20240725151348
/share_5949688.png" className="h-6 w-6" />
</div>
</div>
{isShowCommentBox ? (
<div className="max-h-[14rem] overflow-y-scroll bg-gray-100">
<div>
<hr></hr>
</div>
{comments.map((comment, index) => (
<>
<div key={index}>
<div className="p-2">
<div className="flex justify-start">
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIA
AAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAHV0lEQVR4nO2Ze
1BU5xnGt216ybRNp/2jgWnadKbpGKeddjLTNtPWEKntGDspE20YhYmKxmgSRG
BZFuQuuMhFuUREWA2CDIHdFVBuEXbhnEUuC7vLZbkJCJZzvsOaSgzgjMoiPp2z
sivXugcW8w/PzDN7zpnd731/vN/77tmDSLSuda1rXc5QE8M8rwS+JeQziBZ9c1
Ltuulu7Yu/GVe7aic0Lm0TGpdtomeh7u7u79RxzFaKsIkUR2ppjhCKsI9ojmD
W9ynC9tAcUdEc619LyIZlQSjRc5Ma13ceQ7hi1uP89TUDuMZxv6AJSaUJe3tO
0g6ZIqSVIsTHYDB8e6m1J9QuHTaQcbWrBU0vPe90AN3Y2AsUIWkUR6aEAiwG
YocpjntnEYjGZRtfCR5iQuMa4HSIulHmTX7rrBZgCavUQ0M/mhuL306gXv6e
0yG0hPjShH24BhA2D9aOjPxqYVyprvMlqbFro1MgaI4NW0OAJybsLXp0dF7S
IXqTJkRvuuepVAqagIukZdlDQpKpZUagaryGvIpylLTohPcNxzIahvmZLX5
wS9fbIXqT3+ogGOZPjjZ1eWc7Uk7JIPf7F0rFW1AZtBmF/v9ApM92ZORe4B
v7qWtonxzrlptoK/pCozly46kV+M9NJEUFoShwK2pD3FEXutilgVsQsncHZ
LJY5FVVLLvW5NQUbt+/PzvRSIRTQCiOPeZIJeKDP4RmieSXskbqjmS/ncuu
NTg+jr47d2a3GLlHmc2/XG01fkIRdvJpEKqGeij9tzgEYfPJcF8BzU/kq63
GUUcCJSXEQjO7nZR+bqgUv/n/KxLijtSsMwIan0xpbt16ccUgNGH7HAkUE+
yH2lB3lPi7QZsVBm12BC74/ROlR9wWQSgPP76WFCMROsUCVlYNM/NbR4PER
0mtDV4YtAMzgxqrH/bXwFiYiOKYAyiQeCIvcDsUUftQlSbFlQA3fBLxkTAQ
wtavDIRj/BwNcjb3PColf0Nlkh/6q3JQlhKByvRoTLSXWaHUWTKUp0WhOT8
FDwfUOLd/E9KPBQmsCJmqNpu/LxiE5tgcR4OUd3ZAfmgL2pWnrIl3FmdivO
2KvTq82y+dwcN+tfX49N7NSEk/KQiE5j3K/FE4CGGvObJ4RWcbjvv6IPXQu7
jdorImWiSTQFeQaoeY7lcj3NMDY60l1vNzYi8kHtkPxdVKYVUZZd9bs0YvadW
hqzIHRcc/tm4ba3/Mvs615Xq1/bjhYgJMxanIyMpY+4anOJZpNI9iYmoK/713
b+5tw/zFWQYV+RnouJy5KPnlfP3zHNRdTIaiXiuw4Ynwb3n+dtr05RhsajKPLh
sgN/88Oq5kL0p4tEGBYfXFRRXqqTqPNFmk4B6hCJEIByFEz1dheHICfV89vl1
YznXMCFKSZfaE+UZn6M/woOdza3+MNiqtUDaQ+KNilJs6hYNwzAHBIBTHKoQE
sSZWcs6e7IPeqxisvoC+yvP4ynjZfr23Jh/nigqETyyO38bsZuEghEQKDZSj
/AwjLU+SnuwoxxfNl+znX7ZX4ERclLWvhG8r9lGj2fxTQRAwef/4TqckZ6g/F
w3khqCAucUKUHnJ1m02F0SnyoQsNgrq4aEVVYMmpFtwNSyGXeppoxd43+6OEV
h+BqoUCdoLotGhSkNHcTr0eVEoT/gAxbqmlUFwvNkk4SDGXZM2kAft+xwKVD3Q
j7Py04g7vBsjxaGwULFWP6iLsb6OlYcjyX835PJ01NwYFAxSz7K/EwwybfSS20
DY3rS5TwsXOa+sFFLxYfgHBSJSUYKrzRUY056yg9h8lz6BhiYF4ksuIyAsFGGh
YuRXXHHopy/FkTrRSgSIvjHdtnMbb4pjlpxe/F81VBoASaYcJ1q6kGDsszvR0I
uLrQ2obi5HTXMZilooJBt65r0nvrUbwfJcBAWL8amywPnTaqG0hPycJuzdhYun
nklB3DXjvORW6lDZMVztv75MNViFyFmiCPloYYCYUwlOgeAdVVaDC6rCpSaVmfr
ipovTQGZhCmwB1EM3EJaR4TSQ2NomZH+aOb+5yZCFZll3kbNVNTDwXZpjNXyQUn
0rwvOLnAYia2zDJ+mJdgjjzYYZi9FrxmLc9aHTQewwhFzinyCGKy47DSRB34OEx
DjbhJoyDdf6WQxeGovR2020VqKA57ILC3PC5oDE8YkIrYKhd955dGKC9TFpHcf9
VfSs5LFhww93e3vXB2VkT58w9GKvVicYZO5n4nUmHBGLB/nnaKKvQzve+PPW/e
+/P+B9+uwMn4xQkMiSKvgeDZ94b+fOyr9v3PjrZ5Z4iK7r91J957sLr7/1+uub9n
h7074hobckJ9OnwgpUiNU04Hi9we44WoeI4gpIM7IefSyWTO4+8MGgl8fbwW+9
8soLomctqd5UJm3tnInu7v7Bcu/Z+uqrrjve+Mu/D/rsSTx08GDBQR+fin1eu6
r3e3kp9mz3CPR47bU/eHqKVvc/jtUqyNjzcoihY9OqF1rXuta1LtEK9T8tSk8+
5an1CAAAAABJRU5ErkJggg==" />
<div className="block ml-2">
<div>{comment.username}</div>
<div>
{formatDistanceToNow(new Date(comment.createdAt), {
addSuffix: true,
})}
</div>
</div>
</div>
<div>
<hr></hr>
</div>
<div className="flex justify-start">
<p key={comment._id}>{comment.content}</p>
</div>
</div>
</div>
</>
))}
<div class="w-full ">
<form onSubmit={handleCommentSubmit}>
<div className="">
<div>
<textarea
cols="30"
className="p-2 mt-5 w-full resize-none border-gray-400 border-2"
required
value={commentContent}
onChange={(e) => setCommentContent(e.target.value)}
placeholder="Write your comment...."
></textarea>
</div>
<div className="p-2 flex justify-end">
<button
type="submit"
class="bg-black hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full"
>
Add
</button>
</div>
</div>
</form>
</div>
</div>
) : null}
</div>
</div>
</div>
);
};
export default Feed;
JavaScript
//frontend/src/components/Login.js
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { loginUser } from "../redux/actions/authActions";
import { useNavigate } from "react-router-dom";
import { Link } from "react-router-dom";
const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const dispatch = useDispatch();
const auth = useSelector((state) => state.auth);
const navigate = useNavigate();
const handleSubmit = (e) => {
e.preventDefault();
dispatch(loginUser({ email, password }));
};
useEffect(() => {
if (auth.userInfo) {
// console.log(auth.userInfo)
navigate("/feed");
}
}, [auth.userInfo, navigate]);
return (
<div class="flex flex-col justify-center font-[sans-serif] sm:h-screen p-4
mt-14 bg-white">
<div class="max-w-md w-full mx-auto border border-gray-300 rounded-2xl p-8">
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
<h2 class="mt-10 text-center text-2xl font-bold leading-9
tracking-tight text-gray-900">
Sign in to your account
</h2>
</div>
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
<form class="space-y-6" onSubmit={handleSubmit}>
<div>
<label
for="email"
class="block text-sm font-medium leading-6 text-gray-900"
>
Email address
</label>
<div class="mt-2">
<input
id="email"
name="email"
type="email"
autocomplete="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
class="block w-full p-2 rounded-md border-0 py-1.5
text-gray-900
shadow-sm ring-1 ring-inset ring-gray-300
placeholder:text-gray-400
focus:ring-2 focus:ring-inset focus:ring-indigo-600
sm:text-sm sm:leading-6"
/>
</div>
</div>
<div>
<div class="flex items-center justify-between">
<label
for="password"
class="block text-sm font-medium leading-6 text-gray-900"
>
Password
</label>
<div class="text-sm">
<a
href="#"
class="font-semibold text-indigo-600
hover:text-indigo-500"
>
Forgot password?
</a>
</div>
</div>
<div class="mt-2">
<input
id="password"
name="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
autocomplete="current-password"
required
class="block w-full p-2 rounded-md border-0 py-1.5
text-gray-900
shadow-sm ring-1 ring-inset ring-gray-300
placeholder:text-gray-400
focus:ring-2 focus:ring-inset focus:ring-indigo-600
sm:text-sm sm:leading-6"
/>
</div>
</div>
<div>
<button
type="submit"
class="flex w-full justify-center rounded-md bg-indigo-600 px-3
py-1.5 text-sm font-semibold leading-6 text-white shadow-sm
hover:bg-indigo-500
focus-visible:outline focus-visible:outline-2
focus-visible:outline-offset-2
focus-visible:outline-indigo-600"
>
Sign in
</button>
</div>
<p class="text-gray-800 text-sm mt-6 text-center">
Don't have an account?{" "}
<Link
to="/register"
href="javascript:void(0);"
class="text-blue-600 font-semibold hover:underline ml-1"
>
Register here
</Link>
</p>
</form>
</div>
</div>
</div>
);
};
export default Login;
JavaScript
//frontend/src/components/Nav.js
import React from "react";
import { Link } from "react-router-dom";
import {
Disclosure,
DisclosureButton,
DisclosurePanel,
Menu,
MenuButton,
MenuItem,
MenuItems,
} from "@headlessui/react";
import { Bars3Icon, BellIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { useDispatch } from "react-redux";
import { logoutUser } from "../redux/actions/authActions";
import { useNavigate } from "react-router-dom";
export default function Nav() {
const dispatch = useDispatch();
const navigate = useNavigate();
const navigation = [
{ name: "Dashboard", href: "#", current: true },
];
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
const handleLogout = () => {
dispatch(logoutUser());
navigate('/login');
};
return (
<Disclosure as="nav" className="bg-gray-800 fixed w-full top-0 z-20">
{({ open }) => (
<>
<div className="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
<div className="relative flex h-16 items-center justify-between">
<div className="absolute inset-y-0 left-0 flex items-center
sm:hidden">
{/* Mobile menu button*/}
<DisclosureButton className="relative inline-flex items-center
justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700
hover:text-white focus:outline-none focus:ring-2
focus:ring-inset
focus:ring-white">
<span className="absolute -inset-0.5" />
<span className="sr-only">Open main menu</span>
{open ? (
<XMarkIcon className="block h-6 w-6"
aria-hidden="true" />
) : (
<Bars3Icon className="block h-6 w-6"
aria-hidden="true" />
)}
</DisclosureButton>
</div>
<div className="flex flex-1 items-center justify-center
sm:items-stretch sm:justify-start">
<div className="flex flex-shrink-0 items-center">
<img
className="h-8 w-auto"
src="https://media.geeksforgeeks.org/auth-
dashboard-uploads/
gfgFooterLogoDark.png"
alt="Your Company"
/>
</div>
<div className="hidden sm:ml-6 sm:block">
<div className="flex space-x-4">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current
? "bg-gray-900 text-white"
: "text-gray-300 hover:bg-gray-700
hover:text-white",
"rounded-md px-3 py-2 text-sm font-medium"
)}
aria-current={item.current ? "page" : undefined}
>
{item.name}
</a>
))}
</div>
</div>
</div>
<div className="absolute inset-y-0 right-0 flex items-center pr-2
sm:static sm:inset-auto sm:ml-6 sm:pr-0">
<button
type="button"
className="relative rounded-full bg-gray-800 p-1
text-gray-400
hover:text-white focus:outline-none focus:ring-2
focus:ring-white
focus:ring-offset-2 focus:ring-offset-gray-800"
>
<span className="absolute -inset-1.5" />
<span className="sr-only">View notifications</span>
<BellIcon className="h-6 w-6" aria-hidden="true" />
</button>
{/* Profile dropdown */}
<Menu as="div" className="relative ml-3">
<div>
<MenuButton className="relative flex rounded-full
bg-gray-800
text-sm focus:outline-none focus:ring-2 focus:ring-white
focus:ring-offset-2 focus:ring-offset-gray-800">
<span className="absolute -inset-1.5" />
<span className="sr-only">Open user menu</span>
<img
className="h-8 w-8 rounded-full"
src="https://media.geeksforgeeks.org/
wp-content/uploads/
20240725110755/user-profile_5675059.png"
alt=""
/>
</MenuButton>
</div>
<MenuItems
transition
className="absolute right-0 z-10 mt-2 w-48
origin-top-right rounded-md
bg-white py-1 shadow-lg ring-1 ring-black
ring-opacity-5 transition
focus:outline-none data-[closed]:scale-95
data-[closed]:transform
data-[closed]:opacity-0 data-[enter]:duration-100
data-[leave]:duration-75 data-[enter]:ease-out
data-[leave]:ease-in"
>
<MenuItem>
{({ focus }) => (
<Link
to='/profile'
className={classNames(
focus ? "bg-gray-100" : "",
"block px-4 py-2 text-sm text-gray-700"
)}
>
Your Profile
</Link>
)}
</MenuItem>
<MenuItem>
{({ focus }) => (
<a
href="#"
className={classNames(
focus ? "bg-gray-100" : "",
"block px-4 py-2 text-sm text-gray-700"
)}
>
Settings
</a>
)}
</MenuItem>
<MenuItem>
{({ focus }) => (
<a
href="#"
onClick={handleLogout}
className={classNames(
focus ? "bg-gray-100" : "",
"block px-4 py-2 text-sm text-gray-700"
)}
>
Sign out
</a>
)}
</MenuItem>
</MenuItems>
</Menu>
</div>
</div>
</div>
<DisclosurePanel className="sm:hidden">
<div className="space-y-1 px-2 pb-3 pt-2">
{navigation.map((item) => (
<DisclosureButton
key={item.name}
as="a"
href={item.href}
className={classNames(
item.current
? "bg-gray-900 text-white"
: "text-gray-300 hover:bg-gray-700 hover:text-white",
"block rounded-md px-3 py-2 text-base font-medium"
)}
aria-current={item.current ? "page" : undefined}
>
{item.name}
</DisclosureButton>
))}
</div>
</DisclosurePanel>
</>
)}
</Disclosure>
);
}
JavaScript
//frontend/src/components/PosttViewer.js
import { useState } from "react";
import {
Dialog,
DialogBackdrop,
DialogPanel,
DialogTitle,
} from "@headlessui/react";
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
export default function PosttViewer({
img_url,
content,
isViewerOpen,
onClose,
}) {
return (
<Dialog className="relative z-10" open={isViewerOpen} onClose={onClose}>
<DialogBackdrop
transition
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity
data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:
duration-200 data-[enter]:ease-out data-[leave]:ease-in"
/>
<div className="fixed inset-0 z-10 w-screen overflow-y-auto p-10">
<div className="flex min-h-full items-end justify-center p-4
text-center sm:items-center sm:p-0">
<DialogPanel
transition
className="relative transform overflow-hidden rounded-lg
bg-white text-left shadow-xl transition-all data-[closed]:
translate-y-4 data-[closed]:opacity-0 data-[enter]:
duration-300 data-[leave]:duration-200 data-[enter]:
ease-out data-[leave]:ease-in sm:my-8 sm:w-full
sm:max-w-lg data-[closed]:sm:translate-y-0 data-[closed]:
sm:scale-95"
>
<div className="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0
items-center justify-center rounded-full bg-red-100
sm:mx-0 sm:h-10 sm:w-10">
<ExclamationTriangleIcon
className="h-6 w-6 text-red-600"
aria-hidden="true"
/>
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<div className="mt-2">
<p className="text-sm text-gray-500">{content}</p>
</div>
<div>
{img_url && (
<img
src={`http://localhost:5000/uploads/${img_url}`}
alt="Post"
style={{ maxWidth: "100%", maxHeight: "100%" }}
/>
)}
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse
sm:px-6">
</div>
</DialogPanel>
</div>
</div>
</Dialog>
);
}
JavaScript
//frontend/src/components/PrivateRoute.js
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import { useSelector } from 'react-redux';
const PrivateRoute = () => {
const user = useSelector((state) => state.auth.userInfo);
return user ? <Outlet /> : <Navigate to="/login" />;
};
export default PrivateRoute;
JavaScript
//frontend/src/components/Profile.js
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link } from "react-router-dom";
import { formatDistanceToNow } from "date-fns";
import {
fetchUserProfile,
updateUserProfile,
} from "../redux/actions/userActions";
import { getUserPost } from "../redux/actions/postActions";
const Profile = () => {
const dispatch = useDispatch();
const userInfo = useSelector((state) => state.auth.userInfo);
const loading = useSelector((state) => state.auth.loading);
const posts = useSelector((state) => state.posts) || [];
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
useEffect(() => {
dispatch(fetchUserProfile());
dispatch(getUserPost());
}, [dispatch]);
useEffect(() => {
if (userInfo) {
setUsername(userInfo.username);
setEmail(userInfo.email);
console.log("Username:", userInfo.username, "Email:", userInfo.email);
}
}, [userInfo]);
const handleSubmit = (e) => {
e.preventDefault();
if (userInfo) {
dispatch(updateUserProfile({ username, email }));
}
};
if (loading) {
return <div>Loading...</div>;
}
return (
<>
<div class="h-screen dark:bg-gray-700 bg-gray-200 pt-12">
<h1 className="p-2 ml-5">
<Link to="/feed">Go back</Link>
</h1>
<div class="max-w-sm mx-auto bg-white dark:bg-gray-900
rounded-lg overflow-hidden shadow-lg">
<div class="border-b px-4 pb-6">
<form onSubmit={handleSubmit}>
<div class="text-center my-4">
<img
class="h-32 w-32 rounded-full border-4 border-white
dark:border-gray-800 mx-auto my-4"
src="https://media.geeksforgeeks.org/wp-content/\
uploads/20240725103944/user-sign-icon-front-side-with-white
-background_187299-40022.png"
alt=""
/>
<div class="py-2">
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Username"
required
class="font-bold text-2xl text-gray-800
dark:text-white mb-1 text-center"
/>
<h3 class="font-bold text-2xl text-gray-800
dark:text-white mb-1">
{email}
</h3>
<div class="inline-flex text-gray-700
dark:text-gray-300 items-center">
<svg
class="h-5 w-5 text-gray-400
dark:text-gray-600 mr-1"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
>
<path
class=""
d="M5.64 16.36a9 9 0 1 1 12.72 0l-5.65 5.66a1
1 0 0 1-1.42 0l-5.65-5.66zm11.31-1.41a7 7 0
1 0-9.9 0L12 19.9l4.95-4.95zM12 14a4 4 0 1
1 0-8 4 4 0 0 1 0 8zm0-2a2 2 0 1 0 0-4 2 2
0 0 0 0 4z"
/>
</svg>
New York, NY
</div>
</div>
</div>
<div class="flex gap-2 px-2">
<button
type="submit"
class="flex-1 rounded-full bg-blue-600 dark:bg-blue-800
text-white dark:text-white antialiased font-bold
hover:bg-blue-800 dark:hover:bg-blue-900 px-4 py-2"
>
Update Profile
</button>
</div>
</form>
</div>
<div class="px-4 py-4">
<div class="flex gap-2 items-center text-gray-800
dark:text-gray-300 mb-4">
<svg
class="h-6 w-6 text-gray-600 dark:text-gray-400"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
>
<path
class=""
d="M12 12a5 5 0 1 1 0-10 5 5 0 0 1 0 10zm0-2a3
3 0 1 0 0-6 3 3 0 0 0 0 6zm9 11a1 1 0 0 1-2 0v-2a3
3 0 0 0-3-3H8a3 3 0 0 0-3 3v2a1 1 0 0 1-2 0v-2a5
5 0 0 1 5-5h8a5 5 0 0 1 5 5v2z"
/>
</svg>
<span>
<strong class="text-black dark:text-white">
12</strong> Followers
you know
</span>
</div>
<div class="flex">
<div class="flex justify-end mr-2">
<img
class="border-2 border-white dark:border-gray-800
rounded-full h-10 w-10 -mr-2"
src="https://media.geeksforgeeks.org/wp-content
/uploads/20240729160824/32.jpg"
alt=""
/>
<img
class="border-2 border-white dark:border-gray-800
rounded-full h-10 w-10 -mr-2"
src="https://media.geeksforgeeks.org/wp-content/
uploads/20240729160919/31.jpg"
alt=""
/>
<img
class="border-2 border-white dark:border-gray-800
rounded-full h-10 w-10 -mr-2"
src="https://media.geeksforgeeks.org/wp-content/
uploads/20240729161005/33.jpg"
alt=""
/>
<img
class="border-2 border-white dark:border-gray-800
rounded-full h-10 w-10 -mr-2"
src="https://media.geeksforgeeks.org/wp-content/
uploads/20240729161119/32-w.jpg"
alt=""
/>
<img
class="border-2 border-white dark:border-gray-800
rounded-full h-10 w-10 -mr-2"
src="https://media.geeksforgeeks.org/wp-content/
uploads/20240729161214/44.jpg"
alt=""
/>
<img
class="border-2 border-white dark:border-gray-800
rounded-full h-10 w-10 -mr-2"
src="https://media.geeksforgeeks.org/wp-content/
uploads/20240729161356/42.jpg"
alt=""
/>
<span class="flex items-center justify-center bg-white
dark:bg-gray-800 text-sm text-gray-800 dark:text-white
font-semibold border-2 border-gray-200 dark:border-gray-700
rounded-full h-10 w-10">
+999
</span>
</div>
</div>
</div>
</div>
<PostList posts={posts} />
</div>
{/* all post */}
</>
);
};
const PostList = ({ posts }) => {
if (!Array.isArray(posts)) {
return <div>No posts available</div>;
}
return (
<div className=" dark:bg-gray-700 bg-gray-200 ">
<div className="bg-black p-4 mt-8 text-white font-bold ">
<h1 className="flex justify-center">All posts</h1>
</div>
<div>
{posts.map((post) => (
<div className="flex justify-center">
<Post key={post._id} post={post} />
</div>
))}
</div>
</div>
);
};
const Post = ({ post }) => {
return (
<div className="">
<div className="flex flex-col justify-center
font-[sans-serif] p-4">
<div class="min-w-[40rem] w-full mx-auto border
border-gray-300 rounded-2xl p-4 bg-white
text-black shadow-lg">
<div className="grid grid-cols-2 gap-4 ">
<div className="flex flex-row gap-4 ">
<div className="rounded-lg h-8 w-8
flex flex-row justify-center font-bold">
<img
className="object-cover w-full h-full"
src="https://media.geeksforgeeks.org/
wp-content/uploads/20240725110755/user-profile_5675059.png"
/>
</div>
<div>
<p className="uppercase ">{post.user.username}</p>
<p>
{formatDistanceToNow(new Date(post.createdAt), {
addSuffix: true,
})}
</p>
</div>
</div>
</div>
<div className="mt-2">
<hr></hr>
</div>
<p className=" p-2 overflow-hidden text-nowrap text-ellipsis
whitespace-break-spaces max-w-[100%]">
{post.content}
</p>
<div className="flex justify-center">
{post.image ? (
<img
src={`http://localhost:5000/uploads/${post.image}`}
alt="Post"
className="object-cover h-[220px] w-[220px]"
/>
) : null}
</div>
<div className="mt-4 mb-4">
<hr></hr>
</div>
<div
className="flex flex-row justify-between p-2
bg-gray-100 rounded-md"
style={{ cursor: "pointer" }}
>
<div className="flex flex-row justify-between items-center ">
<div>
<img
src="https://media.geeksforgeeks.org/
wp-content/uploads/20240725145948/like_10950198.png"
className="h-6 w-6"
/>
</div>
<div>
<span className="h-6 w-6">{post.likes}</span>
</div>
</div>
<div className="flex flex-row justify-between items-center">
<div>
<img
src="https://media.geeksforgeeks.org/
wp-content/uploads/20240725150621/bubble-chat_8345612.png"
className="h-6 w-6"
/>
</div>
<div>
<span className="h-6 w-6">
{post.comments.length}</span>
</div>
</div>
<div>
<img
src="https://media.geeksforgeeks.org/
wp-content/uploads/20240725151348/share_5949688.png"
className="h-6 w-6"
/>
</div>
</div>
</div>
</div>
</div>
);
};
export default Profile;
JavaScript
//frontend/src/components/PublicRoute.js
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import { useSelector } from 'react-redux';
const PublicRoute = () => {
const user = useSelector((state) => state.auth.userInfo);
return user ? <Navigate to="/" /> : <Outlet />;
};
export default PublicRoute;
JavaScript
//frontend/src/components/Register.js
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { registerUser } from "../redux/actions/authActions";
import { useNavigate } from "react-router-dom";
import { Link } from "react-router-dom";
const Register = () => {
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const dispatch = useDispatch();
const auth = useSelector((state) => state.auth);
const navigate = useNavigate();
const handleSubmit = (e) => {
e.preventDefault();
dispatch(registerUser({ username, email, password }));
navigate("/login");
};
// useEffect(() => {
// if (auth.userInfo) {
// navigate("/login");
// }
// }, [auth.userInfo, navigate]);
return (
<div class="flex flex-col justify-center font-[sans-serif]
sm:h-screen p-4 mt-14 bg-white">
<div class="max-w-md w-full mx-auto border border-gray-300
rounded-2xl p-8">
<div class="text-center mb-12">
<h2 class="mt-10 text-center text-2xl font-bold
leading-9 tracking-tight text-gray-900">
Register your account
</h2>
</div>
<form onSubmit={handleSubmit}>
<div class="space-y-6">
<div>
<label class="text-gray-800 text-sm mb-2 block">
Name</label>
<input
name="name"
type="text"
class="text-gray-800 bg-white border
border-gray-300 w-full text-sm px-4 py-3
rounded-md outline-blue-500"
placeholder="Enter name"
required
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label class="text-gray-800 text-sm mb-2 block">
Email</label>
<input
name="email"
type="email"
class="text-gray-800 bg-white border border-gray-300
w-full text-sm px-4 py-3 rounded-md
outline-blue-500"
placeholder="Enter email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div>
<label class="text-gray-800 text-sm mb-2 block">
Password</label>
<input
name="password"
type="password"
class="text-gray-800 bg-white border border-gray-300
w-full text-sm px-4 py-3 rounded-md outline-blue-500"
placeholder="Enter password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div class="flex items-center">
<input
id="remember-me"
name="remember-me"
type="checkbox"
class="h-4 w-4 shrink-0 text-blue-600
focus:ring-blue-500 border-gray-300 rounded"
/>
<label for="remember-me" class="text-gray-800
ml-3 block text-sm">
I accept the{" "}
<a
href="javascript:void(0);"
class="text-blue-600 font-semibold
hover:underline ml-1"
>
Terms and Conditions
</a>
</label>
</div>
</div>
<div class="!mt-12">
<button
type="submit"
class="w-full py-3 px-4 text-sm tracking-wider
font-semibold rounded-md text-white bg-blue-600
hover:bg-blue-700 focus:outline-none"
>
Create an account
</button>
</div>
<p class="text-gray-800 text-sm mt-6 text-center">
Already have an account?{" "}
<Link
to="/login"
href="javascript:void(0);"
class="text-blue-600 font-semibold
hover:underline ml-1"
>
Login here
</Link>
</p>
</form>
</div>
</div>
);
};
export default Register;
JavaScript
//frontend/src/components/Sidebarprofile.js
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchUserProfile } from "../redux/actions/userActions";
export default function Sidebarprofile() {
const dispatch = useDispatch();
const userInfo = useSelector((state) => state.auth.userInfo);
const loading = useSelector((state) => state.auth.loading);
const [username, setUsername] = useState("");
useEffect(() => {
dispatch(fetchUserProfile());
}, [dispatch]);
useEffect(() => {
if (userInfo) {
setUsername(userInfo.username);
}
}, [userInfo]);
if (loading) {
return <div>Loading...</div>;
}
return (
<div className="bg-white my-20 pb-6 w-[20%] h-[20%]
justify-center items-center overflow-hidden
rounded-lg shadow-lg ">
<div className="relative h-40">
<img
className="absolute h-full w-full object-cover"
src="https://media.geeksforgeeks.org/
wp-content/uploads/20240725153703/profile_4260134.png"
/>
</div>
<div className="relative shadow mx-auto h-24 w-24
-my-12 border-white rounded-full overflow-hidden border-4">
<img
className="object-cover w-full h-full"
src="https://media.geeksforgeeks.org/wp-content/
uploads/20240725103944/user-sign-icon-front-side-
with-white-background_187299-40022.png"
/>
</div>
<div className="mt-16">
<h1 className="text-lg text-center font-semibold">
<p className="uppercase ">{userInfo.username}</p>
</h1>
<p className="text-sm text-gray-600 text-center">
13 connections in common
</p>
</div>
</div>
);
}
JavaScript
//src/components/Home.js
// src/components/Home.js
import React from "react";
import { Link } from "react-router-dom";
const Home = () => {
return (
<div style={{ padding: "20px" }}>
<header style={{ marginBottom: "20px" }}>
<h1>Welcome to the Social Networking Platform</h1>
<nav>
<Link to="/login" style={{ marginRight: "10px" }}>
Login
</Link>
<Link to="/register">Register</Link>
</nav>
</header>
<main>
<section>
<h2>About Our Platform</h2>
<p>
This platform allows you to connect with friends, share updates, and
stay informed about what's happening in your community.
</p>
</section>
</main>
<footer style={{ marginTop: "20px" }}>
<p>© 2024 Social Networking Platform. All rights reserved.</p>
</footer>
</div>
);
};
export default Home;
Steps to Run the ApplicationBackend- Navigate to the backend directory
- Install dependencies: npm install
- Start the server: npm start
cd backend
npm server.js / nodemon server.js
Frontend- Navigate to the frontend directory
- Install dependencies: npm install
- Create a .env file for environment variables (e.g., API base URL)
- Start the React app: npm start
cd frontend
npm start
Output
|