Compare commits
11 Commits
39b9f05cff
...
b1a1590c1e
| Author | SHA1 | Date |
|---|---|---|
|
|
b1a1590c1e | |
|
|
e17ac1f88f | |
|
|
5e552a439f | |
|
|
dfea794acf | |
|
|
0397efba8a | |
|
|
dd07580943 | |
|
|
829474b200 | |
|
|
ff532359f5 | |
|
|
7b97687e78 | |
|
|
55bae65444 | |
|
|
5889a81358 |
|
|
@ -18,15 +18,15 @@
|
||||||
|
|
||||||
## Programme exécutable
|
## Programme exécutable
|
||||||
|
|
||||||
1. Téléchargez le programme exécutable pour Windows en cliquant [ici](https://github.com/polynux/groupomania-openclassrooms/releases/download/1.0/groupomania-release-1.0.zip).
|
1. Téléchargez le programme exécutable pour Windows en cliquant [ici](https://github.com/polynux/groupomania-openclassrooms/releases/download/1.1/groupomania-release-1.1.zip).
|
||||||
|
|
||||||
2. Décompressez le fichier `groupomania-release-1.0.zip`.
|
2. Décompressez le fichier `groupomania-release-1.1.zip`.
|
||||||
|
|
||||||
3. Lancez le programme `groupomania.exe`.
|
3. Lancez le programme `groupomania.exe`.
|
||||||
|
|
||||||
Si vous souhaitez compiler le programme exécutable, vous pouvez suivre les instructions suivantes :
|
Si vous souhaitez compiler le programme exécutable, vous pouvez suivre les instructions suivantes :
|
||||||
|
|
||||||
Executez `cd groupomania-openclassrooms/client` puis `npm install`, puis `npm run build:neu`.
|
Executez `npm run build:neu`.
|
||||||
|
|
||||||
Le programme exécutable se trouve dans le dossier `groupomania-openclassrooms/client/dist`.
|
Le programme exécutable se trouve dans le dossier `groupomania-openclassrooms/client/dist`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
VITE_API_URL=https://localhost:3000/api
|
||||||
|
|
@ -62,6 +62,7 @@
|
||||||
"cli": {
|
"cli": {
|
||||||
"binaryName": "groupomania",
|
"binaryName": "groupomania",
|
||||||
"resourcesPath": "/dist-vite/",
|
"resourcesPath": "/dist-vite/",
|
||||||
|
"clientLibrary": "/public/js/neutralino.js",
|
||||||
"extensionsPath": "/extensions/",
|
"extensionsPath": "/extensions/",
|
||||||
"binaryVersion": "4.7.0",
|
"binaryVersion": "4.7.0",
|
||||||
"clientVersion": "3.6.0"
|
"clientVersion": "3.6.0"
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -5,7 +5,7 @@ import { toastError, toastSuccess } from '@controllers/Toasts';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { getMeInfo } from '@controllers/UserController';
|
import { getMeInfo } from '@controllers/UserController';
|
||||||
|
|
||||||
const Like = ({ message }: { message: any}) => {
|
const Like = ({ message }: { message: any }) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [liked, setLiked] = useState(false);
|
const [liked, setLiked] = useState(false);
|
||||||
const me = useQuery(['me'], getMeInfo, {
|
const me = useQuery(['me'], getMeInfo, {
|
||||||
|
|
@ -20,7 +20,7 @@ const Like = ({ message }: { message: any}) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (message.likedBy.some((like: any) => like.userId === me.data?.id)) {
|
if (message.likedBy.some((like: any) => like.userId === me.data?.id)) {
|
||||||
setLiked(true);
|
setLiked(true);
|
||||||
}
|
}
|
||||||
}, [message]);
|
}, [message]);
|
||||||
|
|
||||||
const mutateLike = useMutation(liked ? unlikePost : likePost, {
|
const mutateLike = useMutation(liked ? unlikePost : likePost, {
|
||||||
|
|
@ -28,10 +28,11 @@ const Like = ({ message }: { message: any}) => {
|
||||||
queryClient.invalidateQueries(['messages']);
|
queryClient.invalidateQueries(['messages']);
|
||||||
if (data.message === 'Post liked') {
|
if (data.message === 'Post liked') {
|
||||||
setLiked(true);
|
setLiked(true);
|
||||||
toastSuccess('Message aimé');
|
toastSuccess('Message liké');
|
||||||
} else {
|
} else if (data.message === 'Post unliked') {
|
||||||
setLiked(false);
|
setLiked(false);
|
||||||
toastSuccess('Message non aimé');
|
} else {
|
||||||
|
toastError(data.message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
|
@ -45,12 +46,16 @@ const Like = ({ message }: { message: any}) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="absolute -bottom-10 right-0 mb-2 rounded-full bg-grey-dark shadow-lg shadow-slate-900 cursor-pointer"
|
className="absolute -bottom-10 -right-2 mb-2 rounded-full bg-grey-dark shadow-lg shadow-slate-900 cursor-pointer"
|
||||||
onClick={like}
|
onClick={like}
|
||||||
name="like"
|
name="like"
|
||||||
>
|
>
|
||||||
<FaThumbsUp className={'fill-red-light text-xl w-10 h-10 p-2.5' + (liked ? ' fill-red' : '')} />
|
<FaThumbsUp className={'fill-red-light text-xl w-10 h-10 p-2.5' + (liked ? ' fill-red' : '')} />
|
||||||
{message.likes > 0 && <span className="absolute -top-2 right-0 text-white rounded-full bg-red w-5 h-5 text-xs text-center p-0">{message.likes}</span>}
|
{message.likes > 0 && (
|
||||||
|
<span className="absolute -top-2 right-0 text-white rounded-full bg-red w-5 h-5 text-xs text-center p-0">
|
||||||
|
{message.likes}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<span className="sr-only">Aimer</span>
|
<span className="sr-only">Aimer</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ const Message = ({ message }: any) => {
|
||||||
</div>
|
</div>
|
||||||
<Text text={message.content} />
|
<Text text={message.content} />
|
||||||
{message.image && <Image src={message.image} alt="image" className="w-fit rounded-lg cursor-pointer" />}
|
{message.image && <Image src={message.image} alt="image" className="w-fit rounded-lg cursor-pointer" />}
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between gap-3 flex-wrap">
|
||||||
<div className="text-grey-light date">
|
<div className="text-grey-light date">
|
||||||
{new Date(message.createdAt).toLocaleDateString(undefined, {
|
{new Date(message.createdAt).toLocaleDateString(undefined, {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
|
|
@ -51,9 +51,10 @@ const Message = ({ message }: any) => {
|
||||||
minute: 'numeric',
|
minute: 'numeric',
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{message.edited && <div className="text-grey-light italic">Modifié</div>}
|
{message.edited && <div className="text-grey-light italic flex-grow">Modifié</div>}
|
||||||
|
{me.data?.id === message.author.id && message.likes > 0 && <div className="text-white">{message.likes} likes</div>}
|
||||||
</div>
|
</div>
|
||||||
{me.data?.id === message.author.id ? null : <Like message={message} />}
|
{me.data?.id !== message.author.id && <Like message={message} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ const NewMessage = () => {
|
||||||
)}
|
)}
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className="flex gap-2 flex-wrap bg-grey-dark rounded-xl p-2 mx-2 sm:p-3 md:mx-0 shadow-md shadow-grey-dark"
|
className="flex gap-2 bg-grey-dark rounded-xl p-2 mx-2 sm:p-3 md:mx-0 shadow-md shadow-grey-dark"
|
||||||
>
|
>
|
||||||
<div className="file">
|
<div className="file">
|
||||||
<label htmlFor="image" className="cursor-pointer block p-2">
|
<label htmlFor="image" className="cursor-pointer block p-2">
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { Cookies } from "react-cookie";
|
import { Cookies } from "react-cookie";
|
||||||
|
import { api } from "../main";
|
||||||
|
|
||||||
const likePost = async (id: string) => {
|
const likePost = async (id: string) => {
|
||||||
const token = new Cookies().get('token');
|
const token = new Cookies().get('token');
|
||||||
const response = await fetch(`/api/posts/like/${id}`, {
|
const response = await fetch(`${api}/posts/like/${id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -19,7 +20,7 @@ const likePost = async (id: string) => {
|
||||||
|
|
||||||
const unlikePost = async (id: string) => {
|
const unlikePost = async (id: string) => {
|
||||||
const token = new Cookies().get('token');
|
const token = new Cookies().get('token');
|
||||||
const response = await fetch(`/api/posts/unlike/${id}`, {
|
const response = await fetch(`${api}/posts/unlike/${id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { Cookies } from 'react-cookie';
|
import { Cookies } from 'react-cookie';
|
||||||
|
import { api } from '../main';
|
||||||
|
|
||||||
const getMessages = async () => {
|
const getMessages = async () => {
|
||||||
const token = new Cookies().get('token');
|
const token = new Cookies().get('token');
|
||||||
const response = await fetch('/api/posts', {
|
const response = await fetch(api + '/posts', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
|
|
@ -17,7 +18,7 @@ const getMessages = async () => {
|
||||||
|
|
||||||
const newMessage = async (data: FormData) => {
|
const newMessage = async (data: FormData) => {
|
||||||
const token = new Cookies().get('token');
|
const token = new Cookies().get('token');
|
||||||
const response = await fetch('/api/posts/new', {
|
const response = await fetch(api + '/posts/new', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: data,
|
body: data,
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
|
|
@ -30,7 +31,7 @@ const newMessage = async (data: FormData) => {
|
||||||
|
|
||||||
const deleteMessage = async (id: string) => {
|
const deleteMessage = async (id: string) => {
|
||||||
const token = new Cookies().get('token');
|
const token = new Cookies().get('token');
|
||||||
const response = await fetch(`/api/posts/delete/${id}`, {
|
const response = await fetch(`${api}/posts/delete/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
|
|
@ -41,7 +42,7 @@ const deleteMessage = async (id: string) => {
|
||||||
|
|
||||||
const editMessage = async (id: string, data: FormData) => {
|
const editMessage = async (id: string, data: FormData) => {
|
||||||
const token = new Cookies().get('token');
|
const token = new Cookies().get('token');
|
||||||
const response = await fetch(`/api/posts/edit/${id}`, {
|
const response = await fetch(`${api}/posts/edit/${id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: data,
|
body: data,
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { Cookies } from 'react-cookie';
|
import { Cookies } from 'react-cookie';
|
||||||
|
import { api } from '../main';
|
||||||
|
|
||||||
const getMeInfo = async () => {
|
const getMeInfo = async () => {
|
||||||
const token = new Cookies().get('token');
|
const token = new Cookies().get('token');
|
||||||
|
|
||||||
const response = await fetch('/api/me', {
|
const response = await fetch(api + '/me', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -21,7 +22,7 @@ const getMeInfo = async () => {
|
||||||
const login = async ({ email, password }: { email: string; password: string }) => {
|
const login = async ({ email, password }: { email: string; password: string }) => {
|
||||||
const token = new Cookies().get('token');
|
const token = new Cookies().get('token');
|
||||||
|
|
||||||
const response = await fetch('/api/auth/login', {
|
const response = await fetch(api + '/auth/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ email, password }),
|
body: JSON.stringify({ email, password }),
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
|
|
@ -51,7 +52,7 @@ const signup = async (formData: FormData) => {
|
||||||
throw 'Passwords do not match';
|
throw 'Passwords do not match';
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch('/api/auth/signup', {
|
const response = await fetch(api + '/auth/signup', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(form),
|
body: JSON.stringify(form),
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
|
|
@ -69,7 +70,7 @@ const signup = async (formData: FormData) => {
|
||||||
export const giveUserRights = async (userId: string, role: string) => {
|
export const giveUserRights = async (userId: string, role: string) => {
|
||||||
const token = new Cookies().get('token');
|
const token = new Cookies().get('token');
|
||||||
|
|
||||||
const response = await fetch(`/api/users/${userId}/roles`, {
|
const response = await fetch(`${api}/users/${userId}/roles`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -117,7 +118,7 @@ export const changeUserInfo = async (userId: string, formData: FormData) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`/api/users/${userId}`, {
|
const response = await fetch(`${api}/users/${userId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,10 @@ import App from './App';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />);
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />);
|
||||||
|
|
||||||
|
let api = '/api';
|
||||||
|
if (import.meta.env.VITE_API_URL) {
|
||||||
|
api = import.meta.env.VITE_API_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { api };
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import 'react-toastify/dist/ReactToastify.css';
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-full max-h-full bg-grey flex flex-col items-center overflow-y-hidden">
|
<div className="min-h-full max-h-full bg-grey flex flex-col items-center justify-between overflow-y-hidden">
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
<MessageWrapper />
|
<MessageWrapper />
|
||||||
<NewMessage />
|
<NewMessage />
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,11 @@
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon --watch src -e js,ts,json --exec 'ts-node src/index.ts'",
|
"dev": "nodemon --watch src -e js,ts,json --exec 'ts-node src/index.ts'",
|
||||||
"build": "cd client && npm run build",
|
"build": "npm run build:server && npm run build:client",
|
||||||
"start": "ts-node src/index.ts",
|
"build:neu": "cd client && npm run build:neu",
|
||||||
|
"build:server": "tsc && tsc-alias",
|
||||||
|
"build:client": "cd client && npm run build",
|
||||||
|
"start": "node dist/index.js",
|
||||||
"db": "prisma studio",
|
"db": "prisma studio",
|
||||||
"db:build": "prisma generate",
|
"db:build": "prisma generate",
|
||||||
"db:push": "prisma db push",
|
"db:push": "prisma db push",
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ export default async (req: Request, res: Response) => {
|
||||||
}
|
}
|
||||||
return res.status(200).send({ message: 'Post unliked' });
|
return res.status(200).send({ message: 'Post unliked' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
|
||||||
|
|
||||||
return res.status(500).send(error);
|
return res.status(500).send(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -190,11 +190,6 @@ const likePost = async (id: number, userId: number): Promise<PrismaPost | Error>
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
likedBy: {
|
|
||||||
connect: {
|
|
||||||
id: newLike.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
likes: {
|
likes: {
|
||||||
increment: 1,
|
increment: 1,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"target": "es2017",
|
"target": "ES2017",
|
||||||
"lib": [
|
"lib": [
|
||||||
"esnext"
|
"esnext"
|
||||||
],
|
],
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
"module": "CommonJS",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"resolveJsonModule": true,
|
|
||||||
"skipDefaultLibCheck": true,
|
"skipDefaultLibCheck": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue