├── 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 |

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 |

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 |
36 |
37 |
38 |
39 |
{children}
40 |
41 |
42 |
43 |
44 | )
45 | }
46 |
47 | export default Layout
48 |
--------------------------------------------------------------------------------