├── next.config.js ├── public └── favicon.ico ├── .eslintrc ├── src ├── pages │ ├── _app.js │ ├── index.js │ └── country │ │ ├── Country.module.css │ │ └── [id].js ├── components │ ├── layout │ │ ├── Layout.module.css │ │ └── Layout.js │ ├── searchBar │ │ ├── SearchBar.module.css │ │ └── SearchBar.js │ └── countries │ │ ├── Countries.js │ │ └── Countries.module.css └── styles │ └── globals.css ├── .gitignore ├── package.json └── README.md /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | } 4 | 5 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EsrafilElahi/next-world-ranks/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next", 4 | "next/core-web-vitals" 5 | ], 6 | "rules": { 7 | "@next/next/no-img-element": "off" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | 3 | 4 | function MyApp({ Component, pageProps }) { 5 | return 6 | } 7 | 8 | export default MyApp 9 | -------------------------------------------------------------------------------- /src/components/layout/Layout.module.css: -------------------------------------------------------------------------------- 1 | .layout_container { 2 | padding: 20px; 3 | margin: 0 auto; 4 | height: 100vh; 5 | } 6 | 7 | .layout_header { 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | cursor: pointer; 12 | } 13 | 14 | .layout_footer { 15 | display: flex; 16 | justify-content: center; 17 | align-items: flex-end; 18 | font-size: 17px; 19 | padding: 30px 0; 20 | margin-top: -10px; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /.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 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-world-ranks", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@material-ui/core": "^4.12.1", 13 | "@material-ui/icons": "^4.11.2", 14 | "next": "11.0.1", 15 | "react": "17.0.2", 16 | "react-dom": "17.0.2", 17 | "react-toggle": "^4.1.2" 18 | }, 19 | "devDependencies": { 20 | "eslint": "7.30.0", 21 | "eslint-config-next": "11.0.1" 22 | } 23 | } -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Head from 'next/head' 3 | import Layout from '../components/layout/Layout'; 4 | import SearchBar from './../components/searchBar/SearchBar'; 5 | import Countries from './../components/countries/Countries'; 6 | 7 | 8 | export default function Home({data}) { 9 | const [search, setSearch] = useState('') 10 | 11 | const HandleChange = (e) => { 12 | e.preventDefault() 13 | setSearch(e.target.value.toLowerCase()) 14 | } 15 | 16 | const filtereddata = data.filter(country => ( 17 | country.name.toLowerCase().includes(search) || 18 | country.region.toLowerCase().includes(search) 19 | )) 20 | 21 | return ( 22 | 23 | 24 | World Ranks 25 | 26 | 27 | 28 | 29 | 30 | ) 31 | } 32 | 33 | 34 | export const getStaticProps = async () => { 35 | const res = await fetch('https://restcountries.eu/rest/v2/all') 36 | const data = await res.json() 37 | 38 | return { 39 | props: { data } 40 | } 41 | } -------------------------------------------------------------------------------- /src/components/searchBar/SearchBar.module.css: -------------------------------------------------------------------------------- 1 | .search_container { 2 | display: flex; 3 | justify-content: center !important; 4 | padding: 30px; 5 | margin-top: 40px; 6 | margin-bottom: 20px; 7 | } 8 | 9 | .search_toggle { 10 | display: flex; 11 | padding: 10px; 12 | } 13 | 14 | .search_wrapper { 15 | display: flex; 16 | align-items: center; 17 | background-color: var(--background-color-dark); 18 | border-radius: 8px; 19 | padding-left: 16px; 20 | 21 | color: var(--text-color-secondary); 22 | } 23 | 24 | .search_input { 25 | border: none; 26 | background-color: transparent; 27 | width: 250px; 28 | height: 100%; 29 | outline: none; 30 | font-size: 13px; 31 | } 32 | 33 | .search_input::placeholder { 34 | color: var(--text-color-secondary); 35 | } 36 | 37 | /* Media Query */ 38 | 39 | @media screen and (max-width: 992px) { 40 | .search_input::placeholder { 41 | font-size: 14px; 42 | } 43 | .search_input { 44 | font-size: 14px; 45 | } 46 | } 47 | 48 | @media screen and (min-width: 992px) { 49 | .search_input::placeholder { 50 | font-size: 18px; 51 | } 52 | .search_input { 53 | font-size: 18px; 54 | } 55 | .search_toggle { 56 | margin-right: 10px; 57 | } 58 | } 59 | 60 | @media screen and (min-width: 1200px) { 61 | .search_input::placeholder { 62 | font-size: 19px; 63 | } 64 | .search_input { 65 | font-size: 19px; 66 | } 67 | .search_toggle { 68 | margin-right: 15px; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/components/searchBar/SearchBar.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styles from './SearchBar.module.css' 3 | import Toggle from 'react-toggle' 4 | import "react-toggle/style.css" 5 | 6 | 7 | function SearchBar({ ...rest }) { 8 | 9 | const [theme, setTheme] = useState('light') 10 | 11 | const handleTheme = () => { 12 | if (theme === 'light') { 13 | setTheme('dark') 14 | document.documentElement.setAttribute('data-theme', 'dark') 15 | } 16 | else { 17 | setTheme('light') 18 | document.documentElement.setAttribute('data-theme', 'light') 19 | } 20 | } 21 | 22 | return ( 23 |
24 |
25 |
, 31 | unchecked:
🌙
, 32 | }} 33 | /> 34 | 35 |
36 |
37 | 38 |
39 | 40 | ) 41 | } 42 | 43 | export default SearchBar 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | 14 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 15 | 16 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 17 | 18 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. 19 | 20 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 21 | 22 | ## Learn More 23 | 24 | To learn more about Next.js, take a look at the following resources: 25 | 26 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 27 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 28 | 29 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 30 | 31 | ## Deploy on Vercel 32 | 33 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 34 | 35 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 36 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Imbue:wght@300&family=Roboto+Condensed:wght@300&display=swap"); 2 | 3 | html, 4 | body { 5 | padding: 0; 6 | margin: 0; 7 | font-family: "Imbue", "san-serif"; 8 | letter-spacing: 0.5px; 9 | background-color: var(--background-color); 10 | color: var(--text-color); 11 | } 12 | 13 | /* Light Mode */ 14 | :root { 15 | --text-color: #124a63; 16 | --text-color-secondary: #b3c5cd; 17 | 18 | --primary-color: #21b6b7; 19 | 20 | --background-color: #f9f9f9; 21 | --background-color-dark: #eef3f6; 22 | --background-color-light: white; 23 | 24 | --font-family: "Poppins", sans-serif; 25 | --box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.05); 26 | } 27 | 28 | /* Dark Mode */ 29 | [data-theme="dark"] { 30 | --text-color: #f0f0f0; 31 | --text-color-secondary: #b3c5cd; 32 | 33 | --primary-color: #21b6b7; 34 | 35 | --background-color: #252329; 36 | --background-color-dark: #3c393f; 37 | --background-color-light: #120f13; 38 | } 39 | 40 | a { 41 | color: inherit; 42 | text-decoration: none; 43 | } 44 | 45 | * { 46 | box-sizing: border-box; 47 | color: inherit; 48 | font: inherit; 49 | letter-spacing: 1px; 50 | } 51 | 52 | .react-toggle-track-check { 53 | left: 4px !important; 54 | } 55 | 56 | .react-toggle-track-x { 57 | top: -2px !important; 58 | right: 12px !important; 59 | } 60 | 61 | .react-toggle--checked .react-toggle-track { 62 | background-color: var(--text-color-secondary) !important; 63 | } 64 | 65 | .react-toggle--checked .react-toggle-thumb { 66 | left: 27px; 67 | border-color: white !important; 68 | } 69 | 70 | .react-toggle--focus .react-toggle-thumb { 71 | box-shadow: 0px 0px 0px 0px #fff !important; 72 | } 73 | 74 | .react-toggle:active:not(.react-toggle--disabled) .react-toggle-thumb { 75 | box-shadow: 0px 0px 0px 0px #fff !important; 76 | } 77 | -------------------------------------------------------------------------------- /src/pages/country/Country.module.css: -------------------------------------------------------------------------------- 1 | .detail_container { 2 | display: flex; 3 | flex-direction: column; 4 | padding: 35px 20px; 5 | background-color: var(--background-color); 6 | } 7 | 8 | .detail_flag { 9 | display: flex; 10 | flex-direction: column; 11 | background-color: var(--background-color-light); 12 | box-shadow: var(--box-shadow); 13 | padding: 20px 20px 1px 20px; 14 | border-radius: 8px; 15 | } 16 | 17 | .detail_flag img { 18 | width: 100%; 19 | border-radius: 3px; 20 | } 21 | 22 | .detail_flag_title { 23 | text-align: center; 24 | } 25 | 26 | .detail_flag_title h1 { 27 | font-size: 32px; 28 | letter-spacing: 2px; 29 | } 30 | 31 | .detail_flag_title p { 32 | color: var(--text-color-secondary); 33 | font-size: 17px; 34 | font-weight: 300; 35 | } 36 | 37 | .detail_detail { 38 | margin-top: 40px; 39 | background-color: var(--background-color-light); 40 | box-shadow: var(--box-shadow); 41 | } 42 | 43 | .detail_capital, 44 | .detail_language, 45 | .detail_population, 46 | .detail_area, 47 | .detail_native_name, 48 | .detail_currency { 49 | display: flex; 50 | justify-content: space-between; 51 | padding: 5px 20px; 52 | border-bottom: 1px solid #e0e0e0; 53 | } 54 | 55 | .detail_label { 56 | color: var(--text-color-secondary); 57 | } 58 | 59 | /* Media Query */ 60 | @media screen and (min-width: 768px) { 61 | .detail_container { 62 | display: flex; 63 | flex-direction: row; 64 | align-items: center; 65 | padding: 10px; 66 | } 67 | .detail_flag { 68 | flex: 1; 69 | margin-right: 17px; 70 | display: flex; 71 | align-items: center; 72 | } 73 | .detail_detail { 74 | flex: 1; 75 | margin-left: 17px; 76 | } 77 | .detail_flag img { 78 | height: 14rem; 79 | } 80 | } 81 | 82 | @media screen and (min-width: 992px) { 83 | .detail_container { 84 | padding-top: 50px; 85 | } 86 | .detail_flag { 87 | flex: 1.5; 88 | margin-right: 17px; 89 | } 90 | .detail_detail { 91 | flex: 2; 92 | margin-left: 17px; 93 | } 94 | } 95 | 96 | @media screen and (min-width: 1200px) { 97 | .detail_container { 98 | display: flex; 99 | flex-direction: row; 100 | align-items: center; 101 | justify-content: center; 102 | padding: 30px; 103 | } 104 | .detail_flag { 105 | flex: 1; 106 | margin-right: 17px; 107 | } 108 | .detail_detail { 109 | flex: 2; 110 | margin-left: 17px; 111 | } 112 | .detail_flag img { 113 | height: 14rem; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/pages/country/[id].js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Layout from './../../components/layout/Layout'; 3 | import styles from './Country.module.css' 4 | 5 | 6 | export const getStaticPaths = async () => { 7 | const res = await fetch("https://restcountries.eu/rest/v2/all"); 8 | const countries = await res.json(); 9 | 10 | const paths = countries.map((country) => ({ 11 | params: { id: country.alpha3Code }, 12 | })); 13 | 14 | return { 15 | paths, 16 | fallback: false, 17 | }; 18 | }; 19 | 20 | export const getStaticProps = async (context) => { 21 | const id = context.params.id 22 | const res = await fetch(`https://restcountries.eu/rest/v2/alpha/${id}`); 23 | const country = await res.json() 24 | 25 | return { 26 | props: { country } 27 | } 28 | } 29 | 30 | 31 | function Country({ country }) { 32 | 33 | return ( 34 | <> 35 | 36 | 37 | 38 | {country.name} 39 | 40 | 41 | 42 |
43 | 44 |
45 |
46 | {country.name} 47 |
48 |
49 |

