Compare commits

...

11 Commits

16 changed files with 52 additions and 37 deletions

View File

@ -18,15 +18,15 @@
## 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`.
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`.

1
client/.env-example Normal file
View File

@ -0,0 +1 @@
VITE_API_URL=https://localhost:3000/api

View File

@ -62,6 +62,7 @@
"cli": {
"binaryName": "groupomania",
"resourcesPath": "/dist-vite/",
"clientLibrary": "/public/js/neutralino.js",
"extensionsPath": "/extensions/",
"binaryVersion": "4.7.0",
"clientVersion": "3.6.0"

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@ import { toastError, toastSuccess } from '@controllers/Toasts';
import { useEffect, useState } from 'react';
import { getMeInfo } from '@controllers/UserController';
const Like = ({ message }: { message: any}) => {
const Like = ({ message }: { message: any }) => {
const queryClient = useQueryClient();
const [liked, setLiked] = useState(false);
const me = useQuery(['me'], getMeInfo, {
@ -20,7 +20,7 @@ const Like = ({ message }: { message: any}) => {
useEffect(() => {
if (message.likedBy.some((like: any) => like.userId === me.data?.id)) {
setLiked(true);
}
}
}, [message]);
const mutateLike = useMutation(liked ? unlikePost : likePost, {
@ -28,10 +28,11 @@ const Like = ({ message }: { message: any}) => {
queryClient.invalidateQueries(['messages']);
if (data.message === 'Post liked') {
setLiked(true);
toastSuccess('Message aimé');
} else {
toastSuccess('Message liké');
} else if (data.message === 'Post unliked') {
setLiked(false);
toastSuccess('Message non aimé');
} else {
toastError(data.message);
}
},
onError: (error) => {
@ -45,12 +46,16 @@ const Like = ({ message }: { message: any}) => {
return (
<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}
name="like"
>
<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>
</button>
);

View File

@ -41,7 +41,7 @@ const Message = ({ message }: any) => {
</div>
<Text text={message.content} />
{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">
{new Date(message.createdAt).toLocaleDateString(undefined, {
year: 'numeric',
@ -51,9 +51,10 @@ const Message = ({ message }: any) => {
minute: 'numeric',
})}
</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>
{me.data?.id === message.author.id ? null : <Like message={message} />}
{me.data?.id !== message.author.id && <Like message={message} />}
</div>
</div>
</>

View File

@ -57,7 +57,7 @@ const NewMessage = () => {
)}
<form
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">
<label htmlFor="image" className="cursor-pointer block p-2">

View File

@ -1,8 +1,9 @@
import { Cookies } from "react-cookie";
import { api } from "../main";
const likePost = async (id: string) => {
const token = new Cookies().get('token');
const response = await fetch(`/api/posts/like/${id}`, {
const response = await fetch(`${api}/posts/like/${id}`, {
method: 'PUT',
mode: 'cors',
headers: {
@ -19,7 +20,7 @@ const likePost = async (id: string) => {
const unlikePost = async (id: string) => {
const token = new Cookies().get('token');
const response = await fetch(`/api/posts/unlike/${id}`, {
const response = await fetch(`${api}/posts/unlike/${id}`, {
method: 'PUT',
mode: 'cors',
headers: {

View File

@ -1,8 +1,9 @@
import { Cookies } from 'react-cookie';
import { api } from '../main';
const getMessages = async () => {
const token = new Cookies().get('token');
const response = await fetch('/api/posts', {
const response = await fetch(api + '/posts', {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
@ -17,7 +18,7 @@ const getMessages = async () => {
const newMessage = async (data: FormData) => {
const token = new Cookies().get('token');
const response = await fetch('/api/posts/new', {
const response = await fetch(api + '/posts/new', {
method: 'POST',
body: data,
mode: 'cors',
@ -30,7 +31,7 @@ const newMessage = async (data: FormData) => {
const deleteMessage = async (id: string) => {
const token = new Cookies().get('token');
const response = await fetch(`/api/posts/delete/${id}`, {
const response = await fetch(`${api}/posts/delete/${id}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${token}`,
@ -41,7 +42,7 @@ const deleteMessage = async (id: string) => {
const editMessage = async (id: string, data: FormData) => {
const token = new Cookies().get('token');
const response = await fetch(`/api/posts/edit/${id}`, {
const response = await fetch(`${api}/posts/edit/${id}`, {
method: 'PUT',
body: data,
mode: 'cors',

View File

@ -1,9 +1,10 @@
import { Cookies } from 'react-cookie';
import { api } from '../main';
const getMeInfo = async () => {
const token = new Cookies().get('token');
const response = await fetch('/api/me', {
const response = await fetch(api + '/me', {
method: 'GET',
mode: 'cors',
headers: {
@ -21,7 +22,7 @@ const getMeInfo = async () => {
const login = async ({ email, password }: { email: string; password: string }) => {
const token = new Cookies().get('token');
const response = await fetch('/api/auth/login', {
const response = await fetch(api + '/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
mode: 'cors',
@ -51,7 +52,7 @@ const signup = async (formData: FormData) => {
throw 'Passwords do not match';
}
const response = await fetch('/api/auth/signup', {
const response = await fetch(api + '/auth/signup', {
method: 'POST',
body: JSON.stringify(form),
mode: 'cors',
@ -69,7 +70,7 @@ const signup = async (formData: FormData) => {
export const giveUserRights = async (userId: string, role: string) => {
const token = new Cookies().get('token');
const response = await fetch(`/api/users/${userId}/roles`, {
const response = await fetch(`${api}/users/${userId}/roles`, {
method: 'POST',
mode: 'cors',
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',
mode: 'cors',
headers: {

View File

@ -3,3 +3,10 @@ import App from './App';
import './index.css';
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 };

View File

@ -5,7 +5,7 @@ import 'react-toastify/dist/ReactToastify.css';
const Home = () => {
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 />
<MessageWrapper />
<NewMessage />

View File

@ -5,8 +5,11 @@
"main": "index.js",
"scripts": {
"dev": "nodemon --watch src -e js,ts,json --exec 'ts-node src/index.ts'",
"build": "cd client && npm run build",
"start": "ts-node src/index.ts",
"build": "npm run build:server && npm run build:client",
"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:build": "prisma generate",
"db:push": "prisma db push",

View File

@ -10,8 +10,6 @@ export default async (req: Request, res: Response) => {
}
return res.status(200).send({ message: 'Post unliked' });
} catch (error) {
console.log(error);
return res.status(500).send(error);
}
};

View File

@ -190,11 +190,6 @@ const likePost = async (id: number, userId: number): Promise<PrismaPost | Error>
id,
},
data: {
likedBy: {
connect: {
id: newLike.id,
},
},
likes: {
increment: 1,
},

View File

@ -1,15 +1,15 @@
{
"compilerOptions": {
"baseUrl": ".",
"target": "es2017",
"target": "ES2017",
"lib": [
"esnext"
],
"moduleResolution": "node",
"module": "CommonJS",
"esModuleInterop": true,
"strict": true,
"strictNullChecks": true,
"resolveJsonModule": true,
"skipDefaultLibCheck": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,