Skip to content

Commit 237ada4

Browse files
committed
initialization
0 parents  commit 237ada4

30 files changed

+3276
-0
lines changed

.eslintrc.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "next/core-web-vitals"
3+
}

.gitignore

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
.pnpm-debug.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts
37+
38+
.vercel

.vscode/tasks.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
// See https://go.microsoft.com/fwlink/?LinkId=733558
3+
// for the documentation about the tasks.json format
4+
"version": "2.0.0",
5+
"tasks": [
6+
{
7+
"label": "🚴 start",
8+
"type": "shell",
9+
"command": "pnpm run dev",
10+
"presentation": {
11+
"reveal": "always",
12+
"panel": "new"
13+
}
14+
},
15+
{
16+
"label": "🚀 serve",
17+
"type": "shell",
18+
"command": "pnpm run build && pnpm start",
19+
"presentation": {
20+
"reveal": "always",
21+
"panel": "new"
22+
}
23+
}
24+
]
25+
}

README.md

Whitespace-only changes.

components/Card.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//basics
2+
import React from "react";
3+
4+
//utils
5+
import Image from "next/image";
6+
import Link from "next/link";
7+
import { motion, useAnimation } from "framer-motion";
8+
import { useInView } from "react-intersection-observer";
9+
10+
const Card = ({ country }) => {
11+
const cardVariants = {
12+
hidden: { y: "50px", scale: 1 },
13+
visible: {
14+
y: "0",
15+
scale: 1,
16+
transition: {
17+
duration: 0.3,
18+
},
19+
},
20+
};
21+
22+
const controls = useAnimation();
23+
const { ref: cardNode, inView } = useInView();
24+
25+
React.useEffect(() => {
26+
if (inView) {
27+
controls.start("visible");
28+
}
29+
if (!inView) {
30+
controls.start("hidden");
31+
}
32+
}, [controls, inView]);
33+
34+
return (
35+
<Link href={`/countries/${country.cca3}`}>
36+
<motion.a
37+
ref={cardNode}
38+
initial="hidden"
39+
animate={controls}
40+
variants={cardVariants}
41+
whileHover={{ scale: 1.05 }}
42+
className="w-72 hover:drop-shadow-lg cursor-pointer bg-skin-accent text-skin-text md:w-full mx-auto md:pb-0 focus:scale-105 dark:focus-visible:outline-gray-500 focus:outline-none focus:shadow-lg border-red-600 focus-visible:outline-gray-300 focus:outline-offset-0 lg:pb-6 rounded-md pb-8 shadow-md overflow-hidden mb-9 md:mb-0 block">
43+
<div>
44+
<Image
45+
objectFit="cover"
46+
width={500}
47+
height={300}
48+
priority
49+
src={country.flags.svg}
50+
alt={`Flag of ${country.name.common}`}
51+
/>
52+
<div
53+
className="py-4 px-6 mb-3 flex justify-between items-start
54+
">
55+
<div>
56+
<h2 className="text-xl font-extrabold mb-2">
57+
{country.name.common.split(0, 20)}
58+
</h2>
59+
<p className="text-base">
60+
<small className="font-semibold text-base">Population: </small>
61+
{country.population.toLocaleString("en-US")}
62+
</p>
63+
<p className="text-base">
64+
<small className="font-semibold text-base">Region: </small>
65+
{country.region}
66+
</p>
67+
68+
<p className="text-base">
69+
<small className="font-semibold text-base">Capital: </small>
70+
{country.capital}
71+
</p>
72+
</div>
73+
</div>
74+
</div>
75+
</motion.a>
76+
</Link>
77+
);
78+
};
79+
80+
export default Card;
81+
82+
Card.displayName = "Card";

components/Countries.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//basics
2+
import React from "react";
3+
4+
//components
5+
import Card from "./Card";
6+
7+
//utils
8+
import { SkeletonCard } from "./SkeletonCard";
9+
import { FaArrowUp } from "react-icons/fa";
10+
11+
//hooks
12+
import { useScrollToTop } from "./hooks/useScrollToTop";
13+
14+
const Countries = ({ countries }) => {
15+
const [loading, setLoading] = React.useState(true);
16+
17+
//scroll to top
18+
const { show, scrollToTop } = useScrollToTop();
19+
20+
React.useEffect(() => {
21+
setTimeout(() => {
22+
if (countries) {
23+
setLoading(false);
24+
}
25+
}, 1000);
26+
}, [countries]);
27+
28+
return (
29+
<>
30+
<section
31+
aria-label="countries"
32+
className="sm:grid sm:grid-cols-2 sm:gap-9 lg:grid-cols-3 lg:gap-20 xl:grid-cols-4 xl:gap-16 md:grid-cols-2 md:gap-12">
33+
<FaArrowUp
34+
onClick={scrollToTop}
35+
aria-hidden="true"
36+
className={`bg-gray-300 ${
37+
!show ? "invisible" : "visible"
38+
} rounded-full fixed right-3 bottom-6 p-2 z-50 drop-shadow-xl cursor-pointer hover:scale-110 hover:text-5xl`}
39+
fontSize={40}
40+
/>
41+
{loading
42+
? [1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].map((n, i) => (
43+
<SkeletonCard key={n + i} />
44+
))
45+
: Object.values(countries).map((country, i) => (
46+
<Card key={country + i} country={country} />
47+
))}
48+
</section>
49+
</>
50+
);
51+
};
52+
53+
export default Countries;

