Skip to content

Commit

Permalink
feat: add server-side routing
Browse files Browse the repository at this point in the history
  • Loading branch information
Julien Bouquillon committed Oct 3, 2018
1 parent 94acf60 commit 8eb9bcd
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 80 deletions.
53 changes: 53 additions & 0 deletions pages/question.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from "react";
import { withRouter } from "next/router";
import Head from "next/head";
import fetch from "isomorphic-unfetch";
import { Container, Alert } from "@socialgouv/code-du-travail-ui";

import Search from "../src/search/Search";
import Answer from "../src/search/Answer";
import api from "../conf/api.js";

const BigError = ({ children }) => (
<Container style={{ fontSize: "2em", textAlign: "center", margin: "20%" }}>
<Alert warning>{children}</Alert>
</Container>
);

class Question extends React.Component {
static async getInitialProps({ res, query }) {
console.log("getInitialProps", query);
return await fetch(`${api.BASE_URL}/items/faq/${query.slug}`)
.then(r => r.json())
.then(data => ({
data
}))
.catch(e => {
console.log("e", e);
res.statusCode = 404;
throw e;
});
}

render() {
const { data } = this.props;
return (
<React.Fragment>
<Head>
<title>{data._source.title}</title>
</Head>
<Search />
{!data && <BigError>Cette question n'a pas été trouvée</BigError>}
{data && (
<Answer
title={data._source.title}
html={data._source.text}
footer="FAQ"
/>
)}
</React.Fragment>
);
}
}

export default withRouter(Question);
128 changes: 128 additions & 0 deletions pages/theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React from "react";
import { withRouter } from "next/router";
import Head from "next/head";
import { BreadCrumbs, Container, Alert } from "@socialgouv/code-du-travail-ui";

import find from "unist-util-find";
import parents from "unist-util-parents";

import { Link } from "../routes";
import Search from "../src/search/Search";
import Categories from "../src/Categories";
import themes from "../src/data/themes2";

const BigError = ({ children }) => (
<div
style={{
fontSize: "1.3em",
textAlign: "center",
margin: "40px auto",
background: "var(--color-light-background)"
}}
>
<Alert warning>{children}</Alert>
<br />
<br />
</div>
);

// check if node definition match given slug
const slugMatch = (node, slug) =>
Array.isArray(node.slug) ? node.slug.join("/") === slug : node.slug === slug;

// build list of parents data for the breadcrumbs
const getParents = node => {
const p = [];
if (node.type !== "root") {
p.push(node);
}
let cur = node && node.parent;
while (cur) {
p.push(cur);
cur = cur.parent;
}
return p.reverse();
};

// return breadcrumbs components
const getBreadcrumbs = parents =>
(parents &&
parents.map(
(parent, i) =>
i === 0 ? (
<Link key={parent.title} route="themes">
<a title={parent.title}>{parent.title}</a>
</Link>
) : i === parents.length - 1 ? (
<span title={parent.title}>{parent.title}</span>
) : (
<Link
key={parent.title}
route="theme"
params={{ slug: parent.slug || ["/"] }}
>
<a title={parent.title}>{parent.title}</a>
</Link>
)
)) ||
[];

// Theme page
class Theme extends React.Component {
static async getInitialProps({ res, query }) {
// build a unist tree from themes.json
const themeTree = parents({
type: "root",
title: "Thèmes",
children: themes
});
// get current theme
const theme =
find(themeTree, n => slugMatch(n, query.slug || "/")) || themeTree;

// get theme parents for breadcrumbs
const themeParents = getParents(theme);

if (!theme && res) {
res.statusCode = 404;
}
return { theme, parents: themeParents };
}

render() {
const { theme, parents } = this.props;
const breadCrumbs = getBreadcrumbs(parents);
return (
<React.Fragment>
<Head>
<title>{theme && theme.title}</title>
</Head>
<Search />
<Container>
{!theme && <BigError>Ce thème n&apos;a pas été trouvé</BigError>}
{(breadCrumbs &&
breadCrumbs.length && (
<h2 style={{ textAlign: "center", margin: 20 }}>
<BreadCrumbs entries={breadCrumbs} />
</h2>
)) || (
<h2 style={{ textAlign: "center", margin: 20 }}>
Choisissez un thème :
</h2>
)}
{(theme &&
theme.children &&
theme.children.length && (
<Categories title={null} themes={theme.children} />
)) || (
<BigError>
Aucun contenu actuellement disponible sur ce thème :/
</BigError>
)}
</Container>
</React.Fragment>
);
}
}

export default withRouter(Theme);
10 changes: 8 additions & 2 deletions routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ module.exports = routes()
// - http://localhost:3000/
// - http://localhost:3000/?q=travail
// - http://localhost:3000/questions/Zm5o72QB0wLMRXWgrAhM
.add("index", "/:type(questions)/:id")

.add("explorer", "/explorer");
.add({ name: "question", page: "question", pattern: "/questions/:slug" })

.add({ name: "theme", page: "theme", pattern: "/themes/:slug+" }) // slug is an array of slugs
.add({ name: "themes", page: "theme", pattern: "/themes" })

.add({ name: "explorer", page: "explorer", pattern: "/explorer" })

.add({ name: "index", page: "index", pattern: "/" });
17 changes: 17 additions & 0 deletions src/search/Answer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from "react";

import { Section } from "@socialgouv/code-du-travail-ui";

const Answer = ({ title, html, footer }) => (
<Section light>
<header>
<h2>{title}</h2>
</header>
<div dangerouslySetInnerHTML={{ __html: html }} />
<footer>
<p>{footer}</p>
</footer>
</Section>
);

export default Answer;
104 changes: 51 additions & 53 deletions src/search/Search.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,45 @@
import * as nodeUrl from "url";
import memoize from "memoize-state";
import React from "react";
import { Router } from "../../routes";
import { withRouter } from "next/router";
import { Alert, Section } from "@socialgouv/code-du-travail-ui";

import Alert from "../common/Alert";
import { Router } from "../../routes";
import api from "../../conf/api.js";
import SearchAnswer from "./SearchAnswer";
import SearchResults from "./SearchResults";
import Categories from "./Categories";

const Disclaimer = () => (
<p>
Ce site est <b>en cours de construction</b> : les données qui s'y trouvent
peuvent être erronées ou imprécises.
<br />
<a
target="_blank"
className="external-link__after"
rel="noopener noreferrer"
href="https://www.legifrance.gouv.fr/affichTexteArticle.do;jsessionid=AE9DCF75DDCF0465784CEE0E7D62729F.tplgfr37s_2?idArticle=JORFARTI000035607420&cidTexte=JORFTEXT000035607388&dateTexte=29990101&categorieLien=id"
>
L'ouverture officielle du site est prévue pour 2020.
</a>
</p>
);

const SearchForm = ({ query, onChange, onKeyDown, onSubmit }) => (
<form className="search__form" onSubmit={onSubmit}>
<input
aria-label="Posez votre question"
className="search__input"
onChange={onChange}
onKeyDown={onKeyDown}
placeholder="Posez votre question"
type="search"
value={query}
/>
<button type="submit" className="btn btn__img btn__img__search">
<span className="hidden">Rechercher</span>
</button>
</form>
);

class Search extends React.Component {
state = {
Expand Down Expand Up @@ -92,12 +123,13 @@ class Search extends React.Component {

render() {
const { data, error, pendingXHR, query } = this.state;
const { router } = this.props;
// const { router } = this.props;
// console.log({ data, error, pendingXHR, query });

const xhrErrorJsx = error ? (
<div className="section-light">
<div className="container">
<Alert category="danger">{error.message}</Alert>
<Alert danger>{error.message}</Alert>
</div>
</div>
) : null;
Expand All @@ -108,63 +140,29 @@ class Search extends React.Component {
</p>
) : null;

const showAnswer = router.query && router.query.type === "questions";

let content = null;
if (showAnswer) {
content = <SearchAnswer data={data} id={router.query.id} />;
} else {
if (!data) {
// No query.
content = <Categories urlUpdate={this.urlUpdate} />;
} else {
content = <SearchResults data={data} query={query} />;
}
}

return (
<div>
<section className="section-light shadow-bottom">
<div className="container">
<div className=" shadow-bottom">
<Section light>
<div className="search">
<header>
<h1 className="no-margin">
Posez votre question sur le droit du travail
</h1>
<p>
Ce site est <b>en cours de construction</b> : les données qui
s'y trouvent peuvent être erronées ou imprécises.
<br />
<a
target="_blank"
className="external-link__after"
rel="noopener noreferrer"
href="https://www.legifrance.gouv.fr/affichTexteArticle.do;jsessionid=AE9DCF75DDCF0465784CEE0E7D62729F.tplgfr37s_2?idArticle=JORFARTI000035607420&cidTexte=JORFTEXT000035607388&dateTexte=29990101&categorieLien=id"
>
L'ouverture officielle du site est prévue pour 2020.
</a>
</p>
<Disclaimer />
</header>
<form className="search__form" onSubmit={this.onFormSubmit}>
<input
aria-label="Posez votre question"
className="search__input"
onChange={this.onSearchInputChange}
onKeyDown={this.onKeyDown}
placeholder="Exemple: mon contrat de travail doit il être écrit?"
type="search"
value={query}
/>
<button type="submit" className="btn btn__img btn__img__search">
<span className="hidden">Rechercher</span>
</button>
</form>
{loadingJsx}
<SearchForm
query={query}
onChange={this.onSearchInputChange}
onKeyDown={this.onKeyDown}
onSubmit={this.onFormSubmit}
/>
</div>
</div>
</section>
</Section>
</div>
{loadingJsx}
{xhrErrorJsx}
{content}
{data && <SearchResults data={data} query={query} />}
</div>
);
}
Expand Down
25 changes: 8 additions & 17 deletions src/search/SearchAnswer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import React from "react";

import Alert from "../common/Alert";
import api from "../../conf/api.js";
import FeedbackForm from "../common/FeedbackForm.js";
import SeeAlso from "../common/SeeAlso";
import Answer from "./Answer";
// import FeedbackForm from "../common/FeedbackForm.js";
// import SeeAlso from "../common/SeeAlso";

// Display the details of a single result.

Expand Down Expand Up @@ -73,21 +74,11 @@ class SearchAnswer extends React.Component {

return (
<React.Fragment>
<section className="section-light">
<div className="container">
<div className="wrapper-light">
<header>
<h2>{data._source.title}</h2>
</header>
<div dangerouslySetInnerHTML={{ __html: data._source.text }} />
<footer>
<p>{source}</p>
</footer>
</div>
</div>
</section>
<SeeAlso />
<FeedbackForm />
<Answer
title={data._source.title}
html={data._source.text}
footer={source}
/>
</React.Fragment>
);
}
Expand Down
Loading

0 comments on commit 8eb9bcd

Please sign in to comment.