Compare commits

...

6 Commits
1.1 ... main

8 changed files with 59 additions and 29 deletions

View File

@ -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`.

View File

@ -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);
}, },

View File

@ -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>

View File

@ -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>
</> </>
); );

View File

@ -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};

View File

@ -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 />

View File

@ -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",

View File

@ -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,