frontend: Système d'onglets pour /functions/slug

This commit is contained in:
Divlo 2020-03-23 21:22:41 +01:00
parent 0f8c1d3569
commit 53330095be
9 changed files with 185 additions and 20 deletions

View File

@ -0,0 +1,39 @@
.FunctionTabs__nav {
display: flex;
flex-wrap: wrap;
padding-left: 0;
margin-bottom: 0;
list-style: none;
border-bottom: 1px solid #d9e2ef;
margin-bottom: -1px;
}
.FunctionTabs__nav-item {
margin-bottom: -1px;
cursor: pointer;
}
.FunctionTabs__nav-link {
color: var(--text-color);
border: 1px solid #0c0b0b38;
border-bottom: 0px;
border-top-left-radius: .375rem;
border-top-right-radius: .375rem;
display: block;
padding: .5rem 1rem;
}
.FunctionTabs__nav-link-active {
border-color: #d9e2ef #d9e2ef #fff;
color: var(--important);
}
.FunctionTabs__nav-link:hover {
border-color: #f1f4f8 #f1f4f8 #d9e2ef;
text-decoration: none;
}
@media (max-width: 490px) {
.FunctionTabs__nav {
flex-direction: column;
}
.FunctionTabs__nav-link {
border-color: #f1f4f8 #f1f4f8 #d9e2ef;
}
}

View File

@ -0,0 +1,59 @@
import { Fragment, useState } from 'react';
import SwipeableViews from 'react-swipeable-views';
import './FunctionTabs.css';
function FunctionTabs(props) {
const [slideIndex, setSlideIndex] = useState(0);
return (
<Fragment>
{/* Tabs */}
<div className="container">
<div className="row justify-content-center">
<ul className="FunctionTabs__nav">
{(props.type !== 'article') &&
<li className="FunctionTabs__nav-item">
<a
className={`FunctionTabs__nav-link ${(slideIndex === 0) ? "FunctionTabs__nav-link-active" : ""}`}
onClick={() => setSlideIndex(0)}
>
Utilisation
</a>
</li>
}
<li className="FunctionTabs__nav-item">
<a
className={`FunctionTabs__nav-link ${((slideIndex === 1 && props.type !== 'article') || (props.type === 'article' && slideIndex === 0)) ? "FunctionTabs__nav-link-active" : ""}`}
onClick={() => setSlideIndex((props.type === 'article') ? 0 : 1)}
>
📝 Article
</a>
</li>
<li className="FunctionTabs__nav-item">
<a
className={`FunctionTabs__nav-link ${((slideIndex === 2 && props.type !== 'article') || (props.type === 'article' && slideIndex === 1)) ? "FunctionTabs__nav-link-active" : ""}`}
onClick={() => setSlideIndex((props.type === 'article') ? 1 : 2)}
>
📬 Commentaires
</a>
</li>
</ul>
</div>
</div>
{/* Tabs content */}
<div className="container-fluid">
<div className="row justify-content-center">
<SwipeableViews onChangeIndex={(index) => setSlideIndex(index)} index={slideIndex} enableMouseEvents>
{props.children}
</SwipeableViews>
</div>
</div>
</Fragment>
);
}
export default FunctionTabs;

View File

@ -1,26 +1,61 @@
import { Fragment } from 'react'; import { Fragment } from 'react';
import { useRouter } from 'next/router'; import Link from 'next/link';
import HeadTag from '../../components/HeadTag'; import HeadTag from '../../components/HeadTag';
import FunctionTabs from '../../components/FunctionTabs/FunctionTabs';
import redirect from '../../utils/redirect';
import api from '../../config/api';
import { API_URL } from '../../config/config'; import { API_URL } from '../../config/config';
import '../../public/css/pages/FunctionComponent.css';
const FunctionComponent = () => { const FunctionComponent = (props) => {
console.log(props);
const { slug } = useRouter().query; // Constantes
const createdAt = new Date(props.createdAt);
const publicationDate = `${('0'+createdAt.getDate()).slice(-2)}/${('0'+(createdAt.getMonth()+1)).slice(-2)}/${createdAt.getFullYear()}`;
return ( return (
<Fragment> <Fragment>
<HeadTag <HeadTag title={props.title} description={props.description} image={API_URL + props.image} />
title={slug}
description={slug} <div className="container-fluid">
image={`${API_URL}/images/functions/${slug}.png`}
/> <div className="container-fluid">
<div className="container text-center"> <div className="row justify-content-center text-center">
<div className="row justify-content-center"> <div className="FunctionComponent__top col-24">
<h1>{slug}</h1> <img src={API_URL + props.image} alt={props.title} />
<h1 className="FunctionComponent__title title-important">{props.title}</h1>
<p className="FunctionComponent__description">{props.description}</p>
<div className="FunctionCard__info">
<Link href={`/functions?categoryId=${props.categorieId}`}>
<a className="FunctionCard__category" style={{ backgroundColor: props.categorie.color, color: 'inherit' }}>{props.categorie.name}</a>
</Link>
<p className="FunctionCard__publication-date">{publicationDate}</p>
</div> </div>
</div> </div>
</div>
</div>
<div className="container-fluid">
<div className="row justify-content-center">
<FunctionTabs type={props.type}>
<div>Slide 1</div>
<div>Slide 2</div>
<div>Slide 3</div>
</FunctionTabs>
</div>
</div>
</div>
</Fragment> </Fragment>
); );
} }
export async function getServerSideProps(context) {
const { slug } = context.params;
return api.get(`/functions/${slug}`)
.then((response) => ({ props: response.data }))
.catch(() => redirect(context, '/404'));
}
export default FunctionComponent; export default FunctionComponent;

