diff --git a/apps/website/.eslintrc.json b/apps/website/.eslintrc.json index 8d6d104..276d251 100644 --- a/apps/website/.eslintrc.json +++ b/apps/website/.eslintrc.json @@ -23,7 +23,9 @@ } ], "rules": { - "@next/next/no-html-link-for-pages": "off" + "@next/next/no-html-link-for-pages": "off", + "@typescript-eslint/ban-ts-comment": "off", + "react/no-unescaped-entities": "off" }, "env": { "jest": true diff --git a/apps/website/components/blog-pagination/blog-pagination.module.scss b/apps/website/components/blog-pagination/blog-pagination.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/components/blog-pagination/blog-pagination.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/components/blog-pagination/blog-pagination.spec.tsx b/apps/website/components/blog-pagination/blog-pagination.spec.tsx new file mode 100644 index 0000000..f4cca7b --- /dev/null +++ b/apps/website/components/blog-pagination/blog-pagination.spec.tsx @@ -0,0 +1,10 @@ +import { render } from '@testing-library/react'; + +import BlogPagination from './blog-pagination'; + +describe('BlogPagination', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/website/components/blog-pagination/blog-pagination.tsx b/apps/website/components/blog-pagination/blog-pagination.tsx new file mode 100644 index 0000000..0593cd1 --- /dev/null +++ b/apps/website/components/blog-pagination/blog-pagination.tsx @@ -0,0 +1,74 @@ +import styles from './blog-pagination.module.scss'; +import {useRouter} from "next/router"; + +/* eslint-disable-next-line */ +export interface BlogPaginationProps { + paginator: any; +} + +export function BlogPagination({paginator}: BlogPaginationProps) { + const router = useRouter(); + const {total, pageCount, page} = paginator.pagination; + const isOnlyOne = pageCount > 0 && pageCount === 1; + const isEmpty = total === 0; + const isFirst = page === 1; + const isLast = page === pageCount; + const goPrevious = async () => { + await router.push(`${window.location.origin}${window.location.pathname}?page=${page - 1}`); + } + + const goNext = async () => { + await router.push(`${window.location.origin}${window.location.pathname}?page=${page + 1}`); + } + + return ( +
+
+ {isOnlyOne && !isEmpty && ( + {total} article(s))} + {!isOnlyOne && !isEmpty && ( + Articles {page} à {pageCount} sur un total de {total} + )} +
+ + +
+
+
+ ); +} + +export default BlogPagination; diff --git a/apps/website/components/blog-search/blog-search.module.scss b/apps/website/components/blog-search/blog-search.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/components/blog-search/blog-search.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/components/blog-search/blog-search.spec.tsx b/apps/website/components/blog-search/blog-search.spec.tsx new file mode 100644 index 0000000..3f665ab --- /dev/null +++ b/apps/website/components/blog-search/blog-search.spec.tsx @@ -0,0 +1,10 @@ +import { render } from '@testing-library/react'; + +import BlogSearch from './blog-search'; + +describe('BlogSearch', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/website/components/blog-search/blog-search.tsx b/apps/website/components/blog-search/blog-search.tsx new file mode 100644 index 0000000..d3b2b03 --- /dev/null +++ b/apps/website/components/blog-search/blog-search.tsx @@ -0,0 +1,115 @@ +import {useEffect, useState} from "react"; +import {BehaviorSubject, catchError, debounceTime, delay, distinctUntilChanged, filter, map, of, switchMap} from "rxjs"; +import {useRouter} from "next/router"; +import {MeiliSearch} from "meilisearch"; +import {useDispatch} from "react-redux"; + +import {environment} from "../../environments/environment"; + +import styles from './blog-search.module.scss'; +import {setSearchState} from "../../store/searchSlice"; + +/* eslint-disable-next-line */ +export interface BlogSearchProps { +} + +export function BlogSearch(props: BlogSearchProps) { + const router = useRouter(); + const [client, setClient] = useState(null); + const [loading, setLoading] = useState(false); + const [subject, setSubject] = useState(null); + const dispatch = useDispatch(); + + useEffect(() => { + if (subject === null) { + const sub = new BehaviorSubject(''); + const client = new MeiliSearch({ + host: 'http://127.0.0.1:7700', + apiKey: environment.meiliMasterKey + }) + setSubject(sub); + setClient(client); + } else { + subject.pipe( + map((s: string) => s.trim()), + distinctUntilChanged(), + filter((s: string) => s.length >= 2), + debounceTime(200), + map((value) => { + setLoading(true); + return value; + }), + delay(2000), + switchMap(async (value: string) => { + const index = await client.getIndex('article'); + const articles = await index.search(value); + setLoading(false); + await router.push('/search'); + dispatch(setSearchState(articles.hits)); + }), + catchError((err) => of(false)) + ).subscribe(() => setLoading(false)); + } + }, [subject]); + + const onSearchChange = (e) => subject.next(e.target.value); + + return ( +
+
+ +
+
+ +
+ + {!loading && ()} + {loading && ( + )} +
+
+ +
+ ); +} + +export default BlogSearch; diff --git a/apps/website/components/card-blog-details/card-blog-details.module.scss b/apps/website/components/card-blog-details/card-blog-details.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/components/card-blog-details/card-blog-details.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/components/card-blog-details/card-blog-details.spec.tsx b/apps/website/components/card-blog-details/card-blog-details.spec.tsx new file mode 100644 index 0000000..75c020e --- /dev/null +++ b/apps/website/components/card-blog-details/card-blog-details.spec.tsx @@ -0,0 +1,10 @@ +import { render } from '@testing-library/react'; + +import CardBlogDetails from './card-blog-details'; + +describe('CardBlogDetails', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/website/components/card-blog-details/card-blog-details.tsx b/apps/website/components/card-blog-details/card-blog-details.tsx new file mode 100644 index 0000000..6d849c1 --- /dev/null +++ b/apps/website/components/card-blog-details/card-blog-details.tsx @@ -0,0 +1,110 @@ +import delve from 'dlv'; +import NextImage from "next/image"; +import { + FacebookIcon, + FacebookMessengerIcon, + FacebookMessengerShareButton, + FacebookShareButton, + LinkedinIcon, + LinkedinShareButton, + TwitterIcon, + TwitterShareButton, + WhatsappIcon, + WhatsappShareButton +} from "react-share"; + +import {environment} from "../../environments/environment"; + +import styles from './card-blog-details.module.scss'; + +/* eslint-disable-next-line */ +export interface CardBlogDetailsProps { + item: any; +} + +export function CardBlogDetails({item}: CardBlogDetailsProps) { + const {alternativeText, width, height} = item.image.data.attributes; + const {slug} = item.category.data.attributes; + const {title} = item; + const shareUrl = `${environment.appUrl}/blog/${slug}/${item.slug}`; + return ( +
+
+

+ {delve(item, 'title', 'N/A')} +

+
+ + +
+
+
+
+
+
+ +
+
+
+
+
+

Partager cet article

+
+ + + + + + + + + + + + + + + +
+
+
+
+
+ ); +} + +export default CardBlogDetails; diff --git a/apps/website/components/card-blog/card-blog.module.scss b/apps/website/components/card-blog/card-blog.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/components/card-blog/card-blog.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/components/card-blog/card-blog.spec.tsx b/apps/website/components/card-blog/card-blog.spec.tsx new file mode 100644 index 0000000..7068caf --- /dev/null +++ b/apps/website/components/card-blog/card-blog.spec.tsx @@ -0,0 +1,10 @@ +import { render } from '@testing-library/react'; + +import CardBlog from './card-blog'; + +describe('CardBlog', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/website/components/card-blog/card-blog.tsx b/apps/website/components/card-blog/card-blog.tsx new file mode 100644 index 0000000..d3aa0a9 --- /dev/null +++ b/apps/website/components/card-blog/card-blog.tsx @@ -0,0 +1,57 @@ +import delve from 'dlv'; +import { format } from 'date-fns'; +import {environment} from '../../environments/environment'; + +import styles from './card-blog.module.scss'; + +/* eslint-disable-next-line */ +export interface CardBlogProps { + item: object; +} + +export function CardBlog({item}: CardBlogProps) { + return ( +
+
+
+ Modern building architecture +
+
+
+ {format(new Date(delve(item, 'attributes.publishedAt', 'Jun 1, 2020')),'dd/MM/yyyy')} + + {delve(item, 'attributes.category.data.attributes.name', 'N/A')} + +
+
+ + {delve(item, 'attributes.title', 'N/A')} + +

{delve(item, 'attributes.description', 'N/A')}

+
+ +
+ +
+ +
+ ); +} + +export default CardBlog; diff --git a/apps/website/components/categories/categories.module.scss b/apps/website/components/categories/categories.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/components/categories/categories.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/components/categories/categories.spec.tsx b/apps/website/components/categories/categories.spec.tsx new file mode 100644 index 0000000..deaa1e1 --- /dev/null +++ b/apps/website/components/categories/categories.spec.tsx @@ -0,0 +1,10 @@ +import { render } from '@testing-library/react'; + +import Categories from './categories'; + +describe('Categories', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/website/components/categories/categories.tsx b/apps/website/components/categories/categories.tsx new file mode 100644 index 0000000..e31cdce --- /dev/null +++ b/apps/website/components/categories/categories.tsx @@ -0,0 +1,43 @@ +import delve from 'dlv'; + +import {environment} from "../../environments/environment"; + +import styles from './categories.module.scss'; +import Link from "next/link"; + +/* eslint-disable-next-line */ +export interface CategoriesProps { + items: object[]; +} + +// + +export function Categories({items = []}: CategoriesProps) { + return ( +
+
+
Catégories
+
+
+
    + {items.map((item: any, index: number) => (
  • +
    +
    + Neil image +
    +
    +

    {delve(item, 'attributes.name', 'N/A')}

    +
    +
    +
  • )) + } +
+
+
+ ); +} + +export default Categories; diff --git a/apps/website/components/footer/footer.tsx b/apps/website/components/footer/footer.tsx index b76978b..dd74c44 100644 --- a/apps/website/components/footer/footer.tsx +++ b/apps/website/components/footer/footer.tsx @@ -15,7 +15,7 @@ export function Footer({items = []}) { } return ( -