diff --git a/frontend/components/FunctionCard/FunctionCard.css b/frontend/components/FunctionCard/FunctionCard.css
index 5196dbb..feddaa3 100644
--- a/frontend/components/FunctionCard/FunctionCard.css
+++ b/frontend/components/FunctionCard/FunctionCard.css
@@ -11,6 +11,12 @@
cursor: pointer;
transition: all .3s;
}
+.FunctionCard__container {
+ height: 100%;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+}
.FunctionCard:hover {
transform: translateY(-7px);
}
diff --git a/frontend/components/FunctionCard/FunctionCard.js b/frontend/components/FunctionCard/FunctionCard.js
index b6f15c8..5e485aa 100644
--- a/frontend/components/FunctionCard/FunctionCard.js
+++ b/frontend/components/FunctionCard/FunctionCard.js
@@ -1,20 +1,36 @@
import Link from 'next/link';
+import { useState, Fragment } from 'react';
+import Loader from '../Loader/Loader';
import './FunctionCard.css';
-const FunctionCard = (props) => (
-
-
-
-
-
{props.title}
-
{props.description}
-
-
-
{props.category.name}
-
{props.publicationDate}
-
-
-
-);
+const FunctionCard = (props) => {
+
+ const [isLoading, setIsLoading] = useState(true);
+
+ const handleLoad = () => {
+ setIsLoading(false);
+ }
+
+ return (
+
+
+
+ {isLoading &&
}
+
+
+
+
{props.title}
+
{props.description}
+
+
+
{props.category.name}
+
{props.publicationDate}
+
+
+
+
+
+ );
+}
export default FunctionCard;
\ No newline at end of file
diff --git a/frontend/components/Loader/Loader.css b/frontend/components/Loader/Loader.css
new file mode 100644
index 0000000..b7a8ac1
--- /dev/null
+++ b/frontend/components/Loader/Loader.css
@@ -0,0 +1,13 @@
+.Loader {
+ transform-origin: 50% 50%; animation: .9s linear 0s infinite normal forwards running Loader__spin;
+}
+
+@keyframes Loader__spin {
+ 0% {
+ animation-timing-function: cubic-bezier(0.5856,0.0703,0.4143,0.9297);
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
\ No newline at end of file
diff --git a/frontend/components/Loader/Loader.js b/frontend/components/Loader/Loader.js
new file mode 100644
index 0000000..9a6e508
--- /dev/null
+++ b/frontend/components/Loader/Loader.js
@@ -0,0 +1,11 @@
+import './Loader.css';
+
+const Loader = ({ width, height }) => (
+
+);
+
+export default Loader;
\ No newline at end of file
diff --git a/frontend/hooks/useAPI.js b/frontend/hooks/useAPI.js
new file mode 100644
index 0000000..02589a9
--- /dev/null
+++ b/frontend/hooks/useAPI.js
@@ -0,0 +1,31 @@
+import { useEffect, useState } from 'react';
+import api from '../config/api';
+
+/**
+ * @param {String} url
+ * @param {*} defaultData
+ * @param {String} method
+ * @param {Object} options
+ */
+function useAPI(url, defaultData = [], method = "get", options = {}) {
+
+ const [isLoading, setIsLoading] = useState(true);
+ const [data, setData] = useState(defaultData);
+ const [hasError, setHasError] = useState(false);
+
+ useEffect(() => {
+ api[method](url, options)
+ .then((result) => {
+ setData(result.data);
+ setIsLoading(false);
+ })
+ .catch((error) => {
+ setHasError(true);
+ console.error(error);
+ });
+ }, []);
+
+ return [isLoading, data, hasError];
+}
+
+export default useAPI;
\ No newline at end of file
diff --git a/frontend/pages/functions.js b/frontend/pages/functions.js
index fb6c7b5..eed3271 100644
--- a/frontend/pages/functions.js
+++ b/frontend/pages/functions.js
@@ -1,60 +1,50 @@
import { Fragment, useState, useEffect } from 'react';
import HeadTag from '../components/HeadTag';
import FunctionCard from '../components/FunctionCard/FunctionCard';
+import Loader from '../components/Loader/Loader';
import '../public/css/pages/functions.css';
import { API_URL } from '../config/config';
import api from '../config/api';
+import useAPI from '../hooks/useAPI';
const Functions = () => {
// State de recherche et de catégories
- const [categories, setCategories] = useState([]);
+ const [, categories] = useAPI('/categories');
const [inputSearch, setInputSearch] = useState({ search: "", selectedCategory: "0" });
// State pour afficher les fonctions
- const [functions, setFunctions] = useState([]);
+ const [functionsData, setFunctionsData] = useState({ hasMore: true, rows: [] });
const [isLoadingFunctions, setLoadingFunctions] = useState(true);
const [pageFunctions, setPageFunctions] = useState(1);
- const [hasMoreFunctions, sethasMoreFunctions] = useState(false);
- // Récupère les catégories
- useEffect(() => {
- api.get('/categories')
- .then((result) => {
- setCategories(result.data);
- })
- .catch((error) => console.error(error));
- }, []);
-
+ const getFunctionsData = () => {
+ setLoadingFunctions(true);
+ return new Promise(async (next) => {
+ const result = await api.get(`/functions?page=${pageFunctions}&limit=10&categoryId=${inputSearch.selectedCategory}&search=${inputSearch.search}`);
+ setLoadingFunctions(false);
+ next(result.data);
+ });
+ }
+
// Récupère les fonctions si la page change
useEffect(() => {
- api.get(`/functions?page=${pageFunctions}&limit=10&categoryId=${inputSearch.selectedCategory}&search=${inputSearch.search}`)
- .then((result) => {
- setLoadingFunctions(false);
- sethasMoreFunctions(result.data.hasMore);
- setFunctions([...functions, ...result.data.rows]);
- })
- .catch((error) => console.error(error));
+ getFunctionsData().then((data) => setFunctionsData({
+ hasMore: data.hasMore,
+ rows: [...functionsData.rows, ...data.rows]
+ }));
}, [pageFunctions]);
// Récupère les fonctions si la catégorie/recherche change
useEffect(() => {
- api.get(`/functions?page=${pageFunctions}&limit=10&categoryId=${inputSearch.selectedCategory}&search=${inputSearch.search}`)
- .then((result) => {
- setLoadingFunctions(false);
- sethasMoreFunctions(result.data.hasMore);
- setFunctions(result.data.rows);
- })
- .catch((error) => console.error(error));
+ getFunctionsData().then((data) => setFunctionsData(data));
}, [inputSearch.selectedCategory, inputSearch.search]);
const loadMore = () => {
- setLoadingFunctions(true);
setPageFunctions(pageFunctions + 1);
}
const handleChange = (event) => {
- setLoadingFunctions(true);
const inputSearchNew = { ...inputSearch };
inputSearchNew[event.target.name] = event.target.value;
setInputSearch(inputSearchNew);
@@ -72,7 +62,7 @@ const Functions = () => {
Fonctions
-
+
- {functions.map((f) => (
+ {functionsData.rows.map((f) => (
))}
- {
- !isLoadingFunctions && hasMoreFunctions
- ?
-
- : !hasMoreFunctions ?
- null
+ {
+ isLoadingFunctions ?
+
+ : functionsData.hasMore ?
+
+
+
:
- Chargement...
+ null
}
diff --git a/frontend/pages/index.js b/frontend/pages/index.js
index 9ba039d..a21716f 100644
--- a/frontend/pages/index.js
+++ b/frontend/pages/index.js
@@ -1,16 +1,25 @@
-import { Fragment } from 'react';
+import { Fragment, useEffect } from 'react';
import HeadTag from '../components/HeadTag';
-const Home = () => (
-
-
- Home
- {console.log('%c ⚙️ FunctionProject', 'color: #ffd800; font-weight: bold; background-color: #181818;padding: 10px;border-radius: 10px;font-size: 20px')}
-
-);
+const Home = () => {
+
+ useEffect(() => {
+ console.log(
+ '%c ⚙️ FunctionProject',
+ 'color: #ffd800; font-weight: bold; background-color: #181818;padding: 10px;border-radius: 10px;font-size: 20px'
+ );
+ }, []);
+
+ return (
+
+
+ Home
+
+ );
+}
export default Home;
\ No newline at end of file
diff --git a/frontend/public/css/general.css b/frontend/public/css/general.css
index 4591285..9566032 100644
--- a/frontend/public/css/general.css
+++ b/frontend/public/css/general.css
@@ -60,6 +60,9 @@ a, .important {
color: var(--important);
text-decoration: none;
}
+.d-none {
+ display: none !important;
+}
.form-control {
display: block;
height: calc(1.5em + .75rem + 2px);