How to Build Password Generator with Electron & React Pt.2 – Creating the UI

How to Build Password Generator with Electron & React Pt.2

Table of Contents

Every password generator, and app in general, needs a great UI. For two reasons. First, great UI makes using the app easier and faster for anyone who wants to use the app. Second, we often judge the quality of the app according to its design. We see things with great design as better and more valuable. And, if the app also works great, then the sale is made and people become users. Without further ado, let’s create a great UI for our password generator app!

How to Build Password Generator with Electron & React part 1.

How to Build Password Generator with Electron & React part 3.

You can find the password generator app on GitHub and npm.

Everything starts with … the index

In the previous part, we put together the workflow for this project as well as installed all the necessary dependencies. And, we also created the main file for electron, main.js. Now, our firs step is creating the main file for the actual app, our password generator. We will call this file index and put it inside src directory. In this file, we will basically do three things.

First, we will import the main component for our app. Second, we will use renderer method from react-dom to take this component and render it inside an HTML container. But before we do this, we will have to do the third thing. We will need to create this container div and append it as a child node to the body element. Then, we can finally renderthe app component. Let’s take a look at the code.

// src/index.js
import React from 'react'
import { render } from 'react-dom'

import App from './app/App'

// We are using HtmlWebpackPlugin WITHOUT a template. Therefore, we need to create our own root node in the body element before rendering into it
let root = document.createElement('div')

root.id = 'root'
document.body.appendChild(root)

// Now we can render our application into it
render(<App />, document.getElementById('root'))

Creating the main App component

Our next step will be creating a new file inside src/App called App.jsx. This file will wire together all the parts we will soon create for our password generator app. At the top, we will start with imports for React and styled-components. Then, we will add imports for all the “bigger” components the UI will be composed of. Next, we will use injectGlobal helpe imported from styled-components to add some universal styles to the body and text elements.

After that, we will use styled-components and create AppWrapper to add some simple styling for the outmost div that will wrap the main component. Next, we will create a new class called App. The reason for using class instead of stateless component is that we will later work React state. Since we are talking about state. We can prepare some keys we will later use, namelly showAdvancedSettings, showBasicSettings and showResult.

Next, we can prepare the methods we will need to make our password generator fully functional, and useful in general. These toggleAdvancedSettings, toggleBasicSettings and generatePassword. We will leave these methods empty for now since our goal is working on the UI. Well, almost. We can actually add some simple logic so we can work with at least the demo of our password generator and work with all parts of the UI. In the end, we can always change anything.

The AppWrapper will contain a number of components. These components are Navigation, BasicSettings, AdvancedSettings, Info and ButtonWrapper. The Navigation will be a list with two items, one for showing or hiding component with basic settings, BasicSettings, and one for showing or hiding component with advanced settings, the AdvancedSettings. The Info component will contain either welcome text or password generated by our password generator.

Finally, the ButtonWrapper will contain two buttons. One button for generating password and one for resetting the app. We will show only one button at the time, depending on the whether we user wants to generate the password for the first time or wants to try another one. Let’s take a look at the code.

// src/App/App.jsx
import React from 'react'
import styled, { injectGlobal } from 'styled-components'

import AdvancedSettings from './components/AdvancedSettings'
import BasicSettings from './components/BasicSettings'
import { Button, ButtonWrapper } from './components/Button'
import Info from './components/Info'
import Navigation from './components/Navigation'

injectGlobal`
  body {
    margin: 0;
    font: caption; /* Automatically pick whatever font is the UI font on a system */
    line-height: 1.414;
    color: #333;
  }

  h1,
  label {
    -webkit-user-select: none;
    cursor: default;
  }

  h1 {
    margin-top: 0;
    font-size: 24px;
  }
`

const AppWrapper = styled.div`
  padding-right: 16px;
  padding-left: 16px;
`

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      showAdvancedSettings: false,
      showBasicSettings: false,
      showResult: false
    }
  }

  toggleAdvancedSettings() {
    this.setState({
      showAdvancedSettings: !this.state.showAdvancedSettings,
      showBasicSettings: false
    })
  }

  toggleBasicSettings() {
    this.setState({
      showAdvancedSettings: false,
      showBasicSettings: !this.state.showBasicSettings
    })
  }

  generatePassword() {
    this.setState({
      showResult: true
    })
  }

  render() {
    return (
      <AppWrapper>
        {/* Main navigation */}
        <Navigation toggleBasicSettings={() => this.toggleBasicSettings()} toggleAdvancedSettings={() => this.toggleAdvancedSettings()} state={this.state} />

        {/* Component with basic settings */}
        {this.state.showBasicSettings && <BasicSettings />}

        {/* Component with advanced settings */}
        {this.state.showAdvancedSettings && <AdvancedSettings />}

        {/* Component with welcome message and result - the password generated by our password generator */}
        {!this.state.showBasicSettings && !this.state.showAdvancedSettings && <Info showResult={this.state.showResult} />}

        {/* Main control elements - button for generating password and for resetting our password generator */}
        <ButtonWrapper>
          {!this.state.showResult && <Button type="button" onClick={() => this.generatePassword()}>Generate password</Button>}

          {this.state.showResult && <Button type="button" onClick={() => this.generatePassword()}>Generate new</Button>}
        </ButtonWrapper>
      </AppWrapper>
    )
  }
}

