Workplaces for digital nomads: the frontend
Tags: javascript, tutorial, vue, showdev
Published: 2022-11-01 13:59:17

A continuation of the story about the development of a pet project about cafés and co-working spaces in sunny Cyprus. Workplaces for digital nomads ヽ(。_°)ノ

I talked the API microservice in the [first part](https://dev.to/vladimir_mvs/workplaces-for-digital-nomads-the-api-5hk8), the frontend site in the second part.

The [project code](https://github.com/workplacescy) is open, website https://workplaces.cy/.

## Architecture

The site is built as a PWA using the [Vue 3 Composition API](https://vuejs.org/api/sfc-script-setup.html) and the [Vuetify](https://next.vuetifyjs.com/en/) UI framework. Both tools are ideal for a quick start from scratch and contain less redundant code than earlier versions.

The major portion of the site is Google Maps in its always-free tier, which displays data from the microservice's REST API, as well as a panel with filters and a list of workplaces.

The entry component [src/components/Home.vue](https://github.com/workplacescy/frontend/tree/develop/src/components/Home.vue):
* manages the display of sections for mobile and desktop versions;
* manages data from the API, which is retrieved from [src/composable/api.js](https://github.com/workplacescy/frontend/tree/develop/src/composable/api.js);
* handles them with computed filters from [src/components/Filters.vue](https://github.com/workplacescy/frontend/tree/develop/src/components/Filters.vu);
* passes the filtered data to component [src/components/Places.vue](https://github.com/workplacescy/frontend/tree/develop/src/components/Places.vue) to display a list of locations;
* handles a 404 error;
* sets meta tags via component [src/components/PlaceHead.vue](https://github.com/workplacescy/frontend/tree/develop/src/components/PlaceHead.vue).

There is no separate "place card" in the project, a simple scrolling of the place list to the required place is sufficient for now:
```JavaScript
watch(
    () => props.selectedPlaceId,
    id => {
      scrollToPlace(id)
    }
)
```
The filters are integrated in [src/components/Filters.vue](https://github.com/workplacescy/frontend/tree/develop/src/components/Filters.vue) and stylized further for a more compact appearance.

Meta tags are handled by the vueuse/head package.

The [fawmi/vue-google-maps](https://github.com/fawmi/vue-google-maps) library, version 0.9.72, is used to display the map. The author broke the map in later versions and did not publish the source code on GitHub (however, these versions are available via npm).

You may also see local points of interest by displaying your location on a [map](https://github.com/workplacescy/frontend/blob/develop/src/components/Map.vue).

[Swiper/vue](https://swiperjs.com/vue) and a number of components are used to display large and small photographs of locations in a carousel.

In the HSL colour space, a gradient colour rating of places is calculated using a simple function in [src/composable/colors.js](https://github.com/workplacescy/frontend/tree/develop/src/composable/colors.js). Only the hue changes depending on the rating value, while the saturation and brightness remain fixed.

On the place cards, there is a [Complain button](https://github.com/workplacescy/frontend/tree/develop/src/components/ComplainButton.vue) to report any inaccuracies - it's all serious:-)

A simple [src/router/index.js](https://github.com/workplacescy/frontend/tree/develop/src/router/index.js) router based on [vue-router](https://v3.router.vuejs.org/) allows you to avoid utilising the store for now (with the current project capabilities) and helps with 404 error handling.

Most components are loaded asynchronously to speed up page display, for example, `const Navigation = defineAsyncComponent() => import("/Navigation.vue"))`.

When opening/closing the panel, a portion of the template is wrapped into [\<KeepAlive\>](https://vuejs.org/guide/built-ins/keep-alive.html) to cache components. However, it did not work with [Suspense](https://vuejs.org/guide/built-ins/suspense.html) for displaying stubs :-(

Instead of using Vuetify's default font icons, the project imports SVG icons from the mdi/js package at https://materialdesignicons.com. This saves approximately 1 MB in the final package.

Builds the project with [Vite](https://vitejs.dev/) and some magic in [vite.config.js](https://github.com/workplacescy/frontend/tree/develop/vite.config.js) to optimise the final code, including CSS and HTML minification and the creation of a lighter version of Sentry.

PWA is created with [vite-pwa/vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa), and its parameters are set in [vite.config.js](https://github.com/workplacescy/frontend/tree/develop/vite.config.js). In summary, the implementation of PWA was not a project aim, but it did provide good caching of all project components and resulted in a very quick reopening of the site.

### About Vuetify

Overall, I preferred Vuetify over [Quazar](https://quasar.dev/) and other UI frameworks. However, it has its own drawbacks:
* The difficulty in finely customising visual components. Vuetify is built for Material Design, therefore you can't just remove the indents between checkboxes or make mobile buttons smaller. Because of the high DOM depth, you may need to use [:deep](https://vuejs.org/api/sfc-css-features.html#scoped-css) or wrap components in additional divs in some circumstances, lowering your Google PageSpeed score.
* Unable to get rid of unused styles. [Importing components individually](https://next.vuetifyjs.com/en/features/treeshaking/#manual-imports) and [setting styles in SASS](https://next.vuetifyjs.com/en/features/sass-variables/) can greatly simplify builds, but several common unused styles can't be deleted using [PurgeCSS](https://purgecss.com/) and analogues due to dynamic class names.
* Some components, such as the [slider](https://next.vuetifyjs.com/en/components/carousels/), have few features.

But I'm sure, this framework is good enough for many projects.

## Deployment

The same Fly.io platform with managed micro VM Firecracker. It's much easier than hosting the microservice API here, and a four-line [Dockerfile](https://github.com/workplacescy/frontend/tree/develop/Dockerfile) suffices:
```Dockerfile
FROM pierrezemb/gostatic
COPY docker/config/headerConfig.json /config/
COPY dist/ /srv/http/
ENTRYPOINT ["/goStatic", "-fallback", "/index.html"]
```

## CI/CD

This is slightly more complicated: first, [Github Action](https://github.com/workplacescy/frontend/tree/develop/.github/workflows/build.yml) creates a build with Vite, then `flyctl` creates a container from it and deploys it to the production VM. All secrets are stored in the GitHub production environment in this case.

## Monitoring

The same [Sentry](https://sentry.io/) and [Honeybadger](https://www.honeybadger.io/).

**At this stage, the website is live, hosted in a production and available to all users. The project is fully completed :-)**

[Frontend repository](https://github.com/workplacescy/frontend), website https://workplaces.cy/