View File

@ -1,4 +1,5 @@
import { Fragment, useState, useEffect, useRef, useCallback } from 'react'; import { Fragment, useState, useEffect, useRef, useCallback } from 'react';
import { useRouter } from 'next/router';
import HeadTag from '../../components/HeadTag'; import HeadTag from '../../components/HeadTag';
import FunctionCard from '../../components/FunctionCard/FunctionCard'; import FunctionCard from '../../components/FunctionCard/FunctionCard';
import Loader from '../../components/Loader'; import Loader from '../../components/Loader';
@ -8,6 +9,8 @@ import useAPI from '../../hooks/useAPI';
const Functions = () => { const Functions = () => {
const { categoryId } = useRouter().query;
// State de recherche et de catégories // State de recherche et de catégories
const [, categories] = useAPI('/categories'); const [, categories] = useAPI('/categories');
const [inputSearch, setInputSearch] = useState({ search: "", selectedCategory: "0" }); const [inputSearch, setInputSearch] = useState({ search: "", selectedCategory: "0" });
@ -39,6 +42,13 @@ const Functions = () => {
}); });
} }
// Récupère la catégorie avec la query categoryId
useEffect(() => {
if (categoryId) {
handleChange({ target: { name: "selectedCategory", value: categoryId } });
}
}, [categoryId]);
// Récupère les fonctions si la page change // Récupère les fonctions si la page change
useEffect(() => { useEffect(() => {
getFunctionsData().then((data) => setFunctionsData({ getFunctionsData().then((data) => setFunctionsData({

View File

@ -27,7 +27,7 @@ const Home = () => {
{/* Slide 1 */} {/* Slide 1 */}
<div className="row align-items-center justify-content-center"> <div className="row align-items-center justify-content-center">
<div className="col-24"> <div className="col-24">
<h1 className="Home__title-important important">FunctionProject</h1> <h1 className="title-important">FunctionProject</h1>
<p className="Home__description"> <p className="Home__description">
Apprenez la programmation grâce à l'apprentissage par projet alias fonction.<br/> Apprenez la programmation grâce à l'apprentissage par projet alias fonction.<br/>
Découvrez la liste des fonctions disponibles : Découvrez la liste des fonctions disponibles :
@ -43,7 +43,7 @@ const Home = () => {
{/* Slide 2 */} {/* Slide 2 */}
<div className="row align-items-center justify-content-center"> <div className="row align-items-center justify-content-center">
<div className="col-24"> <div className="col-24">
<h1 className="Home__title-important important">Code Source</h1> <h1 className="title-important">Code Source</h1>
<p className="Home__description"> <p className="Home__description">
Le partage est essentiel afin de progresser. <br/> Le partage est essentiel afin de progresser. <br/>
Par conséquent chaque fonction a un article expliquant comment elle fonctionne et <br/> Par conséquent chaque fonction a un article expliquant comment elle fonctionne et <br/>

View File

@ -26,9 +26,6 @@ p {
font-size: 18px; font-size: 18px;
line-height: 1.9; line-height: 1.9;
} }
h2 {
font-size: 1.8em;
}
a:hover { a:hover {
text-decoration: underline; text-decoration: underline;
} }
@ -76,6 +73,10 @@ a:hover {
.align-items-center { .align-items-center {
align-items: center; align-items: center;
} }
.title-important {
color: var(--important);
font-weight: 500;
}
a, .important { a, .important {
color: var(--important); color: var(--important);
text-decoration: none; text-decoration: none;

View File

@ -0,0 +1,18 @@
.FunctionComponent__top {
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-top: 50px;
}
.FunctionComponent__title {
margin: 0;
}
.FunctionComponent__description {
word-break: break-all;
margin-bottom: 0;
}

View File

@ -4,9 +4,6 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.Home__title-important {
font-weight: 500;
}
.Home__logo-spin { .Home__logo-spin {
cursor: pointer; cursor: pointer;
} }

View File

@ -0,0 +1,6 @@
function redirect (ctx, path) {
ctx.res.writeHead(302, { Location: path });
ctx.res.end();
}
module.exports = redirect;