export default App

Building a simple navigation

Now, let’s create the component for the main navigation. As I mentioned it will be a simple list with items. Each of these items will contain a link that will either open or close basic or advanced settings. As you may notice, we passed some of the methods we created, and the app state as well, as a props. We did this so we can use these methods now and change the app state. We are also passing the state itself because we will use it to toggle class on active list item, or clicked link.

// src/components/Navigation.jsx
import React from 'react'
import styled, { css } from 'styled-components'

import Link from './Link'

const NavigationWrapper = styled.header`
  margin-bottom: 16px;

  ul {
    margin-top: 0;
    padding: 0;
    display: flex;
    list-style-type: none;
  }

  li {
    padding-bottom: 1px;
    width: 50%;
    text-align: center;
    color: hsl(234.8, 26.4%, 70%);
    border-bottom: 1px solid hsla(234.8, 26.4%, 70%, .2);
    transition: all .25s ease-in-out;

    &:hover,
    &.active {
      padding-bottom: 0;
      color: hsl(246.9, 74.3%, 63.3%);
      border-bottom: 2px solid;
    }

    &:first-of-type {
      border-right: 1px solid hsla(234.8, 26.4%, 70%, .2);
    }
  }
`

const AdvancedSettings = ({ state, toggleBasicSettings, toggleAdvancedSettings }) => {
  return(
    <NavigationWrapper>
      <nav>
        <ul>
          <li className={state.showBasicSettings ? "active" : null}>
            <Link onClick={toggleBasicSettings}>Show basic settings</Link>
          </li>

          <li className={state.showAdvancedSettings ? "active" : null}>
            <Link onClick={toggleAdvancedSettings}>Show advanced settings</Link>
          </li>
        </ul>
      </nav>
    </NavigationWrapper>
  )
}

export default AdvancedSettings

Adding smaller components

Next, let’s continue by adding the relatively smaller components. These components are Button, Checkbox, Input, Link and something called SettingsOption. The last one is extremely small component that will help us manage spacing between options in basic and advanced settings. It is basically just a wrapper div. We will put all these components on the same place, src/App/components. And, we will use styled-components to create them.

The Input and Checkbox will be a bit more difficult. We will create these two as stateless functions, each with a couple of parameters. We will use these parameters to make both component universal. These parameters are id, label, clickHandler and type for Input and id, hint, label and clickHandler for Checkbox. We will use id to set htmlFor, id and name attributes, label to add some text as a label, type too change the type of the input, hint for a hint and clickHandler for method that will handle click event.

One more thing about the Checkbox. There will be much more styling. We will use SVG to create a custom checkbox. The checkbox will be a gray box with slightly rounder corners and thin border. When user hover over it, the border will gradually change color. And, when the user checks off the checkbox, it will change background color or show white check mark.

The Button component:

// src/components/Button.jsx
import styled from 'styled-components'

import SettingsOptionWrapper from './SettingsOption'

export const Button = styled.button`
  padding: 12px 18px;
  vertical-align: middle;
  display: inline-block;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.5;
  text-align: center;
  white-space: nowrap;
  color: #fff;
  background-color: hsl(246.9, 74.3%, 63.3%);
  border: 1px solid transparent;
  border-radius: 35px;
  cursor: pointer;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  transition: background-color .15s ease-in-out;

  &:hover {
    background-color: hsl(246.9, 74.3%, 53.3%);
  }

  &:active,
  &:focus {
    background-color: hsl(246.9, 74.3%, 43.3%);
    outline: 0;
  }
`

export const ButtonWrapper = SettingsOptionWrapper.extend`
  text-align: center;
`

The Checkbox component:

// src/components/Checkbox.jsx
import React from 'react'
import styled from 'styled-components'

