├── .eslintrc.json ├── .gitignore ├── README.md ├── build.css ├── components ├── AppContextFolder │ └── AppContext.tsx ├── DataPullerProject │ ├── AboutComp │ │ └── About.tsx │ ├── BlockElem │ │ └── BlockElem.tsx │ ├── FuncVar │ │ └── foo.ts │ ├── LatLonTable │ │ └── LatLonTable.tsx │ ├── Map.tsx │ ├── TableRow │ │ └── TableRow.tsx │ └── TimerComp │ │ └── Timer.tsx ├── Footer │ └── Footer.tsx ├── Header │ ├── Header.tsx │ ├── Headercomp │ │ ├── DesktopMenu.tsx │ │ ├── IconMenu.tsx │ │ ├── Logo.tsx │ │ └── MobileMenu.tsx │ └── StartupLogo │ │ └── Startup.tsx ├── Home │ ├── AboutMe │ │ └── AboutMe.tsx │ ├── GetInTouch │ │ └── GetInTouch.tsx │ ├── MyName │ │ └── MyName.tsx │ ├── SocialMediaArround │ │ └── SocialMediaArround.tsx │ ├── SomethingIveBuilt │ │ └── SomethingIveBuilt.tsx │ ├── ThisSiteCantBeReached │ │ └── ThisCantBeReached.tsx │ └── WhereIHaveWorked │ │ ├── Descriptions │ │ ├── AdvancedAgroManagement.tsx │ │ ├── Fantasia.tsx │ │ ├── FeverTokens.tsx │ │ ├── IdealFresh.tsx │ │ ├── SuperBerry.tsx │ │ ├── TrouveTavoie.tsx │ │ └── taskAndType.ts │ │ └── WhereIHaveWorked.tsx ├── Icons │ ├── ArrowIcon.tsx │ ├── ExternalLink.tsx │ ├── GithubIcon.tsx │ ├── GithubIconForSomethingIveBuild.tsx │ ├── InstagramIcon.tsx │ ├── LinkedinIcon.tsx │ ├── Loader.tsx │ └── YoutubeIcon.tsx ├── TypingProject │ ├── AboutComp │ │ └── About.tsx │ ├── CursorCarotComp │ │ └── CursorCarotComp.tsx │ ├── Footer │ │ └── Footer.tsx │ ├── Functions │ │ └── functions.ts │ ├── Icons │ │ └── RestartIcon.tsx │ ├── Image │ │ └── Img.tsx │ ├── Statistics │ │ └── TypingStatistics.tsx │ ├── Types │ │ └── types.tsx │ ├── statisticsTab │ │ └── StatisticsTab.tsx │ └── timer │ │ └── TimerSpan.tsx └── smallComp │ └── image │ └── Img.tsx ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── api │ ├── typing │ │ └── [minLength].ts │ ├── userInfoByIP │ │ └── [userInfo].ts │ └── userInfoByLatLon │ │ └── [lat] │ │ └── [lon].ts ├── index.tsx ├── test │ └── index.tsx ├── typing │ └── index.tsx └── userdatapuller │ └── index.tsx ├── postcss.config.js ├── public ├── CallCenter.png ├── favicon.ico ├── hackme.jpg ├── haircut.png ├── image.jpg ├── img │ ├── Portfolio-portrait-2.jpg │ ├── Portfolio-portrait-3.jpg │ ├── Portfolio-portrait.jpg │ ├── YPredict-v1.jpg │ └── titof.jpg ├── marker-icon.png ├── resume.pdf ├── titofCercle.png ├── typing.png └── vercel.svg ├── styles ├── Home.module.css └── globals.css ├── tailwind.config.cjs ├── tsconfig.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | .env 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Logo 3 |
4 |

5 | anaflous.com - v1 6 |

7 |

8 | The first iteration of anaflous.com built with Nextjs and hosted with Vercel 9 |

10 | 11 |

12 | 13 | Vercel Status 14 | 15 |

