├── .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 | ![IP Address Tracker](https://github.com/catherineisonline/ip-address-tracker-frontendmentor/blob/main/images/project-preview.png?raw=true) 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 |
28 |

IP Address Tracker

29 | 30 |
31 | 32 | 33 | 36 | 37 |
38 |
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 |
68 | 69 |
70 |
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 | --------------------------------------------------------------------------------