components/Filter.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//basics
2+
import React from "react";
3+
4+
const Filter = ({ setRegion }) => {
5+
return (
6+
<>
7+
<select
8+
className="block dark:focus-visible:outline-gray-500 bg-skin-accent text-skin-text outline-none cursor-pointer py-3 pl-1 mb-9 w-48 text-sm md:py-0 shadow rounded-sm border-none focus:outline-1 focus:outline-gray-300"
9+
id="regions"
10+
onChange={(e) => setRegion(e.target.value)}>
11+
<option value="" defaultValue hidden>
12+
Filter by Region
13+
</option>
14+
{["Africa", "America", "Asia", "Europe", "Oceania"].map((option, i) => (
15+
<option key={i} value={option}>
16+
&nbsp; {option}
17+
</option>
18+
))}
19+
</select>
20+
</>
21+
);
22+
};
23+
24+
export default React.memo(Filter);
25+
26+
Filter.displayName = "Filter";

components/Layout.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//basics
2+
import React from "react";
3+
4+
//components
5+
import Navbar from "./Navbar";
6+
7+
const Layout = ({ children }) => {
8+
return (
9+
<>
10+
<header className="px-6 bg-skin-accent sticky md:px-12 lg:px-12 xl:px-16 2xl:px-28 shadow-md shadow-skin-shadow mb-9 lg:mb-12">
11+
<Navbar />
12+
</header>
13+
<main className="mx-6 lg:mx-12 xl:mx-16 2xl:mx-28 md:mx-12 ">
14+
{children}
15+
</main>
16+
</>
17+
);
18+
};
19+
20+
export default Layout;

components/Navbar.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//basics
2+
import Link from "next/link";
3+
import React from "react";
4+
5+
//utils
6+
import { HiOutlineMoon, HiOutlineSun } from "react-icons/hi";
7+
import useLocalStorage from "./hooks/useLocalStorage";
8+
9+
const Navbar = () => {
10+
const { darkmode, setDarkmode } = useLocalStorage();
11+
12+
return (
13+
<nav className="flex text-skin-text justify-between items-center h-16">
14+
<Link href="/">
15+
<a className="md:text-2xl font-bold p-1 focus-visible:outline-none focus:bg-gray-200 dark:focus:bg-gray-500 rounded-lg ">
16+
Where in the world?
17+
</a>
18+
</Link>
19+
<button
20+
className="flex align-middle skin-bg items-center gap-1.5 focus:border hover:border-b-2 hover:border-skin-clr focus:text-skin-text hover:pt-2 focus:outline-skin-clr focus:border-spacing-16 px-3 py-1 rounded-3xl focus:bg-skin-bg"
21+
onClick={() => setDarkmode(!darkmode)}>
22+
{!darkmode ? (
23+
<HiOutlineMoon fontSize={15} />
24+
) : (
25+
<HiOutlineSun fontSize={15} />
26+
)}
27+
<p className="text-sm font-extrabold tracking-wide mt-1">
28+
{!darkmode ? "Dark Mode" : "LightTheme"}
29+
</p>
30+
</button>
31+
</nav>
32+
);
33+
};
34+
35+
export default Navbar;
36+
37+
Navbar.displayName = "Navbar";

components/Search.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//basics
2+
import React from "react";
3+
4+
//utils
5+
import { HiSearch } from "react-icons/hi";
6+
7+
const Search = ({ searchTerm, setSearchTerm }) => {
8+
return (
9+
<div className="flex w-full bg-skin-accent items-center gap-4 shadow-md cursor-pointer py-4 pl-6 md:w-96 pr-2 mb-9 md:mb-9">
10+
<HiSearch className="text-skin-text" fontSize={20} />
11+
<input
12+
className="focus:outline-none w-full focus:placeholder-gray-300 bg-skin-accent placeholder-skin-accent text-skin-input"
13+
type="text"
14+
placeholder="Search for a country..."
15+
value={searchTerm}
16+
onInput={(e) => setSearchTerm(e.target.value)}
17+
/>
18+
</div>
19+
);
20+
};
21+
22+
export default React.memo(Search);
23+
24+
Search.displayName = "Search";

0 commit comments

Comments
 (0)