[
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n.env \n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# production\n/build\n\n# misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n"
  },
  {
    "path": "README.md",
    "content": "\n<h1 align=\"center\">\n  <br>\n  <img src=\"https://cdn-icons-png.flaticon.com/512/10321/10321953.png\" alt=\"Speed Test App\" width=\"200\">\n  <br>\n  <h2 align=\"center\">Speed Test App</h2>\n  <br>\n</h1>\n\n<h4 align=\"center\">A single page internet speed test app built with<a href=\"https://react.dev/\" target=\"_blank\"> React</a> and <a href=\"https://nodejs.org/\" target=\"_blank\">Node.js</a>.</h4>\n<p align=\"center\"></p>\n<p align=\"center\">\n  <a href=\"#key-features\">Key Features</a> •\n  <a href=\"#how-to-use\">How To Use</a> •\n  <a href=\"#credits\">Credits</a> •\n  <a href=\"#license\">License</a>\n</p>\n<h2 align=\"center\">\n  Screenshots\n  <br>\n  <br>\n  <img src=\"https://i.ibb.co/BsVkXMK/localhost-3000.png\" alt=\"localhost-3000\" border=\"0\">\n  <br>\n  <img src=\"https://i.postimg.cc/x1mTJ7ww/localhost-3000-1.png\" alt=\"localhost-3000-1\" border=\"0\" height=\"400\">\n</h2>\n\n\n## Key Features\n\n* Responsive design \n  - The app is optimized for desktop and mobile devices, ensuring a smooth experience for all users.\n* User Details\n  - Displays the user's IP address, location, server, and operating system.\n* Internet Speed Test\n  - Measures the user's upload and download speeds, as well as latency, for accurate results.\n* Testing and Debugging\n  - Includes tests to ensure the app works as expected.\n* Error handling\n  - The app includes error handling features to ensure that users are informed of any issues that arise.\n* User-friendly interface\n\n## How To Use\n\nTo clone and run this application, you'll need [Git](https://git-scm.com) and [Node.js](https://nodejs.org/en/download/) (which comes with [npm](http://npmjs.com)) installed on your computer. From your command line:\n\n```bash\n# Clone this repository\n$ git clone https://github.com/Omerisra6/internet-speed-test-react\n\n# Install Dependencies\n$ npm run install:all\n```\n\nCreate an .env file in the root of your project.\n<br>\nTo add a new environment variable for your API url, set the variable name as REACT_APP_API_URL and assign it to the server API url ( the deafult port is 8000).\n<br>\n```bash\nREACT_APP_API_URL=SERVER_URL\n```\nRun the following command:\n```bash\n# Run the app\n$ npm start\n```\n\n## Credits\n\nThis software uses the following open source packages:\n\n- [React](https://react.dev/)\n- [Node.js](https://nodejs.org/)\n- [Network Speed](https://www.npmjs.com/package/network-speed)\n\n\n## License\n\nMIT\n\n---\n\n> GitHub [@omerisra6](https://github.com/Omerisra6) &nbsp;&middot;&nbsp;\n> Linkedin [@omerisraeli](https://www.linkedin.com/in/omer-israeli6/)\n\n"
  },
  {
    "path": "client/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n.env\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# production\n/build\n\n# misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n"
  },
  {
    "path": "client/package.json",
    "content": "{\n  \"name\": \"internet-speed-test-react\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@testing-library/jest-dom\": \"^5.16.5\",\n    \"@testing-library/user-event\": \"^13.5.0\",\n    \"axios\": \"^0.27.2\",\n    \"caniuse-lite\": \"^1.0.30001481\",\n    \"material-icons\": \"^1.11.10\",\n    \"prop-types\": \"^15.8.1\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-scripts\": \"5.0.1\",\n    \"styled-components\": \"^5.3.5\",\n    \"web-vitals\": \"^2.1.4\"\n  },\n  \"scripts\": {\n    \"start\": \"react-scripts start\",\n    \"build\": \"react-scripts build\",\n    \"test\": \"react-scripts test\",\n    \"eject\": \"react-scripts eject\"\n  },\n  \"eslintConfig\": {\n    \"extends\": [\n      \"react-app\",\n      \"react-app/jest\"\n    ]\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  },\n  \"devDependencies\": {\n    \"@testing-library/react\": \"^13.4.0\",\n    \"dotenv\": \"^16.0.2\",\n    \"jest\": \"^29.0.3\",\n    \"jest-fetch-mock\": \"^3.0.3\",\n    \"jest-watch-typeahead\": \"^0.6.5\",\n    \"react-test-renderer\": \"^18.2.0\"\n  }\n}\n"
  },
  {
    "path": "client/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"%PUBLIC_URL%/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta name=\"theme-color\" content=\"#000000\" />\n    <meta\n      name=\"description\"\n      content=\"Web site created using create-react-app\"\n    />  \n    <!--\n      Notice the use of %PUBLIC_URL% in the tags above.\n      It will be replaced with the URL of the `public` folder during the build.\n      Only files inside the `public` folder can be referenced from the HTML.\n\n      Unlike \"/favicon.ico\" or \"favicon.ico\", \"%PUBLIC_URL%/favicon.ico\" will\n      work correctly both with client-side routing and a non-root public URL.\n      Learn how to configure a non-root public URL by running `npm run build`.\n    -->\n    <title>Speed Test</title>\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"root\"></div>\n    <!--\n      This HTML file is a template.\n      If you open it directly in the browser, you will see an empty page.\n\n      You can add webfonts, meta tags, or analytics to this file.\n      The build step will place the bundled scripts into the <body> tag.\n\n      To begin the development, run `npm start` or `yarn start`.\n      To create a production bundle, use `npm run build` or `yarn build`.\n    -->\n  </body>\n</html>\n"
  },
  {
    "path": "client/src/App.js",
    "content": "import Header from './components/Header';\nimport SpeedTestPage from './pages/speed-test/SpeedTestPage';\n\nfunction App() {\n  return (\n    <div className=\"App\">\n\n      <Header/>\n\n      <SpeedTestPage/>\n\n    </div>\n  );\n}\n\nexport default App;\n"
  },
  {
    "path": "client/src/components/Header.js",
    "content": "import React from 'react'\nimport Logo from './view-components/Logo'\nimport Navigation from './Navigation'\nimport styled from 'styled-components'\nimport UserDetails from './UserDetails'\n\nconst StyledHeader = styled.div`\n\n  z-index: 1;\n  height: 6vh;\n  width: 94vw;\n  position: relative;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  padding: 2vh 3vw;\n\n  @media ( max-width: 640px ){\n\n    & > .app-logo{\n      \n      font-size: 0.7rem;\n    }\n  }\n\n`\nexport default function Header() {\n\n  return (\n    <StyledHeader className='header-container'>\n\n      <Logo size=\"md\"></Logo>\n\n      <Navigation/>\n\n      <UserDetails/>\n\n    </StyledHeader>\n  )\n    \n}\n"
  },
  {
    "path": "client/src/components/Navigation.js",
    "content": "import styled from 'styled-components'\nimport Link from './view-components/Link';\n\nconst StyledNavigation = styled.div`\n\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    position: absolute;\n    left: 50%;\n    transform: translate( -50%);\n\n    & > *{\n        padding: 0 3vw;\n        display: flex;\n        align-items: center;\n    }\n`\n\nconst navButtons = \n[\n    { 'icon': 'speed', 'url':'/', 'checked': true }, \n    { 'icon': 'pie_chart', 'url':'/stats', 'checked': false },\n    { 'icon': 'language', 'url':'/language', 'checked': false }, \n    { 'icon': 'dns', 'url':'/dns' },\n    { 'icon': 'tune', 'url':'/tune', 'checked': false }\n];\n\nexport default function Navigation() {\n\n    \n    return (\n        <StyledNavigation>\n\n            { navButtons.map( ( navButton, index ) => {\n\n                return <Link key={ index } index={ index } icon={ navButton.icon } url={ navButton.url } padding='md' size='sm' color={ navButton.checked ? 'orange': 'theme' } />\n\n            })}\n            \n        </StyledNavigation>\n    )\n}\n"
  },
  {
    "path": "client/src/components/UserDetails.js",
    "content": "import React from 'react'\nimport styled from 'styled-components'\nimport { Circle } from './view-components/Circle'\nimport Link from './view-components/Link'\nimport logo from '../images/N-1.png'\n\nconst StyledUserDetails = styled.div`\n    position: relative;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    gap: 2vw;\n\n    & > *{\n        height: 6vh;\n        display: flex;\n        align-items: center;\n        padding: 0;\n    }\n\n    & > .user-logo-container > img{\n        border-radius: 50%;\n        width: 2vw;\n        height: 2vw;\n    }\n\n\n    & > .chat-container > .chat-circle{\n        position: absolute;\n        top: 1vh;\n        right: 1vw;\n    }\n\n    & > .chat-container > a{\n        height: 6vh;\n        display: flex;\n        align-items: center;\n    }\n\n    & > .chat-container{\n        position: relative;\n    }\n\n`\nexport default function UserDetails() {\n\n    return (\n        <StyledUserDetails>\n            \n            <div className='chat-container' key='chat-container'>\n                <Circle color='red' size='xxs' className='chat-count'>1</Circle>\n                <Link icon={'chat_bubble_outline'} url='/chat' size='xs' color='light'/>\n            </div>\n            \n            <Link className='user-notifications' key='user-notifications' icon={'notifications_none'} url='/chat' size='xs' color='theme'/>\n            <div className='user-logo-container'>\n                <img className='user-logo' key='user-logo' src={ logo }/>\n            </div>\n            \n\n        </StyledUserDetails>\n    )\n}    \n"
  },
  {
    "path": "client/src/components/view-components/Button.js",
    "content": "import styled from 'styled-components'\nimport PropTypes from 'prop-types'\n\n\nconst sizesMap ={\n    xxs: {\n        fontSize: '--font-size-xxxs',\n        width: '--width-xxs',\n        height: '--height-xxs'\n    },\n    xs: {\n        fontSize: '--font-size-xs',\n        width: '--width-xs',\n        height: '--width-xs'\n    },\n    sm: {\n        fontSize: '--font-size-sm',\n        width: '--width-sm',\n        height: '--height-sm'\n    },\n    md: {\n        fontSize: '--font-size-sm',\n        width: '--width-md',\n        height: '--height-md'\n    },\n    lg: {\n        fontSize: '--font-size-lg',\n        width: '--width-lg',\n        height: '--height-lg'\n    },\n}\n\nconst colorsMap ={\n\n    dark: {\n        color: '--black',\n        background: '--white',\n    },\n    light : {\n        color: '--black',\n        background: '--light-grey',\n    },\n    white: {\n        color: '--white',\n        background: '--black',\n    },\n    theme: {\n        color: '--orange-theme',\n        background: '--dark-blue',\n    },\n    red:{\n        color: '--white',\n        background: '--red',\n    }\n    \n}\n\n\nexport const Button = styled.button`\n\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    width: var( ${ ( { size } ) => sizesMap[ size ].width } );\n    height: var( ${ ( { size, circle  } ) => circle ? sizesMap[ size ].width : sizesMap[ size ].height } );\n    color: var( ${ ( { color } ) => colorsMap[ color ].color } );\n    font-size: var( ${ ( { size } ) => sizesMap[ size ].fontSize } );\n    background-color: var( ${ ( { color } ) => colorsMap[ color ].background } );;\n    border-radius: ${ ( { circle } ) => ! circle ? '0%' : '50%' };\n    border: none;\n`\n\nButton.propTypes ={\n\n    size: PropTypes.oneOf([\n        'xxs',\n        'xs',\n        'sm',\n        'md',\n        'lg',\n    ]),\n\n    color: PropTypes.oneOf([\n        'dark',\n        'light',\n        'white',\n        'theme',\n        'red',\n    ]),\n\n    circle: PropTypes.oneOf([\n        true,\n        false,\n    ])\n}\n\n"
  },
  {
    "path": "client/src/components/view-components/Circle.js",
    "content": "import styled from 'styled-components'\nimport PropTypes from 'prop-types'\n\n\nconst sizesMap ={\n    xxs: {\n        fontSize: '--font-size-xxxs',\n        width: '--width-xxs',\n        height: '--width-xxs'\n    },\n    xs: {\n        fontSize: '--font-size-xs',\n        width: '--width-xs',\n        height: '--width-xs'\n    },\n    sm: {\n        fontSize: '--font-size-sm',\n        width: '--width-sm',\n        height: '--width-sm'\n    },\n    md: {\n        fontSize: '--font-size-sm',\n        width: '--width-md',\n        height: '--width-md'\n    },\n    lg: {\n        fontSize: '--font-size-lg',\n        width: '--width-lg',\n        height: '--width-lg'\n    },\n}\n\nconst colorsMap ={\n\n    dark: {\n        color: '--black',\n        background: '--white',\n    },\n    light : {\n        color: '--black',\n        background: '--light-grey',\n    },\n    white: {\n        color: '--white',\n        background: '--black',\n    },\n    theme: {\n        color: '--orange-theme',\n        background: '--dark-blue',\n    },\n    red:{\n        color: '--white',\n        background: '--red',\n    }\n    \n}\n\n\nexport const Circle = styled.div`\n\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    width: var( ${ ( { size } ) => sizesMap[ size ].width } );\n    height: var( ${ ( { size } ) => sizesMap[ size ].height } );\n    color: var( ${ ( { color } ) => colorsMap[ color ].color } );\n    font-size: var( ${ ( { size } ) => sizesMap[ size ].fontSize } );\n    background-color: var( ${ ( { color } ) => colorsMap[ color ].background } );;\n    border-radius: 50%;\n`\n\nCircle.propTypes ={\n\n    size: PropTypes.oneOf([\n        'xxs',\n        'xs',\n        'sm',\n        'md',\n        'lg',\n    ]),\n\n    color: PropTypes.oneOf([\n        'dark',\n        'light',\n        'white',\n        'theme',\n        'red'\n    ]),\n}\n\n"
  },
  {
    "path": "client/src/components/view-components/LinesSpeedometer/LinesSpeedometer.js",
    "content": "import React from 'react'\nimport { useAppAttributes } from '../../../contexts/appAttributesContext'\nimport LongLine from \"./LongLine\"\nimport ShortLine from \"./ShortLine\"\nimport { StyledLinesSpeedometer } from './StyledLinesSpeedometer'\n\nfunction getSpeedometerLines( markedCount, linesCount, loading ) \n{\n    const rot   = `${ 184/linesCount }deg`\n    const lines = []\n\n    for ( var i = 0; i < linesCount; i++ ) \n    {\n        i % 6 === 0 \n        ?\n        lines.push( \n            <LongLine key={ i } i={ i } rot={ rot } linesCount={ linesCount } markedCount={ markedCount } loading={ loading }/>\n        )\n        :\n        lines.push(  \n            <ShortLine key={ i } i={ i } rot={ rot } linesCount={ linesCount } markedCount={ markedCount } loading={ loading }/>\n        )\n    }\n\n    return lines\n}\n\nexport default function LinesSpeedometer( { linesCount, markedCount }) {\n\n    const { loading } = useAppAttributes()\n    const lines       = getSpeedometerLines( markedCount, linesCount, loading )\n\n    return (\n\n        <StyledLinesSpeedometer className='outside-speedometer-bar'>\n\n            { lines }\n\n        </StyledLinesSpeedometer>\n    )\n}\n"
  },
  {
    "path": "client/src/components/view-components/LinesSpeedometer/LongLine.js",
    "content": "import React from 'react'\n\nexport default function LongLine( { i, rot, linesCount, loading, markedCount } ) {\n    return (\n        <div className='long-speed-line-container'  key={ i } style={ { '--rot': rot, '--i': i, '--lines-count': linesCount } }>\n            <span className={ `long-speed-line ${ loading  ? 'loading' :  i <= markedCount && 'marked' }` }></span>\n            <span className={ `speed-number ${ loading  ? 'loading-text' :  i <= markedCount && 'marked-text' }` } >{ ( Math.floor( ( i ) * 3.3334 )) }</span>\n        </div>\n    )\n}"
  },
  {
    "path": "client/src/components/view-components/LinesSpeedometer/ShortLine.js",
    "content": "import React from 'react'\n\nexport default function ShortLine( { i, rot, linesCount, loading, markedCount } ) {\n    return (\n        <span className={ `short-speed-line ${ loading  ? 'loading' :  i <= markedCount && 'marked' }` } key={ i } style={ { '--rot': rot, '--i': i, '--lines-count': linesCount } }></span>\n    )\n}\n"
  },
  {
    "path": "client/src/components/view-components/LinesSpeedometer/StyledLinesSpeedometer.js",
    "content": "import styled from \"styled-components\";\n\nexport const StyledLinesSpeedometer = styled.div`\n    \n    position: absolute;\n    top: 100%;\n    left: 50%;\n    display: flex;\n    align-items: center;\n    rotate: -90deg;\n    \n\n    .short-speed-line, .long-speed-line{\n        background-color: var( --lighter-theme );\n    }\n\n    .long-speed-line-container{\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n    }\n\n    .short-speed-line, .long-speed-line-container {\n        position: absolute;\n        transform: rotate( calc( var( --i ) * var( --rot ) ) ) translateY( -18vw );\n    }\n\n    .long-speed-line-container > .speed-number{  \n\n        font-size: var( --font-size-xs );\n        color: var( --lighter-theme );\n        position: absolute;\n        rotate: calc( 90deg - ( var( --i ) * var( --rot ) ) ) ;\n        top: calc( ( var( --i ) * 0.025vh ) + 4vh ) ;\n        font-weight: bold;\n    }\n\n    .marked{\n        animation: glow-orange 0.03s linear forwards;\n        animation-delay: calc( var( --i ) * 0.05s );\n    } \n\n    .loading{   \n        --first-delay: 0.02s;\n        --all-delay: calc( var( --first-delay ) * var( --lines-count ) );\n\n        animation: load-orange var( --all-delay ) var( --first-delay )  linear infinite;\n    }\n\n\n    .marked-text{\n        animation: glow-white 0.03s linear forwards;\n        animation-delay: calc( var( --i ) * 0.05s );\n    }\n\n    .short-speed-line{\n        width: 0.2vw;\n        height: 1vw;\n    }\n\n    .long-speed-line{\n        width: 0.4vw;\n        height: 1.5vw;\n    }\n\n    @media ( max-width: 1100px ) {\n\n        .short-speed-line, .long-speed-line-container {\n\n            position: absolute;\n            transform: rotate( calc( var( --i ) * var( --rot ) ) ) translateY( -200px );\n        }\n    }\n\n\n    @media ( min-height: 1100px ) {\n\n        .long-speed-line-container > .speed-number{  \n\n            top: 10px;\n            \n        }\n    }\n\n    @media ( max-width: 640px ){\n\n        .short-speed-line{\n            width: 0.2vw;\n            height: 1vw;\n        }\n\n        .long-speed-line{\n            width: 0.4vw;\n            height: 1.5vw;\n        }   \n\n        .short-speed-line, .long-speed-line-container {\n            position: absolute;\n            transform: rotate( calc( var( --i ) * var( --rot ) ) ) translateY( -100px );\n        }\n\n        .outside-speedometer-bar {\n            position: absolute;\n            top: 50%;\n            left: 50%;\n            display: flex;\n            align-items: center;\n            rotate: -90deg;\n        }\n\n        .long-speed-line-container > .speed-number{  \n            top: calc( ( var( --i ) * 0.025vh ) + 1vh ) ;\n        }\n\n    }\n\n    @media ( max-width: 400px ){\n        .short-speed-line, .long-speed-line-container {\n            transform: rotate( calc( var( --i ) * var( --rot ) ) ) translateY( -80px );\n        }\n    }\n\n    @keyframes glow-orange {\n        0%{\n            background: var( --lighter-theme );\n            box-shadow: none;\n        }\n        100%{\n            background: var( --orange-theme );\n            box-shadow: 0 0 10px var( --orange-theme );\n        }\n    }\n\n    @keyframes load-orange {\n        0%{\n            background: var( --orange-theme );\n            box-shadow: 0 0 10px var( --orange-theme );\n        }\n\n        5%{\n            background: var( --orange-theme );\n            box-shadow: 0 0 10px var( --orange-theme );\n        }\n\n        95%{\n            background: var( --lighter-theme );\n            box-shadow: none;\n        }\n\n        100%{\n            background: var( --orange-theme );\n            box-shadow: 0 0 10px var( --orange-theme );\n        }\n    }\n\n    @keyframes glow-white {\n        0%{\n            color: var( --lighter-theme );\n        }\n        100%{\n            color: var( --white );\n        }\n    }\n\n`"
  },
  {
    "path": "client/src/components/view-components/Link.js",
    "content": "import React from 'react'\nimport styled from 'styled-components'\nimport PropTypes from 'prop-types'\n\nconst sizesMap ={ \n    xs: {\n        fontSize: '--font-size-sm',\n    },\n    sm: {\n        fontSize: '--font-size-sm',\n    },\n    md: {\n        fontSize: '--font-size-md',\n    },\n    lg: {\n        fontSize: '--font-size-lg',\n    },\n}\n\nconst colorsMap ={\n\n    dark: {\n        color: '--theme',\n        backgroundHover: '--lighter-theme',\n    },\n    light : {\n        color: '--light-grey',\n        backgroundHover: '--orange-theme',\n    },\n    white: {\n        color: '--white',\n        backgroundHover: '--theme',\n    },\n    theme: {\n        color: '--lighter-theme',\n        backgroundHover: '--orange-theme',\n    },\n    orange: {\n        color: '--orange-theme',\n        backgroundHover: '--orange-theme',\n    },\n    \n}\n\n\nconst StyledLink = styled.a`\n    \n    background-color: inherit;\n\n    & > .link-icon{\n\n        font-size: var( ${ ( { size } ) => sizesMap[ size ].fontSize } );\n        cursor: pointer;\n        color: var( ${ ( { color } ) => colorsMap[ color ].color } );\n    }\n\n    &:hover > .link-icon{\n\n        color: var( ${ ( { color } ) => colorsMap[ color ].backgroundHover } );\n    }\n`\n\nStyledLink.propTypes ={\n\n    size: PropTypes.oneOf([\n        'xs',\n        'sm',\n        'md',\n        'lg',\n    ]),\n\n    color: PropTypes.oneOf([\n        'dark',\n        'light',\n        'white',\n        'theme',\n        'orange',\n    ]),\n}\n\nexport default function Link( { icon, size, color, url } ) {\n\n\n    return (\n        <StyledLink href={ url } size={ size } color={ color } >\n\n            <span className='material-icons link-icon'>{ icon }</span>\n\n        </StyledLink>\n    )\n}\n"
  },
  {
    "path": "client/src/components/view-components/Logo.js",
    "content": "import React from 'react'\nimport styled from 'styled-components';\nimport PropTypes from 'prop-types'\n\nconst sizeMap = {\n    'sm':{\n        'size': '--size-sm'\n    },\n    'md':{\n        'size': '--size-md'\n    },\n    'lg':{\n        'size': '--size-lg'\n    }\n}\n\nconst StyledLogo = styled.div`\n\n    --size-sm: 0.8vw;\n    --size-md: 1vw;\n    --size-lg: 1.2vw;\n\n    display: flex;\n    flex-direction: column;\n    background-color: var( --theme );\n    font-size: var( ${ ( { size } ) => sizeMap[ size ].size  } );\n    font-weight: bold;\n    \n    & > .logo-pro-text{\n\n        color: var( --white );\n    }\n\n    & > .logo-earth-text{\n\n        color: var( --orange-theme );\n    }\n`\n\nStyledLogo.propTypes ={\n    size: PropTypes.oneOf([\n        'sm',\n        'md',\n        'lg',\n    ]),\n}\n\nexport default function Logo( { size } ) {\n\n\n    return(\n        <StyledLogo className='app-logo' size={ size }>\n\n            <span className='logo-pro-text'>PRO</span>\n            <span className='logo-earth-text'>EARTH</span>\n\n        </StyledLogo>\n    )\n}\n"
  },
  {
    "path": "client/src/contexts/appAttributesContext.js",
    "content": "import React, { useContext, useState } from \"react\";\n\nconst AppAttributesContext = React.createContext( null );\n\nexport const useAppAttributes = () => {\n    \n    return useContext( AppAttributesContext )  \n}\n\nexport const AppAttributesProvider = ( { children } ) => {\n\n    const [ loading, setLoading ] = useState( false )\n    const [ error, setError ]     = useState( false )\n\n\n    const value = \n    {\n        loading,\n        setLoading,\n        error,\n        setError\n    };\n   \n    return <AppAttributesContext.Provider children={ children } value={ value }  />;\n};\n"
  },
  {
    "path": "client/src/contexts/testResultContext.js",
    "content": "import React, { useContext, useState } from \"react\";\n\nconst TestResultContext = React.createContext( null );\n\nexport const useTestResult = () => {\n    \n    return useContext( TestResultContext )  \n}\n\nexport const TestResultProvider = ( { children } ) => {\n    \n\n    const [ testResult, setTestResult ] = useState( {\n        uploadSpeed: 0.0, downloadSpeed: 0.0,\n        latency: 0, userIp: '',\n        userLocation: '', os: '',\n        server: ''\n    } )\n\n\n    const value = \n    {\n        testResult,\n        setTestResult\n    };\n\n    return <TestResultContext.Provider children={ children } value={ value } />;\n};\n"
  },
  {
    "path": "client/src/index.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Georama:wght@500&display=swap');\n\nbody {\n  background: rgb(10,11,59);\n  background: radial-gradient(circle, rgba(10,11,59,1) 35%, rgba(1,2,51,1) 100%);\n  margin: 0;\n  font-family: georama;\n  overflow: hidden;\n}\n\n*{\n  margin: 0;\n  padding: 0;\n  text-decoration: none;\n}\n\n:root{\n\n  --theme: #010233;\n  --orange-theme: #FF4848;\n  --lighter-theme: #4A5380;\n  --white: #ffffff;\n  --light-text-color: #DCDBDE;\n  --lighter-text-color: #EFF0F3;\n  --blue: #131746;\n  --black: #000;\n  --light-grey: #8A8AA4;\n  --dark-grey: #706C82; \n  --darker-grey: #34383c;\n  --grey: #9A9A9A;\n  --light-blue: #A8B9BD;\n  --lighter-blue: #F2F8FF;\n  --purple: #653FB4;\n  --dark-purple: #01103B;\n  --dark-blue: #01062E;\n  --yellow: #F9CD45;\n  --light-pink: #F9248E;\n  --turquoise: #17BDC0;\n  --light-theme: #5dfdfe;\n  --dark-turquoise: #0F3F62;\n  --green: #2FC201;\n  --bordeaux: #994446;\n  --red: #FF4747;\n  --dark-blue: #1C1F4C;\n  --primary-shadow: rgba(68,96,170,.2);\n  --primary-weak-shadow: rgba(68,96,170,.07);\n\n  --padding-xs: 1vh 0.5vw;\n  --padding-sm: 1vh 2vw;\n  --padding-md: 2vh 3vw;\n  --padding-lg: 2vh 6vw;\n  \n  --font-size-xxxs: 0.5vw;\n  --font-size-xxs: 0.8vw;\n  --font-size-xs: 1.2vw;\n  --font-size-sm: 1.5vw;\n  --font-size-md: 2vw;\n  --font-size-lg: 2.5vw;\n\n  --width-xxs: 0.8vw;\n  --width-xs: 1vw;\n  --width-sm: 3vw;\n  --width-md: 5vw;\n  --width-lg: 8vw;\n\n  --height-xxs: 0.9vh;\n  --height-xs: 1vh;\n  --height-sm: 3vh;\n  --height-md: 5vh;\n  --height-lg: 8vh;\n}\n"
  },
  {
    "path": "client/src/index.js",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport './index.css';\nimport App from './App';\nimport 'material-icons/iconfont/material-icons.css';\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\nroot.render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>\n);"
  },
  {
    "path": "client/src/pages/speed-test/SpeedTestPage.js",
    "content": "import React from 'react'\nimport styled from 'styled-components'\nimport { TestResultProvider } from '../../contexts/testResultContext'\nimport ResultsCardsContainer from './components/ResultsCardsContainer'\nimport ClientDetails from './components/ClientDetails'\nimport SpeedDetails from './components/SpeedDetails/SpeedDetails'\n\nconst StyledSpeedTestPage = styled.div`\n\n    width: 100vw;\n    height: 90vh;\n    min-height: 390px;\n    min-width: 910px;\n\n    @media ( max-width: 640px )\n    {\n        min-width: 0;\n        min-height: 0;\n    }\n`\n\nconst StyledTestDetails = styled.div`\n    \n    position: relative;\n    display: flex;\n    justify-content: space-between;\n    padding: 7vh 5vw 0 5vw;\n    min-height: 410px;\n    min-width: 750px;\n    \n    @media ( max-width: 640px )\n    {\n        min-width: 0;\n        min-height: 0;\n    }\n`\n\nexport default function SpeedTestPage() {\n\n    return (\n        <StyledSpeedTestPage>\n\n            <StyledTestDetails>\n\n                <TestResultProvider>\n\n                    <ClientDetails/>\n\n                    <SpeedDetails/>\n\n                    <ResultsCardsContainer/>\n                    \n                </TestResultProvider>\n\n            </StyledTestDetails>`\n\n        </StyledSpeedTestPage>\n    )\n}\n"
  },
  {
    "path": "client/src/pages/speed-test/SpeedTestPage.test.js",
    "content": "import axios from \"axios\";\nimport { fireEvent, render, screen } from '@testing-library/react'\nimport '@testing-library/jest-dom'\nimport SpeedTestPage from \"./SpeedTestPage\";\nimport { act } from \"react-dom/test-utils\";\n\n\n\njest.mock( 'axios' )\n\nconst MOCK_TEST_DETAILS = \n{ \n    \"downloadSpeed\":50,\"uploadSpeed\":7.9,\n    \"downloaded\":110,\"uploaded\":20,\n    \"latency\":21,\"bufferBloat\":192,\n    \"userLocation\":\"Jerusalem, IL\",\"userIp\":\"2a00:a040:197:22aa:804e:c312:1c2e:3b9e\",\n    \"server\":\"LAPTOP-MJG5S0F3\",\"os\":\"win32\"\n}\n\nit( 'Shows test details on a succesfull result', async () => {\n\n    //Arrange\n    axios.get.mockResolvedValueOnce( { data: MOCK_TEST_DETAILS } )\n    render( \n\n        <SpeedTestPage/>\n    )\n\n    //Act\n    const testButton = screen.getByTestId( 'test-button' )\n    await act( () => fireEvent.click( testButton ) )\n    \n    \n    //Assert\n    const testDataElementsLists =\n    { \n        userLocation: screen.getAllByTestId( 'userLocation' ),\n        userIp: screen.getAllByTestId( 'userIp' ),\n        server: screen.getAllByTestId( 'server' ),\n        os: screen.getAllByTestId( 'os' ),\n        downloadSpeed: screen.getAllByTestId( 'downloadSpeed' ),\n        uploadSpeed: screen.getAllByTestId( 'uploadSpeed' ),\n        latency: screen.getAllByTestId( 'latency' ),\n    }\n\n    Object.keys( testDataElementsLists ).forEach( dataKey => {\n\n        testDataElementsLists[ dataKey ].forEach( dataElement => {\n            \n            expect( dataElement ).toHaveTextContent( MOCK_TEST_DETAILS[ dataKey ] )\n        })\n    })\n\n})"
  },
  {
    "path": "client/src/pages/speed-test/components/ClientDetail.js",
    "content": "import React from 'react'\nimport styled from 'styled-components'\n\nconst StyledClientDetail = styled.div`\n\n    padding: 3vh 2vw;\n    display: flex;\n    gap: 1vw;\n`\n\nconst StyledLeftClientDetail = styled.div`\n\n    width: 20%;\n    display: flex;\n    flex-direction: column;\n    justify-content: flex-start;\n\n    & > .client-detail-icon{\n        \n        font-size: var( --font-size-sm );\n        color: var( --orange-theme );\n    }\n`\n\nconst StyledRightClientDetail = styled.div`\n\n    width: 80%;\n    height: 5vh;\n    display: flex;\n    flex-direction: column;\n    gap: 0.5vh;\n    font-size: var( --font-size-xxs );\n\n    & > .client-detail-data{\n        \n        color: var( --light-text-color );\n    }\n\n    & > .client-detail-title{\n        \n        color: var( --lighter-theme );\n    }\n\n`\n\nexport default function ClientDetail( { detailKey, icon, data, title } ) {\n\n    return (\n        <StyledClientDetail>\n\n            <StyledLeftClientDetail className='left-client-detail'>\n                \n                <span className='material-icons client-detail-icon'>{ icon }</span>\n\n            </StyledLeftClientDetail>\n\n            <StyledRightClientDetail className='right-client-detail'>\n\n                <span className='client-detail-data' data-testid={ detailKey }>{ ! data ? '?' : data }</span>\n                <span className='client-detail-title'>{ title }</span>\n\n            </StyledRightClientDetail>\n\n        </StyledClientDetail>                               \n    )\n}\n"
  },
  {
    "path": "client/src/pages/speed-test/components/ClientDetails.js",
    "content": "import styled from 'styled-components'\nimport { useTestResult } from '../../../contexts/testResultContext'\nimport ClientDetail from './ClientDetail'\nimport LiveChat from './LiveChat'\n\nconst StyledClientDetails = styled.div`\n    z-index: 1;\n    width: 18%;\n    display: flex;  \n    flex-direction: column;\n    padding-top: 5vh;\n`\n\nconst clientDetailsObject = {\n    userIp: { icon: 'wifi', title: 'IP Address' },\n    userLocation: { icon: 'pin_drop', title: 'Location' },\n    server: { icon: 'language', title: 'Server' },\n    os: { icon: 'album',title: 'OS' },\n};\n\nexport default function ClientDetails() {\n\n    const { testResult } = useTestResult()\n    return (\n        <StyledClientDetails>\n\n            { Object.keys( clientDetailsObject ).map( ( detailKey  ) => {\n                \n                return <ClientDetail key={ detailKey } detailKey={ detailKey } icon={ clientDetailsObject[ detailKey ].icon } data={ testResult[ detailKey ] } title={ clientDetailsObject[ detailKey ].title }/>\n\n            })}\n\n            <LiveChat/>\n\n        </StyledClientDetails>\n    )\n}\n"
  },
  {
    "path": "client/src/pages/speed-test/components/LiveChat.js",
    "content": "import React from 'react'\nimport styled from 'styled-components'\n\nconst StyledLiveChat = styled.div`\n\n    display: flex;\n    height: 12vh;\n    min-height: 65px;\n    background-color: var( --black );\n    justify-content: flex-start;\n    align-items: center;\n    color: var( --light-text-color );\n    padding: 3vh 1vw 3vh 1vw;\n    margin-top: 5vh;\n    font-weight: bold;\n    border-radius:  8px;\n    font-size: var( --font-size-xs );\n\n    & > .live-chat-text-container{\n\n        display: flex;\n        justify-self: center;\n        flex-direction: column;\n        height: 70%;\n    }\n\n    & > div > .help-you-text{\n        \n        margin-bottom: auto;\n    }\n\n    & > div > .live-chat-text{\n\n        cursor: pointer;\n        color: var( --orange-theme );\n    }\n\n\n    & > div > .live-chat-text:hover{\n\n        font-size: var( --font-size-sm );\n    }\n\n    & > .headset-icon{\n\n        font-size: var( --font-size-lg );\n        margin: auto;\n    }\n\n    @media ( max-width: 640px ){\n\n        height: 8vh;\n        min-height: 0;    \n        width: 20vw;\n        \n        padding: 2vw 4vw;\n\n        & > div > *{\n        \n            font-size: var( --font-size-sm );\n        }\n\n        & > div > .live-chat-text:hover{\n\n            font-size: var( --font-size-md );\n        }   \n    }\n`\nexport default function LiveChat() {\n\n    return (\n        <StyledLiveChat>\n            \n            <div className='live-chat-text-container'>\n\n                <div className='how-can-text'>How can we</div>\n                <div className='help-you-text'>help you?</div>\n                <div className='live-chat-text'>Live chat</div>\n\n            </div>\n\n            <span className='material-icons headset-icon'>headset_mic</span>\n            \n\n        </StyledLiveChat>\n        \n    )\n}\n\n"
  },
  {
    "path": "client/src/pages/speed-test/components/ResultCard.js",
    "content": "import React from 'react'\nimport styled from 'styled-components'\n\nconst StyledResultCard = styled.div`\n\n    z-index: 1;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 2vh;\n    padding: 2vh 5vw;\n    background-color: var( --blue );\n    -webkit-box-shadow: -2px -4px 39px 13px rgba(0,0,0,0.09); \n    box-shadow: -2px -4px 39px 13px rgba(0,0,0,0.09);\n    border-radius: 8px;\n\n    & > .result-card-top{\n\n        display: flex;\n        gap: 0.5vw;\n        align-items: center;\n        color: var( --orange-theme );\n    }\n\n    & > .result-card-top > .result-card-icon{\n\n        font-size: var( --font-size-sm );\n    }\n\n    & > .result-card-top > .result-card-text{\n\n        display: inline-block;\n        font-size: var( --font-size-xs );\n    }\n\n    & > .result-card-data{\n\n        font-size: var( --font-size-md );\n        font-weight: bold;\n        color: var( --light-text-color );\n    }\n\n    & > .result-card-unit{\n\n        font-size: var( --font-size-xxs );\n        color: var( --lighter-theme );\n    }\n\n`\nexport default function ResultCard( { dataKey, icon, text, unit, data }) {\n\n    return (\n        <StyledResultCard>\n\n            <div className='result-card-top'>\n\n                <span className='material-icons result-card-icon'>{ icon }</span>\n                <span className='result-card-text'>{ text }</span>\n\n            </div>\n\n            <div className='result-card-data' data-testid={ dataKey }>{ data }</div>\n\n            <div className='result-card-unit'>{ unit }</div>\n\n        </StyledResultCard>\n    )\n}\n"
  },
  {
    "path": "client/src/pages/speed-test/components/ResultsCardsContainer.js",
    "content": "import React from 'react'\nimport styled from 'styled-components'\nimport { useTestResult } from '../../../contexts/testResultContext'\nimport ResultCard from './ResultCard'\n\nconst StyledResultsCardsContainer = styled.div`\n\n    display: flex;\n    flex-direction: column;\n    gap: 3vh;\n    justify-content: center;\n`\n\nconst resultsCardsDetails =\n{\n    downloadSpeed: { icon: 'cloud_download', text: 'Download', unit: 'mbps' },\n    uploadSpeed: { icon: 'cloud_upload', text: 'Upload', unit: 'mbps' },\n    latency: { icon: 'monitor_heart', text: 'Latency', unit: 's' },\n}\n\nexport default function ResultsCardsContainer() \n{\n    const { testResult } = useTestResult()\n\n    return (\n        <StyledResultsCardsContainer>\n            { Object.keys( resultsCardsDetails ).map( ( cardKey ) =>{\n\n                const currentCard = resultsCardsDetails[ cardKey ]\n                return <ResultCard key={ cardKey } dataKey={ cardKey } icon={ currentCard.icon } text={ currentCard.text } unit={ currentCard.unit }  data={ testResult[ cardKey ] }/>\n            }) }\n        </StyledResultsCardsContainer>\n    )\n}\n"
  },
  {
    "path": "client/src/pages/speed-test/components/SpeedDetails/SpeedDetails.js",
    "content": "import React from 'react'\nimport styled from 'styled-components'\nimport { AppAttributesProvider } from '../../../../contexts/appAttributesContext'\nimport SpeedTestChart from '../SpeedTestChart'\nimport TestButton from '../TestButton'\nimport TestExtraDetails from '../TestExtraDetails'\n\nconst StyledSpeedDetails = styled.div`\n    width: 60%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 7vh;\n    position: absolute;\n    left: 50%;\n    top: 53%;\n    transform: translate( -50%, -50%);\n`\nexport default function SpeedDetails() { \n\n    return (\n        <StyledSpeedDetails>\n\n            <AppAttributesProvider>\n\n                <SpeedTestChart/>\n\n                <TestExtraDetails/> \n\n                <TestButton/>\n\n            </AppAttributesProvider>\n\n        </StyledSpeedDetails>\n    )\n}\n"
  },
  {
    "path": "client/src/pages/speed-test/components/SpeedDetails/SpeedDetails.test.js",
    "content": "import axios from \"axios\";\nimport { fireEvent, render, screen, waitFor } from '@testing-library/react'\nimport '@testing-library/jest-dom'\nimport { TestDetailsProvider } from '../../../../contexts/testDetails'\nimport SpeedDetails from './SpeedDetails';\n\n\n\njest.mock( 'axios' )\n\nconst MOCK_TEST_DETAILS = \n{ \n    \"downloadSpeed\":50,\"uploadSpeed\":7.9,\n    \"downloaded\":110,\"uploaded\":20,\n    \"latency\":21,\"bufferBloat\":192,\n    \"userLocation\":\"Jerusalem, IL\",\"userIp\":\"2a00:a040:197:22aa:804e:c312:1c2e:3b9e\",\n    \"server\":\"LAPTOP-MJG5S0F3\",\"os\":\"win32\"\n}\n\nit( 'Disables the test button until the promise resolved', async () => {\n\n    //Arrange\n    axios.get.mockResolvedValueOnce( { data: MOCK_TEST_DETAILS } )\n    render( \n\n        <TestDetailsProvider>\n            \n            <SpeedDetails/>\n\n        </TestDetailsProvider>\n    )\n\n    //Act\n    const testButton = screen.getByTestId( 'test-button' )\n    fireEvent.click( testButton ) \n    \n    //Assert\n    expect( testButton.disabled ).toBe( true )    \n    await waitFor( () => expect( testButton.disabled ).toEqual( false ) );\n\n})"
  },
  {
    "path": "client/src/pages/speed-test/components/SpeedTestChart.js",
    "content": "import React from 'react'\nimport styled from 'styled-components'\nimport Speedometer from './Speedometer/Speedometer'\n\nconst StyledSpeedTestChart = styled.div`\n\n    width: 70%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    text-align: center;\n    padding: 0 8vw;\n    color: var( --lighter-text-color );\n    \n    & > .speed-test-text{\n\n        position: relative;\n        bottom: 130px;\n        z-index: 1;\n        font-size: var( --font-size-lg );\n        font-weight: bold;\n    }\n`\nexport default function SpeedTestChart() {\n\n    return (\n        <StyledSpeedTestChart>\n\n            <div className='speed-test-text'>\n\n                <div className='speed-text'>SPEED</div>\n                <div className='test-text'>TEST</div>                \n\n            </div>\n\n            <Speedometer/>\n\n        </StyledSpeedTestChart>\n    )\n}\n"
  },
  {
    "path": "client/src/pages/speed-test/components/Speedometer/Speedometer.js",
    "content": "import React from 'react'\nimport LinesSpeedometer from '../../../../components/view-components/LinesSpeedometer/LinesSpeedometer'\nimport { useAppAttributes } from '../../../../contexts/appAttributesContext'\nimport { useTestResult} from '../../../../contexts/testResultContext'\nimport TestResults from '../TestResults/TestResults'\nimport { StyledSpeedometer } from './StyledSpeedometer'\n\nexport default function Speedometer( ) {\n\n    const { testResult }     = useTestResult()\n    const { downloadSpeed }  = testResult\n    const { loading, error } = useAppAttributes()\n    const linesCount         = 49\n    const maxSpeed           = 160\n    const speedRatio         = ! error ? downloadSpeed / maxSpeed : 0\n    const speedPercentage    = speedRatio * 100 \n    const markedCount        = speedRatio * linesCount \n\n    return (\n        <StyledSpeedometer>\n\n            <div className={ `inside-speedometer-bar ${ ! loading ? 'forwards-animation' : 'loading-animation' }` } data-testid='inside-speedometer-bar' style={ { '--value' : speedPercentage, '--lines-count': linesCount, '--line-delay': 20 } }>\n                \n                <LinesSpeedometer key='outside-speedometer-bar' linesCount={ linesCount } markedCount={ markedCount }/>\n                <TestResults key='test-results'/>\n\n            </div>\n\n            <div className='speedometer-background'></div>\n\n        </StyledSpeedometer>\n    )\n}\n"
  },
  {
    "path": "client/src/pages/speed-test/components/Speedometer/StyledSpeedometer.js",
    "content": "import styled from \"styled-components\";\n\nexport const StyledSpeedometer = styled.div`\n    \n    @property --percentage {\n        syntax: '<number>';\n        inherits: true;\n        initial-value: 0;\n    }\n\n    @keyframes progress {\n        0% {\n            --percentage: 0;\n        }\n\n        100% {\n            --percentage: var( --value );\n        }\n    }\n\n    @keyframes progress-back-and-forwards{\n\n        0% {\n            --percentage: 0;\n        }\n\n        50%{\n            --percentage: 100;\n        }\n\n        100% {\n            --percentage: 0;\n        }\n    }\n\n    \n\n    position: absolute;\n    top: 65%;\n    left: 50%;\n    transform: translate( -50%, -50% );\n    \n\n    .inside-speedometer-bar {\n\n        width: 28vw;\n        min-width: 300px;\n        aspect-ratio: 2 / 1;\n        position: relative;\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n        justify-content: center;\n    }\n\n    .inside-speedometer-bar::before {\n\n        content: \"\";\n        position: absolute;\n        top: 0;\n        left: 0;\n        width: 100%;\n        height: 100%;\n        background: conic-gradient( from 0.75turn at 50% 100%, var( --orange-theme ) calc( var( --percentage ) * 1% / 2 ), var( --dark-blue ) 0 );\n        mask: radial-gradient(at 50% 100%, white 68%, transparent 0);\n        mask-mode: alpha;\n        -webkit-mask: radial-gradient(at 50% 100%, #0000 68%, #000 0);\n        -webkit-mask-mode: alpha;\n        border-radius: 50% / 100% 100% 0 0;\n    }\n\n    .loading-animation{\n\n        --line-delay-ms: calc( var( --line-delay ) * 0.001s );\n        --circle-duration: calc( var( --line-delay-ms ) * var( --lines-count ) * 2 );\n        animation: progress-back-and-forwards var( --circle-duration ) linear infinite;\n    }\n\n    .forwards-animation{\n        \n        animation: progress 2s 0.05s forwards;\n    }\n\n    .speedometer-background{\n\n        z-index: -110;\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        transform: translate( -50%, -50%);\n        width: 550px;\n        height: 550px;\n        background: rgb(10,11,59);\n        background: radial-gradient(circle, rgb(12, 13, 61) 39%, rgba(7,8,56,1) 100%);\n        border-radius: 50%;\n        border: 2px solid #0c0c41;\n    }\n\n    .speedometer-background::after{\n\n        content: \"\";\n        z-index: -100;\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        transform: translate( -50%, -50%);\n        width: 280px;\n        height: 280px;\n        background: rgb(10,11,59);\n        background: radial-gradient(circle, rgba(10,11,59,1) 39%, rgba(7,8,56,1) 100%);\n        border-radius: 50%;\n        border: 2px solid #0d0d46;\n    }\n\n    .speedometer-background::before{\n\n        content: \"\";\n        z-index: -120;\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        transform: translate( -50%, -50%);\n        width: 420px;\n        height: 420px;\n        background: rgb(10,11,59);\n        background: radial-gradient(circle, rgb(12, 13, 61) 39%, rgba(7,8,56,1) 100%);\n        border-radius: 50%;\n        border: 2px solid #0c0c41;\n    }\n\n    @media ( max-width: 640px ){\n\n        .speedometer-background{\n\n            width: 275px;\n            height: 275px;\n        }\n        \n        .speedometer-background::after{\n        \n            width: 140px;\n            height: 140px;\n        }\n        \n        .speedometer-background::before{\n\n            width: 210px;\n            height: 210px;\n        }\n\n        .inside-speedometer-bar{\n            width: 0;\n            min-width: 0;\n        }\n\n        \n    }\n\n\n`"
  },
  {
    "path": "client/src/pages/speed-test/components/TestButton.js",
    "content": "import React from 'react'\nimport styled from 'styled-components'\nimport { Button } from '../../../components/view-components/Button'\nimport { getData } from '../../../utils/api.js'\nimport { useTestResult } from '../../../contexts/testResultContext'\nimport { useAppAttributes } from '../../../contexts/appAttributesContext'\n\nconst StyledTestButton = styled( Button )`\n\n    z-index: 1;\n    cursor: pointer;\n    padding: 0.5vw;\n    position: relative;\n    top: 18vh;\n\n    & > .test-button-icon{\n        \n        color: var( --orange-theme );\n        font-size: var( --font-size-sm );\n    }\n\n    &:hover{\n\n        background-color: var( --black );\n    }\n\n    &:hover > .test-button-icon{\n        \n        color: var( --lighter-theme );\n    }\n\n    @media ( max-width: 640px ) {\n        top: 7vh;\n        padding: 2vh;\n    }\n\n   \n\n`\n\nexport default function TestButton(  ) {\n    \n    const { setTestResult }                 = useTestResult()\n    const { loading, setLoading, setError } = useAppAttributes()\n    return (\n\n        <StyledTestButton size='sm' color='theme' circle={ true } data-testid='test-button' disabled={ loading } onClick={ () => { testSpeed( setTestResult, setLoading, setError ) } }>\n\n            <span className='test-button-icon material-icons'>replay</span>\n\n        </StyledTestButton>\n    )\n\n    async function testSpeed( setTestResult, setLoading, setError )\n    {   \n        setError( false )\n        setLoading( true )\n        \n        await getData( '' ).then( res => {\n\n            setTestResult( res.data )        \n        }).catch( function( error ){\n\n            setError( true )\n        })  \n\n        setLoading( false )\n    }\n}\n\n"
  },
  {
    "path": "client/src/pages/speed-test/components/TestExtraDetail.js",
    "content": "import React from 'react'\nimport styled from 'styled-components'\n\nconst StyledExtraDetail = styled.div`\n\n    display: flex;\n    gap: 5px;\n\n    & > .extra-detail-name{\n\n        font-size: var( --font-size-xxs );\n        color: var( --lighter-theme );\n    }\n\n    & > .extra-detail-value{\n\n        font-size: var( --font-size-xxs );\n        color: var( --light-text-color );\n    }\n`\nexport default function TestExtraDetail( { detailKey, name, value } ) {\n    \n    return (\n        <StyledExtraDetail>\n\n            <span className='extra-detail-name'>{ name }</span>\n            <span className='extra-detail-value' data-testid={ detailKey }>{ ! value ? '?' : value }</span>\n\n        </StyledExtraDetail>\n    )\n}\n"
  },
  {
    "path": "client/src/pages/speed-test/components/TestExtraDetails.js",
    "content": "import styled from 'styled-components'\nimport { useTestResult } from '../../../contexts/testResultContext'\nimport TestExtraDetail from './TestExtraDetail'\n\nconst StyledTestExtraDetails = styled.div`\n    position: relative;\n    top: 21vh;\n    z-index: 1;\n    display: flex;\n    gap: 10px;\n\n    @media ( max-width: 640px ) {\n        top: 11vh;\n    }\n`\n\nconst extraDetails = {\n    server: { 'name': 'Server' },\n    userIp: { 'name': 'IP Address' },\n}\n\nexport default function TestExtraDetails() {\n\n    const { testResult } = useTestResult()\n\n    return (\n\n        <StyledTestExtraDetails>\n\n            { Object.keys( extraDetails ).map( ( detailKey ) => {\n\n                const currentDetail = extraDetails[ detailKey ]\n                return <TestExtraDetail key={ detailKey } detailKey={ detailKey } name={ currentDetail.name } value={ testResult[ detailKey ] }/>\n            })}\n\n        </StyledTestExtraDetails>\n    )\n}\n"
  },
  {
    "path": "client/src/pages/speed-test/components/TestResults/TestResults.js",
    "content": "import React from 'react'\nimport styled from 'styled-components'\nimport { useAppAttributes } from '../../../../contexts/appAttributesContext'\nimport { useTestResult } from '../../../../contexts/testResultContext'\n\nconst StyledTestResults = styled.div`\n\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    height: 100px;\n    margin-top: 20px;\n\n    & > .speed-result > .speed-type-icon{\n\n        font-size: var( --font-size-sm );\n        color: var( --orange-theme );\n    }\n\n    & > .speed-result > .speed-result-text{\n\n        font-weight: bold;\n        font-size: var( --font-size-lg );\n        color: var( --white );\n    }\n\n    & > .speed-result > .mbps-text{\n\n        font-size: var( --font-size-xs );\n        color: var( --lighter-theme );\n    }\n\n    & > .ping-result-container{\n\n        display: flex;\n        padding-top: 10px;\n        gap: 0.5vw;\n    }\n\n    & > .ping-result-container > .ping-text{\n\n        font-size: var( --font-size-xxs );\n        color: var( --orange-theme );\n    }\n\n\n    & > .ping-result-container > .ping-result-text{\n\n        font-size: var( --font-size-xxs );\n        color: var( --white );\n    }\n\n\n`\nexport default function TestResults() {\n\n    const { loading, error } = useAppAttributes()\n    const { testResult }     = useTestResult()\n    const { downloadSpeed, latency } = testResult\n    \n    return (\n        <StyledTestResults className='test-results'>\n\n            <div className='speed-result'>\n                <span className='material-icons speed-type-icon'>download</span>\n                <div className='speed-result-text' data-testid='downloadSpeed' >{ ! loading && ! error ? downloadSpeed : loading ? 'Testing...' : 'No connection' }</div>    \n                <div className='mbps-text'>mbps</div>\n            </div>\n\n            <div className='ping-result-container'>\n\n                <span className='ping-text'>Latency</span>\n                <span className='ping-result-text' data-testid='latency'>{ latency }</span>\n            </div>\n\n        </StyledTestResults>\n    )\n}\n\n"
  },
  {
    "path": "client/src/pages/speed-test/components/TestResults/TestResults.test.js",
    "content": "import {render, screen} from '@testing-library/react'\nimport '@testing-library/jest-dom'\nimport TestResults from './TestResults';\nimport { AppAttributesProvider } from '../../../../contexts/appAttributes'\nimport { TestDetailsProvider } from '../../../../contexts/testDetails'\n\nit( 'Shows loading text when waiting for server response', () => {\n    \n    //Arrange\n    render( \n\n        <TestDetailsProvider>\n\n            <AppAttributesProvider value={ { error: false, loading: true } }>\n\n                <TestResults/>\n\n            </AppAttributesProvider>\n            \n        </TestDetailsProvider>\n    )\n    \n    //Assert\n    const speedResultText = screen.getByTestId( 'downloadSpeed' )\n    expect( speedResultText ).toHaveTextContent( 'Testing...' )\n\n})\n\nit( 'Shows Error text when error is thrown', () => {\n    \n    //Arrange\n    render( \n\n        <TestDetailsProvider>\n\n            <AppAttributesProvider value={ { error: true, loading: false } }>\n\n                <TestResults/>\n\n            </AppAttributesProvider>\n            \n        </TestDetailsProvider>\n    )\n    \n    //Assert\n    const speedResultText = screen.getByTestId( 'downloadSpeed' )\n    expect( speedResultText ).toHaveTextContent( 'No connection' )\n\n})"
  },
  {
    "path": "client/src/setupTests.js",
    "content": "import fetchMock from \"jest-fetch-mock\";\n\nfetchMock.enableMocks();\n\n\n"
  },
  {
    "path": "client/src/utils/api.js",
    "content": "import axios from 'axios';\nconst API_URL = process.env.REACT_APP_API_URL\n\nexport async function getData( url ) {\n\n    const fetchUrl = url ? API_URL + url : API_URL \n\n    return await axios.get( fetchUrl )\n} "
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"internet-speed-test-react\",\n  \"version\": \"1.0.0\",\n  \"description\": \"This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"cd server && npm start\",\n    \"server\": \"cd server && npm run dev\",\n    \"client\": \"cd client && npm start\",\n    \"dev\": \"concurrently \\\"npm run server\\\" \\\"npm run client\\\"\",\n    \"install:client\": \"cd client && npm install --legacy-peer-deps\",\n    \"install:server\": \"cd server && npm install\",\n    \"install:all\": \"npm install && npm run install:client && npm run install:server\",\n    \"build:client\": \"npm run install:client && cd client && npm run build && mkdir -p ../server/public && cp -r build/* ../server/public\",\n    \"build:server\": \"npm run install:server\",  \n    \"build\": \"npm run build:client && npm run build:server\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"concurrently\": \"^8.0.1\"\n  }\n}\n"
  },
  {
    "path": "server/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n.env \n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# production\n/build\n\n# misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n"
  },
  {
    "path": "server/api-handlers-helpers.js",
    "content": "const { exec } = require('child_process');\nclass os_func{\n\n    execCommand( cmd )\n    {\n        return new Promise( ( resolve, reject ) => {\n            exec( cmd, ( err, stdout, stderr ) => {   \n                \n                if ( err ) \n                {\n                    reject( err )\n                    return\n                }\n\n                if ( stderr ) \n                {\n                    reject( stderr )\n                    return\n                }\n                \n                resolve( stdout)\n            });\n        })\n    }\n}\n\nexports.getExecOutput = async function( command ){\n\n    let execOutput\n    const osFunction = new os_func()\n    await osFunction.execCommand( command )\n    .then( res => {\n\n        execOutput = { 'status': 200, 'data': res }\n    })\n    .catch( err =>{\n\n        execOutput = { 'status': 400, 'data': err }\n    })\n\n    return execOutput\n}"
  },
  {
    "path": "server/api-handlers.js",
    "content": "const { getExecOutput } = require( './api-handlers-helpers' );\nvar os = require('os');\n\nexports.testSpeedHandler = async() => {\n\n    const testCommandOutput = await getExecOutput( 'fast --upload --json' )\n\n    //Handle no internet error\n    if ( testCommandOutput.data == '› Please check your internet connection\\n\\n\\n' ) \n    {    \n        testCommandOutput.status = 400\n    }\n\n    if ( testCommandOutput.status !== 200 ) \n    {    \n        return testCommandOutput\n    }\n\n    return {\n        ...testCommandOutput,\n        data: {\n            ...JSON.parse( testCommandOutput.data ),\n            server: os.hostname(),\n            os: process.platform,\n        }\n    }\n}"
  },
  {
    "path": "server/index.js",
    "content": "const cors = require('cors')\nconst express = require(\"express\");\nconst app = express();\nconst PORT = process.env.SERVER_PORT || 8000\nconst { testSpeedHandler } = require( './api-handlers' )\n\nrequire('dotenv').config();\nconst corsOptions = {\n    origin: '*',\n    optionsSuccessStatus: 200,\n}\napp.use(cors(corsOptions));\n\napp.get(\"/\", async ( req, res ) => { \n\n    const speedTestData = await testSpeedHandler()\n    res.status( speedTestData.status )\n    res.send( speedTestData.data )    \n});\n\napp.listen( PORT, () => {\n\n    console.log( `Listening on port ${ PORT }` );\n});"
  },
  {
    "path": "server/package.json",
    "content": "{\n  \"name\": \"server\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"dependencies\": {\n    \"cors\": \"^2.8.5\",\n    \"dotenv\": \"^16.0.2\",\n    \"express\": \"^4.18.1\",\n    \"fast-cli\": \"^3.2.0\",\n    \"network-speed\": \"^2.1.1\",\n    \"nodemon\": \"^2.0.19\"\n  },\n  \"scripts\": {\n    \"start\": \"node index.js\",\n    \"dev\": \"nodemon index.js\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\"\n}\n"
  }
]