{country.name}

50 |

{country.region}

51 |
52 |
53 | 54 |
55 |
56 |

Capital

57 |

{country.capital}

58 |
59 |
60 |

Language

61 |

{country.languages.map(({ name }) => name).join(", ")}

62 |
63 |
64 |

Population

65 |

{country.population}

66 |
67 |
68 |

Area (km2)

69 |

{country.area}

70 |
71 |
72 |

NativeName

73 |

{country.nativeName}

74 |
75 |
76 |

Currency

77 |

{country.currencies.map(cur => cur.code)}

78 |
79 |
80 | 81 |
82 |
83 | 84 | ) 85 | } 86 | 87 | export default Country 88 | -------------------------------------------------------------------------------- /src/components/countries/Countries.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Link from 'next/link' 3 | import styles from './Countries.module.css' 4 | import { KeyboardArrowDownRounded, KeyboardArrowUpRounded } from "@material-ui/icons"; 5 | 6 | 7 | const orderData = (data, value, direction) => { 8 | if (direction === 'asc') { 9 | return [...data].sort((a, b) => a[value] > b[value] ? 1 : -1) 10 | } 11 | if (direction === 'desc') { 12 | return [...data].sort((a, b) => a[value] > b[value] ? -1 : 1) 13 | } 14 | 15 | return data 16 | } 17 | 18 | const Arrow = ({ direction }) => { 19 | if (!direction) return; 20 | else if (direction === 'asc') { 21 | return ( 22 |
23 | 24 |
25 | ) 26 | } else { 27 | return ( 28 |
29 | 30 |
31 | ) 32 | } 33 | } 34 | 35 | function Countries({ data }) { 36 | 37 | const [direction, setDirection] = useState() 38 | const [value, setValue] = useState('') 39 | const orderedData = orderData(data, value, direction) 40 | 41 | const setDirectionAndValue = (value) => { 42 | if (!direction) { 43 | setDirection('desc') 44 | } else if (direction === 'desc') { 45 | setDirection('asc') 46 | } else { 47 | setDirection(null) 48 | } 49 | 50 | setValue(value) 51 | } 52 | 53 | 54 | return ( 55 | <> 56 |
57 |
58 |
59 | 63 | 67 | 71 |
72 | { 73 | orderedData.map(country => ( 74 | 75 |
76 |
77 | {country.name} 78 |
79 |
{country.name}
80 |
{country.population}
81 |
{country.area || 0}
82 |
83 | 84 | )) 85 | } 86 |
87 | 88 | ) 89 | } 90 | 91 | export default Countries 92 | -------------------------------------------------------------------------------- /src/components/countries/Countries.module.css: -------------------------------------------------------------------------------- 1 | .country_header { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | padding: 15px; 6 | } 7 | 8 | .country_header button { 9 | border: none; 10 | background-color: transparent; 11 | outline: none; 12 | cursor: pointer; 13 | } 14 | 15 | .country_flag { 16 | display: flex; 17 | flex: 1; 18 | } 19 | 20 | .country_name { 21 | flex: 3; 22 | color: var(--text-color-secondary); 23 | margin-left: 13px; 24 | font-weight: 500; 25 | display: flex; 26 | justify-content: flex-start; 27 | align-items: center; 28 | } 29 | 30 | .country_population { 31 | flex: 3; 32 | color: var(--text-color-secondary); 33 | font-weight: 500; 34 | display: flex; 35 | justify-content: center; 36 | align-items: center; 37 | } 38 | 39 | .country_area { 40 | flex: 3; 41 | color: var(--text-color-secondary); 42 | font-weight: 500; 43 | display: flex; 44 | justify-content: center; 45 | align-items: center; 46 | } 47 | 48 | .row_header { 49 | display: flex; 50 | padding: 15px; 51 | text-align: center; 52 | background-color: var(--background-color-light); 53 | border-radius: 8px; 54 | margin-bottom: 16px; 55 | box-shadow: var(--box-shadow); 56 | font-weight: 500; 57 | cursor: pointer; 58 | transition: box-shadow 0.2s ease-in-out; 59 | } 60 | 61 | .row_header:hover { 62 | box-shadow: 2px 3px 6px rgba(33, 183, 151, 0.6); 63 | } 64 | 65 | .row_flag { 66 | flex: 1; 67 | margin-right: 16px; 68 | display: flex; 69 | align-items: center; 70 | justify-content: flex-start; 71 | } 72 | 73 | .row_flag img { 74 | width: 100%; 75 | border-radius: 2px; 76 | } 77 | 78 | .row_name { 79 | flex: 3; 80 | text-align: left; 81 | display: flex; 82 | align-items: center; 83 | justify-content: flex-start; 84 | } 85 | 86 | .row_population { 87 | flex: 3; 88 | display: flex; 89 | align-items: center; 90 | justify-content: center; 91 | } 92 | 93 | .row_area { 94 | flex: 3; 95 | display: flex; 96 | align-items: center; 97 | justify-content: center; 98 | } 99 | 100 | .arrow { 101 | color: var(--text-color-secondary); 102 | display: flex; 103 | justify-content: center; 104 | align-items: center; 105 | width: 2px; 106 | height: 2px; 107 | margin-left: 11px; 108 | margin-top: 2px; 109 | } 110 | 111 | /* Media Query */ 112 | 113 | @media screen and (max-width: 992px) { 114 | .row_flag { 115 | flex: 1; 116 | } 117 | .country_container { 118 | padding: 1rem 1rem; 119 | } 120 | } 121 | 122 | @media screen and (min-width: 768px) { 123 | .country_container { 124 | padding: 1rem 3.5rem; 125 | } 126 | } 127 | 128 | @media screen and (min-width: 992px) { 129 | .row_flag { 130 | flex: 0.7; 131 | } 132 | .country_name { 133 | flex: 3; 134 | color: var(--text-color-secondary); 135 | margin-left: -13px; 136 | font-weight: 500; 137 | display: flex; 138 | justify-content: flex-start; 139 | align-items: center; 140 | } 141 | .country_header { 142 | font-size: 20px; 143 | } 144 | .row_header { 145 | font-size: 20px; 146 | } 147 | .country_container { 148 | padding: 1rem 5rem; 149 | } 150 | } 151 | 152 | @media screen and (min-width: 1200px) { 153 | .row_flag { 154 | flex: 0.7; 155 | } 156 | .country_name { 157 | flex: 3; 158 | color: var(--text-color-secondary); 159 | margin-left: -13px; 160 | font-weight: 500; 161 | display: flex; 162 | justify-content: flex-start; 163 | align-items: center; 164 | } 165 | .country_header { 166 | font-size: 24px; 167 | } 168 | .row_header { 169 | font-size: 24px; 170 | } 171 | .country_container { 172 | padding: 1rem 10rem; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/components/layout/Layout.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import styles from './Layout.module.css' 3 | 4 | 5 | function Layout({ children, title = 'World Ranks' }) { 6 | return ( 7 |
8 | 9 |
10 | 11 | 18 | 22 | 26 | 27 | 34 | 35 | 36 | 37 |
38 | 39 |
{children}
40 | 41 | 42 | 43 |
44 | ) 45 | } 46 | 47 | export default Layout 48 | --------------------------------------------------------------------------------