const LabelEl = styled.label`
  margin-bottom: 0;
  display: flex;
  align-items: center;
  user-select: none;
  cursor: pointer;

  & .invisible {
    position: absolute;
    z-index: -1;
    width: 0;
    height: 0;
    opacity: 0;
  }

  & input:checked {
    & + .checkbox {
      border-color: hsl(246.9, 74.3%, 63.3%);

      svg {
        path {
          fill: hsl(246.9, 74.3%, 63.3%);
        }

        polyline {
          stroke-dashoffset: 0;
        }
      }
    }
  }

  &:hover {
    .checkbox {
      svg {
        path {
          stroke-dashoffset: 0
        }
      }
    }
  }

  .checkbox {
    position: relative;
    margin-right: 8px;
    width: 16px;
    height: 16px;
    border: 2px solid hsl(208.9, 11.9%, 80%);
    border-radius: 3px;

    svg {
      position: absolute;
      top: -2px;
      left: -2px;

      path {
        fill: none;
        stroke: hsl(246.9, 74.3%, 63.3%);
        stroke-width: 2;
        stroke-linecap: round;
        stroke-linejoin: round;
        stroke-dasharray: 71px;
        stroke-dashoffset: 71px;
        transition: all .6s ease;
      }

      polyline {
        fill: none;
        stroke: #fff;
        stroke-width: 2;
        stroke-linecap: round;
        stroke-linejoin: round;
        stroke-dasharray: 18px;
        stroke-dashoffset: 18px;
        transition: all .3s ease;
      }
    }
  }

  & > span {
    vertical-align: middle;
    color: hsl(208.9, 11.9%, 50%);
    font-size: 15px;
    pointer-events: none;
  }

  em {
    font-size: 14px;
  }
`

const Checkbox = ({id, hint, label, clickHandler}) => {
  return(
    <LabelEl htmlFor={id} onClick={clickHandler}>
      <input id={id} name={id} type="checkbox" className="invisible" />

      <div className="checkbox">
        <svg width="20px" height="20px" viewBox="0 0 20 20">
          <path d="M3,1 L17,1 L17,1 C18.1045695,1 19,1.8954305 19,3 L19,17 L19,17 C19,18.1045695 18.1045695,19 17,19 L3,19 L3,19 C1.8954305,19 1,18.1045695 1,17 L1,3 L1,3 C1,1.8954305 1.8954305,1 3,1 Z"></path>

          <polyline points="4 11 8 15 16 6"></polyline>
        </svg>
      </div>

      <span>{label} <em>({hint})</em></span>
    </LabelEl>
  )
}

export default Checkbox

The Input component:

// src/components/Input.jsx
import React from 'react'
import styled from 'styled-components'

const LabelEl = styled.label`
  display: flex;
  flex-direction: column;
  align-items: flex-start;

  span {
    font-size: 15px;
    color: hsl(208.9, 11.9%, 50%);
  }

  span + input {
    margin-top: 6px;
  }

  input {
    padding: 4px;
    height: 16px;
    border: 2px solid hsl(208.9, 11.9%, 80%);
    border-radius: 3px;
  }
`

const Input = ({id, label, clickHandler, type}) => {
  return(
    <LabelEl htmlFor={id} className="label" onClick={clickHandler}>

      <span>{label}</span>

      <input id={id} name={id} type={type} />
    </LabelEl>
  )
}

export default Input

The Link component:

// src/components/Link.jsx
import styled from 'styled-components'

const Link = styled.a`
  padding-top: 16px;
  padding-bottom: 16px;
  display: block;
  color: inherit;
  cursor: pointer;
`

export default Link

The SettingsOption component:

// src/components/SettingsOption.jsx
import styled from 'styled-components'

const SettingsOptionWrapper = styled.div`
  & + & {
    margin-top: 12px;
  }
`

export default SettingsOptionWrapper

Adding bigger components

Now we are getting to the final and it is time for the last step, creating the main components we imported in App.jsx. These components are AdvancedSettings, BasicSettings and Info. We will again put all these components on the same place as the previous, src/App/components. The AdvancedSettings and BasicSettings components will contain various options for our password generator.

User will be able to use options in order to customize the password our password generator will create and generate stronger passwords as a result. The basic options will include using lowercase (checkboxe) and uppercase letters (checkboxe), numbers (checkboxe), ASCII symbols (checkboxe), space (checkboxe) and setting the length of the password (number input). Advanced settings will allow the user to use and specify any custom characters (text input) she wants to use as well as use entropy (number input).

Lastly, the Info component will contain a welcome message with short instructions and the password generated by our app. We will show only one of these will at the time. If user just launched the password generator, it will be the welcome message. If new password is ready it will be the password.

The BasicSettings component:

