├── .gitignore
├── LICENSE
├── README.md
├── app.js
├── config.js
├── images
├── favicon-32x32.png
├── icon-arrow.svg
├── icon-location.svg
├── pattern-bg.png
└── project-preview.png
├── index.html
└── styles.css
/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/catherineisonline/ip-address-tracker-frontendmentor/f9f876c2e3867da31a5a5e0609e582ff6a6cff00/.gitignore
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Ekaterine (Catherine) Mitagvaria
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 |
IP Address Tracker
5 |
6 |
7 |
8 | [Live](https://catherineisonline.github.io/ip-address-tracker-frontendmentor/)
9 | | [Solution](https://www.frontendmentor.io/solutions/ip-address-tracker-02_5ChONI)
10 | | [Challenge](https://www.frontendmentor.io/challenges/ip-address-tracker-I8-0yYAH0)
11 |
12 | Solution for a challenge from [frontendmentor.io](https://www.frontendmentor.io/)
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## About The Project
20 |
21 | In this challenge, I will be using two separate APIs together to create an IP Address Tracking app.
22 | The main challenge is to build out this IP Address Tracker app and get it looking as close to the design as possible. To get the IP Address locations, I will be using the IP Geolocation API by IPify. So if you've got something you'd like to practice, feel free to give it a go.
23 |
24 |
25 | Users should be able to:
26 | 1. View the optimal layout depending on their device's screen size.
27 | 2. See hover states for all interactive elements on the page.
28 | 3. See their own IP Address on the map on the initial page load.
29 | 4. Search for any IP addresses or domains and see the key information and location.
30 |
31 |
32 | I do not have access to the Figma sketch so the design is not pixel perfect.
33 |
34 |
35 |
36 |
37 | ## Built with
38 |
39 | - Semantic HTML5 markup
40 | - CSS custom properties
41 | - Flex
42 | - Mobile-first workflow
43 | - Free IP Geo Location
44 | - Leaflet (map API)
45 |
46 | ## What I learned
47 | This is my very first project on Frontend Mentor using API. I decided to spend more time on it from now on. I already have a project using API which was my very first experience, you can check it [here](https://github.com/catherineisonline/covid19-awareness) I decided not to spend too much time and effort on the design as my main goal here is practicing API.
48 |
49 | # Important Update
50 |
51 | For some reason the limit of 1000 requests has ended very fast. Possible my API key isn't hidden well enough, I am not sure and didn't test it much. The limit doesn't seem to be available once it's used up and this project is not worth any financial investment right now. I changed to another API and hoping that it will work however it does lack some information like ISP, for example. For now I decided to leave this way. The new API I used is provided down below.
52 | For some reason the gitignore is havin'g issues so I decided to keep the API key visible on purpose.
53 |
54 | ## Useful resources
55 |
56 | 1. [Figma](https://www.figma.com/) - Paste your design image to check the size of containers, width, etc.
57 | 2. [Perfect Pixel](https://chrome.google.com/webstore/detail/perfectpixel-by-welldonec/dkaagdgjmgdmbnecmcefdhjekcoceebi) - Awesome Chrome extension that helps you to match the pixels of the provided design.
58 | 4. [Leaflet](https://geo.ipify.org/) - an open-source JavaScript library for mobile-friendly interactive maps
59 | 5. [Free IP Geo Location](https://ipgeolocation.io/) - provides a free IP gelocation API for software developers I am currently using. This is a temporary replacement
60 |
61 |
62 |
63 | ## Acknowledgments
64 |
65 | A big thank you to anyone providing feedback on my [solution](https://www.frontendmentor.io/solutions/ip-address-tracker-02_5ChONI). It definitely helps to find new ways to code and find easier solutions!
66 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | import API_KEY from './config.js'
3 |
4 | const ipAddressField = document.querySelector('.ipAddressField')
5 | const timezoneInput = document.querySelector('.timezoneInput')
6 | const countryLocationInput = document.querySelector('.locationInput')
7 | const ispInput = document.querySelector('.ispInput')
8 | const submitBtn = document.querySelector('.submit-btn')
9 | const inputField = document.querySelector('.input-field')
10 |
11 | //Map
12 |
13 | let map = L.map('map').setView([51.505, -0.09], 13)
14 |
15 | L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
16 | attribution:
17 | '© OpenStreetMap contributors',
18 | }).addTo(map)
19 |
20 | //API
21 | let ipAddress
22 | let randomIP = ''
23 | let timeZone
24 | let countryLocation
25 | let cityLocation
26 | let postalCode
27 | let isp
28 | let lat
29 | let lng
30 |
31 | let url = `https://api.ipgeolocation.io/ipgeo?apiKey=${API_KEY}=`
32 | fetch(url)
33 | .then((response) => response.json())
34 | .then((response) => {
35 | ipAddress = response.ip
36 | timeZone = response.time_zone.offset
37 | countryLocation = response.country_name
38 | cityLocation = response.city
39 | postalCode = response.zipcode
40 | isp = response.isp
41 | lat = response.latitude
42 | lng = response.longitude
43 |
44 | ipAddressField.innerHTML = ipAddress
45 | timezoneInput.innerHTML = ` UTC ${timeZone}`
46 | countryLocationInput.innerHTML = `${countryLocation}, ${cityLocation} ${postalCode}`
47 | ispInput.innerHTML = isp
48 | mapLocation(lat, lng)
49 | }).catch(error => console.log(error))
50 |
51 | const mapLocation = (lat, lng) => {
52 | var markerIcon = L.icon({
53 | iconUrl: 'images/icon-location.svg',
54 |
55 | iconSize: [46, 56], // size of the icon
56 | iconAnchor: [23, 55], // point of the icon which will correspond to marker's location
57 | })
58 | map.setView([lat, lng], 17)
59 |
60 | L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
61 | attribution: false,
62 | }).addTo(map)
63 |
64 | L.marker([lat, lng], { icon: markerIcon }).addTo(map)
65 | }
66 |
67 | //Search by IP + validation
68 | submitBtn.addEventListener('click', (event) => {
69 | event.preventDefault()
70 | if (
71 | inputField.value.match(
72 | /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
73 | )
74 | ) {
75 | randomIP = inputField.value
76 | url = `https://api.ipgeolocation.io/ipgeo?apiKey=${API_KEY}=` + randomIP
77 | fetch(url)
78 | .then((response) => response.json())
79 | .then((response) => {
80 | ipAddress = response.ip
81 | timeZone = response.time_zone.offset
82 | countryLocation = response.country_name
83 | cityLocation = response.city
84 | postalCode = response.zipcode
85 | isp = response.isp
86 | lat = response.latitude
87 | lng = response.longitude
88 |
89 | ipAddressField.innerHTML = ipAddress
90 | timezoneInput.innerHTML = ` UTC ${timeZone}`
91 | countryLocationInput.innerHTML = `${countryLocation}, ${cityLocation} ${postalCode}`
92 | ispInput.innerHTML = isp
93 | mapLocation(lat, lng)
94 | }).catch(error => console.log(error))
95 | } else {
96 | alert('You have entered an invalid IP address!')
97 | return false
98 | }
99 | })
100 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | // config.js
2 | const API_KEY = '9c095da267384cf0a9fccc8c7cb83ec5&ip'
3 | export default API_KEY
4 |
--------------------------------------------------------------------------------
/images/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/catherineisonline/ip-address-tracker-frontendmentor/f9f876c2e3867da31a5a5e0609e582ff6a6cff00/images/favicon-32x32.png
--------------------------------------------------------------------------------
/images/icon-arrow.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/icon-location.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/pattern-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/catherineisonline/ip-address-tracker-frontendmentor/f9f876c2e3867da31a5a5e0609e582ff6a6cff00/images/pattern-bg.png
--------------------------------------------------------------------------------
/images/project-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/catherineisonline/ip-address-tracker-frontendmentor/f9f876c2e3867da31a5a5e0609e582ff6a6cff00/images/project-preview.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
21 | IP Address Tracker
22 |
23 |
24 |
25 |
26 |
27 |
39 |
40 |
41 |
42 |
43 |
44 | IP Address
45 |
46 |
47 |
48 |
49 |
50 | Location
51 |
52 |
53 |
54 |
55 |
56 | Timezone
57 |
58 |
59 |
60 |
61 |
62 | ISP
63 |
64 |
65 |
66 |
67 |
71 |
72 |
73 |
74 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;700&display=swap");
2 |
3 | :root {
4 | --body-font: "Rubik", sans-serif;
5 | --white: rgb(255, 255, 255);
6 | --black: rgb(0, 0, 0);
7 | --very-dark-gray: hsl(0, 0%, 17%);
8 | --dark-gray: hsl(0, 0%, 59%);
9 | }
10 |
11 | * {
12 | margin: 0;
13 | padding: 0;
14 | }
15 |
16 | body {
17 | min-height: 100vh;
18 | -ms-overflow-style: none; /* for Internet Explorer, Edge */
19 | scrollbar-width: none; /* for Firefox */
20 | overflow-y: scroll;
21 | box-sizing: border-box;
22 | font-family: var(--body-font);
23 | }
24 | body::-webkit-scrollbar {
25 | display: none; /* for Chrome, Safari, and Opera */
26 | }
27 |
28 | .main-container {
29 | width: 100%;
30 | display: flex;
31 | flex-direction: column;
32 | justify-content: center;
33 | align-items: center;
34 | }
35 |
36 | /** Header Section **/
37 |
38 | .header-section {
39 | position: relative;
40 | width: 100%;
41 | background-image: url("images/pattern-bg.png");
42 | background-position: center;
43 | background-repeat: no-repeat;
44 | background-size: cover;
45 | text-align: center;
46 | display: flex;
47 | flex-direction: column;
48 | gap: 1.6rem;
49 | height: 20rem;
50 | }
51 |
52 | .header-section h1 {
53 | font-size: 2rem;
54 | color: var(--white);
55 | margin-top: 1.9rem;
56 | font-weight: 500;
57 | word-spacing: 9px;
58 | }
59 |
60 | /** Search Form **/
61 |
62 | .search-form {
63 | position: relative;
64 | display: flex;
65 | flex-direction: row;
66 | justify-content: center;
67 | }
68 | .input-field {
69 | cursor: text;
70 | width: 80%;
71 | max-width: 29rem;
72 | border-color: transparent;
73 | border-radius: 15px 0 0 15px;
74 | }
75 |
76 | .input-field::placeholder {
77 | font-size: 1.1rem;
78 | }
79 | .input-field[type="text"] {
80 | font-size: 18px;
81 | padding: 1.1rem;
82 | font-weight: 400;
83 | }
84 |
85 | .submit-btn {
86 | border-color: transparent;
87 | background-color: var(--black);
88 | padding: 1rem;
89 | border-radius: 0 15px 15px 0;
90 | cursor: pointer;
91 | transition: all ease-in-out 0.3s;
92 | }
93 |
94 | .fa-angle-right {
95 | font-size: 1.5rem;
96 | color: var(--white);
97 | }
98 |
99 | /** Data Section **/
100 |
101 | .data-section {
102 | position: absolute;
103 | z-index: 1;
104 | top: 25%;
105 | display: flex;
106 | flex-direction: column;
107 | background-color: var(--white);
108 | border-radius: 15px;
109 | width: 80%;
110 | justify-items: center;
111 | text-align: center;
112 | gap: 1.5rem;
113 | padding: 1rem;
114 | line-height: 1.6rem;
115 | box-shadow: 0px 2px 20px 0px #00000038;
116 | }
117 | hr {
118 | display: none;
119 | }
120 | .data-section h2 {
121 | color: var(--dark-gray);
122 | font-size: 0.8rem;
123 | text-transform: uppercase;
124 | }
125 |
126 | .data-section p {
127 | color: var(--very-dark-gray);
128 | font-size: 3vw;
129 | font-weight: 700;
130 | }
131 |
132 | /** Map Section **/
133 | .map-section {
134 | position: relative;
135 | width: 100%;
136 | height: 40rem;
137 | z-index: 0;
138 | }
139 |
140 | #map {
141 | height: 100%;
142 | }
143 |
144 | /** Responsive **/
145 |
146 | @media (hover: hover) {
147 | .submit-btn:hover {
148 | transition: all ease-in-out 0.3s;
149 | background-color: var(--very-dark-gray);
150 | }
151 | }
152 |
153 | @media screen and (min-width: 1110px) {
154 | .header-section {
155 | height: 17.6rem;
156 | }
157 | .data-section {
158 | flex-direction: row;
159 | width: 65rem;
160 | max-width: 90%;
161 | justify-content: space-between;
162 | align-items: center;
163 | text-align: left;
164 | padding: 10px 30px;
165 | line-height: 2.4rem;
166 | height: 9rem;
167 | max-height: 90%;
168 | }
169 | .data-section article:first-child {
170 | width: 16rem;
171 | word-wrap: break-word;
172 | }
173 | .data-section p {
174 | font-size: 1.5rem;
175 | }
176 | hr {
177 | display: inline-block;
178 | height: 60%;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------