16 | 17 | ## ![demo](https://user-images.githubusercontent.com/62770500/199337431-d632cc3c-12fb-40db-8f96-0d5e55555579.png) 18 | 19 | ## 🚨 About this repo (please read!) 20 | 21 | You can use this code for your own website, but please with attribution\*\*. 22 | 23 | Please note that the design of the website is inspired from "brittanychiang.com", but didn't copied even a single piece of code from brittanyching repo, However, i built the portfolio from scratch with different technolgoies, like "Tailwind CSS" and "Framer Motion" and i added some other functionalities to it, so it might seems the same, but believe me the entire website is different!!!!. If you have questions about implementation, please refer to the [Next.js DOCS](https://nextjs.org/docs) same thing for Tailwind CSS and Framer Motion check out the Documentation, Or you can simply reach to me directly. 24 | 25 | --- 26 | 27 | ### Table of Contents 28 | 29 | - [Description](#description) 30 | - [How To Use](#how-to-use) 31 | - [🎨 Color Reference](#references) 32 | - [License](#license) 33 | - [Author Info](#author-info) 34 | 35 | --- 36 | 37 | ## Description 38 | 39 | Without a doubt a portfolio website is a unique way to showcase your work and let others know about yourself. It’s like an evergreen platform for your projects, case studies, and information about you. However, Why why did i choose Next.js? Because it is a React framework with Server-Side Rendering, which is good for SEO (Good for us if we get found on Google, right?). 40 | 41 | Also, Next.js helps us build a full back-end & blazing-fast websites along with benefits such as Image optimization. 42 | 43 | Why tailwindcss? Because TailwindCSS is a framework which reduces a lot of styling efforts. It has low level CSS classes that you can directly embed into the HTML code. 44 | 45 | --- 46 | 47 | ## Technologies & libraries 48 | 49 | Since i integrated some of my project into my website i'll i mention most of technologies & libraries that i used. 50 | 51 | - Next.js 52 | - Nodejs 53 | - TypeScript 54 | - Tailwind CSS 55 | - framer-motion 56 | - Google API 57 | - cookie-cutter 58 | - react-leaflet 59 | - Vercel Analytics 60 | 61 | ##### Note : 62 | 63 | ##### You can find the rest of packages in the file `/package.json` 64 | 65 | --- 66 | 67 | ### How To Use 68 | 69 | Yes, you can fork this repo. Please give me proper credit by linking back to [anaflous.com](https://anaflous.com). Thanks! 70 | 71 | ## 🛠 Installation & Set Up 72 | 73 | 1. Clone the repo CLI 74 | 75 | ```sh 76 | git clone https://github.com/hktitof/my-website.git 77 | ``` 78 | 79 | 2. Install and use the correct version of Node using [NVM](https://github.com/nvm-sh/nvm) 80 | 81 | ```sh 82 | nvm install 83 | ``` 84 | 85 | 3. Install dependencies 86 | 87 | ```sh 88 | yarn 89 | ``` 90 | 91 | 4. (OPTIONAL) : Add .env file to the root project 92 | 93 | ```bash 94 | touch .env 95 | ``` 96 | 97 | 5. (OPTIONAL) : Add your Google API key inside .env file. 98 | 99 | ###### **_Note :_** 100 | 101 | ###### not Adding Google API to the project will cause not returning the correct zip code, it might be always "00000" 102 | 103 | ###### make sure you enabled Geolocation to this API 104 | 105 | ```Javascript 106 | NEXT_PUBLIC_KEY_GOOGLE_API="your API key" 107 | ``` 108 | 109 | 6. Start the development server 110 | 111 | ```sh 112 | yarn dev 113 | ``` 114 | 115 | ## 🚀 Building and Running for Production 116 | 117 | 1. Generate a full static production build 118 | 119 | ```sh 120 | yarn build 121 | ``` 122 | 123 | 1. Preview the site as it will appear once deployed 124 | 125 | ```sh 126 | yarn run serve 127 | ``` 128 | 129 | --- 130 | 131 | ## API Description : 132 | 133 | ##### Endpoint 1 : 134 | 135 | the following endpoint will return a json object contains a bunch of information about the ip address 136 | 137 | ```api 138 | /api/userInfoByIP/[IP-Address] 139 | ``` 140 | 141 | example : 142 | 143 | ```api 144 | /api/userInfoByIP/159.89.173.104 145 | ``` 146 | 147 | ###### **_Get Request to above endpoint will return the following json data :_** 148 | 149 | ```JavaScript 150 | {"zip":"560002","country":"India","countryCode":"IN","region":"KA","regionName":"Karnataka","city":"Bengaluru","datetime":"9/6/2022, 1:24:30 AM","lat":12.9634,"lon":77.5855,"timezone":"Asia/Kolkata","isp":"DigitalOcean, LLC","org":"Digital Ocean","as":"AS14061 DigitalOcean, LLC","query":"159.89.173.104"} 151 | ``` 152 | 153 | ##### Endpoint 2 : 154 | 155 | the following endpoint will return a json object contains the zip code for the latitude and logitude 156 | 157 | ```api 158 | "/api/userInfoByLatLon/" + lat + "/" + lon 159 | ``` 160 | 161 | example : 162 | 163 | ```api 164 | /api/userInfoByIP/159.89.173.104 165 | ``` 166 | 167 | ###### **_Get Request to above endpoint will return the zipcode of the lat and long provided :_** 168 | 169 | ```JavaScript 170 | {"zipcode" : "56998"} 171 | ``` 172 | 173 | ###### **_the Response below is returned if the lat and long provided has no zip code in Google maps, like lat & long in positioned in the ocean :_** 174 | 175 | ```JavaScript 176 | {"zipcode" : "00000"} 177 | ``` 178 | 179 | ##### Endpoint 3 : 180 | 181 | the following endpoint will return a json object contains "quote" and "author", for SpeedTyping project i displayed only the quote, **minLength** is considered as the minimum of characters. 182 | 183 | ```api 184 | /api/typing/[minLength] 185 | ``` 186 | 187 | ##### notes : 188 | 189 | - **_minLength_** should be between 10 - 300. 190 | - the returned quote is a chain of 191 | - i costumized the original Endpoint using The API Route of Nextjs, here is the Original Endpoint. 192 | 193 | ##### Original Endpiont : 194 | 195 | ###### URL : 196 | 197 | ```api 198 | https://api.quotable.io/random?minLength=[minLength] 199 | ``` 200 | 201 | --- 202 | 203 | ## References 204 | 205 | | Color | Hex | 206 | | -------------- | ------------------------------------------------------------------ | 207 | | Navy | ![#0a192f](https://via.placeholder.com/10/0a192f?text=+) `#0a192f` | 208 | | Light Navy | ![#112240](https://via.placeholder.com/10/0a192f?text=+) `#112240` | 209 | | Lightest Navy | ![#233554](https://via.placeholder.com/10/303C55?text=+) `#233554` | 210 | | Slate | ![#8892b0](https://via.placeholder.com/10/8892b0?text=+) `#8892b0` | 211 | | Light Slate | ![#a8b2d1](https://via.placeholder.com/10/a8b2d1?text=+) `#a8b2d1` | 212 | | Lightest Slate | ![#ccd6f6](https://via.placeholder.com/10/ccd6f6?text=+) `#ccd6f6` | 213 | | White | ![#e6f1ff](https://via.placeholder.com/10/e6f1ff?text=+) `#e6f1ff` | 214 | | Green | ![#64ffda](https://via.placeholder.com/10/64ffda?text=+) `#64ffda` | 215 | 216 | --- 217 | 218 | ## License 219 | 220 | MIT License 221 | 222 | Copyright (c) [2022] [Anthony Miller] 223 | 224 | Permission is hereby granted, free of charge, to any person obtaining a copy 225 | of this software and associated documentation files (the "Software"), to deal 226 | in the Software without restriction, including without limitation the rights 227 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 228 | copies of the Software, and to permit persons to whom the Software is 229 | furnished to do so, subject to the following conditions: 230 | 231 | The above copyright notice and this permission notice shall be included in all 232 | copies or substantial portions of the Software. 233 | 234 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 235 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 236 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 237 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 238 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 239 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 240 | SOFTWARE. 241 | 242 | --- 243 | 244 | ## Author Info 245 | 246 | - Linkedin - [@abdellatif-anaflous](https://www.linkedin.com/in/abdellatif-anaflous/) 247 | - Website - [Anthony Miller](https://anaflous.com) 248 | 249 | [Back To The Top](#description) : 250 | -------------------------------------------------------------------------------- /build.css: -------------------------------------------------------------------------------- 1 | /*! tailwindcss v3.1.6 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}*{scrollbar-color:auto;scrollbar-width:auto}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::-webkit-backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.left-0{left:0}.mt-2{margin-top:.5rem}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.h-96{height:24rem}.h-0\.5{height:.125rem}.h-0{height:0}.h-12{height:3rem}.h-full{height:100%}.h-1{height:.25rem}.h-24{height:6rem}.h-2{height:.5rem}.w-full{width:100%}.w-8{width:2rem}.w-6{width:1.5rem}.w-4{width:1rem}.w-10{width:2.5rem}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-24{width:6rem}.w-16{width:4rem}.border-spacing-2{--tw-border-spacing-x:0.5rem;--tw-border-spacing-y:0.5rem;border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.translate-y-0{--tw-translate-y:0px}.-translate-y-full,.translate-y-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-full{--tw-translate-y:-100%}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(2rem*var(--tw-space-x-reverse));margin-left:calc(2rem*(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(2rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem*var(--tw-space-y-reverse))}.rounded-sm{border-radius:.125rem}.rounded{border-radius:.25rem}.border{border-width:1px}.border-AAsecondary{--tw-border-opacity:1;border-color:rgb(100 255 218/var(--tw-border-opacity))}.bg-AAprimary{--tw-bg-opacity:1;background-color:rgb(11 25 47/var(--tw-bg-opacity))}.bg-red-300{--tw-bg-opacity:1;background-color:rgb(252 165 165/var(--tw-bg-opacity))}.bg-blue-300{--tw-bg-opacity:1;background-color:rgb(147 197 253/var(--tw-bg-opacity))}.bg-yellow-500{--tw-bg-opacity:1;background-color:rgb(234 179 8/var(--tw-bg-opacity))}.bg-AAsecondary{--tw-bg-opacity:1;background-color:rgb(100 255 218/var(--tw-bg-opacity))}.bg-MobileNavBarColor{--tw-bg-opacity:1;background-color:rgb(17 35 64/var(--tw-bg-opacity))}.bg-red-600{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-opacity-75{--tw-bg-opacity:0.75}.px-2{padding-left:.5rem;padding-right:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-Header{font-family:Lato,sans-serif}.font-sans{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.font-Text2{font-family:Lato,sans-serif}.text-xs{font-size:.75rem;line-height:1rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-AAsecondary{--tw-text-opacity:1;color:rgb(100 255 218/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.drop-shadow-lg{--tw-drop-shadow:drop-shadow(0 10px 8px #0000000a) drop-shadow(0 4px 3px #0000001a);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.hover\:cursor-pointer:hover{cursor:pointer}.hover\:bg-ResumeButtonHover:hover{--tw-bg-opacity:1;background-color:rgb(21 48 64/var(--tw-bg-opacity))}.hover\:text-AAsecondary:hover{--tw-text-opacity:1;color:rgb(100 255 218/var(--tw-text-opacity))}@media (min-width:640px){.sm\:px-12{padding-left:3rem;padding-right:3rem}.sm\:py-4{padding-top:1rem;padding-bottom:1rem}.sm\:px-10{padding-left:2.5rem;padding-right:2.5rem}.sm\:text-base{font-size:1rem;line-height:1.5rem}}@media (min-width:768px){.md\:flex{display:flex}.md\:hidden{display:none}} -------------------------------------------------------------------------------- /components/AppContextFolder/AppContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | const AppContext = createContext(null); 4 | 5 | export default AppContext; 6 | -------------------------------------------------------------------------------- /components/DataPullerProject/AboutComp/About.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Img from "../../smallComp/image/Img"; 3 | export default function About() { 4 | return ( 5 |
6 |
7 | About picture 12 |
13 |
14 | 15 | What's this project for? 16 | 17 | 18 | It's no secret that sites want to know as much as possible 19 | about their visitors, whether it's to show them targeted ads 20 | or improve their user experience. The goal of this project is to 21 | give you an idea about types of information that websites can 22 | collect and access from you. No matter what the privacy settings 23 | of your browser are, certain information about you is inevitably 24 | revealed to the sites you visit. For example, you start sharing 25 | your IP address as soon as you go online, which can be used to 26 | pinpoint your approximate location. 27 | 28 |
29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /components/DataPullerProject/BlockElem/BlockElem.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type Props = { 4 | size; 5 | title; 6 | value; 7 | }; 8 | 9 | // repeted code for setting Additional Data user location 10 | 11 | export default function BlockElem(props: Props) { 12 | return ( 13 |
14 | 17 | {props.title} 18 | 19 | 20 | {props.value} 21 | 22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /components/DataPullerProject/FuncVar/foo.ts: -------------------------------------------------------------------------------- 1 | // ? this will update secUnits each second, secTens, minUnits, minTens cookies then update the span from cookies values 2 | export const CookieTimeCounter = ({ 3 | context, 4 | secUnits, 5 | secTens, 6 | minUnits, 7 | minTens, 8 | cookieCutter, 9 | }) => { 10 | if (typeof window != undefined) { 11 | // Cookie existence verification 12 | if (cookieCutter.get("timer-sec-units")) { 13 | console.log( 14 | "current cookie timer-sec-units value in useEffect: ", 15 | cookieCutter.get("timer-sec-units") 16 | ); 17 | } else { 18 | console.log("timer cookie not exist"); 19 | cookieCutter.set("timer-sec-units", String("0")); 20 | cookieCutter.set("timer-sec-tens", String("0")); 21 | cookieCutter.set("timer-min-units", String("0")); 22 | cookieCutter.set("timer-min-tens", String("0")); 23 | } 24 | // set setInterval for the context.sharedState.userdata.timerCookieRef 25 | context.sharedState.userdata.timerCookieRef.current = setInterval( 26 | function () { 27 | const countSec = Number(cookieCutter.get("timer-sec-units")) + 1; 28 | cookieCutter.set("timer-sec-units", String(countSec)); 29 | 30 | if (countSec > 9) { 31 | cookieCutter.set("timer-sec-units", String("0")); 32 | cookieCutter.set( 33 | "timer-sec-tens", 34 | String(Number(cookieCutter.get("timer-sec-tens")) + 1) 35 | ); 36 | const countSecTens = Number(cookieCutter.get("timer-sec-tens")); 37 | if (countSecTens > 5) { 38 | cookieCutter.set("timer-sec-tens", String("0")); 39 | cookieCutter.set( 40 | "timer-min-units", 41 | String(Number(cookieCutter.get("timer-min-units")) + 1) 42 | ); 43 | const countMinUnits = Number(cookieCutter.get("timer-min-units")); 44 | if (countMinUnits > 9) { 45 | cookieCutter.set("timer-min-units", String("0")); 46 | cookieCutter.set( 47 | "timer-min-tens", 48 | String(Number(cookieCutter.get("timer-min-tens")) + 1) 49 | ); 50 | } 51 | } 52 | } 53 | // this checking is to prevent from type checking, 54 | // "secUnits.current" will be undefined if it is not yet rendered on the other pages 55 | if (secUnits.current) { 56 | secUnits.current.innerText = cookieCutter.get("timer-sec-units"); 57 | secTens.current.innerText = cookieCutter.get("timer-sec-tens"); 58 | minUnits.current.innerText = cookieCutter.get("timer-min-units"); 59 | minTens.current.innerText = cookieCutter.get("timer-min-tens"); 60 | } 61 | 62 | console.log("Cookie Timer Setter..."); 63 | }, 64 | 1000 65 | ); 66 | } 67 | }; 68 | 69 | // ? Declare Mouse Event and Window size tracker event 70 | export const MouseWindowEventListners = ({ 71 | context, 72 | windowWidth, 73 | windowHeight, 74 | mouseX, 75 | mouseY, 76 | }) => { 77 | // assign context windowSize Ref here in useEffect once, so to make sure that it only assigned once 78 | context.sharedState.userdata.windowSizeTracker.current = () => { 79 | if (windowWidth.current) { 80 | windowWidth.current.innerText = String(window.innerWidth); 81 | windowHeight.current.innerText = String(window.innerHeight); 82 | } 83 | console.log("Window Size Tracker..."); 84 | }; 85 | // assint mousePositionTracker.current here to use in the as fallback function for the event 86 | // and to remove the event in the other pages 87 | context.sharedState.userdata.mousePositionTracker.current = event => { 88 | if (mouseX.current) { 89 | mouseX.current.innerText = String(event.pageX); 90 | mouseY.current.innerText = String(event.pageY); 91 | } 92 | console.log("Mouse Position Tracker..."); 93 | }; 94 | // Apply this event Listener on Client 95 | if (typeof window !== "undefined") { 96 | // window size tracker 97 | window.addEventListener( 98 | "resize", 99 | context.sharedState.userdata.windowSizeTracker.current 100 | ); 101 | // mouse position tracker 102 | window.addEventListener( 103 | "mousemove", 104 | context.sharedState.userdata.mousePositionTracker.current, 105 | false 106 | ); 107 | } 108 | }; 109 | 110 | import { detect } from "detect-browser"; 111 | import { getGPUTier } from "detect-gpu"; 112 | // ? async function for getting user information. IP, location, zip code, browser, OS, GPU, etc. 113 | export const userInfo = async ({ 114 | setLocation, 115 | setZipCode, 116 | setGpuTier, 117 | userData, 118 | cookieCutter, 119 | lastVisit_Ref, 120 | firstVisit_Ref, 121 | }) => { 122 | // this api will return current ip address of the requester 123 | const IP_Address = async () => { 124 | return fetch("https://api.ipify.org/?format=json") 125 | .then(res => res.json()) 126 | .then(data => data.ip); 127 | }; 128 | // call api by passing the IP address of the requester & store in api_data 129 | const api_data = async () => { 130 | return fetch("/api/userInfoByIP/" + (await IP_Address())) 131 | .then(res => res.json()) 132 | .then(data => data); 133 | }; 134 | //to determine the browser info 135 | const browser = detect(); 136 | // get user Data from the api 137 | const result = await api_data(); 138 | // Client side checks 139 | if (browser) { 140 | result["browser"] = browser.name; 141 | result["browserVersion"] = browser.version; 142 | result["browserOS"] = browser.os; 143 | } 144 | if (screen) { 145 | result["screenWidth"] = screen.width; 146 | result["screenHeight"] = screen.height; 147 | result["screenOrientationType"] = screen.orientation.type; 148 | result["screenColorDepth"] = screen.colorDepth + " bits"; 149 | } 150 | if (navigator) { 151 | result["NavigatorLanguages"] = navigator.languages; 152 | result["NavigatorLogicalCores"] = navigator.hardwareConcurrency + " cores"; 153 | } 154 | // ? this will add battery level if it's supported on the browser 155 | if (navigator) { 156 | if (navigator.hasOwnProperty("getBattery")) { 157 | //@ts-ignore 158 | navigator.getBattery().then(battery => { 159 | result["batteryLevel"] = battery.level + " %"; 160 | console.log("battery level : ", battery.level + " %"); 161 | }); 162 | } else { 163 | result["batteryLevel"] = "Not supported"; 164 | } 165 | } 166 | const temp_array_location = []; 167 | temp_array_location.push(result.lat); 168 | temp_array_location.push(result.lon); 169 | setLocation([...temp_array_location]); 170 | console.log("useEffect run, data :", result); 171 | setZipCode(result.zip); 172 | userData.current = result; 173 | // first & last visit tracker with conditional statement using cookies. 174 | //it's inside userInfo function to get the current time by the ip Address 175 | if (cookieCutter.get("first-visit")) { 176 | lastVisit_Ref.current.innerText = cookieCutter.get("last-visit"); 177 | cookieCutter.set("last-visit", result.datetime); 178 | } else { 179 | lastVisit_Ref.current.innerText = "Now"; 180 | cookieCutter.set("first-visit", result.datetime); 181 | cookieCutter.set("last-visit", result.datetime); 182 | } 183 | firstVisit_Ref.current.innerText = cookieCutter.get("first-visit"); 184 | // set up gpuTier state value 185 | const gpuTier_data = await getGPUTier(); 186 | setGpuTier(Object(gpuTier_data)); 187 | }; 188 | 189 | // ? update Location on click event callback function 190 | export const onClickUpdateLocation = async ( 191 | setUpdatingLocatinResult, 192 | setUpdatingLocation, 193 | setLocation, 194 | setZipCode 195 | ) => { 196 | if (!navigator.geolocation) { 197 | alert("Geolocation is not supported by your browser"); 198 | return; 199 | } 200 | // function will be executed after permission is authorized 201 | function success(position) { 202 | setLocation([position.coords.latitude, position.coords.longitude]); 203 | const temp_array_location = []; 204 | temp_array_location.push(position.coords.latitude); 205 | temp_array_location.push(position.coords.longitude); 206 | // set new lat and lon 207 | setLocation([...temp_array_location]); 208 | // Show Map 209 | setUpdatingLocation(false); 210 | // Hide "Unable to retieve location" message 211 | setUpdatingLocatinResult(false); 212 | 213 | // call the api by passing new lat and lon 214 | const api_get_zip = async (lat, lon) => { 215 | return fetch("/api/userInfoByLatLon/" + lat + "/" + lon) 216 | .then(res => res.json()) 217 | .then(data => { 218 | return data; 219 | }); 220 | }; 221 | // change zipcode useState 222 | const setNewZip = async () => 223 | setZipCode( 224 | await api_get_zip(position.coords.latitude, position.coords.longitude) 225 | ); 226 | setNewZip(); 227 | 228 | console.log( 229 | "Updated == > Longitude:", 230 | position.coords.longitude, 231 | "Latitude:", 232 | position.coords.latitude 233 | ); 234 | } 235 | // function will be executed after permission is denied 236 | function error() { 237 | // error Show Unable to retieve location message 238 | setUpdatingLocatinResult(true); 239 | //Show Map after failed to update location 240 | setUpdatingLocation(false); 241 | } 242 | // ask for permission to access location 243 | navigator.geolocation.getCurrentPosition(success, error); 244 | }; 245 | 246 | // data for Additional Information Section 1 247 | export const Additional_data = (userData, gpuTier) => { 248 | return [ 249 | { title: "Browser :", value: userData.current?.browser || "Checking..." }, 250 | { 251 | title: "Browser Version :", 252 | value: userData.current?.browserVersion || "Checking...", 253 | }, 254 | { 255 | title: "Languages :", 256 | value: 257 | userData.current?.NavigatorLanguages.toString().replace(",", ", ") || 258 | "Checking...", 259 | }, 260 | { title: "OS :", value: userData.current?.browserOS || "Checking..." }, 261 | { 262 | title: "CPU cores :", 263 | value: userData.current?.NavigatorLogicalCores || "Checking...", 264 | }, 265 | { 266 | title: "GPU :", 267 | value: gpuTier?.gpu || "Checking...", 268 | }, 269 | ]; 270 | }; 271 | 272 | // data for the table 273 | export const tableData = (userData, zipCode) => { 274 | return [ 275 | { 276 | title: "IP Address :", 277 | value: userData.current?.query || "Checking...", 278 | }, 279 | { title: "City :", value: userData.current?.city || "Checking..." }, 280 | { title: "Zip Code :", value: zipCode || "Checking..." }, 281 | { 282 | title: "Region :", 283 | value: userData.current?.regionName || "Checking...", 284 | }, 285 | { 286 | title: "Region Code :", 287 | value: userData.current?.region || "Checking...", 288 | }, 289 | { title: "Country :", value: userData.current?.country || "Checking..." }, 290 | { 291 | title: "Current Date/time :", 292 | value: userData.current?.datetime || "Checking...", 293 | }, 294 | { 295 | title: "Battery :", 296 | value: userData.current?.batteryLevel || "Checking...", 297 | }, 298 | { title: "As :", value: userData.current?.as || "Checking..." }, 299 | ]; 300 | }; 301 | -------------------------------------------------------------------------------- /components/DataPullerProject/LatLonTable/LatLonTable.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | type Props = { 3 | type; 4 | location; 5 | }; 6 | export default function LatLonTable(props: Props) { 7 | switch (props.type) { 8 | case "Desktop": 9 | return ; 10 | case "Mobile": 11 | return ; 12 | } 13 | } 14 | 15 | const LatLongTableDesktop = location => { 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
Latitude :{location.location[0]}
Longitude :{location.location[1]}
29 | ); 30 | }; 31 | 32 | const LatLongTableMobile = location => { 33 | return ( 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
Latitude :{location.location[0]}
Longitude :{location.location[1]}
46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /components/DataPullerProject/Map.tsx: -------------------------------------------------------------------------------- 1 | import { MapContainer, Marker, Popup, TileLayer } from "react-leaflet"; 2 | import "leaflet/dist/leaflet.css"; 3 | import markerIconPng from "leaflet/dist/images/marker-icon.png"; 4 | import { Icon } from "leaflet"; 5 | type position = { 6 | lat?: number; 7 | lon?: number; 8 | }; 9 | const Map = ({ lat = 30.4301, lon = -9.5912 }: position) => { 10 | const position = [lat, lon]; 11 | return ( 12 | 19 | 23 | 33 | 34 | {position[0]}, {position[1]} 35 | 36 | 37 | 38 | ); 39 | }; 40 | 41 | export default Map; 42 | -------------------------------------------------------------------------------- /components/DataPullerProject/TableRow/TableRow.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | type Props = { 3 | item: { title; value }; 4 | }; 5 | // repeated table row code for setting General Information ip address,city,Zip Code ...etc 6 | export default function TableRow(props:Props) { 7 | return ( 8 | 9 | 10 | {props.item.title} 11 | 12 | 13 | {props.item.value} 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /components/DataPullerProject/TimerComp/Timer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | type Props = { 3 | minTens, 4 | minUnits, 5 | secTens, 6 | secUnits, 7 | } 8 | export default function Timer(props:Props) { 9 | return ( 10 |
11 |
12 | Time you spent 13 |
14 |
15 |
16 |
17 |
18 | 22 | 0 23 | 24 |
25 |
26 |
27 |
28 | 32 | 0 33 | 34 |
35 |
36 |
37 | Minutes 38 |
39 |
40 |
41 |
42 |
43 | 47 | 0 48 | 49 |
50 |
51 |
52 |
53 | 57 | 0 58 | 59 |
60 |
61 |
62 | Seconds 63 |
64 |
65 |
66 |
67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /components/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import GithubIcon from "../Icons/GithubIcon"; 3 | import LinkedinIcon from "../Icons/LinkedinIcon"; 4 | import InstagramIcon from "../Icons/InstagramIcon"; 5 | import YoutubeIcon from "../Icons/YoutubeIcon"; 6 | const ClickableIcon = (props) => { 7 | return ( 8 | 9 | 14 | 15 | ); 16 | }; 17 | const IconsData = [ 18 | { href: "https://github.com/hktitof", Icon: GithubIcon }, 19 | { 20 | href: "https://www.linkedin.com/in/abdellatif-anaflous/", 21 | Icon: LinkedinIcon, 22 | }, 23 | { href: "https://www.instagram.com/titof_abdo/", Icon: InstagramIcon }, 24 | { href: "https://www.youtube.com/@abdellatif_anaflous", Icon: YoutubeIcon }, 25 | ]; 26 | 27 | export default function Fotter(props: { 28 | githubUrl: string; 29 | hideSocialsInDesktop: boolean; 30 | }) { 31 | return ( 32 |
33 | {/* // ? Reach me at */} 34 |
39 | {IconsData.map((iconData, index) => { 40 | return ( 41 | 46 | ); 47 | })} 48 |
49 | 50 |
54 | 55 | Designed & Built by Anthony Miller 56 | 57 | 58 | 59 | 64 | Source code - Github 65 | 66 |
67 |
68 |
69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, useEffect, useContext } from "react"; 2 | import Logo from "./Headercomp/Logo"; 3 | import DesktopMenu from "./Headercomp/DesktopMenu"; 4 | import IconMenu from "./Headercomp/IconMenu"; 5 | import MobileMenu from "./Headercomp/MobileMenu"; 6 | import { motion } from "framer-motion"; 7 | import AppContext from "../AppContextFolder/AppContext"; 8 | 9 | const addClass = (ref: any, myclass: string) => { 10 | ref.current?.classLIst.add(myclass); 11 | }; 12 | const Header = (props: { finishedLoading: boolean,sectionsRef }) => { 13 | const RefNavBar = useRef(null); 14 | const [ShowElement, setShowElement] = useState(false); 15 | const [rotate, setRotate] = useState(false); 16 | const context = useContext(AppContext); 17 | const scrollSizeY=useRef(0); 18 | 19 | // Define the EventListener for the NavBar 20 | useEffect(() => { 21 | if (context.sharedState.portfolio.NavBar.IntervalEvent == null) { 22 | context.sharedState.portfolio.NavBar.IntervalEvent=() => { 23 | if (scrollSizeY.current == 0) { 24 | scrollSizeY.current = window.scrollY; 25 | } else { 26 | if (window.scrollY > 50) { 27 | if (window.scrollY > scrollSizeY.current) { 28 | if (RefNavBar) { 29 | RefNavBar.current?.classList.remove("translate-y-0"); 30 | RefNavBar.current?.classList.add("-translate-y-full"); 31 | } 32 | } else { 33 | RefNavBar.current?.classList.add("translate-y-0"); 34 | RefNavBar.current?.classList.remove("-translate-y-full"); 35 | } 36 | scrollSizeY.current = window.scrollY; 37 | } 38 | } 39 | console.log("Scrolling checking for NavBar ", scrollSizeY.current); 40 | } 41 | } 42 | }, [context.sharedState.portfolio.NavBar, context.sharedState.portfolio.NavBar.IntervalEvent]); 43 | 44 | //Adding the EventListener for the NavBar 45 | useEffect(() => { 46 | if (context.sharedState.portfolio.NavBar.scrolling == null) { 47 | context.sharedState.portfolio.NavBar.scrolling = true; 48 | scrollSizeY.current = 0; 49 | //Hide when scroll down & show when scroll up 50 | if (typeof window !== "undefined") { 51 | window.addEventListener("scroll", context.sharedState.portfolio.NavBar.IntervalEvent); 52 | } 53 | } 54 | }, [context.sharedState.portfolio.NavBar, context.sharedState.portfolio.NavBar.scrolling]); 55 | 56 | 57 | 58 | useEffect(() => { 59 | setTimeout(() => { 60 | setShowElement(true); 61 | }, 10400); 62 | }, []); 63 | 64 | console.log("rotate from header : ", rotate); 65 | //veify document for serverSide rendering 66 | if (typeof document !== "undefined") { 67 | rotate ? (document.body.style.overflow = "hidden") : (document.body.style.overflow = "auto"); 68 | } 69 | 70 | return ( 71 | <> 72 | {/* Mobile visible Navbar component, controlling ShowElement state to hide itself and rotate itself */} 73 | 74 | {/* This parent element for Menu */} 75 | 84 | {/* Logo A */} 85 | 86 | 87 | {/* Hide icon Designed by me */} 88 | 89 | 96 | 97 | {/* ? Desktop Menu by Titof */} 98 | 99 | 100 | 101 | ); 102 | }; 103 | export default Header; 104 | -------------------------------------------------------------------------------- /components/Header/Headercomp/DesktopMenu.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { motion } from "../../../node_modules/framer-motion/dist/framer-motion"; 3 | import { Link as ReactScrollLink } from "react-scroll"; 4 | 5 | export default function DesktopMenu(props: { finishedLoading: boolean }) { 6 | return ( 7 |
8 | 25 | 26 | > 01. About 27 | 28 | 29 | 46 | 47 | > 02.{" "} 48 | Experience 49 | 50 | 51 | 67 | 68 | > 03. Work 69 | 70 | 71 | 72 | 73 | 89 | 90 | > 04. Contact 91 | 92 | 93 | 94 | {router.push("/resume.pdf")}} 109 | className="text-AAsecondary border border-spacing-2 py-2 px-3 rounded-sm border-AAsecondary hover:bg-ResumeButtonHover" 110 | > 111 | Resume 112 | 113 | 114 | 115 |
116 | ); 117 | } 118 | -------------------------------------------------------------------------------- /components/Header/Headercomp/IconMenu.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { motion } from "../../../node_modules/framer-motion/dist/framer-motion"; 3 | const IconMenu = (props: { rotate; setRotate; setShowElement; ShowElement,finishedLoading }) => { 4 | return ( 5 |
{ 8 | props.setRotate(!props.rotate); 9 | props.setShowElement(!props.ShowElement); 10 | }} 11 | > 12 |
13 | 20 |
21 | 26 |
27 |
28 |
29 | 36 |
37 |
38 | ); 39 | }; 40 | export default IconMenu; 41 | -------------------------------------------------------------------------------- /components/Header/Headercomp/Logo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { motion } from "../../../node_modules/framer-motion/dist/framer-motion"; 3 | export default function Logo(props: { finishedLoading: boolean }) { 4 | return ( 5 | <> 6 | 16 | 20 | A 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /components/Header/Headercomp/MobileMenu.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "../../../node_modules/framer-motion/dist/framer-motion"; 2 | import { Link } from "react-scroll"; 3 | const MobileMenu = props => { 4 | const closeMenu = () => { 5 | props.setRotate(!props.rotate); 6 | props.setShowElement(!props.ShowElement); 7 | }; 8 | return ( 9 | <> 10 | 16 |
closeMenu()} 18 | className="w-1/4 h-full backdrop-blur-sm bg-MobileNavColor/30 hover:cursor-pointer" 19 | >
20 |
24 | closeMenu()} 31 | className="flex flex-col text-center space-y-2" 32 | > 33 | 01. 34 | 38 | About 39 | 40 | 41 | closeMenu()} 48 | className="flex flex-col text-center space-y-2" 49 | > 50 | 02. 51 | 55 | Experience 56 | 57 | 58 | closeMenu()} 65 | className="flex flex-col text-center space-y-2" 66 | > 67 | 03. 68 | 72 | Work 73 | 74 | 75 | closeMenu()} 82 | className="flex flex-col text-center space-y-2" 83 | > 84 | 04. 85 | 89 | Contact 90 | 91 | 92 | 93 | 99 | 100 |
101 |
102 | 103 | ); 104 | }; 105 | export default MobileMenu; 106 | -------------------------------------------------------------------------------- /components/Header/StartupLogo/Startup.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { motion } from "../../../node_modules/framer-motion/dist/framer-motion"; 3 | const Startup = (props) => { 4 | let WidthBy2 = 0; 5 | let HeightBy2 = 0; 6 | let greaterThanSmall=false; 7 | if (typeof window !== "undefined") { 8 | if (window.innerWidth > 768) { 9 | WidthBy2 = window.innerWidth / 2 - 48 - 20; 10 | HeightBy2 = window.innerHeight / 2-44; 11 | greaterThanSmall=true; 12 | }else{ 13 | WidthBy2 = window.innerWidth / 2 -28; 14 | HeightBy2 = window.innerHeight / 2-40; 15 | } 16 | 17 | console.log("width by 2: ", WidthBy2); 18 | } 19 | 20 | return ( 21 | 22 | 27 | 38 | 48 | 58 | 70 | 82 | 94 | 106 | 112 | A 113 | 114 | 115 | 116 | ); 117 | }; 118 | export default Startup; 119 | -------------------------------------------------------------------------------- /components/Home/AboutMe/AboutMe.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Img from "../../../components/smallComp/image/Img"; 3 | import ArrowIcon from "../../../components/Icons/ArrowIcon"; 4 | export default function AboutMe(props) { 5 | const technologies = [ 6 | ["Solidity", "Next.js","Ether.js","JavaScript (ES6+)", "Tailwind CSS"], 7 | ["Hardhat", "Node.js","react-moralis", "TypeScript", "Framer Motion"], 8 | ]; 9 | return ( 10 |
11 | {/* // ? 0.1 About Me */} 12 |
16 |
17 |
18 | 19 | 01. 20 | 21 | About Me 22 | 23 |
24 |
25 |
26 | {/* // ? Paragraphs */} 27 | 28 |
29 |
30 |
31 | 32 | Hello! My name is Abdellatif and I enjoy solving problems and creating codes that live on the internet. 33 | My interest in computer science started back in 2009 when I decided to try learning{" "} 34 | Ethical Hacking using BackTrack OS — it turns out programming 35 | skills are essential to achieve that, my journey with programming started from that time! 36 | 37 |
38 |
39 | 40 | Fast-forward to today, I've had the privilege of working at 41 | a huge manufacturing company, 42 | a start-up,{" "} 43 | export-import companies, also 44 | freelancing and recently as Lead for the{" "} 45 | Google Developer Student club. Experienced in Desktop & Web 46 | Development, lately with Arduino Development. My main focus these days is creating and testing{" "} 47 | Smart Contracts with Hardhat. 48 | 49 |
50 | 51 |
52 | Here are a few technologies I've been working with recently: 53 |
54 |
55 |
56 |
57 | {technologies[0].map((tech, index) => { 58 | return ( 59 |
60 | 61 | {tech} 62 |
63 | ); 64 | })} 65 |
66 |
67 |
68 |
69 | {technologies[1].map((tech, index) => { 70 | return ( 71 |
72 | 73 | {tech} 74 |
75 | ); 76 | })} 77 |
78 |
79 |
80 |
81 | {/* // ? Image in Desktop and Tablet */} 82 |
83 |
88 | 89 |
90 |
91 | My Image Not Found 92 |
93 |
94 | {/* // ?Image in Mobile */} 95 |
96 |
97 |
98 | My Image Not Found 99 |
100 |
101 |
102 |
103 |
104 |
105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /components/Home/GetInTouch/GetInTouch.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ArrowIcon from "../../Icons/ArrowIcon"; 3 | export default function GetInTouch() { 4 | return ( 5 |
6 | {/* // ? Title === > What's Next? */} 7 |
8 | 9 |
10 | 11 | {" "} 12 | 04. 13 | 14 | 15 | What's Next? 16 | 17 |
18 |
19 | {/* // ? Get In Touch */} 20 | 21 | Get In Touch 22 | 23 |

24 | Although I'm currently looking for any new opportunities, my inbox 25 | is always open. Whether you have a question or just want to say hi, I'll 26 | try my best to get back to you! 27 |

28 | 38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /components/Home/MyName/MyName.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { motion } from "../../../node_modules/framer-motion/dist/framer-motion"; 3 | import Link from "next/link"; 4 | import { useRouter } from "next/router"; 5 | export default function MyName(props: { finishedLoading: boolean }) { 6 | const router = useRouter(); 7 | return ( 8 |
12 | 27 | Hi, my name is 28 | 29 | 44 | Anthony Miller. 45 | 46 | 61 | I make ideas & things alive. 62 | 63 | 64 | 79 | I'm a Full Stack Engineer{" "} 80 | skilled in problem-solving and specializing in building 81 |
(and occasionally designing) 82 | exceptional digital experiences. Currently.{" "} 83 |
84 | I'm focused on creating and deploying{" "} 85 | Smart Contracts on the 86 | Blockchain. 87 |
88 | 103 | 104 | 107 | 108 | 109 |
110 | ); 111 | } 112 | -------------------------------------------------------------------------------- /components/Home/SocialMediaArround/SocialMediaArround.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { motion } from "../../../node_modules/framer-motion/dist/framer-motion"; 3 | import GithubIcon from "../../Icons/GithubIcon"; 4 | import LinkedinIcon from "../../Icons/LinkedinIcon"; 5 | import InstagramIcon from "../../Icons/InstagramIcon"; 6 | import YoutubeIcon from "../../Icons/YoutubeIcon"; 7 | 8 | const IconClickableWithAnimation = props => { 9 | return ( 10 | 17 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | export default function SocialMediaEmail(props: { finishedLoading: boolean }) { 24 | return ( 25 | <> 26 | 32 |
33 |
34 | {/* Github Icon */} 35 | 36 | {/* Linkedin icon */} 37 | 38 | {/* Instagram Icon */} 39 | 40 | {/* Youtube Icon */} 41 | 42 |
43 |
44 |
45 |
46 | 47 | {/* // ? Email Address bar */} 48 | 55 |
56 | {/* Open Email on click */} 57 | 65 | 66 | 67 | abdellatif@anaflous.com 68 | 69 | 70 | 71 | 72 |
73 |
74 |
75 | 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /components/Home/SomethingIveBuilt/SomethingIveBuilt.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { useRouter } from "next/router"; 3 | import React from "react"; 4 | import ArrowIcon from "../../Icons/ArrowIcon"; 5 | import Img from "../../smallComp/image/Img"; 6 | import GithubIcon from "../../Icons/GithubIconForSomethingIveBuild"; 7 | import ExternalLink from "../../Icons/ExternalLink"; 8 | 9 | export default function SomethingIveBuilt() { 10 | const router = useRouter(); 11 | return ( 12 |
17 | {/* // ? Title */} 18 |
19 | 20 |
21 | 03. 22 | 23 | {" "} 24 | Some Things I've Built 25 | 26 |
27 |
28 |
29 | 30 |
31 | {/* // ? Project 1 */} 32 |
33 | {/* Left image */} 34 |
38 |
39 | 40 |
44 |
45 | {"Project 46 |
47 |
48 | 49 | {/* right Content */} 50 |
51 | {/* background for text in mobile responsive */} 52 |
53 |
54 |
55 |
56 | {"Project 57 |
58 |
59 | 60 |
64 |
65 | Recent Project 66 | 67 | 68 | YPredict - v1 69 | 70 | 71 |
72 |
73 |

74 | I had the opportunity to lead the development of a token project, which aimed to create a 75 | decentralized ecosystem for peer-to-peer transactions. Overseeing the planning and development of the 76 | project, including the design and implementation of the{" "} 77 | smart contract and{" "} 78 | blockchain technology. Here i share with you{" "} 79 | YPredict - v1 for the private sale. 80 |

81 |
82 |
    86 | Token 87 | Smart contract 88 | Blockchain 89 | Nextjs 90 | Token tracking 91 |
92 |
93 | 94 | 95 | 96 | 97 |
98 |
99 |
100 |
101 | {/* // ? Project 2*/} 102 |
103 | {/* Left image */} 104 |
108 |
109 | 110 |
115 | 116 | 117 | {"Project 118 |
119 |
120 | 121 | {/* right Content */} 122 |
123 | {/* background for text in mobile responsive */} 124 |
125 |
126 |
127 |
128 | {"Project 129 |
130 |
131 | 132 |
136 |
137 | Recent Project 138 | 139 | 140 | Speed Typing 141 | 142 | 143 |
144 |
145 |

146 | This project aim to help you to improve your typing by 147 | tracking your progress in each round and give you a{" "} 148 | score based on your typing speed and accuracy through a 149 | table of statistics. 150 |

151 |
152 |
    156 | Algorithms 157 | Framer Motion 158 | Tailwind CSS 159 | TypeScript 160 |
161 |
162 | 163 | 164 |
165 |
166 |
167 |
168 | 169 | {/* // ? Project 3 */} 170 |
171 | {/* Left image */} 172 |
176 |
177 | 178 |
182 | 183 | {"Project 184 |
185 |
186 | 187 | {/* right Content */} 188 |
189 | {/* background for text in mobile responsive */} 190 |
191 |
192 |
193 |
194 | {"Project 195 |
196 |
197 | 198 |
202 |
203 | Recent Project 204 | 205 | 206 | User Data puller 207 | 208 | 209 |
210 |
211 |

212 | This project allows you to understand how easy it is to identify and track your browser based on how 213 | it appears to websites. Such as your 214 | location,{" "} 215 | IP Address,{" "} 216 | {","} software, Hardware and some additional information 217 | with the help of cookies. 218 |

219 |
220 |
    224 | Cookies 225 | Google APi 226 | Data collecting 227 | IP Address 228 | Location 229 |
230 |
231 | 232 | 233 |
234 |
235 |
236 |
237 | 238 | {/* // ? Project 4 */} 239 |
240 | {/* Left image */} 241 |
245 |
246 | 247 |
252 |
253 | 254 |
255 | In initiation... 256 |
257 | 258 | {"Project 259 |
260 |
261 | 262 | {/* right Content */} 263 |
264 | {/* background for text in mobile responsive */} 265 |
266 |
267 |
268 |
269 | {"Project 270 |
271 |
272 |
273 |
274 | In initiation... 275 |
276 |
280 |
281 | Recent Project 282 | 283 | 284 | Haircut Appointment 285 | 286 | 287 |
288 |
289 |

290 | It' a barber shop appointment scheduling & management 291 | software it provides online scheduling, appointment 292 | reminders, payments, marketing, and much more! Currently in 293 | the Initiation phase. 294 |

295 |
296 |
    300 | Appointment 301 | Management 302 | Scheduling 303 | Booking 304 |
305 |
306 | 307 | 308 | 309 | 310 |
311 |
312 |
313 |
314 | 315 | {/* // ? Project 5 */} 316 |
317 | {/* Left image */} 318 |
322 |
323 | 324 |
328 |
329 | 330 | {"Project 331 |
332 |
333 | 334 | {/* right Content */} 335 |
336 | {/* background for text in mobile responsive */} 337 |
338 |
339 |
340 |
341 | {"Project 342 |
343 |
344 | 345 |
349 | 357 |
358 |

359 | A JavaFX call center management system project it is used 360 | for receiving or transmitting a large volume of enquiries between the agent and the customer,it 361 | handles the communication between agents & clients it can be used for the both side client & agent 362 | side to handle the messages and issues. 363 |

364 |
365 |
    369 | JavaFX 370 | Java Concurrency 371 | MultiThreading 372 | MySQL 373 |
374 |
375 | 376 |
377 |
378 |
379 |
380 |
381 |
382 | ); 383 | } 384 | -------------------------------------------------------------------------------- /components/Home/ThisSiteCantBeReached/ThisCantBeReached.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { motion } from "../../../node_modules/framer-motion/dist/framer-motion"; 3 | export default function ThisCantBeReached() { 4 | const [ShowText, setShowText] = React.useState(false); 5 | let CenterWidth = 0; 6 | let CenterHeight = 0; 7 | React.useEffect(() => { 8 | setTimeout(function () { 9 | setShowText(true); 10 | }, 1000); 11 | }, []); 12 | if (typeof window !== "undefined") { 13 | if(window.innerHeight>640){ 14 | CenterHeight = (window.innerHeight)/2-160-20; 15 | }else{ 16 | CenterHeight = (window.innerHeight)/2-64-20; 17 | } 18 | if(window.innerWidth>1280){ 19 | CenterWidth = (window.innerWidth)/2-384-18; 20 | }else if(window.innerWidth>1024){ 21 | CenterWidth = (window.innerWidth)/2-192-18; 22 | }else if(window.innerWidth>768){ 23 | CenterWidth = (window.innerWidth)/2-144-18; 24 | }else if(window.innerWidth>640){ 25 | CenterWidth = (window.innerWidth)/2-96-18; 26 | }else{ 27 | CenterWidth = (window.innerWidth)/2-16-18; 28 | } 29 | 30 | } 31 | 32 | return ( 33 | 43 |
44 | {/* Icon for Desktop and Table */} 45 | 46 | 50 |
51 |
52 |
53 |
54 | 55 | {/* Left Eye */} 56 | 57 | 80 | 81 | {/* Right Eye */} 82 | 110 | 111 | {/* Corner */} 112 | 113 | 122 | 131 | 137 |
138 |
139 |
140 |
141 |
142 | 143 | {/* Smile */} 144 | 145 |
146 | 151 | 158 | {/* ! Hello animation text */} 159 |
160 | 165 | Hello! 166 | 167 | 168 | 169 | 170 | {/* Text start from here */} 171 | 172 | 178 | 179 | This site{" "} 180 | {ShowText ? ( 181 | 186 | actually can 187 | 188 | ) : ( 189 | can't 190 | )}{" "} 191 | be reached 192 | 193 | 194 | www.anaflous.com 195 | unexpectedly{" "} 196 | {ShowText ? ( 197 | 202 | opened 203 | 204 | ) : ( 205 | closed 206 | )}{" "} 207 | the connection. 208 | 209 |
210 | Try: 211 |
212 | 213 | Checking 214 | the connection 215 | 216 | 217 | Checking 218 | the proxy and the firewall 219 | 220 | 221 | Running 222 | Windows Network Diagnostics 223 | 224 |
225 |
226 | 227 | {ShowText ? ( 228 | 233 | SUCC_CONNECTION_OPENED 234 | 235 | ) : ( 236 | ERR_CONNECTION_CLOSED 237 | )} 238 | 239 |
240 |
241 | 247 | 250 | 251 |
252 | ); 253 | } 254 | -------------------------------------------------------------------------------- /components/Home/WhereIHaveWorked/Descriptions/AdvancedAgroManagement.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ArrowIcon from "../../../Icons/ArrowIcon"; 3 | import { getTasksTextWithHighlightedKeyword } from "./taskAndType"; 4 | 5 | export default function AdvancedAgroManagement() { 6 | const tasks = [ 7 | { 8 | text: "Developed and Build a new version customizable website for Advanced Agro Management.", 9 | keywords: ["Advanced Agro Management"], 10 | }, 11 | { 12 | text: "Monitored website performance and handled troubleshooting and WordPress issues.", 13 | keywords: ["scripts"], 14 | }, 15 | { 16 | text: "Managed company WordPress website back-end with Oracle Database including plugins, tools, and themes.", 17 | keywords: ["Oracle Database"], 18 | }, 19 | ]; 20 | return ( 21 | <> 22 |
23 |
24 | {/* Title */} 25 | 26 | Software Developer @ Wordpress 27 | 28 | {/* Date */} 29 | june - August 2019 30 |
31 |
32 | {/* Tasks Description 1 */} 33 | {tasks.map((item, index) => { 34 | return ( 35 |
36 | 37 | 43 |
44 | ); 45 | })} 46 |
47 |
48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /components/Home/WhereIHaveWorked/Descriptions/Fantasia.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ArrowIcon from "../../../Icons/ArrowIcon"; 3 | import { getTasksTextWithHighlightedKeyword } from "./taskAndType"; 4 | 5 | export default function Fantasia() { 6 | const tasks = [ 7 | { 8 | text: "Responsible for Windows Server 2008 r2 installs, configuration and support Active Directory, DNS, DHCP, WINS..", 9 | keywords: ["Windows Server 2008"], 10 | }, 11 | { 12 | text: "Migration of Windows 2008 to Windows Server 2012 .", 13 | keywords: ["Migration"], 14 | }, 15 | { 16 | text: "Responsible for Server full, differential and incremental backups using Veeam Endpoint Backup with SQL Server 2012.", 17 | keywords: ["Veeam Endpoint Backup", "SQL Server 2012"], 18 | }, 19 | ]; 20 | return ( 21 |
22 |
23 | {/* Title */} 24 | 25 | Server Manager Assistant @ Windows Server 26 | 27 | {/* Date */} 28 | June - July 2018 29 |
30 |
31 | {tasks.map((item, index) => { 32 | return ( 33 |
34 | 35 | 41 |
42 | ); 43 | })} 44 |
45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /components/Home/WhereIHaveWorked/Descriptions/FeverTokens.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ArrowIcon from "../../../Icons/ArrowIcon"; 3 | import { getTasksTextWithHighlightedKeyword } from "./taskAndType"; 4 | export default function FeverTokens() { 5 | const tasks = [ 6 | { 7 | text: "Leading the development of the NFT Marketplace v2 for the Platform FeverTokens.", 8 | keywords: ["NFT Marketplace v2"], 9 | }, 10 | { 11 | text: "Worked with a team of six developers to build a temporary NFT Marketplace platform for VivaTech2022 event, an ambitious startup originating from France,Paris.", 12 | keywords: ["VivaTech2022 event"], 13 | }, 14 | { 15 | text: "Interacted with the blockchain & Smart Contracts to build the new FeverTokens Platform.", 16 | keywords: ["blockchain & Smart Contracts"], 17 | }, 18 | ]; 19 | return ( 20 | <> 21 |
22 |
23 | {/* Title */} 24 | 25 | Full Stack Engineer @ web3 26 | 27 | {/* Date */} 28 | 29 | Mars - August 2022 30 | 31 |
32 |
33 | {/* Tasks Description 1 */} 34 | {tasks.map((item, index) => { 35 | return ( 36 |
37 | 38 | 47 |
48 | ); 49 | })} 50 |
51 |
52 | 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /components/Home/WhereIHaveWorked/Descriptions/IdealFresh.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ArrowIcon from "../../../Icons/ArrowIcon"; 3 | import { getTasksTextWithHighlightedKeyword } from "./taskAndType"; 4 | 5 | export default function IdealFresh() { 6 | const tasks = [ 7 | { 8 | text: "Designed and Built a Desktop app for IdealFresh that manage billings, client circumstances using Apache POI with Microsoft Office Excel.", 9 | keywords: ["Apache POI"], 10 | }, 11 | { 12 | text: "Creating customized scripts for pulling, managing and refactoring files from Clouds.", 13 | keywords: ["scripts"], 14 | }, 15 | { 16 | text: "Daily communications and interactions with a non-developers to solve their problems by providing technological solutions and expertise.", 17 | keywords: ["non-developers"], 18 | }, 19 | ]; 20 | return ( 21 | <> 22 |
23 |
24 | {/* Title */} 25 | 26 | Software Developer @ JavaFX 27 | 28 | {/* Date */} 29 | June - August 2021 30 |
31 |
32 | {/* Tasks Description 1 */} 33 | {tasks.map((item, index) => { 34 | return ( 35 |
36 | 37 | 43 |
44 | ); 45 | })} 46 |
47 |
48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /components/Home/WhereIHaveWorked/Descriptions/SuperBerry.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ArrowIcon from "../../../Icons/ArrowIcon"; 3 | import { getTasksTextWithHighlightedKeyword } from "./taskAndType"; 4 | 5 | export default function SuperBerry() { 6 | const tasks = [ 7 | { 8 | text: "Designed and Built user-friendly customizable static web application using ReactJS, TailwindCSS v1, and TypeScript.", 9 | keywords: ["ReactJS", "TailwindCSS v1", "TypeScript"], 10 | }, 11 | { 12 | text: "upgraded the web application for better SEO with Next.js and exploiting the server-side rendering benefits.", 13 | keywords: ["Next.js"], 14 | }, 15 | { 16 | text: "Provided technical support and troubleshoots errors and/or problems with web based applications.", 17 | keywords: [], 18 | }, 19 | ]; 20 | 21 | return ( 22 | <> 23 |
24 |
25 | {/* Title */} 26 | 27 | Full Stack Engineer{" "} 28 | @ Web App 29 | 30 | {/* Date */} 31 | 32 | June - August 2020 33 | 34 |
35 |
36 | {/* Tasks Description 1 */} 37 | {tasks.map((item, index) => { 38 | return ( 39 |
40 | 41 | 50 |
51 | ); 52 | })} 53 |
54 |
55 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /components/Home/WhereIHaveWorked/Descriptions/TrouveTavoie.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ArrowIcon from "../../../Icons/ArrowIcon"; 3 | import { getTasksTextWithHighlightedKeyword } from "./taskAndType"; 4 | 5 | export default function TrouveTavoie() { 6 | const tasks = [ 7 | { 8 | text: "Spearhead & implemented a new design user workflow system for the Frond-End Architecture of a NFT Marketplace.", 9 | keywords: ["NFT Marketplace"], 10 | }, 11 | { 12 | text: "Work with a variety of different languages, platforms, frameworks, and content management systems such as JavaScript, TypeScript, Next.js/React, AWS and Vercel.", 13 | keywords: ["Next.js/React", "AWS", "Vercel"], 14 | }, 15 | { 16 | text: "Interfaced with developers on a daily basis, providing technological expertise.", 17 | keywords: [], 18 | }, 19 | ]; 20 | 21 | return ( 22 | <> 23 |
24 |
25 | {/* Title */} 26 | 27 | Full Stack Engineer{" "} 28 | @ Blockchain 29 | 30 | {/* Date */} 31 | 32 | Mars - August 2022 33 | 34 |
35 |
36 | {/* Tasks Description 1 */} 37 | {tasks.map((item, index) => { 38 | return ( 39 |
40 | 41 | 50 |
51 | ); 52 | })} 53 |
54 |
55 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /components/Home/WhereIHaveWorked/Descriptions/taskAndType.ts: -------------------------------------------------------------------------------- 1 | export type task = [{ text: string; keywords: string[] }]; 2 | 3 | export const getTasksTextWithHighlightedKeyword = (text: string, keyword: string[] | []) => { 4 | if (keyword.length > 0) { 5 | const regex = new RegExp(keyword.join("|"), "gi"); 6 | console.log("regex", regex); 7 | return text.replace(regex, match => `${match}`); 8 | } 9 | return text; 10 | }; 11 | -------------------------------------------------------------------------------- /components/Home/WhereIHaveWorked/WhereIHaveWorked.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { motion } from "../../../node_modules/framer-motion/dist/framer-motion"; 3 | import ArrowIcon from "../../Icons/ArrowIcon"; 4 | import TrouveTavoie from "./Descriptions/TrouveTavoie"; 5 | import FeverTokens from "./Descriptions/FeverTokens"; 6 | import IdealFresh from "./Descriptions/IdealFresh"; 7 | import AdvancedAgroManagement from "./Descriptions/AdvancedAgroManagement"; 8 | import Fantasia from "./Descriptions/Fantasia"; 9 | import SuperBerry from "./Descriptions/SuperBerry"; 10 | export default function WhereIHaveWorked() { 11 | const barRef = React.useRef(null); 12 | // ? INFORMATIONAL control the green position using px, 13 | // ? INFORMATIONAL the default value of barRef's class should be at the beginning translate-y-[0px] 14 | const GetDescription = () => { 15 | switch (DescriptionJob) { 16 | case "TrouveTavoie": 17 | return ; 18 | case "FeverTokens": 19 | return ; 20 | case "IdealFresh": 21 | return ; 22 | case "Advanced Agro Management": 23 | return ; 24 | case "Fantasia": 25 | return ; 26 | case "SuperBerry": 27 | return ; 28 | } 29 | }; 30 | const [DescriptionJob, setDescriptionJob] = React.useState("TrouveTavoie"); 31 | return ( 32 |
33 | {/* // ? Title "Where I've Worked" */} 34 |
35 |
36 | 37 | 02. 38 |
39 | 40 | 41 | Where I've Worked 42 | 43 |
44 |
45 | {/* // ? Where I've Worked Content section */} 46 |
50 | {/* // ? Left side of Where I've Worked, contains the bar and name of companies */} 51 | 52 | {/* // ? Description for The job */} 53 | {GetDescription()} 54 |
55 |
56 | ); 57 | } 58 | 59 | const CompaniesBar = props => { 60 | const [barPosition, setBarPosition] = React.useState(-8); // Green bar position by the default it's -20px 61 | const [barAbovePosition, setBarAbovePosition] = React.useState(0); 62 | const [companyNameBackgroundColorGreen, setCompanyNameBackgroundColorGreen] = React.useState([ 63 | true, 64 | false, 65 | false, 66 | false, 67 | false, 68 | , 69 | false, 70 | ]); 71 | const CompanyButton = props => { 72 | return ( 73 | 91 | ); 92 | }; 93 | 94 | return ( 95 |
101 | {/* // ? left bar Holder */} 102 |
106 | {/* // ? animated left bar */} 107 | 112 |
113 | {/* // ? Companies name as buttons */} 114 |
115 |
116 | 125 | 134 | 143 | 152 | 161 | 170 |
171 |
172 | 173 |
174 |
175 |
176 | ); 177 | }; 178 | -------------------------------------------------------------------------------- /components/Icons/ArrowIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function ArrowIcon(props) { 4 | return ( 5 | 11 | 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /components/Icons/ExternalLink.tsx: -------------------------------------------------------------------------------- 1 | import {NextRouter} from 'next/router'; 2 | const ExternalLink = (props: { router: NextRouter; url: string }) => { 3 | return ( 4 | props.router.push(props.url)} 6 | xmlns="http://www.w3.org/2000/svg" 7 | role="img" 8 | viewBox="0 0 24 24" 9 | fill="none" 10 | stroke="currentColor" 11 | strokeWidth="2" 12 | strokeLinecap="round" 13 | strokeLinejoin="round" 14 | className="w-6 h-6 text-gray-300 hover:text-AAsecondary hover:cursor-pointer 15 | transition ease-in-out delay-50 hover:-translate-y-1 16 | hover:scale-110 duration-200" 17 | > 18 | External Link 19 | 20 | 21 | 22 | 23 | ); 24 | }; 25 | export default ExternalLink; -------------------------------------------------------------------------------- /components/Icons/GithubIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function GithubIcon(props) { 4 | return ( 5 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /components/Icons/GithubIconForSomethingIveBuild.tsx: -------------------------------------------------------------------------------- 1 | const GithubIcon = props => { 2 | return ( 3 | 4 | 17 | GitHub 18 | 19 | 20 | 21 | ); 22 | }; 23 | export default GithubIcon; -------------------------------------------------------------------------------- /components/Icons/InstagramIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function InstagramIcon(props) { 4 | return ( 5 | 12 | 13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /components/Icons/LinkedinIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function LinkedinIcon(props) { 4 | return ( 5 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /components/Icons/Loader.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | type Props = { 3 | className; 4 | }; 5 | export default function Loader(props:Props) { 6 | return ( 7 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /components/Icons/YoutubeIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function YoutubeIcon(props) { 4 | return ( 5 | 12 | 13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /components/TypingProject/AboutComp/About.tsx: -------------------------------------------------------------------------------- 1 | 2 | import Img from "..//Image/Img"; 3 | export default function About() { 4 | return ( 5 |
13 |
14 | About picture 15 |
16 |
17 | 18 | What's this project for? 19 | 20 | 21 | Most jobs do not explicitly require certain typing speeds, but that's because basic typing skills are 22 | taken as a given. Thus, you should{" "} 23 | aim for a typing speed of at least 40 WPM to keep up a 24 | standard level of efficiency at work..However this project aim to help you to improve your typing speed by 25 | tracking your progress in each round and give you a score based on your typing speed and accuracy. 26 | 27 |
28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /components/TypingProject/CursorCarotComp/CursorCarotComp.tsx: -------------------------------------------------------------------------------- 1 | import {motion} from "framer-motion" 2 | export default function CursorCarotComp() { 3 | return ( 4 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /components/TypingProject/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import GithubIcon from "../../Icons/GithubIcon"; 3 | import LinkedinIcon from "../../Icons/LinkedinIcon"; 4 | import InstagramIcon from "../../Icons/InstagramIcon"; 5 | import YoutubeIcon from "../../Icons/YoutubeIcon"; 6 | type Props = { href: string; Icon: React.FC<{ className: string }> }; 7 | const ClickableIcon = (props: Props) => { 8 | return ( 9 | 10 | 15 | 16 | ); 17 | }; 18 | 19 | export default function Fotter(props: { link: string; className: string }) { 20 | return ( 21 | 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /components/TypingProject/Functions/functions.ts: -------------------------------------------------------------------------------- 1 | import { wordsStatus,Data,ActiveWordWithIndex } from "../Types/types"; 2 | 3 | /** 4 | * @note use minLength & maxLength to limit the quote length 5 | * @default_URL : https://api.quotable.io/random?minLength=100&maxLength=140 6 | */ 7 | export const getData = async ( 8 | arg_state: React.Dispatch>, 9 | setActiveWordWithIndex: React.Dispatch>, 10 | setRoundCounter: React.Dispatch>, 11 | roundCounter: number 12 | ) => { 13 | fetch("/api/typing/10") 14 | .then(response => response.json()) 15 | .then(data => { 16 | // ?UNCOMMENT THIS TO MODIFY THE QUOTE FOR TESTING 17 | // data.quote = "j"; 18 | const wordsAndStatus: wordsStatus = []; // this aaay will hold the words and their status 19 | data.quote.split(" ").forEach((item: string, index: number) => { 20 | const word = () => { 21 | if (data.quote.split(" ").length - 1 == index) { 22 | return item; 23 | } else { 24 | return item + " "; 25 | } 26 | }; 27 | wordsAndStatus.push({ 28 | word: word(), 29 | indexFrom: 0, 30 | indexTo: 0, 31 | }); 32 | }); 33 | // getting index of the first char and last char in the text. 34 | let LastIndex = 0; 35 | wordsAndStatus.forEach((item, index) => { 36 | if (index == 0) { 37 | item.indexFrom = 0; 38 | item.indexTo = item.word.length - 1; 39 | LastIndex = item.indexTo; 40 | } else { 41 | item.indexFrom = LastIndex + 1; 42 | item.indexTo = item.indexFrom + item.word.length - 1; 43 | LastIndex = item.indexTo; 44 | } 45 | }); 46 | const temArray: Data = [wordsAndStatus, [], { CursorPosition: 0 }]; //temporary array to hold the data 47 | 48 | /** 49 | * @@explanation for the following action 50 | * this will will convert data to array of char then push each char to the tempArray second Array 51 | * as objects with background default value "" 52 | */ 53 | data.quote.split("").forEach((item: string, index: number) => { 54 | // pushing the char to the tempArray second Array 55 | temArray[1].push({ 56 | char: item, 57 | charColor: "text-gray-500", 58 | }); 59 | }); 60 | setRoundCounter(roundCounter + 1); 61 | setActiveWordWithIndex({ wordIndex: 0, wordDetail: temArray[0][0] }); // set the first active word as active after Data is loaded 62 | /** 63 | * @stateChange : this will change the state that contains the data 64 | */ 65 | arg_state(temArray); 66 | }) 67 | .catch(err => console.error(err)); 68 | }; 69 | 70 | type CharAndColor = { char: string; charColor: string }; 71 | // this function will calculate the wpm 72 | export const calculateWpm = (input: CharAndColor[], time: number) => { 73 | let cpm = 0; 74 | for (let i = 0; i < input.length; i++) { 75 | if (input[i].charColor == "text-AAsecondary") { 76 | cpm++; 77 | } else if (input[i].charColor == "text-gray-500") { 78 | break; 79 | } 80 | } 81 | return Math.floor(Math.round((cpm / time) * 60) / 5); 82 | }; 83 | 84 | // this function will calculate the accuracy 85 | export const calculateAccuracy = (input: CharAndColor[]) => { 86 | let correct = 0; 87 | let incorrect = 0; 88 | for (let i = 0; i < input.length; i++) { 89 | if (input[i].charColor == "text-AAsecondary") { 90 | correct++; 91 | } else if (input[i].charColor == "text-AAerror") { 92 | incorrect++; 93 | } 94 | } 95 | return Math.floor((correct * 100) / input.length); 96 | }; 97 | 98 | // this will handle onCharChange event and will update the states 99 | export const handleOnChangeInput = ( 100 | input: string, 101 | event: React.ChangeEvent, 102 | activeWordWithIndex: ActiveWordWithIndex, 103 | setActiveWordWithIndex:React.Dispatch>, 104 | myText:Data, 105 | setMyText:React.Dispatch>, 106 | setIsFinished:React.Dispatch>, 107 | timerCountingInterval:React.MutableRefObject, 108 | updateStatistics:() => void, 109 | ) => { 110 | /** 111 | * @nextForLoop 112 | * this for loop to give the char its default color back, starting from activeWord first char index 113 | * this loop will help when user delete a character 114 | */ 115 | for (let j = activeWordWithIndex.wordDetail.indexFrom; j < myText[1].length; j++) { 116 | myText[1][j].charColor = "text-gray-500"; 117 | } 118 | 119 | // start validating from this index CharIndex initial 120 | let targetWordIndexIncrement = activeWordWithIndex.wordDetail.indexFrom; 121 | input.split("").forEach((element, index) => { 122 | myText[1][targetWordIndexIncrement].charColor = 123 | element === myText[1][targetWordIndexIncrement].char ? "text-AAsecondary" : "text-AAError"; 124 | targetWordIndexIncrement++; 125 | }); 126 | // checks if input is equal to the active word ( true => set inputValue to "" ) 127 | if (input.localeCompare(activeWordWithIndex.wordDetail.word) == 0) { 128 | const nextWordIndex = activeWordWithIndex.wordIndex + 1; 129 | setActiveWordWithIndex({ 130 | wordIndex: nextWordIndex, 131 | wordDetail: myText[0][nextWordIndex], 132 | }); 133 | event.target.value = ""; // clear the input 134 | } 135 | 136 | // set the cursor position to next target Char that will be typed of the active word 137 | /** 138 | * @note : normal for loop is used here to break the loop 139 | */ 140 | for (let i = 0; i < myText[1].length; i++) { 141 | if (myText[1][i].charColor.localeCompare("text-gray-500") == 0) { 142 | myText[2].CursorPosition = i; 143 | break; 144 | } 145 | } 146 | setMyText([...myText]); // update the state 147 | // Checking if the user finished typing by checking if the last char gray color is changed! 148 | if (!(myText[1][myText[1].length - 1].charColor === "text-gray-500")) { 149 | console.log("Player Finished typing!!"); 150 | updateStatistics(); // update statistics 151 | /** 152 | * @note : next line will prevent from showing the previous text when user restarts 153 | * by checking !(myText[1].length==0) 154 | */ 155 | myText[1] = []; 156 | setMyText([...myText]); 157 | setIsFinished(true); 158 | clearInterval(timerCountingInterval.current); // stop the timer 159 | } 160 | }; 161 | -------------------------------------------------------------------------------- /components/TypingProject/Icons/RestartIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function RestartIcon() { 4 | return ( 5 | 13 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /components/TypingProject/Image/Img.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | type Props={src:string,alt:string,className:string} 3 | export default function Img(props:Props) { 4 | return ( 5 | 6 | // eslint-disable-next-line @next/next/no-img-element 7 | {props.alt} 8 | ) 9 | } -------------------------------------------------------------------------------- /components/TypingProject/Statistics/TypingStatistics.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import StatisticsTab from "../statisticsTab/StatisticsTab"; 3 | import RestartIcon from "../Icons/RestartIcon"; 4 | import About from "../AboutComp/About"; 5 | import { Statistics } from "../Types/types"; 6 | import { MutableRefObject } from "react"; 7 | type Props = { 8 | restart: () => void; 9 | statistics: Statistics; 10 | roundCounter: number; 11 | timeToType: number; 12 | seconds: MutableRefObject; 13 | }; 14 | export default function TypingStatistics(props: Props) { 15 | return ( 16 | <> 17 |
18 | {/* Shortcuts mention */} 19 | 25 | Windows : Ctrl + / 26 | Or 27 | Mac : Cmd + / 28 | 29 | {/**Separator */} 30 |
31 | {/* Restart part */} 32 | { 37 | console.log("Restarted By a click!!!!"); 38 | props.restart(); 39 | }} 40 | className="group flex flex-row space-x-3 items-center hover:cursor-pointer" 41 | > 42 |
43 | 44 |
45 | 46 | Restart 47 | 48 |
49 |
50 | {/* Round Statistics I caDetails */} 51 |
52 | 57 |
58 | 59 | 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /components/TypingProject/Types/types.tsx: -------------------------------------------------------------------------------- 1 | export type ActiveWordWithIndex = { 2 | wordIndex: number; 3 | wordDetail: { 4 | word: ReturnType<() => string>; 5 | indexFrom: number; 6 | indexTo: number; 7 | }; 8 | }; 9 | export type Data = [wordsStatus, [{ char: string; charColor: string }?], { CursorPosition: number }]; 10 | export type wordsStatus = [{ word: string; indexFrom: number; indexTo: number }?]; 11 | export type Statistics = [{ round: number; wpm: number; accuracy: number }?]; -------------------------------------------------------------------------------- /components/TypingProject/statisticsTab/StatisticsTab.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { motion } from "framer-motion"; 3 | 4 | const getTopScore = (st: Statistics) => { 5 | if (st.length > 1) { 6 | const statics = [...st.slice(0).reverse()]; 7 | let topScore = statics[0].wpm; 8 | let topScoreIndex = 0; 9 | statics.forEach((item, index) => { 10 | if (item.wpm > topScore) { 11 | topScore = item.wpm; 12 | topScoreIndex = index; 13 | } 14 | }); 15 | return topScoreIndex; 16 | } else { 17 | return null; 18 | } 19 | }; 20 | 21 | const isTopScore = (index: number, statistics: Statistics) => { 22 | const result = getTopScore(statistics); 23 | return result == null ? ( 24 | <> 25 | ) : index === result ? ( 26 | 32 | TopScore 33 | 34 | ) : ( 35 | <> 36 | ); 37 | 38 | }; 39 | 40 | type Statistics = [{ round: number; wpm: number; accuracy: number }?]; 41 | export default function StatisticsTab({ 42 | statistics, 43 | round, 44 | finishedTime, 45 | }: { 46 | round: number; 47 | finishedTime: string; 48 | statistics: Statistics; 49 | }) { 50 | console.log("score list : ", statistics); 51 | return ( 52 | <> 53 |
54 |
55 | Statistics 56 |
57 |
58 |
round {round.toString()} :
59 |
{finishedTime} sec
60 |
61 |
62 |
63 |
64 |
65 |
66 | 67 | 68 | 69 | 72 | 75 | 78 | 79 | 80 | 81 | {statistics 82 | .slice(0) 83 | .reverse() 84 | .map((item, index) => { 85 | return index == 0 ? ( 86 | 103 | 104 | 108 | 109 | 110 | 111 | ) : ( 112 | 113 | 114 | 118 | 119 | 120 | ); 121 | })} 122 | 123 |
70 | ROUND 71 | 73 | Wpm 74 | 76 | Accuracy 77 |
{item.round} 105 | {isTopScore(index, statistics)} 106 | {item.wpm} wpm 107 | {item.accuracy}%
{item.round} 115 | {isTopScore(index, statistics)} 116 | {item.wpm} wpm 117 | {item.accuracy}%
124 |
125 |
126 |
127 |
128 | 129 | ); 130 | } 131 | -------------------------------------------------------------------------------- /components/TypingProject/timer/TimerSpan.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | import { motion } from "framer-motion"; 3 | 4 | 5 | // this will return min and sec Tens and Units example of of seconds is 180 sec return is [3,0,0] 6 | const getMinutesAndSeconds = (secondsCounts: number) => { 7 | if (secondsCounts >= 60) { 8 | const minutes = Math.floor(secondsCounts / 60); 9 | const secondsUnits = secondsCounts - minutes * 60; 10 | if (secondsUnits > 9) { 11 | const secondsUnit = Math.floor(secondsUnits / 10); 12 | return [minutes, secondsUnit, secondsUnits - secondsUnit * 10]; 13 | } else { 14 | return [minutes, 0, secondsUnits]; 15 | } 16 | } else { 17 | if (secondsCounts > 9) { 18 | const secondsTen = Math.floor(secondsCounts / 10); 19 | return [0, secondsTen, secondsCounts - secondsTen * 10]; 20 | } else { 21 | return [0, 0, secondsCounts]; 22 | } 23 | } 24 | }; 25 | export default function TimerSpan({ 26 | setIsFinished, 27 | inputLostFocus, 28 | seconds, 29 | timerCountingInterval, 30 | updateStatistics, 31 | 32 | }) { 33 | const [secondsState, setSecondsState] = useState(seconds.current); 34 | const timerSpanRef = useRef(null); 35 | const [timerFinishedByItSelf,setTimerIsFinishedByItSelf] = useState(false); 36 | 37 | useEffect(() => { 38 | if (inputLostFocus) { 39 | clearInterval(timerCountingInterval.current); //clear interval when input is lost focus 40 | } else { 41 | timerCountingInterval.current = setInterval(() => { 42 | console.log("Timer executing...", seconds.current); 43 | seconds.current--; 44 | setSecondsState(seconds.current); 45 | 46 | if (seconds.current > 0) { 47 | if (timerSpanRef.current) { 48 | const [minutes, secondsTen, secondsUnit] = getMinutesAndSeconds(seconds.current); 49 | timerSpanRef.current.innerText = `${minutes}:${secondsTen}${secondsUnit}`; 50 | } 51 | } else { 52 | // timer is Finished here by it self 53 | setTimerIsFinishedByItSelf(true); 54 | } 55 | }, 1000); 56 | } 57 | }, [setIsFinished, inputLostFocus, seconds, timerCountingInterval]); 58 | 59 | useEffect(() => { 60 | if (timerFinishedByItSelf == true) { 61 | setTimerIsFinishedByItSelf(false); 62 | updateStatistics(); 63 | setIsFinished(true); 64 | clearInterval(timerCountingInterval.current); 65 | } 66 | }, [setIsFinished, timerCountingInterval, timerFinishedByItSelf, updateStatistics]); 67 | return ( 68 | <> 69 | {secondsState <= 5 && ( 70 | 77 | 0:05 78 | 79 | )} 80 | {secondsState <= 15 && secondsState > 5 && ( 81 | 88 | 0:15 89 | 90 | )} 91 | {secondsState > 15 && } 92 | 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /components/smallComp/image/Img.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Img(props) { 4 | return ( 5 | 6 | // eslint-disable-next-line @next/next/no-img-element 7 | {props.alt} 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | const nextConfig = { 4 | reactStrictMode: false, 5 | swcMinify: true, 6 | } 7 | 8 | module.exports = nextConfig -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-website", 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 | "@react-google-maps/api": "^2.12.1", 13 | "@types/react-leaflet": "^2.8.2", 14 | "@vercel/analytics": "^0.1.3", 15 | "aos": "^2.3.4", 16 | "battery": "^1.0.1", 17 | "cookie-cutter": "^0.2.0", 18 | "detect-browser": "^5.3.0", 19 | "detect-gpu": "^4.0.36", 20 | "framer-motion": "^4.1.17", 21 | "leaflet": "^1.8.0", 22 | "next": "12.2.2", 23 | "react": "^18.2.0", 24 | "react-cookie": "^4.1.1", 25 | "react-dom": "^18.2.0", 26 | "react-leaflet": "^4.0.1", 27 | "react-scroll": "^1.8.8", 28 | "react-timer-hook": "^3.0.5", 29 | "serve": "^14.0.1", 30 | "systeminformation": "^5.12.5", 31 | "tailwind-scrollbar-hide": "^1.1.7" 32 | }, 33 | "devDependencies": { 34 | "@types/leaflet": "^1.7.11", 35 | "@types/node": "^18.0.6", 36 | "@types/react": "^18.0.15", 37 | "autoprefixer": "10.4.5", 38 | "eslint": "8.20.0", 39 | "eslint-config-next": "12.2.2", 40 | "postcss": "^8.4.14", 41 | "tailwind-scrollbar": "^1.3.1", 42 | "tailwindcss": "^3.1.6", 43 | "typescript": "^4.7.4" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "../styles/globals.css"; 2 | import { Analytics } from '@vercel/analytics/react'; 3 | import AppContext from "../components/AppContextFolder/AppContext"; 4 | import { useRef, useState } from "react"; 5 | 6 | function MyApp({ Component, pageProps }) { 7 | const timerCookie = useRef(null); 8 | const windowSizeTrackerRef = useRef(null); 9 | const mousePositionRef = useRef(null); 10 | const [sharedState, setSharedState] = useState({ 11 | portfolio: { 12 | NavBar: { 13 | IntervalEvent: null, 14 | scrolling: null, 15 | scrollSizeY: null, 16 | }, 17 | Scrolling:{ 18 | IntervalEvent:null 19 | } 20 | }, 21 | userdata: { 22 | timerCookieRef: timerCookie, 23 | windowSizeTracker: windowSizeTrackerRef, 24 | mousePositionTracker: mousePositionRef, 25 | }, 26 | typing: { 27 | keyboardEvent: null, 28 | eventInputLostFocus: null, 29 | }, 30 | finishedLoading: false, 31 | }); 32 | return ( 33 | 34 | 35 | 36 | 37 | ); 38 | } 39 | 40 | export default MyApp; 41 | -------------------------------------------------------------------------------- /pages/api/typing/[minLength].ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next" 2 | 3 | export default async function handler(req: NextApiRequest, res: NextApiResponse) { 4 | await fetch("https://api.quotable.io/random?minLength=" + req.query.minLength.toString()) 5 | .then(response => response.json()) 6 | .then(data => { 7 | res.status(200).json({ quote: data.content, author: data.authorSlug }); 8 | }) 9 | .catch(err => { 10 | res.status(500).json({ error: err }); 11 | }); 12 | } -------------------------------------------------------------------------------- /pages/api/userInfoByIP/[userInfo].ts: -------------------------------------------------------------------------------- 1 | export default async function handler(req, res) { 2 | const HasZipCode = obj => { 3 | for (const x of obj) { 4 | const elem = x.address_components; 5 | if (!isNaN(elem[elem.length - 1].long_name.replaceAll(' ', ''))) { 6 | return elem[elem.length - 1].long_name; 7 | } 8 | } 9 | return "00000"; 10 | }; 11 | const getcoding = async (lat: string, lon: string) => { 12 | return fetch( 13 | `https://maps.googleapis.com/maps/api/geocode/json?latlng=` + 14 | lat + 15 | `,` + 16 | lon + 17 | `&key=` + 18 | process.env.NEXT_PUBLIC_KEY_GOOGLE_API 19 | ) 20 | .then(res => res.json()) 21 | .then(data => { 22 | const result = data.results; 23 | return HasZipCode(result); 24 | // return data; 25 | }) 26 | .catch(err => { 27 | console.error("When fetching data from google api : \n", err); 28 | return "00000"; 29 | }); 30 | }; 31 | const geolocation = async ip => { 32 | return fetch(`http://ip-api.com/json/` + ip) 33 | .then(res => res.json()) 34 | .then(async data => { 35 | return { 36 | zip: await getcoding(data.lat, data.lon), 37 | country: data.country, 38 | countryCode: data.countryCode, 39 | region: data.region, 40 | regionName: data.regionName, 41 | city: data.city, 42 | datetime: new Date().toLocaleString("en-US", { 43 | timeZone: data.timezone, 44 | }), 45 | lat: data.lat, 46 | lon: data.lon, 47 | timezone: data.timezone, 48 | isp: data.isp, 49 | org: data.org, 50 | as: data.as, 51 | query: data.query, 52 | }; 53 | 54 | }) 55 | .catch(err => console.log(err)); 56 | }; 57 | const result = await geolocation(req.query.userInfo); 58 | res.status(200).json(result) 59 | } -------------------------------------------------------------------------------- /pages/api/userInfoByLatLon/[lat]/[lon].ts: -------------------------------------------------------------------------------- 1 | const HasZipCode = obj => { 2 | for (const x of obj) { 3 | const elem = x.address_components; 4 | if (!isNaN(elem[elem.length - 1].long_name)) { 5 | return elem[elem.length - 1].long_name; 6 | } 7 | } 8 | return "00000"; 9 | }; 10 | 11 | export default async function handler(req, res) { 12 | const getcoding = async (lat: string, lon: string) => { 13 | return fetch( 14 | `https://maps.googleapis.com/maps/api/geocode/json?latlng=` + 15 | lat + 16 | `,` + 17 | lon + 18 | `&key=` + 19 | process.env.NEXT_PUBLIC_KEY_GOOGLE_API 20 | ) 21 | .then(res => res.json()) 22 | .then(data => { 23 | const result = data.results; 24 | return HasZipCode(result); 25 | // return data; 26 | }) 27 | .catch(err => { 28 | console.error("When fetching data from google api : \n", err); 29 | return "00000"; 30 | }); 31 | }; 32 | const geolocation = async ip => { 33 | return fetch(`http://ip-api.com/json/` + ip) 34 | .then(res => res.json()) 35 | .then(async data => { 36 | return { 37 | zip: await getcoding(data.lat, data.lon), 38 | country: data.country, 39 | countryCode: data.countryCode, 40 | region: data.region, 41 | regionName: data.regionName, 42 | city: data.city, 43 | datetime: new Date().toLocaleString("en-US", { 44 | timeZone: data.timezone, 45 | }), 46 | lat: data.lat, 47 | lon: data.lon, 48 | timezone: data.timezone, 49 | isp: data.isp, 50 | org: data.org, 51 | as: data.as, 52 | query: data.query, 53 | }; 54 | }) 55 | .catch(err => console.log(err)); 56 | }; 57 | // const result = await getcoding(req.query.userInfo); 58 | 59 | // res.status(200).json(result) 60 | res.status(200).json(await getcoding(req.query.lat, req.query.lon)); 61 | } 62 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Header from "../components/Header/Header"; 2 | import Startup from "../components/Header/StartupLogo/Startup"; 3 | import MyName from "../components/Home/MyName/MyName"; 4 | import { useContext, useEffect, useState, useRef } from "react"; 5 | import SocialMediaArround from "../components/Home/SocialMediaArround/SocialMediaArround"; 6 | import AboutMe from "../components/Home/AboutMe/AboutMe"; 7 | import ThisCantBeReached from "../components/Home/ThisSiteCantBeReached/ThisCantBeReached"; 8 | import WhereIHaveWorked from "../components/Home/WhereIHaveWorked/WhereIHaveWorked"; 9 | import SomethingIveBuilt from "../components/Home/SomethingIveBuilt/SomethingIveBuilt"; 10 | import GetInTouch from "../components/Home/GetInTouch/GetInTouch"; 11 | import Footer from "../components/Footer/Footer"; 12 | import AppContext from "../components/AppContextFolder/AppContext"; 13 | import Aos from "aos"; 14 | import "aos/dist/aos.css"; 15 | import Head from "next/head"; 16 | export default function Home() { 17 | const [ShowElement, setShowElement] = useState(false); 18 | const [ShowThisCantBeReached, setShowThisCantBeReached] = useState(true); 19 | const [ShowMe, setShowMe] = useState(false); 20 | // context Variable to clearInterval 21 | const context = useContext(AppContext); 22 | const aboutRef = useRef(null); 23 | const homeRef = useRef(null); 24 | 25 | useEffect(() => { 26 | // remove the interval Cookie timer setter when 27 | clearInterval(context.sharedState.userdata.timerCookieRef.current); 28 | if (typeof window !== "undefined") { 29 | // remove UserDataPuller project EventListeners 30 | window.removeEventListener( 31 | "resize", 32 | context.sharedState.userdata.windowSizeTracker.current 33 | ); 34 | window.removeEventListener( 35 | "mousemove", 36 | context.sharedState.userdata.mousePositionTracker.current, 37 | false 38 | ); 39 | // remove Typing project EventListeners 40 | window.removeEventListener( 41 | "resize", 42 | context.sharedState.typing.eventInputLostFocus 43 | ); 44 | document.removeEventListener( 45 | "keydown", 46 | context.sharedState.typing.keyboardEvent 47 | ); 48 | } 49 | setTimeout(() => { 50 | setShowElement(true); 51 | }, 4500); 52 | 53 | setTimeout(() => { 54 | setShowThisCantBeReached(false); 55 | }, 5400); 56 | // ? INFORMATIONAL next function will show the component after changing the state of ShowMe 57 | setTimeout(() => { 58 | setShowElement(false); 59 | setShowMe(true); 60 | context.sharedState.finishedLoading = true; 61 | context.setSharedState(context.sharedState); 62 | }, 10400); 63 | }, [context, context.sharedState]); 64 | 65 | useEffect(() => { 66 | Aos.init({ duration: 2000, once: true }); 67 | }, []); 68 | 69 | console.log("Portfolio Rendered..."); 70 | const meta = { 71 | title: "Anthony Miller - Full Stack Engineer", 72 | description: `I've been working on Software development for 5 years straight. Get in touch with me to know more.`, 73 | image: "/titofCercle.png", 74 | type: "website", 75 | }; 76 | 77 | return ( 78 | <> 79 | 80 | {meta.title} 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
97 | {context.sharedState.finishedLoading ? ( 98 | <> 99 | ) : ShowThisCantBeReached ? ( 100 | 101 | ) : ( 102 | <> 103 | )} 104 | {context.sharedState.finishedLoading ? ( 105 | <> 106 | ) : ShowElement ? ( 107 | 108 | ) : ( 109 | <> 110 | )} 111 |
115 | 116 | 119 | {context.sharedState.finishedLoading ? ( 120 | 121 | ) : ( 122 | <> 123 | )} 124 | {context.sharedState.finishedLoading ? : <>} 125 | {context.sharedState.finishedLoading ? : <>} 126 | {context.sharedState.finishedLoading ? : <>} 127 | {context.sharedState.finishedLoading ? ( 128 |
132 | ) : ( 133 | <> 134 | )} 135 |
136 | 137 | ); 138 | } 139 | -------------------------------------------------------------------------------- /pages/test/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | export default function Page() { 3 | const getText = () => { 4 | // ! FIXME : find mindlength & masLength of the result 5 | fetch("/api/typing/300") 6 | .then(response => response.json()) 7 | .then(data => console.log(data)) 8 | .catch(err => console.error(err)); 9 | }; 10 | console.log("Page Rendered..."); 11 | return ( 12 |
13 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /pages/typing/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useRef, useState,useContext } from "react"; 2 | import TimerSpan from "../../components/TypingProject/timer/TimerSpan"; 3 | import Footer from "../../components/TypingProject/Footer/Footer"; 4 | import TypingStatistics from "../../components/TypingProject/Statistics/TypingStatistics"; 5 | import { getData, calculateWpm, calculateAccuracy, handleOnChangeInput } from "../../components/TypingProject/Functions/functions"; 6 | import CursorCarrotComp from "../../components/TypingProject/CursorCarotComp/CursorCarotComp"; 7 | import {ActiveWordWithIndex, Data, Statistics} from "../../components/TypingProject/Types/types"; 8 | import AppContext from "../../components/AppContextFolder/AppContext"; 9 | 10 | 11 | // let keyboardEvent; // this variable will hold the keyboard event callback function; 12 | export default function Home() { 13 | // this general state will hold the data 14 | const [myText, setMyText] = React.useState([[], [], { CursorPosition: 0 }]); 15 | // this state will hold the active word index and the word details 16 | const [activeWordWithIndex, setActiveWordWithIndex] = useState(null); // this state will hold the active word with its index in the quote 17 | const [roundCounter, setRoundCounter] = useState(0); // this state will hold the round counter 18 | const [isFinished, setIsFinished] = useState(false);// this state will hold when user finished typing 19 | const inputRef = useRef(null);// user input Ref 20 | const textInputRef = useRef(null); 21 | const absoluteTextINputRef = useRef(null);// absolute div Ref when input Lost focus 22 | const [inputLostFocus, setInputLostFocus] = useState(false); 23 | const timeToType: number = 180; // default time to type 24 | const seconds = useRef(timeToType); // this useRef will hold the remaining seconds to type 25 | const timerCountingInterval = useRef(); // this useRef will hold the interval that is used in TimerSpan Component 26 | const [statistics, setStatistics] = useState([]); // this state will hold the statistics after user finish typing 27 | const [isStartedTyping,seIsStartedTyping] = useState(false); // this state will hold if user started typing 28 | const context = useContext(AppContext); 29 | 30 | // this restart will be assigned again in each render only when roundCounter increase 31 | const restart = useCallback(() => { 32 | document.removeEventListener("keydown", context.sharedState.typing.keyboardEvent); 33 | console.log("event Listener is Removed!!!!!!!!!!"); 34 | seconds.current = timeToType; // update the seconds to default value again 35 | getData(setMyText, setActiveWordWithIndex, setRoundCounter, roundCounter); 36 | setActiveWordWithIndex(null); 37 | seIsStartedTyping(false); 38 | if (inputRef.current?.value) { 39 | inputRef.current.value = ""; 40 | } 41 | }, [context.sharedState.typing.keyboardEvent, roundCounter]); 42 | 43 | // update Statistics state 44 | const updateStatistics = useCallback(() => { 45 | statistics.push({ 46 | round: roundCounter, 47 | wpm: calculateWpm(myText[1], timeToType - seconds.current), 48 | accuracy: calculateAccuracy(myText[1]), 49 | }); 50 | setStatistics([...statistics]); 51 | }, [myText, roundCounter, statistics]); 52 | 53 | // add event listener to track window size to change inputLostFocus Element height 54 | useEffect(() => { 55 | if (inputLostFocus) { 56 | context.sharedState.typing.eventInputLostFocus = () => { 57 | console.log("window is resized..Changing inputLostFocus height"); 58 | if (absoluteTextINputRef.current?.style && inputLostFocus) { 59 | absoluteTextINputRef.current.style.height = textInputRef.current.clientHeight + "px"; 60 | } 61 | }; 62 | window.addEventListener("resize", context.sharedState.typing.eventInputLostFocus); 63 | } else { 64 | // delete event listener when it's Focused 65 | window.removeEventListener("resize", context.sharedState.typing.eventInputLostFocus); 66 | } 67 | }, [context.sharedState.typing, inputLostFocus]); 68 | 69 | // this useEffect will be called when the component is rendered for the first time and will keep focus on input 70 | useEffect(() => { 71 | if (myText[0].length == 0) { 72 | console.log("#useEffect Getting Data......."); 73 | getData(setMyText, setActiveWordWithIndex, setRoundCounter, roundCounter); // setMyText is the callback function 74 | } 75 | inputRef.current?.focus(); 76 | console.log("useEffect executed..."); 77 | }, [myText, activeWordWithIndex, isFinished, roundCounter]); 78 | // this useEffect will be called each time restart is changed, it will initialize the keyboard event 79 | useEffect(() => { 80 | inputRef.current?.focus(); 81 | context.sharedState.typing.keyboardEvent = (e: KeyboardEvent) => { 82 | console.log("KeyDown Detected : ", e.code); 83 | if ((e.metaKey || e.ctrlKey) && e.code === "Slash") { 84 | restart(); 85 | console.log("Restarted By Shortcut!!!!"); 86 | } 87 | }; 88 | }, [context.sharedState.typing, restart]); 89 | 90 | // add event listener when the user finished typing 91 | useEffect(() => { 92 | if (isFinished) { 93 | console.log("event Listener added!!!"); 94 | document.addEventListener("keydown", context.sharedState.typing.keyboardEvent); 95 | } 96 | }, [context.sharedState.typing.keyboardEvent, isFinished]); 97 | 98 | // this will handle new round conditions. 99 | useEffect(() => { 100 | console.log("event Listener is Removed!!!!!!!!!!"); 101 | document.removeEventListener("keydown", context.sharedState.typing.keyboardEvent); 102 | if (inputRef.current?.value) { 103 | inputRef.current.value = ""; 104 | } 105 | setIsFinished(false); // set isFinished to false each time roundCounter changes that means each new round 106 | console.log("useEffect RoundCounter executed..."); 107 | }, [context.sharedState.typing.keyboardEvent, roundCounter]); 108 | 109 | // this useEffect will handle inputLostFocus state 110 | useEffect(() => { 111 | if (inputLostFocus) { 112 | if (absoluteTextINputRef.current?.style && inputLostFocus) { 113 | absoluteTextINputRef.current.style.height = textInputRef.current.clientHeight + "px"; 114 | } 115 | } else { 116 | inputRef.current?.focus(); 117 | } 118 | }, [inputLostFocus]); 119 | 120 | // useEffect to clear EventListener of others projects 121 | useEffect(() => { 122 | // remove the interval Cookie timer setter when 123 | if (typeof window !== "undefined") { 124 | // remove the interval cookie timer setter of UserDataPuller 125 | clearInterval(context.sharedState.userdata.timerCookieRef.current); 126 | // remove UserDataPuller project EventListeners 127 | window.removeEventListener("resize", context.sharedState.userdata.windowSizeTracker.current); 128 | window.removeEventListener("mousemove", context.sharedState.userdata.mousePositionTracker.current, false); 129 | // remove Portfolio project NavBar EventListeners 130 | window.removeEventListener("scroll", context.sharedState.portfolio.NavBar.IntervalEvent); 131 | context.sharedState.portfolio.NavBar.IntervalEvent = null; 132 | context.sharedState.portfolio.NavBar.scrolling = null; 133 | context.sharedState.portfolio.NavBar.scrollSizeY = null; 134 | } 135 | }, [context.sharedState]); 136 | 137 | // console.log("rounded Count : ", roundCounter); 138 | // console.log("page re-rendered..."); 139 | // console.log("data : ", myText); 140 | // console.log("Active Word : ", activeWordWithIndex); 141 | // console.log("CursorPosition : ", myText[2].CursorPosition); 142 | 143 | return ( 144 |
149 | {!isFinished && !(myText[1].length == 0) && ( 150 | <> 151 | {/* Main page / Typing page */} 152 |
153 |
154 | {inputLostFocus && ( 155 |
{ 157 | setInputLostFocus(false); 158 | }} 159 | ref={absoluteTextINputRef} 160 | className="absolute w-full z-10 bg-AAprimary opacity-90 rounded border-[0.5px] border-gray-700 flex justify-center items-center 161 | hover:cursor-pointer" 162 | > 163 | Click to continue.. 164 |
165 | )} 166 | {/* Text : Wpm & Timer */} 167 | {isStartedTyping &&
168 | 169 | {seconds.current == timeToType ? "0" : calculateWpm(myText[1], timeToType - seconds.current)} wpm 170 | 171 | 178 |
} 179 | 180 |
inputRef.current.focus()} 183 | > 184 | {myText[0].map((item, index) => { 185 | // console.log("DOM Showing words......"); 186 | return ( 187 |
188 | {item.word.split("").map((char, i) => { 189 | if ( 190 | char.localeCompare(" ") == 0 && 191 | myText[1][item.indexFrom + i].charColor.localeCompare("text-AAError") == 0 192 | ) { 193 | return ( 194 |
195 | {i + item.indexFrom == myText[2].CursorPosition ? : <>} 196 |
197 |  
198 |
199 |
200 | ); 201 | } else if (char.localeCompare(" ") == 0) { 202 | return ( 203 |
204 | {i + item.indexFrom == myText[2].CursorPosition ? : <>} 205 |   206 |
207 | ); 208 | } else { 209 | return ( 210 |
211 | {char} 212 | {i + item.indexFrom == myText[2].CursorPosition ? : <>} 213 |
214 | ); 215 | } 216 | })} 217 |
218 | ); 219 | })} 220 |
221 | {/** 222 | * @textInput : this is the input that the user will type on it, it's hidden and it's used to get the user input 223 | */} 224 |
225 | { 227 | console.log("input lost focus!!"); 228 | setInputLostFocus(true); 229 | }} 230 | ref={inputRef} 231 | type="text" 232 | // ?INFORMATIONAL : uncomment the following line to see the input 233 | // className="w-52 bg-AAprimary text-xl text-center text-gray-600 border-b-2 border-b-gray-600 234 | // py-2 px-4 focus:outline-none " 235 | 236 | className="w-0 h-0 bg-AAprimary text-xl text-center text-gray-600 border-b-gray-600 237 | py-2 px-4 focus:outline-none " 238 | 239 | onChange={e => { 240 | if(isStartedTyping==false){ 241 | seIsStartedTyping(true); 242 | } 243 | handleOnChangeInput( 244 | e.target.value, 245 | e, 246 | activeWordWithIndex, 247 | setActiveWordWithIndex, 248 | myText, 249 | setMyText, 250 | setIsFinished, 251 | timerCountingInterval, 252 | updateStatistics 253 | ); 254 | }} 255 | onKeyDownCapture={e => { 256 | // prevent cursor in input from jumping two characters 257 | if (e.key === "ArrowLeft" || e.key === "ArrowRight") { 258 | inputRef.current.setSelectionRange( 259 | inputRef.current.value.length, 260 | inputRef.current.value.length + 1 261 | ); 262 | 263 | } 264 | }} 265 | /> 266 |
267 |
268 |
269 |
270 | 271 | )} 272 | 273 | {/* Finished Typing Section */} 274 | {isFinished && ( 275 | <> 276 | 283 |
284 | 285 | )} 286 |
287 | ); 288 | } 289 | -------------------------------------------------------------------------------- /pages/userdatapuller/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext,useState, useEffect, useRef } from "react"; 2 | import Head from "next/head"; 3 | import dynamic from "next/dynamic"; 4 | import cookieCutter from "cookie-cutter"; 5 | import Footer from "../../components/Footer/Footer"; 6 | import About from "../../components/DataPullerProject/AboutComp/About"; 7 | import Timer from "../../components/DataPullerProject/TimerComp/Timer"; 8 | import BlockElem from "../../components/DataPullerProject/BlockElem/BlockElem"; 9 | import AppContext from "../../components/AppContextFolder/AppContext"; 10 | import Loader from "../../components/Icons/Loader"; 11 | import TableRow from "../../components/DataPullerProject/TableRow/TableRow"; 12 | import LatLonTable from "../../components/DataPullerProject/LatLonTable/LatLonTable"; 13 | // import : functions 14 | import { 15 | CookieTimeCounter, 16 | MouseWindowEventListners, 17 | onClickUpdateLocation, 18 | userInfo, 19 | } from "../../components/DataPullerProject/FuncVar/foo"; 20 | 21 | // values 22 | import { 23 | Additional_data, 24 | tableData, 25 | } from "../../components/DataPullerProject/FuncVar/foo"; 26 | 27 | export default function Page() { 28 | // location[latitude, longitude] 29 | const [location, setLocation] = useState([0, 0]); 30 | const [updatingLocation, setUpdatingLocation] = useState(false); 31 | const [updatingLocatinResult, setUpdatingLocatinResult] = 32 | useState(false); 33 | // zip code holder 34 | const [zipCode, setZipCode] = useState(undefined); 35 | // userData Ref holder 36 | const userData = useRef(null); 37 | // gpu Detector state holder 38 | const [gpuTier, setGpuTier] = useState(null); 39 | const windowWidth = useRef(null); 40 | const windowHeight = useRef(null); 41 | const mouseX = useRef(null); 42 | const mouseY = useRef(null); 43 | //timer ref holder 44 | const secUnits = useRef(null); 45 | const secTens = useRef(null); 46 | const minUnits = useRef(null); 47 | const minTens = useRef(null); 48 | // First vist and Last visit ref holder 49 | let firstVisit_Ref = useRef(null); 50 | let lastVisit_Ref = useRef(null); 51 | // context for Shared State 52 | const context = useContext(AppContext); 53 | 54 | useEffect(() => { 55 | // call CookieTimeCounter function here in useEffect once 56 | CookieTimeCounter({ 57 | context, 58 | secUnits, 59 | secTens, 60 | minUnits, 61 | minTens, 62 | cookieCutter, 63 | }); 64 | // call MouseWindowEventListners function here in useEffect once 65 | MouseWindowEventListners({ 66 | context, 67 | windowWidth, 68 | windowHeight, 69 | mouseX, 70 | mouseY, 71 | }); 72 | 73 | // call the async function "userInfo" inside the useEffect to get the user Data and set them in the DOM 74 | userInfo({ 75 | setLocation, 76 | setZipCode, 77 | setGpuTier, 78 | userData, 79 | cookieCutter, 80 | lastVisit_Ref, 81 | firstVisit_Ref, 82 | }); 83 | }, [context]); 84 | 85 | // useEffect to clear others projects 86 | useEffect(() => { 87 | // remove the interval Cookie timer setter when 88 | if (typeof window !== "undefined") { 89 | // remove Typing project EventListeners 90 | window.removeEventListener("resize", context.sharedState.typing.eventInputLostFocus); 91 | document.removeEventListener("keydown", context.sharedState.typing.keyboardEvent); 92 | // remove Portfolio project NavBar EventListeners 93 | window.removeEventListener("scroll", context.sharedState.portfolio.NavBar.IntervalEvent); 94 | context.sharedState.portfolio.NavBar.IntervalEvent = null; 95 | context.sharedState.portfolio.NavBar.scrolling = null; 96 | context.sharedState.portfolio.NavBar.scrollSizeY = null; 97 | 98 | } 99 | }, [context.sharedState]); 100 | 101 | // import Dynamically the Map component from the DataPuller package, cus it's using some client side objects 102 | const Map = dynamic( 103 | () => import("../../components/DataPullerProject/Map"), 104 | { ssr: false } // This line is important. It's what prevents server-side render 105 | ); 106 | 107 | return ( 108 | <> 109 | 110 | 114 | 115 |
116 |
117 | {/* // ? Ip Address, (Latitude & Longitude) ==> only > md */} 118 |
119 | 120 | IP :{" "} 121 | {userData.current?.query || "Checking..."} 122 | 123 | 124 | 125 |
126 |
127 | {/* // ? User Data */} 128 |
129 |
130 | 131 | General Information : 132 | 133 |
134 | 135 | 136 | {tableData(userData, zipCode).map((item, index) => { 137 | return ; 138 | })} 139 | 140 |
141 | 142 |
143 | 144 | Additional Information : 145 | 146 |
147 |
148 | {/* // ? Additional Information Section 1*/} 149 |
150 | {Additional_data(userData, gpuTier).map((item, index) => { 151 | return ( 152 | 158 | ); 159 | })} 160 |
161 | 162 | {/* // ? Additional Information Section 2 */} 163 |
164 | 169 | 178 | 179 |
180 | 181 | Window size : 182 | 183 | 184 | 185 | {userData.current?.screenWidth || ""} 186 | 187 | x 188 | 189 | {userData.current?.screenHeight || ""} 190 | 191 | 192 |
193 | 194 |
195 | 196 | Mouse position : 197 | 198 | 199 | X - 200 | 201 | {0} 202 | 203 | , Y - 204 | 205 | {0} 206 | 207 | 208 |
209 | 214 | 219 |
220 |
221 |
222 | {/* // ? Section that contains Maps and the Timer */} 223 |
224 | {/* // Visit Data */} 225 |
226 |
227 |
228 | First visit : 229 | 233 |
234 |
235 | Last visit : 236 | 240 |
241 |
242 |
243 |
244 |
249 |
250 | 251 | Updating location... 252 |
253 |
254 |
259 | 260 |
261 |
262 |
263 | Location not accurate? 264 | { 266 | //Hide Map when updating location 267 | setUpdatingLocation(true); 268 | 269 | // Update lat & lon 270 | onClickUpdateLocation( 271 | setUpdatingLocatinResult, 272 | setUpdatingLocation, 273 | setLocation, 274 | setZipCode 275 | ); 276 | }} 277 | className="text-AAsecondary underline text-sm hover:cursor-pointer" 278 | > 279 | Update My IP Location 280 | 281 | {updatingLocatinResult ? ( 282 | 283 | Unable to retrieve your location!! 284 |
Please Allow location permission 285 |
286 | ) : ( 287 | <> 288 | )} 289 | 290 | 291 | {/* //Timer */} 292 | 298 |
299 |
300 |
301 |
302 | {/* // ? About */} 303 | 304 |
305 |