⚡️ UserCard: React.memo + <a> instead of <div>
This commit is contained in:
parent
337868cf5f
commit
dc962c9120
@ -33,20 +33,20 @@ const FunctionCard = memo(forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{/* FunctionCard a une hauteur pendant chargement */}
|
{/* FunctionCard a une hauteur pendant chargement */}
|
||||||
<a ref={ref} style={isLoading ? { height: '360px', justifyContent: 'center' } : null} className='FunctionCard col-sm-24 col-md-10 col-xl-7'>
|
<a ref={ref} style={isLoading ? { height: '360px', justifyContent: 'center' } : null} className='FunctionCard col-sm-24 col-md-10 col-xl-7'>
|
||||||
{isLoading && <Loader width='125px' height='125px' />}
|
{isLoading && <Loader width='125px' height='125px' />}
|
||||||
|
|
||||||
<div className={`FunctionCard__container ${isLoading ? 'd-none' : ''}`}>
|
<div className={`FunctionCard__container ${isLoading ? 'd-none' : ''}`}>
|
||||||
<div className='FunctionCard__top'>
|
<div className='FunctionCard__top'>
|
||||||
<img onLoad={handleLoad} onError={handleError} className='FunctionCard__image' alt={props.title} src={API_URL + props.image} />
|
<img onLoad={handleLoad} onError={handleError} className='FunctionCard__image' alt={props.title} src={API_URL + props.image} />
|
||||||
<h2 className='FunctionCard__title'>{props.title}</h2>
|
<h2 className='FunctionCard__title'>{props.title}</h2>
|
||||||
<p className='FunctionCard__description text-center'>{props.description}</p>
|
<p className='FunctionCard__description text-center'>{props.description}</p>
|
||||||
</div>
|
|
||||||
<div className='FunctionCard__info'>
|
|
||||||
<p className='FunctionCard__category' style={{ backgroundColor: props.categorie.color }}>{props.categorie.name}</p>
|
|
||||||
<p className='FunctionCard__publication-date'>{date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className='FunctionCard__info'>
|
||||||
|
<p className='FunctionCard__category' style={{ backgroundColor: props.categorie.color }}>{props.categorie.name}</p>
|
||||||
|
<p className='FunctionCard__publication-date'>{date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
|
47
website/components/UserCard/UserCard.css
Normal file
47
website/components/UserCard/UserCard.css
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
.UserCard {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
flex-direction: column;
|
||||||
|
word-wrap: break-word;
|
||||||
|
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1rem;
|
||||||
|
margin: 0 0 50px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.UserCard__container {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--text-color);
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
.UserCard:hover {
|
||||||
|
transform: translateY(-7px);
|
||||||
|
}
|
||||||
|
/* col-md */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.UserCard {
|
||||||
|
margin: 0 30px 50px 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* col-xl */
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
.UserCard {
|
||||||
|
margin: 0 20px 50px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.UserCard__logo {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.UserCard__name {
|
||||||
|
margin: 30px 0 10px 0;
|
||||||
|
}
|
25
website/components/UserCard/UserCard.jsx
Normal file
25
website/components/UserCard/UserCard.jsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import Link from 'next/link'
|
||||||
|
import { forwardRef, memo } from 'react'
|
||||||
|
import { API_URL } from '../../utils/config/config'
|
||||||
|
import './UserCard.css'
|
||||||
|
|
||||||
|
const UserCard = memo(
|
||||||
|
forwardRef((props, ref) => {
|
||||||
|
return (
|
||||||
|
<div className='UserCard col-sm-24 col-md-10 col-xl-7' ref={ref}>
|
||||||
|
<Link href='/users/[name]' as={`/users/${props.name}`}>
|
||||||
|
<a className='UserCard__container'>
|
||||||
|
<img
|
||||||
|
className='UserCard__logo'
|
||||||
|
src={API_URL + props.logo}
|
||||||
|
alt={props.name}
|
||||||
|
/>
|
||||||
|
<h2 className='UserCard__name'>{props.name}</h2>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export default UserCard
|
@ -1,74 +1,95 @@
|
|||||||
import { useState, useEffect, useRef, useCallback } from 'react'
|
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||||
import Link from 'next/link'
|
|
||||||
import HeadTag from '../../components/HeadTag'
|
import HeadTag from '../../components/HeadTag'
|
||||||
import Loader from '../../components/Loader'
|
import Loader from '../../components/Loader'
|
||||||
|
import UserCard from '../../components/UserCard/UserCard'
|
||||||
import api from '../../utils/api'
|
import api from '../../utils/api'
|
||||||
import '../../public/css/pages/users.css'
|
import '../../public/css/pages/users.css'
|
||||||
import { API_URL } from '../../utils/config/config'
|
|
||||||
|
|
||||||
const Users = () => {
|
const Users = () => {
|
||||||
let pageUsers = 1
|
let pageUsers = 1
|
||||||
|
|
||||||
const [inputSearch, setInputSearch] = useState('')
|
const [inputSearch, setInputSearch] = useState('')
|
||||||
const [usersData, setUsersData] = useState({ totalItems: 0, hasMore: true, rows: [] })
|
const [usersData, setUsersData] = useState({
|
||||||
|
totalItems: 0,
|
||||||
|
hasMore: true,
|
||||||
|
rows: []
|
||||||
|
})
|
||||||
const [isLoadingUsers, setLoadingUsers] = useState(true)
|
const [isLoadingUsers, setLoadingUsers] = useState(true)
|
||||||
|
|
||||||
// Récupère les users si la recherche change
|
// Récupère les users si la recherche change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
pageUsers = 1
|
pageUsers = 1
|
||||||
getUsersData().then((data) => setUsersData(data))
|
getUsersData().then(data => setUsersData(data))
|
||||||
}, [inputSearch])
|
}, [inputSearch])
|
||||||
|
|
||||||
const getUsersData = async () => {
|
const getUsersData = async () => {
|
||||||
setLoadingUsers(true)
|
setLoadingUsers(true)
|
||||||
const { data } = await api.get(`/users?page=${pageUsers}&limit=15&search=${inputSearch}`)
|
const { data } = await api.get(
|
||||||
|
`/users?page=${pageUsers}&limit=15&search=${inputSearch}`
|
||||||
|
)
|
||||||
setLoadingUsers(false)
|
setLoadingUsers(false)
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSearchChange = (event) => {
|
const handleSearchChange = event => {
|
||||||
setInputSearch(event.target.value)
|
setInputSearch(event.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Permet la pagination au scroll
|
// Permet la pagination au scroll
|
||||||
const observer = useRef()
|
const observer = useRef()
|
||||||
const lastUserCardRef = useCallback((node) => {
|
const lastUserCardRef = useCallback(
|
||||||
if (isLoadingUsers) return
|
node => {
|
||||||
if (observer.current) observer.current.disconnect()
|
if (isLoadingUsers) return
|
||||||
observer.current = new window.IntersectionObserver((entries) => {
|
if (observer.current) observer.current.disconnect()
|
||||||
if (entries[0].isIntersecting && usersData.hasMore) {
|
observer.current = new window.IntersectionObserver(
|
||||||
pageUsers += 1
|
entries => {
|
||||||
getUsersData().then((data) => {
|
if (entries[0].isIntersecting && usersData.hasMore) {
|
||||||
setUsersData((oldData) => {
|
pageUsers += 1
|
||||||
return {
|
getUsersData().then(data => {
|
||||||
totalItems: data.totalItems,
|
setUsersData(oldData => {
|
||||||
hasMore: data.hasMore,
|
return {
|
||||||
rows: [...oldData.rows, ...data.rows]
|
totalItems: data.totalItems,
|
||||||
}
|
hasMore: data.hasMore,
|
||||||
})
|
rows: [...oldData.rows, ...data.rows]
|
||||||
})
|
}
|
||||||
}
|
})
|
||||||
}, { threshold: 1 })
|
})
|
||||||
if (node) observer.current.observe(node)
|
}
|
||||||
}, [isLoadingUsers, usersData.hasMore])
|
},
|
||||||
|
{ threshold: 1 }
|
||||||
|
)
|
||||||
|
if (node) observer.current.observe(node)
|
||||||
|
},
|
||||||
|
[isLoadingUsers, usersData.hasMore]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeadTag
|
<HeadTag title='Utilisateurs' description='Liste des utilisateurs.' />
|
||||||
title='Utilisateurs'
|
|
||||||
description='Liste des utilisateurs.'
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className='container text-center'>
|
<div className='container text-center'>
|
||||||
<div className='row justify-content-center'>
|
<div className='row justify-content-center'>
|
||||||
<div className='col-24'>
|
<div className='col-24'>
|
||||||
<h1 style={{ marginBottom: 0, paddingTop: '20px' }}>Utilisateurs</h1>
|
<h1 style={{ marginBottom: 0, paddingTop: '20px' }}>
|
||||||
<p style={{ marginTop: '5px' }}>La liste des utilisateurs - Total de {usersData.totalItems} utilisateurs :</p>
|
Utilisateurs
|
||||||
|
</h1>
|
||||||
|
<p style={{ marginTop: '5px' }}>
|
||||||
|
La liste des utilisateurs - Total de {usersData.totalItems}{' '}
|
||||||
|
utilisateurs :
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='Users__search-container row justify-content-center'>
|
<div className='Users__search-container row justify-content-center'>
|
||||||
<input value={inputSearch} onChange={handleSearchChange} type='search' className='Users__form-control Users__search-input' name='search' id='search' placeholder='🔎 Rechercher...' />
|
<input
|
||||||
|
value={inputSearch}
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
type='search'
|
||||||
|
className='Users__form-control Users__search-input'
|
||||||
|
name='search'
|
||||||
|
id='search'
|
||||||
|
placeholder='🔎 Rechercher...'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='row justify-content-center'>
|
<div className='row justify-content-center'>
|
||||||
@ -76,25 +97,11 @@ const Users = () => {
|
|||||||
// Si c'est le dernier élément
|
// Si c'est le dernier élément
|
||||||
if (usersData.rows.length === index + 1) {
|
if (usersData.rows.length === index + 1) {
|
||||||
return (
|
return (
|
||||||
<div ref={lastUserCardRef} key={user.id} className='UserCard col-sm-24 col-md-10 col-xl-7'>
|
<UserCard key={index} {...user} ref={lastUserCardRef} />
|
||||||
<Link href='/users/[name]' as={`/users/${user.name}`}>
|
|
||||||
<div className='UserCard__container'>
|
|
||||||
<img className='UserCard__logo' src={API_URL + user.logo} alt={user.name} />
|
|
||||||
<h2 className='UserCard__name'>{user.name}</h2>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div key={user.id} className='UserCard col-sm-24 col-md-10 col-xl-7'>
|
<UserCard key={index} {...user} />
|
||||||
<Link href='/users/[name]' as={`/users/${user.name}`}>
|
|
||||||
<div className='UserCard__container'>
|
|
||||||
<img className='UserCard__logo' src={API_URL + user.logo} alt={user.name} />
|
|
||||||
<h2 className='UserCard__name'>{user.name}</h2>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,65 +1,19 @@
|
|||||||
.Users__form-control {
|
.Users__form-control {
|
||||||
display: block;
|
display: block;
|
||||||
height: calc(1.5em + .75rem + 2px);
|
height: calc(1.5em + 0.75rem + 2px);
|
||||||
padding: .375rem .75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: #495057;
|
color: #495057;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
border: 1px solid #ced4da;
|
border: 1px solid #ced4da;
|
||||||
border-radius: .5em;
|
border-radius: 0.5em;
|
||||||
}
|
}
|
||||||
.Users__search-container {
|
.Users__search-container {
|
||||||
margin-bottom: 50px;
|
margin-bottom: 50px;
|
||||||
}
|
}
|
||||||
.Users__search-input {
|
.Users__search-input {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.UserCard {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
position: relative;
|
|
||||||
flex-direction: column;
|
|
||||||
word-wrap: break-word;
|
|
||||||
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25);
|
|
||||||
border: 1px solid black;
|
|
||||||
border-radius: 1rem;
|
|
||||||
margin: 0 0 50px 0;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all .3s;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
.UserCard__container {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.UserCard:hover {
|
|
||||||
transform: translateY(-7px);
|
|
||||||
}
|
|
||||||
/* col-md */
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.UserCard {
|
|
||||||
margin: 0 30px 50px 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* col-xl */
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
.UserCard {
|
|
||||||
margin: 0 20px 50px 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.UserCard__logo {
|
|
||||||
width: 150px;
|
|
||||||
height: 150px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
.UserCard__name {
|
|
||||||
margin: 30px 0 10px 0;
|
|
||||||
}
|
|
Reference in New Issue
Block a user