diff --git a/package-lock.json b/package-lock.json
index df276ce8a..a2d822adb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,7 +20,7 @@
},
"devDependencies": {
"@cypress/react18": "^2.0.1",
- "@mate-academy/scripts": "^1.9.12",
+ "@mate-academy/scripts": "^2.1.3",
"@mate-academy/students-ts-config": "*",
"@mate-academy/stylelint-config": "*",
"@types/node": "^20.14.10",
@@ -1184,10 +1184,11 @@
}
},
"node_modules/@mate-academy/scripts": {
- "version": "1.9.12",
- "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.9.12.tgz",
- "integrity": "sha512-/OcmxMa34lYLFlGx7Ig926W1U1qjrnXbjFJ2TzUcDaLmED+A5se652NcWwGOidXRuMAOYLPU2jNYBEkKyXrFJA==",
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-2.1.3.tgz",
+ "integrity": "sha512-a07wHTj/1QUK2Aac5zHad+sGw4rIvcNl5lJmJpAD7OxeSbnCdyI6RXUHwXhjF5MaVo9YHrJ0xVahyERS2IIyBQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@octokit/rest": "^17.11.2",
"@types/get-port": "^4.2.0",
diff --git a/package.json b/package.json
index 919fbd42b..77fc93ccf 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
},
"devDependencies": {
"@cypress/react18": "^2.0.1",
- "@mate-academy/scripts": "^1.9.12",
+ "@mate-academy/scripts": "^2.1.3",
"@mate-academy/students-ts-config": "*",
"@mate-academy/stylelint-config": "*",
"@types/node": "^20.14.10",
diff --git a/src/App.css b/src/App.css
new file mode 100644
index 000000000..bbb4a70df
--- /dev/null
+++ b/src/App.css
@@ -0,0 +1,3 @@
+iframe {
+ display: none;
+}/*# sourceMappingURL=App.css.map */
\ No newline at end of file
diff --git a/src/App.css.map b/src/App.css.map
new file mode 100644
index 000000000..e5169759d
--- /dev/null
+++ b/src/App.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["App.scss","App.css"],"names":[],"mappings":"AAAA;EACE,aAAA;ACCF","file":"App.css"}
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
index adcb8594e..ae72b4b2d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,20 +1,28 @@
-import { PeoplePage } from './components/PeoplePage';
import { Navbar } from './components/Navbar';
+import { Routes, Route, Navigate } from 'react-router-dom';
+import { HomePage } from './components/HomePage';
+import { PeoplePage } from './components/PeoplePage';
+import { NotFoundPage } from './components/NotFoundPage/NotFoundPage';
+import { HashRouter } from 'react-router-dom';
import './App.scss';
-export const App = () => {
- return (
+export const App = () => (
+
-
+
-
Home Page
-
Page not found
-
+
+ }>
+ }>
+ } />
+ } />
+ }>
+
-
+
- );
-};
+
+);
diff --git a/src/components/HomePage/HomePage.tsx b/src/components/HomePage/HomePage.tsx
new file mode 100644
index 000000000..e4cd51ac5
--- /dev/null
+++ b/src/components/HomePage/HomePage.tsx
@@ -0,0 +1,3 @@
+export const HomePage = () => {
+ return
Home Page
;
+};
diff --git a/src/components/HomePage/index.ts b/src/components/HomePage/index.ts
new file mode 100644
index 000000000..11e53da67
--- /dev/null
+++ b/src/components/HomePage/index.ts
@@ -0,0 +1 @@
+export * from './HomePage';
diff --git a/src/components/Loader/Loader.css b/src/components/Loader/Loader.css
new file mode 100644
index 000000000..243caac85
--- /dev/null
+++ b/src/components/Loader/Loader.css
@@ -0,0 +1,25 @@
+.Loader {
+ display: flex;
+ width: 100%;
+ justify-content: center;
+ align-items: center;
+}
+
+.Loader__content {
+ border-radius: 50%;
+ width: 2em;
+ height: 2em;
+ margin: 1em auto;
+ border: 0.3em solid #ddd;
+ border-left-color: #000;
+ animation: load8 1.2s infinite linear;
+}
+
+@keyframes load8 {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/src/components/Loader/Loader.css.map b/src/components/Loader/Loader.css.map
new file mode 100644
index 000000000..7255ff4a7
--- /dev/null
+++ b/src/components/Loader/Loader.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["Loader.scss","Loader.css"],"names":[],"mappings":"AAAA;EACE,aAAA;EACA,WAAA;EACA,uBAAA;EACA,mBAAA;ACCF;ADCE;EACE,kBAAA;EACA,UAAA;EACA,WAAA;EACA,gBAAA;EACA,wBAAA;EACA,uBAAA;EACA,qCAAA;ACCJ;;ADGA;EACE;IACE,uBAAA;ECAF;EDEA;IACE,yBAAA;ECAF;AACF","file":"Loader.css"}
\ No newline at end of file
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx
deleted file mode 100644
index 3f63898b2..000000000
--- a/src/components/Navbar.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-export const Navbar = () => {
- return (
-
- );
-};
diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx
new file mode 100644
index 000000000..f87d5743e
--- /dev/null
+++ b/src/components/Navbar/Navbar.tsx
@@ -0,0 +1,39 @@
+import { NavLink } from 'react-router-dom';
+
+export const Navbar = () => {
+ return (
+
+ );
+};
diff --git a/src/components/Navbar/index.ts b/src/components/Navbar/index.ts
new file mode 100644
index 000000000..e8a656233
--- /dev/null
+++ b/src/components/Navbar/index.ts
@@ -0,0 +1 @@
+export * from './Navbar';
diff --git a/src/components/NotFoundPage/NotFoundPage.tsx b/src/components/NotFoundPage/NotFoundPage.tsx
new file mode 100644
index 000000000..87fe719a7
--- /dev/null
+++ b/src/components/NotFoundPage/NotFoundPage.tsx
@@ -0,0 +1,3 @@
+export const NotFoundPage = () => {
+ return Page not found
;
+};
diff --git a/src/components/NotFoundPage/index.ts b/src/components/NotFoundPage/index.ts
new file mode 100644
index 000000000..6197aa75a
--- /dev/null
+++ b/src/components/NotFoundPage/index.ts
@@ -0,0 +1 @@
+export * from './NotFoundPage';
diff --git a/src/components/PeopleFilters.tsx b/src/components/PeopleFilters.tsx
deleted file mode 100644
index c9c819cd3..000000000
--- a/src/components/PeopleFilters.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-export const PeopleFilters = () => {
- return (
-
- );
-};
diff --git a/src/components/PeopleFilters/PeopleFilters.tsx b/src/components/PeopleFilters/PeopleFilters.tsx
new file mode 100644
index 000000000..bc603ec5c
--- /dev/null
+++ b/src/components/PeopleFilters/PeopleFilters.tsx
@@ -0,0 +1,154 @@
+import { useSearchParams } from 'react-router-dom';
+import { SearchLink } from '../SearchLink/SearchLink';
+import classNames from 'classnames';
+import { getSearchWith } from '../../utils/searchHelper';
+
+export const PeopleFilters = () => {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const sex = searchParams.get('sex');
+ const centuries = searchParams.getAll('centuries');
+ const query = searchParams.get('query');
+
+ const changeFilter = (type?: string) => {
+ if (!type) {
+ return { sex: null };
+ } else {
+ return { sex: type };
+ }
+ };
+
+ const handleCenturyClick = (century: string) => {
+ if (centuries.includes(century)) {
+ return centuries.filter(cent => cent !== century);
+ } else {
+ return [...centuries, century];
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/PeoplePage.tsx b/src/components/PeoplePage.tsx
deleted file mode 100644
index b682bad9b..000000000
--- a/src/components/PeoplePage.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { PeopleFilters } from './PeopleFilters';
-import { Loader } from './Loader';
-import { PeopleTable } from './PeopleTable';
-
-export const PeoplePage = () => {
- return (
- <>
- People Page
-
-
-
-
-
-
-
-
-
-
Something went wrong
-
-
There are no people on the server
-
-
There are no people matching the current search criteria
-
-
-
-
-
-
- >
- );
-};
diff --git a/src/components/PeoplePage/PeoplePage.tsx b/src/components/PeoplePage/PeoplePage.tsx
new file mode 100644
index 000000000..e9480ece5
--- /dev/null
+++ b/src/components/PeoplePage/PeoplePage.tsx
@@ -0,0 +1,76 @@
+/* eslint-disable @typescript-eslint/indent */
+import { useState, useEffect } from 'react';
+import { PeopleTable } from '../../components/PeopleTable';
+import { Loader } from '../../components/Loader';
+import { Person } from '../../types';
+import { getPeople } from '../../api';
+import { PeopleFilters } from '../PeopleFilters/PeopleFilters';
+import { useSearchParams } from 'react-router-dom';
+
+export const PeoplePage = () => {
+ const [people, setPeople] = useState([]);
+ const [isLoading, setIsLoading] = useState(false);
+ const [hasError, setHasError] = useState(false);
+ const [searchParams] = useSearchParams();
+ const sex = searchParams.get('sex');
+ const centuries = searchParams.getAll('centuries');
+ const query = searchParams.get('query');
+ const filteredPeople = people.filter(person => {
+ const centry = Math.ceil(person.born / 100);
+ const sexMatch = sex === null || person.sex === sex;
+ const centuryMatch =
+ centuries.includes(String(centry)) || centuries.length === 0;
+ const queryMatch =
+ query === null ||
+ person.name.toLowerCase().includes(query.toLowerCase()) ||
+ person.motherName?.toLowerCase().includes(query.toLowerCase()) ||
+ person.fatherName?.toLowerCase().includes(query.toLowerCase());
+
+ return sexMatch && centuryMatch && queryMatch;
+ });
+
+ useEffect(() => {
+ const loadingPeople = async () => {
+ setIsLoading(true);
+ try {
+ const data = await getPeople();
+
+ setPeople(data);
+ } catch (error) {
+ setHasError(true);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ loadingPeople();
+ }, []);
+
+ return (
+ <>
+ People Page
+
+ {isLoading && }
+ {hasError && Something went wrong
}
+ {!isLoading && !hasError && people.length === 0 && (
+ There are no people on the server
+ )}
+ {!isLoading && !hasError && people.length > 0 && (
+
+
+
+
+ {filteredPeople.length === 0 ? (
+
There are no people matching the current search criteria
+ ) : (
+
+ )}
+
+
+
+ )}
+ >
+ );
+};
diff --git a/src/components/PeoplePage/index.ts b/src/components/PeoplePage/index.ts
new file mode 100644
index 000000000..cf328da6f
--- /dev/null
+++ b/src/components/PeoplePage/index.ts
@@ -0,0 +1 @@
+export * from './PeoplePage';
diff --git a/src/components/PeopleTable.tsx b/src/components/PeopleTable.tsx
deleted file mode 100644
index fdd814b4a..000000000
--- a/src/components/PeopleTable.tsx
+++ /dev/null
@@ -1,645 +0,0 @@
-/* eslint-disable jsx-a11y/control-has-associated-label */
-export const PeopleTable = () => {
- return (
-
- );
-};
diff --git a/src/components/PeopleTable/PeopleTable.tsx b/src/components/PeopleTable/PeopleTable.tsx
new file mode 100644
index 000000000..1b97a37ec
--- /dev/null
+++ b/src/components/PeopleTable/PeopleTable.tsx
@@ -0,0 +1,193 @@
+import { useParams, useSearchParams } from 'react-router-dom';
+import { Person } from '../../types';
+import { PersonLink } from '../PersonLink';
+import classNames from 'classnames';
+import { SearchLink } from '../SearchLink/SearchLink';
+
+type Props = {
+ people: Person[];
+};
+
+export const PeopleTable = ({ people }: Props) => {
+ const [searchParams] = useSearchParams();
+ const sort = searchParams.get('sort');
+ const order = searchParams.get('order');
+ const { slug } = useParams();
+ const sortedPeople = [...people];
+
+ const tableSort = (columnName: string) => {
+ if (sort !== columnName) {
+ return { sort: columnName, order: null };
+ }
+
+ if (sort !== null && order === null) {
+ return { sort: columnName, order: 'desc' };
+ }
+
+ if (sort !== null && order === 'desc') {
+ return { sort: null, order: null };
+ }
+
+ return { sort: null, order: null };
+ };
+
+ if (sort !== null) {
+ switch (sort) {
+ case 'name':
+ if (order === 'desc') {
+ sortedPeople.sort((a, b) => b.name.localeCompare(a.name));
+ } else {
+ sortedPeople.sort((a, b) => a.name.localeCompare(b.name));
+ }
+
+ break;
+ case 'sex':
+ if (order === 'desc') {
+ sortedPeople.sort((a, b) => b.sex.localeCompare(a.sex));
+ } else {
+ sortedPeople.sort((a, b) => a.sex.localeCompare(b.sex));
+ }
+
+ break;
+ case 'born':
+ if (order === 'desc') {
+ sortedPeople.sort((a, b) => b.born - a.born);
+ } else {
+ sortedPeople.sort((a, b) => a.born - b.born);
+ }
+
+ break;
+ case 'died':
+ if (order === 'desc') {
+ sortedPeople.sort((a, b) => b.died - a.died);
+ } else {
+ sortedPeople.sort((a, b) => a.died - b.died);
+ }
+
+ break;
+ }
+ }
+
+ return (
+
+
+
+ |
+
+ Name
+
+
+
+
+
+
+ |
+
+
+
+ Sex
+
+
+
+
+
+
+ |
+
+
+
+ Born
+
+
+
+
+
+
+ |
+
+
+
+ Died
+
+
+
+
+
+
+ |
+
+ Mother |
+ Father |
+
+
+
+
+ {sortedPeople.map(person => {
+ const mother = people.find(p => p.name === person.motherName);
+ const father = people.find(p => p.name === person.fatherName);
+
+ return (
+
+ |
+
+ |
+
+ {person.sex} |
+ {person.born} |
+ {person.died} |
+
+
+ {mother ? (
+
+ ) : (
+ person.motherName || '-'
+ )}
+ |
+
+
+ {father ? (
+
+ ) : (
+ person.fatherName || '-'
+ )}
+ |
+
+ );
+ })}
+
+
+ );
+};
diff --git a/src/components/PeopleTable/index.ts b/src/components/PeopleTable/index.ts
new file mode 100644
index 000000000..45984e7c3
--- /dev/null
+++ b/src/components/PeopleTable/index.ts
@@ -0,0 +1 @@
+export * from './PeopleTable';
diff --git a/src/components/PersonLink/PersonLink.tsx b/src/components/PersonLink/PersonLink.tsx
new file mode 100644
index 000000000..d7da02f7b
--- /dev/null
+++ b/src/components/PersonLink/PersonLink.tsx
@@ -0,0 +1,18 @@
+import { Link } from 'react-router-dom';
+import { Person } from '../../types';
+import classNames from 'classnames';
+
+type Props = {
+ person: Person;
+};
+
+export const PersonLink = ({ person }: Props) => {
+ return (
+
+ {person.name}
+
+ );
+};
diff --git a/src/components/PersonLink/index.ts b/src/components/PersonLink/index.ts
new file mode 100644
index 000000000..87453ce6c
--- /dev/null
+++ b/src/components/PersonLink/index.ts
@@ -0,0 +1 @@
+export * from './PersonLink';
diff --git a/src/components/SearchLink.tsx b/src/components/SearchLink/SearchLink.tsx
similarity index 94%
rename from src/components/SearchLink.tsx
rename to src/components/SearchLink/SearchLink.tsx
index f78b83cbc..de19fae82 100644
--- a/src/components/SearchLink.tsx
+++ b/src/components/SearchLink/SearchLink.tsx
@@ -1,5 +1,5 @@
import { Link, LinkProps, useSearchParams } from 'react-router-dom';
-import { getSearchWith, SearchParams } from '../utils/searchHelper';
+import { getSearchWith, SearchParams } from '../../utils/searchHelper';
/**
* To replace the the standard `Link` we take all it props except for `to`
diff --git a/src/index.tsx b/src/index.tsx
index d72ba5730..529d64448 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,13 +1,8 @@
import { createRoot } from 'react-dom/client';
-import { HashRouter as Router } from 'react-router-dom';
import 'bulma/css/bulma.css';
import '@fortawesome/fontawesome-free/css/all.css';
import { App } from './App';
-createRoot(document.getElementById('root') as HTMLDivElement).render(
-
-
- ,
-);
+createRoot(document.getElementById('root') as HTMLDivElement).render();
diff --git a/tsconfig.json b/tsconfig.json
index cfb168bb2..2dad8c814 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,7 +4,11 @@
"src"
],
"compilerOptions": {
+ "jsx": "react-jsx",
"sourceMap": false,
- "types": ["node", "cypress"]
+ "types": [
+ "node",
+ "cypress"
+ ]
}
}