Compare commits
6 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
f878fe314d | |
|
|
37181d714a | |
|
|
6d1072cc43 | |
|
|
b1a1590c1e | |
|
|
e17ac1f88f | |
|
|
5e552a439f |
|
|
@ -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`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { getMessages } from '@controllers/MessageController';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { toastError } from '@controllers/Toasts';
|
import { toastError } from '@controllers/Toasts';
|
||||||
import ScrollToBottom from './ScrollToBottom';
|
import ScrollToBottom from './ScrollToBottom';
|
||||||
|
import { api } from '../main';
|
||||||
|
|
||||||
const MessageWrapper = () => {
|
const MessageWrapper = () => {
|
||||||
const {
|
const {
|
||||||
|
|
@ -10,6 +11,14 @@ const MessageWrapper = () => {
|
||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
} = useQuery(['messages'], getMessages, {
|
} = useQuery(['messages'], getMessages, {
|
||||||
|
onSuccess: (data) => {
|
||||||
|
data.map((message: any) => {
|
||||||
|
if (message.image) {
|
||||||
|
message.image = api.slice(0, -4) + message.image;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toastError(error as string);
|
toastError(error as string);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import { FaPlus, FaTimes } from 'react-icons/fa';
|
import { FaPlus, FaTimes } from 'react-icons/fa';
|
||||||
import { newMessage } from '@controllers/MessageController';
|
import { newMessage } from '@controllers/MessageController';
|
||||||
|
|
||||||
|
|
@ -8,6 +8,7 @@ const NewMessage = () => {
|
||||||
const [image, setImage] = useState('');
|
const [image, setImage] = useState('');
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [preview, setPreview] = useState('');
|
const [preview, setPreview] = useState('');
|
||||||
|
const imageRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const handleImageSelect = (e: any) => {
|
const handleImageSelect = (e: any) => {
|
||||||
setImage(e.target.value);
|
setImage(e.target.value);
|
||||||
|
|
@ -37,6 +38,9 @@ const NewMessage = () => {
|
||||||
setMessage('');
|
setMessage('');
|
||||||
setImage('');
|
setImage('');
|
||||||
setPreview('');
|
setPreview('');
|
||||||
|
if (imageRef.current) {
|
||||||
|
imageRef.current.value = '';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveImage = () => {
|
const handleRemoveImage = () => {
|
||||||
|
|
@ -74,6 +78,7 @@ const NewMessage = () => {
|
||||||
className="hidden"
|
className="hidden"
|
||||||
{...(image !== '' && { value: image })}
|
{...(image !== '' && { value: image })}
|
||||||
onChange={handleImageSelect}
|
onChange={handleImageSelect}
|
||||||
|
ref={imageRef}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ReactNode, useCallback, useEffect, useState } from 'react';
|
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { FaChevronDown } from 'react-icons/fa';
|
import { FaChevronDown } from 'react-icons/fa';
|
||||||
|
|
||||||
const ScrollToBottom = ({ children, className = '' }: { children: ReactNode; className?: string }) => {
|
const ScrollToBottom = ({ children, className = '' }: { children: ReactNode; className?: string }) => {
|
||||||
|
|
@ -12,21 +12,37 @@ const ScrollToBottom = ({ children, className = '' }: { children: ReactNode; cla
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useMemo(() => {
|
||||||
|
if (node) {
|
||||||
const container = document.querySelector('main > div');
|
const container = document.querySelector('main > div');
|
||||||
container?.addEventListener('scroll', () => {
|
|
||||||
if (node?.getBoundingClientRect() && node.getBoundingClientRect().y >= window.innerHeight) {
|
|
||||||
setShow(true);
|
|
||||||
} else {
|
|
||||||
setShow(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
container?.addEventListener('DOMNodeInserted', (e) => {
|
container?.addEventListener('DOMNodeInserted', (e) => {
|
||||||
if (!(e.target as Element).classList.contains('message')) return;
|
if (!(e.target as Element).classList.contains('message')) return;
|
||||||
if (node?.getBoundingClientRect() && node.getBoundingClientRect().y >= window.innerHeight) {
|
if (node?.getBoundingClientRect() && node.getBoundingClientRect().y >= window.innerHeight) {
|
||||||
node?.scrollIntoView({ behavior: 'smooth' });
|
node?.scrollIntoView({ behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
container?.querySelectorAll('.message > div > img').forEach((img) => {
|
||||||
|
img.addEventListener('load', () => {
|
||||||
|
if (node?.getBoundingClientRect() && node.getBoundingClientRect().y >= window.innerHeight) {
|
||||||
|
node?.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setShow(false);
|
||||||
|
} else {
|
||||||
|
setShow(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 1 }
|
||||||
|
);
|
||||||
|
observer.observe(node);
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}
|
||||||
}, [node]);
|
}, [node]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -43,7 +59,7 @@ const ScrollToBottom = ({ children, className = '' }: { children: ReactNode; cla
|
||||||
<span className="popup-btn block cursor-pointer rounded-full shadow-lg shadow-slate-900 bg-grey-dark hover:bg-grey-light transition-all">
|
<span className="popup-btn block cursor-pointer rounded-full shadow-lg shadow-slate-900 bg-grey-dark hover:bg-grey-light transition-all">
|
||||||
<FaChevronDown className="fill-grey-light hover:fill-grey-dark transition-all text-xl w-10 h-10 p-2.5" />
|
<FaChevronDown className="fill-grey-light hover:fill-grey-dark transition-all text-xl w-10 h-10 p-2.5" />
|
||||||
</span>
|
</span>
|
||||||
<span className='sr-only'>Descendre en bas de la page</span>
|
<span className="sr-only">Descendre en bas de la page</span>
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -132,9 +132,6 @@ export const changeUserInfo = async (userId: string, formData: FormData) => {
|
||||||
newPassword,
|
newPassword,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
|
||||||
return {error: response.statusText};
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
return {error: data.error};
|
return {error: data.error};
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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