generated from lucien/api-template
add: added pfps and user profile modification
This commit is contained in:
parent
56d171439e
commit
7781e6b8a1
20 changed files with 404 additions and 36 deletions
3
back/.gitignore
vendored
3
back/.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
/node_modules
|
/node_modules
|
||||||
.env
|
.env
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
data
|
|
@ -1,8 +1,16 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const sha256 = require("sha256");
|
const sha256 = require("sha256");
|
||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
const { getConnection, getUserByUsername, addUser, getUser } = require('../libs/mysql');
|
const { getConnection, getUserByUsername, addUser, setUserPfp, setUserUsername, setUserPassword } = require('../libs/mysql');
|
||||||
const { checkAuth } = require('../libs/middlewares');
|
const { checkAuth } = require('../libs/middlewares');
|
||||||
|
const multer = require('multer')
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const upload = multer({ dest: 'data/pfps/' })
|
||||||
|
upload.limits = {
|
||||||
|
fileSize: 1024 * 1024 * 5,
|
||||||
|
files: 1,
|
||||||
|
};
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
@ -59,10 +67,75 @@ router.post('/register', async (req, res) => {
|
||||||
res.send({ message: 'User added' });
|
res.send({ message: 'User added' });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.use('/me', checkAuth);
|
router.post('/me', checkAuth, async (req, res) => {
|
||||||
router.post('/me', async (req, res) => {
|
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
res.send({ id: user.id, username: user.username, admin: user.admin });
|
res.send({ id: user.id, username: user.username, admin: user.admin });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/me/uploadpfp', upload.single('pfp'), checkAuth, async (req, res) => {
|
||||||
|
const fileName = req.file.filename;
|
||||||
|
const user = req.user;
|
||||||
|
|
||||||
|
if (user.pfp && fs.existsSync(`data/pfps/${user.pfp}`)) {
|
||||||
|
fs.unlinkSync(`data/pfps/${user.pfp}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const connection = await getConnection();
|
||||||
|
await setUserPfp(connection, user.id, fileName);
|
||||||
|
connection.end();
|
||||||
|
res.send({ message: 'Profile picture uploaded.' });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/me/deletepfp', checkAuth, async (req, res) => {
|
||||||
|
const user = req.user;
|
||||||
|
|
||||||
|
if (user.pfp && fs.existsSync(`data/pfps/${user.pfp}`)) {
|
||||||
|
fs.unlinkSync(`data/pfps/${user.pfp}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const connection = await getConnection();
|
||||||
|
await setUserPfp(connection, user.id, null);
|
||||||
|
connection.end();
|
||||||
|
res.send({ message: 'Profile picture deleted.' });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/me/setusername', checkAuth, async (req, res) => {
|
||||||
|
const { username } = req.body;
|
||||||
|
const user = req.user;
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
return res.status(400).send({ error: 'Invalid username' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^[a-zA-Z0-9-_]+$/.test(username)) {
|
||||||
|
return res.status(400).send({ error: 'Invalid username' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const connection = await getConnection();
|
||||||
|
const userExists = await getUserByUsername(connection, username);
|
||||||
|
if (userExists[0]) {
|
||||||
|
connection.end();
|
||||||
|
return res.status(401).send({ error: 'Username already exists' });
|
||||||
|
}
|
||||||
|
|
||||||
|
await setUserUsername(connection, user.id, username);
|
||||||
|
connection.end();
|
||||||
|
res.send({ message: 'Username changed.' });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
router.post('/me/setpassword', checkAuth, async (req, res) => {
|
||||||
|
const { oldPassword, password } = req.body;
|
||||||
|
const user = req.user;
|
||||||
|
|
||||||
|
if (!password || !oldPassword || sha256(oldPassword) !== user.password) {
|
||||||
|
return res.status(400).send({ error: 'Invalid password' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const connection = await getConnection();
|
||||||
|
await setUserPassword(connection, user.id, sha256(password));
|
||||||
|
connection.end();
|
||||||
|
res.send({ message: 'Password changed.' });
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
|
@ -123,8 +123,7 @@ router.get('/:name/messages', async (req, res) => {
|
||||||
res.send(messages);
|
res.send(messages);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.use('/:name/messages/send', checkAuth);
|
router.post('/:name/messages/send', checkAuth, async (req, res) => {
|
||||||
router.post('/:name/messages/send', async (req, res) => {
|
|
||||||
const { message } = req.body;
|
const { message } = req.body;
|
||||||
const name = req.params.name;
|
const name = req.params.name;
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
|
@ -165,8 +164,7 @@ router.post('/:name/messages/send', async (req, res) => {
|
||||||
res.send({ message: 'Message sent' });
|
res.send({ message: 'Message sent' });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.use('/:name/messages/delete', checkAuth);
|
router.post('/:name/messages/delete', checkAuth, async (req, res) => {
|
||||||
router.post('/:name/messages/delete', async (req, res) => {
|
|
||||||
const { message_id } = req.body;
|
const { message_id } = req.body;
|
||||||
const name = req.params.name;
|
const name = req.params.name;
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
|
@ -207,8 +205,7 @@ router.post('/:name/messages/delete', async (req, res) => {
|
||||||
res.send({ message: 'Message deleted' });
|
res.send({ message: 'Message deleted' });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.use('/add', checkAuth);
|
router.post('/add', checkAuth, async (req, res) => {
|
||||||
router.post('/add', async (req, res) => {
|
|
||||||
const { name, description } = req.body;
|
const { name, description } = req.body;
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { getConnection, getUsers, getUserByUsername, getUserLastMessages, getMentions, deleteUser, deleteUserMessages, deleteUserMentions } = require('../libs/mysql');
|
const { getConnection, getUsers, getUserByUsername, getUserLastMessages, getMentions, deleteUser, deleteUserMessages, deleteUserMentions } = require('../libs/mysql');
|
||||||
const { checkAuth } = require("../libs/middlewares")
|
const { checkAuth } = require("../libs/middlewares")
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
@ -41,8 +42,26 @@ router.get('/:username/lastmessages', async (req, res) => {
|
||||||
res.send(messages);
|
res.send(messages);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.use("/:username/delete", checkAuth);
|
router.get('/:username/pfp', async (req, res) => {
|
||||||
router.post('/:username/delete', async (req, res) => {
|
const username = req.params.username;
|
||||||
|
const connection = await getConnection();
|
||||||
|
const user = await getUserByUsername(connection, username);
|
||||||
|
connection.end();
|
||||||
|
|
||||||
|
if (!user[0]) {
|
||||||
|
return res.status(400).send({ error: 'No user found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const pfp = user[0].pfp;
|
||||||
|
|
||||||
|
if (!pfp) {
|
||||||
|
return res.sendFile(path.join(__dirname, `../images/default-pfp.png`), { headers: { 'Content-Type': 'image' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.sendFile(path.join(__dirname, `../data/pfps/${pfp}`), { headers: { 'Content-Type': 'image' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/:username/delete', checkAuth, async (req, res) => {
|
||||||
const username = req.params.username;
|
const username = req.params.username;
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
|
|
||||||
|
|
BIN
back/images/default-pfp.png
Normal file
BIN
back/images/default-pfp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 94 KiB |
|
@ -118,6 +118,51 @@ function deleteUser(connection, id) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function setUserPfp(connection, id, pfp) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
connection.query(
|
||||||
|
`UPDATE users SET pfp = ? WHERE id = ?`,
|
||||||
|
[pfp, id], // Use parameterized query
|
||||||
|
(error, result) => {
|
||||||
|
if (error) {
|
||||||
|
reject(new Error(error));
|
||||||
|
}
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUserUsername(connection, id, username) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
connection.query(
|
||||||
|
`UPDATE users SET username = ? WHERE id = ?`,
|
||||||
|
[username, id], // Use parameterized query
|
||||||
|
(error, result) => {
|
||||||
|
if (error) {
|
||||||
|
reject(new Error(error));
|
||||||
|
}
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUserPassword(connection, id, password) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
connection.query(
|
||||||
|
`UPDATE users SET password = ? WHERE id = ?`,
|
||||||
|
[password, id], // Use parameterized query
|
||||||
|
(error, result) => {
|
||||||
|
if (error) {
|
||||||
|
reject(new Error(error));
|
||||||
|
}
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getChannels(connection) {
|
function getChannels(connection) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
connection.query(
|
connection.query(
|
||||||
|
@ -440,6 +485,9 @@ module.exports = {
|
||||||
getUserByUsername,
|
getUserByUsername,
|
||||||
addUser,
|
addUser,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
|
setUserPfp,
|
||||||
|
setUserUsername,
|
||||||
|
setUserPassword,
|
||||||
getUserLastMessages,
|
getUserLastMessages,
|
||||||
getChannels,
|
getChannels,
|
||||||
getActiveChannels,
|
getActiveChannels,
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"fs": "^0.0.1-security",
|
"fs": "^0.0.1-security",
|
||||||
"https": "^1.0.0",
|
"https": "^1.0.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"multer": "^1.4.5-lts.2",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"sha256": "^0.2.0"
|
"sha256": "^0.2.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ services:
|
||||||
- "traefik.http.services.forum.loadbalancer.server.port=3000"
|
- "traefik.http.services.forum.loadbalancer.server.port=3000"
|
||||||
networks:
|
networks:
|
||||||
- traefik
|
- traefik
|
||||||
|
volumes:
|
||||||
|
- ./data:/app/data
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
traefik:
|
traefik:
|
||||||
|
|
|
@ -13,20 +13,24 @@ export default function MessageComponent({ message, user, channel, deleteMessage
|
||||||
return (
|
return (
|
||||||
<div key={message.id} className="message">
|
<div key={message.id} className="message">
|
||||||
<div className="message-content">
|
<div className="message-content">
|
||||||
<Link to={`/u/${message.username}`}>{message.username}</Link>:{" "}
|
<img src={`/api/users/${message.username}/pfp`} alt="" className="message-user-pfp" />
|
||||||
{message.content.split(" ").map((word, index) => {
|
<div className="message-content-right">
|
||||||
if (word.startsWith("@")) {
|
<span><Link to={`/u/${message.username}`}>{message.username}</Link> {new Date(message.date * 1000).toLocaleString()}</span>
|
||||||
const mention = message.mentions.find((mention) => `@${mention.username}` === word);
|
<div className="message-text">
|
||||||
if (mention) {
|
{message.content.split(" ").map((word, index) => {
|
||||||
return <span><Link key={index} to={`/u/${mention.username}`}>{word}</Link> </span>;
|
if (word.startsWith("@")) {
|
||||||
}
|
const mention = message.mentions.find((mention) => `@${mention.username}` === word);
|
||||||
} else if (word.startsWith("https://") || word.startsWith("http://")) {
|
if (mention) {
|
||||||
return <span><Link to={word}>{word}</Link> </span>
|
return <span><Link key={index} to={`/u/${mention.username}`}>{word}</Link> </span>;
|
||||||
}
|
}
|
||||||
return <span key={index}>{word} </span>;
|
} else if (word.startsWith("https://") || word.startsWith("http://")) {
|
||||||
})}
|
return <span><Link to={word}>{word}</Link> </span>
|
||||||
|
}
|
||||||
|
return <span key={index}>{word} </span>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>{new Date(message.date * 1000).toLocaleString()}</p>
|
|
||||||
<div>
|
<div>
|
||||||
{message.content.toLocaleLowerCase().includes("gros cochon") && (
|
{message.content.toLocaleLowerCase().includes("gros cochon") && (
|
||||||
<img src="/pig.png" alt="Gros cochon" className="pig" />
|
<img src="/pig.png" alt="Gros cochon" className="pig" />
|
||||||
|
|
|
@ -27,6 +27,7 @@ export default function TopBar({ user }: { user: User | undefined }) {
|
||||||
}}>
|
}}>
|
||||||
Logout
|
Logout
|
||||||
</button>
|
</button>
|
||||||
|
<img src={`/api/users/${user.username}/pfp`} alt="pfp" className="topbar-user-pfp" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="topbar-right">
|
<div className="topbar-right">
|
||||||
|
|
|
@ -10,6 +10,7 @@ import Register from './pages/Register'
|
||||||
import CreateChannel from './pages/CreateChannel'
|
import CreateChannel from './pages/CreateChannel'
|
||||||
import ChannelsPage from './pages/ChannelsPage'
|
import ChannelsPage from './pages/ChannelsPage'
|
||||||
import UsersPage from './pages/UsersPage'
|
import UsersPage from './pages/UsersPage'
|
||||||
|
import EditProfile from './pages/EditProfile'
|
||||||
|
|
||||||
const socket = new WebSocket("/api/ws");
|
const socket = new WebSocket("/api/ws");
|
||||||
|
|
||||||
|
@ -33,6 +34,7 @@ createRoot(document.getElementById('root')!).render(
|
||||||
<Route path="/create-channel" element={<CreateChannel />} />
|
<Route path="/create-channel" element={<CreateChannel />} />
|
||||||
<Route path="/channels" element={<ChannelsPage socket={socket} />} />
|
<Route path="/channels" element={<ChannelsPage socket={socket} />} />
|
||||||
<Route path="/users" element={<UsersPage socket={socket} />} />
|
<Route path="/users" element={<UsersPage socket={socket} />} />
|
||||||
|
<Route path="/edit-profile" element={<EditProfile />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
|
|
134
front/src/pages/EditProfile.tsx
Normal file
134
front/src/pages/EditProfile.tsx
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
import { useState, useEffect } from "react"
|
||||||
|
import { User } from "../types"
|
||||||
|
import { Link } from "react-router-dom"
|
||||||
|
import axios from "axios"
|
||||||
|
import TopBar from "../components/TopBar"
|
||||||
|
|
||||||
|
import "../styles/EditProfile.css"
|
||||||
|
|
||||||
|
export default function EditProfile() {
|
||||||
|
const [token, setToken] = useState<string>("");
|
||||||
|
const [user, setUser] = useState<User>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const localToken = localStorage.getItem("token");
|
||||||
|
if (localToken) {
|
||||||
|
setToken(localToken)
|
||||||
|
|
||||||
|
axios
|
||||||
|
.post("/api/auth/me", { token: localToken })
|
||||||
|
.then((res) => {
|
||||||
|
setUser(res.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function uploadPfp(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
const fileInput = document.getElementById("pfp") as HTMLInputElement;
|
||||||
|
if (fileInput.files) {
|
||||||
|
formData.append("pfp", fileInput.files[0]);
|
||||||
|
}
|
||||||
|
formData.append("token", token);
|
||||||
|
axios
|
||||||
|
.post("/api/auth/me/uploadpfp", formData, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err.response.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deletePfp(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
axios
|
||||||
|
.post("/api/auth/me/deletepfp", { token: token })
|
||||||
|
.then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err.response.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function editUsername(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
const newUsername = (document.getElementById("username") as HTMLInputElement).value;
|
||||||
|
axios
|
||||||
|
.post("/api/auth/me/setusername", { token: token, username: newUsername })
|
||||||
|
.then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err.response.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function editPassword(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
const oldPassword = (document.getElementById("old-password") as HTMLInputElement).value;
|
||||||
|
const newPassword = (document.getElementById("password") as HTMLInputElement).value;
|
||||||
|
const confirmPassword = (document.getElementById("confirm-password") as HTMLInputElement).value;
|
||||||
|
if (newPassword !== confirmPassword) {
|
||||||
|
alert("Passwords do not match");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
axios
|
||||||
|
.post("/api/auth/me/setpassword", { token: token, oldPassword: oldPassword, password: newPassword })
|
||||||
|
.then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err.response.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return (
|
||||||
|
<div className="edit-profile-page">
|
||||||
|
<TopBar user={user}/>
|
||||||
|
<div className="edit-login">
|
||||||
|
<p>Please log in to edit your profile</p>
|
||||||
|
<Link to="/login">Login</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="edit-profile-page">
|
||||||
|
<TopBar user={user}/>
|
||||||
|
<div className="edit-pfp">
|
||||||
|
<h2>Edit Profile Picture</h2>
|
||||||
|
<form>
|
||||||
|
<input type="file" name="pfp" id="pfp" />
|
||||||
|
<button onClick={uploadPfp}>Save</button>
|
||||||
|
<button onClick={deletePfp}>Delete</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div className="edit-username">
|
||||||
|
<h2>Edit Username</h2>
|
||||||
|
<form>
|
||||||
|
<input type="text" name="username" id="username" placeholder={user?.username} />
|
||||||
|
<button onClick={editUsername}>Save</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div className="edit-password">
|
||||||
|
<h2>Edit Password</h2>
|
||||||
|
<form>
|
||||||
|
<input type="password" name="old-password" id="old-password" placeholder="Old password" />
|
||||||
|
<input type="password" name="password" id="password" placeholder="New password" />
|
||||||
|
<input type="password" name="confirm-password" id="confirm-password" placeholder="Confirm new password" />
|
||||||
|
<button onClick={editPassword}>Save</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { User, Messages } from "../types";
|
import { User, Messages } from "../types";
|
||||||
import TopBar from "../components/TopBar";
|
import TopBar from "../components/TopBar";
|
||||||
|
@ -75,8 +75,14 @@ export default function UserPage({socket}: {socket: WebSocket}) {
|
||||||
<div className="user-page">
|
<div className="user-page">
|
||||||
<TopBar user={user} />
|
<TopBar user={user} />
|
||||||
<div className="user">
|
<div className="user">
|
||||||
<h2>{pageUser.username}</h2>
|
<div className="user-top">
|
||||||
|
<img src={`/api/users/${pageUser.username}/pfp`} alt="pfp" className="user-page-pfp" />
|
||||||
|
<h2>{pageUser.username}</h2>
|
||||||
|
</div>
|
||||||
{pageUser.admin ? <p>Admin</p> : <p>User</p>}
|
{pageUser.admin ? <p>Admin</p> : <p>User</p>}
|
||||||
|
{pageUser.id === user?.id && (
|
||||||
|
<Link to="/edit-profile">Edit profile</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="user-messages">
|
<div className="user-messages">
|
||||||
<h2>Last messages</h2>
|
<h2>Last messages</h2>
|
||||||
|
|
|
@ -74,7 +74,7 @@ export default function UsersPage({socket}: {socket: WebSocket}) {
|
||||||
return (
|
return (
|
||||||
<div className="users-page">
|
<div className="users-page">
|
||||||
<TopBar user={thisUser} />
|
<TopBar user={thisUser} />
|
||||||
<div className="users-page-channels">
|
<div className="users-page-users">
|
||||||
<h2>Users</h2>
|
<h2>Users</h2>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
44
front/src/styles/EditProfile.css
Normal file
44
front/src/styles/EditProfile.css
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
.edit-profile-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: start;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-login {
|
||||||
|
width: 97%;
|
||||||
|
border: 1px solid #270722;
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #fff6fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-pfp {
|
||||||
|
width: 97%;
|
||||||
|
border: 1px solid #270722;
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #fff6fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-username {
|
||||||
|
width: 97%;
|
||||||
|
border: 1px solid #270722;
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #fff6fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-password {
|
||||||
|
width: 97%;
|
||||||
|
border: 1px solid #270722;
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #fff6fd;
|
||||||
|
}
|
|
@ -4,7 +4,24 @@
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-user-pfp {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.message-content {
|
.message-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
|
|
|
@ -22,6 +22,12 @@
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.topbar-user-pfp {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 560px) {
|
@media (max-width: 560px) {
|
||||||
.topbar {
|
.topbar {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -16,6 +16,18 @@
|
||||||
background-color: #fff6fd;
|
background-color: #fff6fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-page-pfp {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.user-messages {
|
.user-messages {
|
||||||
width: 97%;
|
width: 97%;
|
||||||
border: 1px solid #270722;
|
border: 1px solid #270722;
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.users-page-channels {
|
.users-page-users {
|
||||||
width: 97%;
|
width: 97%;
|
||||||
border: 1px solid #270722;
|
border: 1px solid #270722;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
|
@ -26,12 +26,12 @@ export type Mention = {
|
||||||
export type Mentions = Mention[]
|
export type Mentions = Mention[]
|
||||||
|
|
||||||
export type Message = {
|
export type Message = {
|
||||||
id: number,
|
id: number
|
||||||
user_id: number,
|
user_id: number
|
||||||
username: string,
|
username: string
|
||||||
content: string,
|
content: string
|
||||||
date: number,
|
date: number
|
||||||
channel_id: number,
|
channel_id: number
|
||||||
channel_name: string
|
channel_name: string
|
||||||
mentions: Mentions
|
mentions: Mentions
|
||||||
}
|
}
|
||||||
|
@ -39,9 +39,10 @@ export type Message = {
|
||||||
export type Messages = Message[]
|
export type Messages = Message[]
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
id: number,
|
id: number
|
||||||
username: string
|
username: string
|
||||||
admin: number
|
admin: number
|
||||||
|
pfp: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Users = User[]
|
export type Users = User[]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue