A unit conversion site that prides itself on simplicity. It can be found over at conversion.urbanlife.me.
UrbanConversion is built using pnpm and Vite for their speed and excellent developer experiences.
To run the app locally pnpm must first be installed using the following:
# Other options to install pnpm can be found here: https://pnpm.io/installation
curl -fsSL https://get.pnpm.io/install.sh | sh -
# After installation is completed, either open a new shell session or source the shell's configuration file
# BASH
source ~/.bashrc
# Zsh
source ~/.zshrc
To install dependencies, run the following:
# Installs all dependencies specified in the package.json file
pnpm install
UrbanConversion can be run locally using the following command:
# Uses Vite to run a dev server on localhost:3000
pnpm run dev
Once running, the site can be accessed at localhost:3000
.
UrbanConversion can be built using the following:
# Uses vite to bundle the application
pnpm run build
The application bundle will be output to the dist/
directory. This can then be served with any static hosting service.
The live version of UrbanConversion is continuously deployed to an Nginx server running on an Ubuntu VPS. This is done using a Jenkins build server running on a Raspberry Pi. Jenkins polls the Git repository every 5 minutes. If changes are detected, it fetches the repo, builds the release, and deploys over SSH.
UrbanConversion is built using Vue3 and styled using Tailwind. I chose Vue due to my prior experience with the framework. While the app is objectively simple and could have been written without a framework, using one allowed for faster development time and a more manageable architecture. The reason for Tailwind was the effortless way it provides for "dark mode" implementations.
The app is designed to be extendable. As of version 1.0.0
, it only supports converting degrees Celsius to Fahrenheit however, new conversions can be added by creating a new conversion component under the src/components/conversions/
directory and then wrapping it in a Card
component inside of App.vue
.
Example Adding a new "Distance" calculator:
├── src/
│ ├── App.vue
│ ├── assets/
│ ├── components/
│ │ ├── Card.vue
│ │ ├── Input.vue
│ │ ├── ThemeButton.vue
│ │ └── conversions/
│ │ ├── Temperature.vue
│ │ └── Distance.vue # <--- new conversion component
│ ├── main.js
│ └── utils/
<script setup>
...
import Temperature from './components/conversions/Temperature.vue';
<!-- Import new conversion component -->
import Distance from './components/conversions/Distance.vue';
<!-- -->
...
</script>
<div :class="['grid', 'justify-items-center']">
...
<Card title="Temperature">
<Temperature />
</Card>
<!-- Add new conversion component -->
<Card title="Distance">
<Distance />
</Card>
<!-- -->
</div>
The ability to extend the application to support other conversion calculations was intended from the beginning and was one of the driving reasons that a JavaScript framework was used.
Dark/Light modes
As previously mentioned, I used Tailwind because it provides a simple way to add both light and dark modes using its dark
variant feature. UrbanConversion will default to the user's OS theme settings. Changing the theme can also be done by clicking the "sun"/"moon" icon in the upper right-hand corner of the window. When the user does toggle the app's theme, the result of the toggle will is stored in the browser's local storage. This will then be used to determine the user's theme during their next visit.
The theme settings are determined during App.vue
's onMounted
lifecycle event. First, it checks the local storage for a saved theme value, then the OS's preferred mode. Helper functions exist under src/utils/theme.js
for querying the OS's preferred theme and toggling dark mode: osPrefersDarkTheme
and toggleDarkTheme
.
/**
* Check if the user's OS theme settings are configured to prefer dark mode.
*
* @returns True if the user's OS theme settings are configured to prefer dark mode, and false otherwise.
*/
function osPrefersDarkTheme() {}
/**
* Toggle website dark theme on/off depending on the boolean value specified.
* `true`: will toggle dark mode on
* `false`: will toggle dark mode off
*
* @param {Boolean} enable
* @returns Returns true if dark mode is enabled, and false otherwise.
*/
function toggleDarkTheme(enable) {}
Conversions
For the temperature conversion, the following calculations are used:
- Celsius:
(degrees Fahrenheit - 32) / 1.8
- Fahrenheit:
(degrees Celsius * 1.8) + 32
Calculated results are rounded to two decimal places.
To save the user from having to click a button, conversions are calculated as the user enters their value. User input is limited to positive or negative numbers/floats. Calculations will not be done if the input value is invalid, and the corresponding input field will be blank.
For easy extendibility, calculations should be managed by the conversion components themselves when adding new conversions
For the UI, I wanted the branding to be consistent with other sites that I host on the same domain, urbanlife.me. To achieve this, I used the same color palette, Ethan Schoonover's Solarized, and font family, Mozilla's Zilla Slab. The icons that I used are from OpenMoji. I discovered this icon set while working on this project. I thought they looked cute and would add a friendly personality to the site.
I also made the UI components, cards, input fields, titles, etc., all match with UrbanLife's stylings. Tailwind made this process very easy. I could reuse most of my Tailwind configuration from my other projects. This meant things like my color palette were instantly available inside my project.
Ideas for how the project can be improved:
- Localization
- Eliminate the need to modify
App.js
when adding new conversions