├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── README.md
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.css
├── App.test.tsx
├── App.tsx
├── assets
│ └── icons
│ │ ├── check.svg
│ │ └── select.svg
├── components
│ ├── CharacterTable
│ │ ├── CharacterRow
│ │ │ ├── index.tsx
│ │ │ └── styles.module.scss
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ ├── Container
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ ├── CustomButton
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ └── Select
│ │ ├── index.tsx
│ │ └── styles.module.scss
├── index.css
├── index.tsx
├── layouts
│ └── MainLayout
│ │ ├── index.tsx
│ │ └── styles.module.scss
├── logo.svg
├── pages
│ ├── HouseDetails
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ └── TableOfCharacters
│ │ ├── index.tsx
│ │ └── styles.module.scss
├── react-app-env.d.ts
├── reportWebVitals.ts
├── routes
│ └── MainRoute
│ │ └── index.tsx
├── setupTests.ts
└── shared
│ └── type.d.ts
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | 'airbnb-typescript',
4 | 'airbnb/hooks',
5 | 'plugin:@typescript-eslint/recommended',
6 | 'plugin:jest/recommended',
7 | 'plugin:prettier/recommended',
8 | ],
9 | plugins: ['react', '@typescript-eslint', 'jest', 'import'],
10 | env: {
11 | browser: true,
12 | es6: true,
13 | jest: true,
14 | },
15 | globals: {
16 | Atomics: 'readonly',
17 | SharedArrayBuffer: 'readonly',
18 | },
19 | parser: '@typescript-eslint/parser',
20 | parserOptions: {
21 | ecmaFeatures: {
22 | jsx: true,
23 | },
24 | ecmaVersion: 2018,
25 | sourceType: 'module',
26 | project: './tsconfig.json',
27 | },
28 | rules: {
29 | 'linebreak-style': 'off',
30 | 'react-hooks/exhaustive-deps': 'off',
31 | 'prettier/prettier': [
32 | 'error',
33 | {
34 | endOfLine: 'auto',
35 | },
36 | ],
37 | },
38 | };
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "es5",
8 | "bracketSpacing": true,
9 | "arrowParens": "avoid",
10 | "rangeStart": 0,
11 | "rangeEnd": 9007199254740991,
12 | "requirePragma": false,
13 | "insertPragma": false,
14 | "proseWrap": "preserve",
15 | "endOfLine": "auto"
16 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "got-api-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@headlessui/react": "^1.6.5",
7 | "@testing-library/jest-dom": "^5.16.4",
8 | "@testing-library/react": "^13.3.0",
9 | "@testing-library/user-event": "^13.5.0",
10 | "@types/jest": "^27.5.2",
11 | "@types/node": "^16.11.41",
12 | "@types/react": "^18.0.14",
13 | "@types/react-dom": "^18.0.5",
14 | "axios": "^0.27.2",
15 | "classnames": "^2.3.1",
16 | "react": "^18.2.0",
17 | "react-dom": "^18.2.0",
18 | "react-router-dom": "^6.3.0",
19 | "react-scripts": "5.0.1",
20 | "typescript": "^4.7.4",
21 | "web-vitals": "^2.1.4"
22 | },
23 | "scripts": {
24 | "start": "react-scripts start",
25 | "build": "react-scripts build",
26 | "test": "react-scripts test",
27 | "eject": "react-scripts eject",
28 | "lint": "tsc --noEmit && eslint src/**/*.ts{,x}"
29 | },
30 | "eslintConfig": {
31 | "extends": [
32 | "react-app",
33 | "react-app/jest"
34 | ]
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | },
48 | "devDependencies": {
49 | "@typescript-eslint/eslint-plugin": "^5.30.0",
50 | "@typescript-eslint/parser": "^5.30.0",
51 | "autoprefixer": "^10.4.7",
52 | "eslint": "8.2.0",
53 | "eslint-config-airbnb": "19.0.4",
54 | "eslint-config-airbnb-typescript": "^17.0.0",
55 | "eslint-config-prettier": "^8.5.0",
56 | "eslint-plugin-import": "2.25.3",
57 | "eslint-plugin-jest": "^26.5.3",
58 | "eslint-plugin-jsx-a11y": "6.5.1",
59 | "eslint-plugin-prettier": "^4.1.0",
60 | "eslint-plugin-react": "^7.30.1",
61 | "eslint-plugin-react-hooks": "4.3.0",
62 | "postcss": "^8.4.14",
63 | "prettier": "^2.7.1",
64 | "sass": "^1.53.0",
65 | "tailwindcss": "^3.1.4"
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3solution/got-api-app_React_Typescript/affd8f59bcbb65cb8f163962830b1622520422ba/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | GoT API App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3solution/got-api-app_React_Typescript/affd8f59bcbb65cb8f163962830b1622520422ba/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3solution/got-api-app_React_Typescript/affd8f59bcbb65cb8f163962830b1622520422ba/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | render();
7 | const linkElement = screen.getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './App.css';
3 |
4 | import MainRoute from './routes/MainRoute';
5 |
6 | function App() {
7 | return ;
8 | }
9 |
10 | export default App;
11 |
--------------------------------------------------------------------------------
/src/assets/icons/check.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/select.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/CharacterTable/CharacterRow/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Select from '../../Select';
3 | import styles from './styles.module.scss';
4 |
5 | type Props = {
6 | character: CharacterType;
7 | };
8 |
9 | const CharacterRow: React.FC = ({ character }) => {
10 | const {
11 | allegiances: { 0: initialAllegiances },
12 | } = character;
13 | const [selectedItem, setSelectedItem] = useState(initialAllegiances);
14 |
15 | useEffect(() => {
16 | setSelectedItem(initialAllegiances);
17 | }, [initialAllegiances]);
18 |
19 | return (
20 |
21 | {character.name} |
22 | {character.alive} |
23 | {character.gender} |
24 | {character.culture} |
25 |
26 |
32 | |
33 |
34 | );
35 | };
36 |
37 | export default CharacterRow;
38 |
--------------------------------------------------------------------------------
/src/components/CharacterTable/CharacterRow/styles.module.scss:
--------------------------------------------------------------------------------
1 | .tdCharacter {
2 | @apply px-6 py-4 text-sm text-gray-900 whitespace-nowrap;
3 | }
4 |
5 | .td {
6 | @apply px-6 py-4 text-sm text-gray-500 whitespace-nowrap;
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/CharacterTable/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CharacterRow from './CharacterRow';
3 |
4 | import styles from './styles.module.scss';
5 |
6 | type Props = {
7 | characters: Array;
8 | gender: string;
9 | filter: string;
10 | };
11 |
12 | const CharacterTable: React.FC = ({ characters, gender, filter }) => {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Character
23 | |
24 |
25 | Alive
26 | |
27 |
28 | Gender
29 | |
30 |
31 | Culture
32 | |
33 |
34 | Allegiances
35 | |
36 |
37 |
38 |
39 | {characters !== undefined &&
40 | characters
41 | .filter(
42 | item =>
43 | (filter && filter.length > 0
44 | ? item.culture.toLowerCase().includes(filter.toLowerCase())
45 | : true) &&
46 | (gender.toLowerCase() !== 'all' ? item.gender.toLowerCase() === gender.toLowerCase() : true)
47 | )
48 | .map((character: CharacterType, index) => )}
49 |
50 |
51 |
52 |
53 |
54 |
55 | );
56 | };
57 |
58 | export default CharacterTable;
59 |
--------------------------------------------------------------------------------
/src/components/CharacterTable/styles.module.scss:
--------------------------------------------------------------------------------
1 | .tableWrapper {
2 | @apply flex flex-col my-5;
3 |
4 | .overflow {
5 | @apply -my-2 overflow-x-auto overflow-y-auto sm:-mx-6 lg:-mx-8;
6 |
7 | .contentWrapper {
8 | @apply inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8;
9 |
10 | .content {
11 | @apply overflow-hidden border-b border-gray-200 shadow sm:rounded-lg;
12 |
13 | .table {
14 | @apply min-w-full divide-y divide-gray-200;
15 |
16 | .thead {
17 | @apply bg-gray-50;
18 |
19 | .th {
20 | @apply px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase;
21 | }
22 | }
23 | }
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/Container/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 |
4 | import styles from './styles.module.scss';
5 |
6 | type ContainerProps = {
7 | children: React.ReactNode;
8 | className?: string;
9 | };
10 |
11 | const Container: React.FC = ({ children, className = '' }) => {
12 | return {children}
;
13 | };
14 |
15 | export default Container;
16 |
--------------------------------------------------------------------------------
/src/components/Container/styles.module.scss:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | @apply container mx-auto px-2;
3 | }
--------------------------------------------------------------------------------
/src/components/CustomButton/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 |
4 | import styles from './styles.module.scss';
5 | type Props = {
6 | text: string;
7 | onClick: React.MouseEventHandler;
8 | disable?: boolean;
9 | className?: string;
10 | };
11 | const CustomButton: React.FC = ({ text, className = '', onClick, disable = false }) => {
12 | return (
13 |
16 | );
17 | };
18 |
19 | export default CustomButton;
20 |
--------------------------------------------------------------------------------
/src/components/CustomButton/styles.module.scss:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | @apply px-4 py-2 border border-gray-300 hover:bg-gray-400 disabled:bg-white disabled:cursor-no-drop;
3 | }
--------------------------------------------------------------------------------
/src/components/Select/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect } from 'react';
2 | import { Listbox, Transition } from '@headlessui/react';
3 | import classNames from 'classnames';
4 |
5 | import { ReactComponent as SelectIcon } from '../../assets/icons/select.svg';
6 | import { ReactComponent as CheckIcon } from '../../assets/icons/check.svg';
7 |
8 | import styles from './styles.module.scss';
9 | import { Link } from 'react-router-dom';
10 |
11 | type Props = {
12 | options: Array;
13 | selectedItem: string;
14 | setSelectedItem: React.Dispatch>;
15 | link: boolean;
16 | className?: string;
17 | };
18 |
19 | const Select: React.FC = ({ className = '', options, selectedItem, setSelectedItem, link }) => {
20 | return (
21 |
22 |
23 |
24 |
25 | {selectedItem}
26 |
27 |
28 |
29 |
30 |
31 |
32 | {options.map((item, itemIdx) => (
33 |
36 | classNames(styles.content, {
37 | [styles.active]: active,
38 | })
39 | }
40 | value={item}
41 | >
42 | {({ selected }) => (
43 | <>
44 | {link === true && item !== 'No allegiances' ? (
45 |
52 | {item}
53 |
54 | ) : (
55 |
60 | {item}
61 |
62 | )}
63 | {selected ? (
64 |
65 |
66 |
67 | ) : null}
68 | >
69 | )}
70 |
71 | ))}
72 |
73 |
74 |
75 |
76 |
77 | );
78 | };
79 |
80 | export default Select;
81 |
--------------------------------------------------------------------------------
/src/components/Select/styles.module.scss:
--------------------------------------------------------------------------------
1 | .contentWrapper {
2 | @apply w-full mt-1 relative;
3 |
4 | .listBoxButton {
5 | @apply w-full py-2 pl-3 pr-10 text-left bg-white border border-gray-300 rounded-lg shadow-md cursor-default focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm;
6 |
7 | .selectedItem {
8 | @apply block truncate;
9 | }
10 |
11 | .selectedIconWrapper {
12 | @apply absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none;
13 |
14 | .selectIcon {
15 | @apply w-5 h-5 text-gray-400;
16 | }
17 | }
18 | }
19 |
20 | .listBoxOptionWrapper {
21 | @apply absolute py-1 mt-1 w-full overflow-auto text-base bg-gray-100 z-50 rounded-md shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm;
22 |
23 | .content {
24 | @apply relative cursor-default select-none py-2 pl-10 pr-4 text-gray-900;
25 |
26 | &.active {
27 | @apply bg-amber-100 text-amber-900;
28 | }
29 |
30 | .text {
31 | @apply block truncate font-normal;
32 |
33 | &.selected {
34 | @apply font-medium;
35 | }
36 | }
37 |
38 | .icon {
39 | @apply absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600;
40 |
41 | .checkIcon {
42 | @apply w-5 h-5;
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | body {
6 | margin: 0;
7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
8 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
9 | sans-serif;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | }
13 |
14 | code {
15 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
16 | monospace;
17 | }
18 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
8 | root.render(
9 |
10 |
11 |
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/src/layouts/MainLayout/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Container from '../../components/Container';
4 |
5 | import styles from './styles.module.scss';
6 |
7 | type Props = {
8 | children: React.ReactNode;
9 | };
10 |
11 | const MainLayout: React.FC = ({ children }) => {
12 | return (
13 |
14 | GoT API App
15 | {children}
16 |
17 | );
18 | };
19 |
20 | export default MainLayout;
21 |
--------------------------------------------------------------------------------
/src/layouts/MainLayout/styles.module.scss:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | @apply flex justify-center items-center py-10 text-4xl text-emerald-600;
3 | }
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/HouseDetails/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { Link, useLocation } from 'react-router-dom';
3 | import axios from 'axios';
4 |
5 | import styles from './styles.module.scss';
6 |
7 | const HouseDetails = () => {
8 | const [homeDetails, setHomeDetails] = useState();
9 | const { state }: { state: any } = useLocation();
10 |
11 | const getHomeDetailsData = () => {
12 | axios.get(`https://www.anapioficeandfire.com/api/houses/${state.homeAPI}`).then(res => {
13 | setHomeDetails({
14 | name: res.data.name,
15 | region: res.data.region,
16 | costOfArms: res.data.costOfArms,
17 | words: res.data.words,
18 | titles: res.data.titles,
19 | seats: res.data.seats,
20 | diedOut: res.data.dieOut,
21 | overlord: res.data.overlord,
22 | cadetBranches: res.data.cadetBranches.length,
23 | });
24 | });
25 | };
26 |
27 | useEffect(() => {
28 | getHomeDetailsData();
29 | }, []);
30 |
31 | return (
32 |
33 |
34 |
Name of the House:
35 |
{homeDetails?.name}
36 |
Region:
37 |
{homeDetails?.region}
38 |
Coat of Arms:
39 |
{homeDetails?.costOfArms}
40 |
Words:
41 |
{homeDetails?.words}
42 |
Titles:
43 |
44 | {homeDetails?.titles.map(item => (
45 |
{`- ${item}`}
46 | ))}
47 |
48 |
Seats:
49 |
50 | {homeDetails?.seats.map(item => (
51 |
{`- ${item}`}
52 | ))}
53 |
54 |
Has died out:
55 |
{homeDetails?.diedOut}
56 |
Has overlord:
57 |
{homeDetails?.overlord}
58 |
Number of Cadet Branches:
59 |
{homeDetails?.cadetBranches}
60 |
61 |
62 |
63 | Go to Character Page
64 |
65 |
66 |
67 | );
68 | };
69 |
70 | export default HouseDetails;
71 |
--------------------------------------------------------------------------------
/src/pages/HouseDetails/styles.module.scss:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | @apply flex flex-col;
3 |
4 | .mainContent {
5 | @apply p-4 grid lg:grid-cols-5 lg:px-20 md:grid-cols-4 grid-cols-2;
6 |
7 | .title {
8 | @apply md:col-span-2 col-span-1 font-bold text-lg py-2;
9 | }
10 |
11 | .value {
12 | @apply lg:col-span-3 md:col-span-2 col-span-1 text-lg py-2 flex flex-col;
13 |
14 | .array {
15 | @apply text-lg;
16 | }
17 | }
18 | }
19 |
20 | .backButtonWrapper {
21 | @apply flex justify-center items-center my-10;
22 |
23 | .button {
24 | @apply px-4 py-2 text-xl text-black border rounded-md border-gray-500 hover:bg-slate-200 focus:bg-slate-100;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/pages/TableOfCharacters/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { ChangeEvent, useState, useEffect } from 'react';
2 | import axios from 'axios';
3 |
4 | import CharacterTable from '../../components/CharacterTable';
5 | import CustomButton from '../../components/CustomButton';
6 | import Select from '../../components/Select';
7 |
8 | import styles from './styles.module.scss';
9 |
10 | const pageSize: Array = ['10', '25', '50'];
11 |
12 | const gender: Array = ['All', 'Male', 'Female'];
13 |
14 | const TableOfCharacters = () => {
15 | const [selectedPageSize, setSelectedPageSize] = useState(pageSize[0]);
16 | const [selectedGender, setSelectedGender] = useState(gender[0]);
17 | // const [selectedLink, setSelectedLink] = useState('No');
18 | const [pageNum, setPageNum] = useState(1);
19 | const [filter, setFilter] = useState('');
20 | const [characters, setCharacters] = useState>([]);
21 |
22 | const getCharacterData = async (size: number, num: number) => {
23 | const res = await axios.get(`https://www.anapioficeandfire.com/api/characters`, {
24 | params: {
25 | pageSize: size,
26 | page: num,
27 | },
28 | });
29 | setCharacters([
30 | ...res.data.map((item: any) => {
31 | const nameString: string =
32 | item.name + item.aliases.length > 0 && item.name !== '' ? ',' : '' + item.aliases.join(',');
33 |
34 | const bornYear = item.born.match(/\d+/g);
35 | const diedYear = item.died.match(/\d+/g);
36 |
37 | let aliveString = '';
38 |
39 | if (item.born === '' && item.died === '') {
40 | aliveString = 'Unknown';
41 | } else if (item.born === '') {
42 | aliveString = 'No';
43 | } else if (item.died !== '') {
44 | if (bornYear !== null && diedYear != null) {
45 | const startAge = Math.min(...diedYear) - Math.max(...bornYear);
46 | const endAge = Math.max(...diedYear) - Math.min(...bornYear);
47 | aliveString = `No, died at ${startAge === endAge ? startAge : `${startAge} to ${endAge}`} years old`;
48 | } else {
49 | aliveString = 'No';
50 | }
51 | } else aliveString = 'Yes';
52 |
53 | return {
54 | name: nameString,
55 | alive: aliveString,
56 | gender: item.gender,
57 | culture: item.culture === '' ? 'Unknown' : item.culture,
58 | allegiances:
59 | item.allegiances.length === 0
60 | ? ['No allegiances']
61 | : item.allegiances.map((aItem: any) => aItem.match(/\d+/g)[0]),
62 | };
63 | }),
64 | ]);
65 | };
66 |
67 | useEffect(() => {
68 | getCharacterData(parseInt(selectedPageSize), pageNum);
69 | }, [selectedPageSize, pageNum]);
70 |
71 | return (
72 |
73 |
74 |
75 |
Filter:
76 |
) => setFilter(e.target.value)}
82 | />
83 |
84 |
91 |
92 |
93 |
94 |
95 |
102 |
{pageNum} page
103 |
104 |
105 | {
108 | setPageNum(1);
109 | }}
110 | className={'rounded-l-md'}
111 | />
112 | {
115 | setPageNum(prev => prev - 1);
116 | }}
117 | disable={pageNum === 1 ? true : false}
118 | />
119 | {
122 | setPageNum(prev => prev + 1);
123 | }}
124 | disable={
125 | (selectedPageSize === '10' && pageNum === 214) ||
126 | (selectedPageSize === '25' && pageNum === 85) ||
127 | (selectedPageSize === '50' && pageNum === 43)
128 | ? true
129 | : false
130 | }
131 | />
132 | {
135 | setPageNum(selectedPageSize === '10' ? 214 : selectedPageSize === '25' ? 85 : 43);
136 | }}
137 | className={'rounded-r-md'}
138 | />
139 |
140 |
141 |
142 | );
143 | };
144 |
145 | export default TableOfCharacters;
146 |
--------------------------------------------------------------------------------
/src/pages/TableOfCharacters/styles.module.scss:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | @apply py-10;
3 |
4 | .filterWrapper {
5 | @apply flex flex-row justify-between flex-wrap px-4;
6 |
7 | .filter {
8 | @apply border-none flex flex-row items-center;
9 |
10 | .label {
11 | @apply text-base text-black font-bold;
12 | }
13 |
14 | .input {
15 | @apply w-64 border border-gray-300 rounded-md mx-4 px-4 py-2;
16 |
17 | &:focus,
18 | &:focus-visible,
19 | &:focus-within {
20 | @apply outline-none;
21 | }
22 | }
23 | }
24 |
25 | .genderSelect {
26 | @apply w-32;
27 | }
28 | }
29 |
30 | .pagenationWrapper {
31 | @apply flex flex-row justify-between flex-wrap gap-2 px-4;
32 |
33 | .pageNumWrapper {
34 | @apply flex flex-row items-center;
35 |
36 | .pageNumSelect {
37 | @apply w-32;
38 | }
39 |
40 | .pageNum {
41 | @apply text-gray-500 ml-5;
42 | }
43 | }
44 |
45 | .pagenation {
46 | @apply flex flex-row;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/src/routes/MainRoute/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
3 |
4 | import MainLayout from '../../layouts/MainLayout';
5 | import TableOfCharacters from '../../pages/TableOfCharacters';
6 | import HouseDetails from '../../pages/HouseDetails';
7 |
8 | const MainRoute = () => {
9 | return (
10 |
11 |
12 |
13 | } />
14 | } />
15 | } />
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default MainRoute;
23 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/shared/type.d.ts:
--------------------------------------------------------------------------------
1 | type CharacterType = {
2 | name: string;
3 | alive: string;
4 | gender: string;
5 | culture: string;
6 | allegiances: Array;
7 | };
8 |
9 | type HouseDetailsType = {
10 | name: string;
11 | region: string;
12 | costOfArms: string;
13 | words: string;
14 | titles: Array;
15 | seats: Array;
16 | diedOut: string;
17 | overlord: string;
18 | cadetBranches: number;
19 | };
20 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ['./src/**/*.{js,jsx,ts,tsx}'],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | };
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------