// src/components/BasicSettings.jsx
import React from 'react'
import styled from 'styled-components'

import Checkbox from './Checkbox'
import Input from './Input'
import SettingsOptionWrapper from './SettingsOption'

const BasicSettingsWrapper = styled.div`
  padding-bottom: 16px;
`

const BasicSettings = ({  }) => {
  return(
    <BasicSettingsWrapper>
    {/* Option for lowercase letters */}
      <SettingsOptionWrapper>
        <Checkbox id="settingsLower" label="Lowercase" hint="abcdefghijklmnopqrstuvwxyz" />
      </SettingsOptionWrapper>

      {/* Option for uppercase letters */}
      <SettingsOptionWrapper>
        <Checkbox id="settingsUpper" label="Uppercase" hint="ABCDEFGHIJKLMNOPQRSTUVWXYZ" />
      </SettingsOptionWrapper>

      {/* Option for numbers */}
      <SettingsOptionWrapper>
        <Checkbox id="settingsNumbers" label="Numbers" hint="0123456789" />
      </SettingsOptionWrapper>

      {/* Option for ASCII symbols */}
      <SettingsOptionWrapper>
        <Checkbox id="settingsAsci" label="ASCII symbols" hint={"!" + "\"" + "#$%&'()*+,-./:;<=>?@[\]^_`{|}~"} />
      </SettingsOptionWrapper>

      {/* Option for space */}
      <SettingsOptionWrapper>
        <Checkbox id="settingsSpace" label="Space" hint=" " />
      </SettingsOptionWrapper>

      {/* Option for setting password length */}
      <SettingsOptionWrapper>
        <Input id="settingsLength" label="Length" type="number" />
      </SettingsOptionWrapper>
    </BasicSettingsWrapper>
  )
}

export default BasicSettings

The AdvancedSettings component:

// src/components/AdvancedSettings.jsx
import React from 'react'
import styled from 'styled-components'

import Input from './Input'
import SettingsOptionWrapper from './SettingsOption'

const AdvancedSettingsWrapper = styled.div`
  padding-bottom: 16px;
`

const AdvancedSettings = ({  }) => {
  return(
    <AdvancedSettingsWrapper>
      {/* Option for custom characters */}
      <SettingsOptionWrapper>
        <Input id="settingsCustom" label="Custom characters" type="text" />
      </SettingsOptionWrapper>

      {/* Option for entropy */}
      <SettingsOptionWrapper>
        <Input id="settingsEntropy" label="Entropy" type="number" />
      </SettingsOptionWrapper>
    </AdvancedSettingsWrapper>
  )
}

export default AdvancedSettings

The Info component:

// src/components/Info.jsx
import React from 'react'
import styled from 'styled-components'

const InfoWrapper = styled.div`
  margin-top: 32px;
  margin-bottom: 32px;
`

const InfoText = styled.p`
  margin: 0;
  text-align: center;
  color: hsl(208.9, 11.9%, 50%);
`

const Info = ({ showResult }) => {
  return(
    <InfoWrapper>
      {/* Welcome message */}
      {!showResult && <InfoText>Please, open the basic and/or advanced settings and choose which options do you want to use. Then, click on the button below to generate your password.</InfoText>}

      {/* New password */}
      {showResult && <InfoText></InfoText>}
    </InfoWrapper>
  )
}

export default Info

Closing thoughts on how to build password generator

Great job! You were able to create a pretty good UI for your own password generator app. Do you like it? If so, that’s great and I like to hear that. If not, let me and the other readers know what would you change. The UI is a very important part of every app and, as the saying goes, more people know more. So, let’s let the UI evolve. Today, we did more coding than talking than we usually do and I hope you liked this approach and enjoyed this part.

I also hope that you had some opportunity to learn something new or at least practice what you already know. In the end, the best way to learn is by practice. And, additional practice will only help you keep your knowledge fresh. There is one last question that should be answered. What’s coming in the next and final part? In the third part of this tutorial, our goal will be putting together the code necessary to make our password generator work.

We will not work on UI and design. Well, we may change and improve something here and there. However, it will not be our main goal. Instead, we will focus primarily on creating the logic that will allow our password generator create passwords. In other words, we will transform our password generator from demo or “mockup” to a fully working prototype. With that, I look forward to seeing you here again the next week and, until then, have a great time!

If you liked this article, please subscribe so you don't miss any future post.

If you'd like to support me and this blog, you can become a patron, or you can buy me a coffee 🙂

By Alex Devero

I'm Founder/CEO of DEVERO Corporation. Entrepreneur, designer, developer. My mission and MTP is to accelerate the development of humankind through technology.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.