DEV Community

Cover image for Storybook
fentybit
fentybit

Posted on

Storybook

This is it. 🥺

This is module 5. 🤓

This is my last module at Flatiron School. 😱

What a journey it has been, 10 months of coding from building a Command Line Interface (CLI), Sinatra, Ruby on Rails, Vanilla JavaScript and now, ReactJS/Redux. I have grown so much, and am truly excited to learn more languages, frameworks and libraries upon graduation. While my previous mod projects encompass personal interests of mine (from space exploration, Street Fighter, tele-health platform to trivia game app), I have kept this particular idea until the very end.

I have been a long-advocate for having a meaningful connection through self-reflection. Having a digital journaling app to log events, places, moods, and self-reflections from different points of view would fulfill my personal pursuits of journaling experience. While some journal apps I have seen simply record special memories and events, some focus more on mental health, mindfulness and self-care. I have decided to approach this app build with basic features of recorded events and necessary attributes. Future improvements can possibly include mood tracker with A.I. collecting info on how the user is doing. After dedicating sometime to research on a few journal apps such as Reflectly, Diaro, Daylio, and others, I emulate most of my app build features following Day One and Notion. I love the overall user flow from Day One, and the postulation of all-in-one workspace from Notion. There are a couple of technical challenges I would like to pursue personally such as working with Google Maps API.


Table of Contents

  1. User Story and Model Associations
  2. Rails API Back-End
  3. Routes, Controllers and Serializers
  4. React — Getting Started
  5. ActionReducerNew State
  6. Nested Routes in React Router
  7. Google Maps Platform APIs
  8. Material-UI and Lessons Learned
  9. Build Status and Future Improvement

1. User Story and Model Associations

I brainstormed my app through building wireframes at first. The exercise helped me to collect some understanding of model relationships, necessary attributes, components and overall user interface. I realized that my wireframing exercise eventually became an overarching goal. 🥺

Alt Text

As the user begins their journaling experience, the user will be prompted to fill out a form of an entry event. Each entry event carries event title, date, time, location, vibe, description and photo. The user can personalize each entry by assigning a category. After several entries and categories propagated over some time, when the user selects a category, it should list its respective event entries. For example, under category 'restaurants', the user will see all of their food ventures entries. As the user selects a specific entry, it will prompt a show page specific to the selected event id. The user can reflect all of their journal entries through various points of view: calendar, map and photos. For example, if the user selects a map view, it will show all pinpoints of recorded places. The user can select each pinpoint, and it should also display event details in correspond to the selected entry id.

Alt Text

There are 4 main models User, Category, Event and Image with their associations as follows.

user has_many :events

category has_many :events

event belongs_to :user
event belongs_to :category
event has_one :image

image belongs_to :event


2. Rails API Back-End

I have built Rails API previously, and surprisingly... I have only a small memory recollection. 😅

I initiated the prompt command rails new Storybook_backend --database=postgresql --api --no-test-framework. The --api will remove unnecessary features and middleware with controllers inheriting from ActionController::API, and --no-test-framework will remove any testing framework. PostgreSQL database is helpful when I need to deploy on Heroku. Make sure to include gem rack-cors and bcrypt when bundle install. The next step is to generate Active Record Models for User, Category, Event and Image, and execute rails db:create && rails db:migrate.

ActiveRecord::Schema.define(version: 2021_05_24_194555) do
  create_table "categories", force: :cascade do |t|
    t.string "name"
  end

  create_table "events", force: :cascade do |t|
    t.bigint "category_id", null: false
    t.bigint "user_id", null: false
    t.string "title"
    t.date "date"
    t.time "time"
    t.string "location"
    t.string "latitude"
    t.string "longitude"
    t.string "vibe"
    t.string "description"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["category_id"], name: "index_events_on_category_id"
    t.index ["user_id"], name: "index_events_on_user_id"
  end

  create_table "images", force: :cascade do |t|
    t.bigint "event_id", null: false
    t.string "url"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["event_id"], name: "index_images_on_event_id"
  end

  create_table "users", force: :cascade do |t|
    t.string "username"
    t.string "password_digest"
    t.string "firstname"
    t.string "lastname"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  add_foreign_key "events", "categories"
  add_foreign_key "events", "users"
  add_foreign_key "images", "events"
end
Enter fullscreen mode Exit fullscreen mode

I am content with my schema.rb build, and it is always a good practice to test my models and associations with rails console.


3. Routes, Controllers and Serializers

I provided only the required back-end routes for my front-end's asynchronous fetch() actions.

Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :users, only: [:index, :create]
      post '/login', to: 'auth#create'
      get '/profile', to: 'users#profile'

      resources :events, only: [:index, :create, :show, :update]
      resources :categories, only: [:index, :create]
      resources :images, only: [:index]
    end 
  end 
end
Enter fullscreen mode Exit fullscreen mode

Moving on to my controllers, I spent most of my time applying JWT (JSON Web Tokens) in my ApplicationController, Api::V1::UsersController and Api::V1::AuthController. The ApplicationController defines JWT.encode, JWT.decode and most importantly authorized instance method to barricade access to the other controllers. Only an authorized user can access other controllers. The AuthController create action will provide authentication for users logging in, and the UsersController create action allows a new user signing up.

class Api::V1::EventsController < ApplicationController
  skip_before_action :authorized, only: [:create]
  ...

  def create 
    if params[:category] != ''
      @category = Category.find_or_create_by(name: params[:category])

      @event = Event.create(title: params[:title], vibe: params[:vibe], date: params[:date], time: params[:time], location: params[:location], latitude: params[:latitude], longitude: params[:longitude], description: params[:description], category_id: @category.id, user_id: current_user.id)

      if params[:image] != ''
        uploaded_image = Cloudinary::Uploader.upload(params[:image])

        @image = Image.create(url: uploaded_image['url'], event_id: @event.id)
      end 

      render json: { event: EventSerializer.new(@event), category: CategorySerializer.new(@category) }, status: :created  
    else 
      render json: { error: 'Failed to create Event.' }, status: :not_acceptable
    end 
  end

  ...
end
Enter fullscreen mode Exit fullscreen mode

I had many byebug exercises on create and update actions in Api::V1::EventsController. Not only a new event will be created, but also its respective category and image. I have an event entry form on my front-end to accommodate user inputs. I utilize Cloudinary in order to manipulate images with a URL-based API. The rest of my controllers' actions are mostly index and show. This is where Active Model Serializers helps with displaying any intended attributes to pass information necessary over to front-end's Redux state management. Including model relationships helps to display arrays of event's category and image in one single Object.

class EventSerializer < ActiveModel::Serializer
  attributes :id, :title, :date, :date_strftime, :time, :time_strftime, :location, :latitude, :longitude, :vibe, :description

  belongs_to :category
  belongs_to :user
  has_one :image
end
Enter fullscreen mode Exit fullscreen mode

I believe that's all I have for my back-end! I have attached my GitHub repo below.

GitHub logo fentybit / Storybook_backend

The Minimum Viable Product (MVP) of Storybook app is to allow the user to log events, places, moods and self-reflect from various points of views (calendar, map, photos).

Storybook

Domain Modeling :: Digital Journaling
Welcome to my simplistic version of digital journaling app.

Front-End GitHub Repo

YouTube Demo

DEV Blog

About

I have been a long-advocate for having meaningful connection through self-reflection. While some journal apps I have seen simply record special memories and events, some focus more on mental health, mindfulness and self-care. I have decided to approach this app build with basic features of recorded events and necessary attributes.

The Minimum Viable Product (MVP) of Storybook app is to allow the user to log events, places, moods and self-reflect from various points of views (calendar, map, photos).

Features




Models
User, Event, Category, Image

user has_many :events

event belongs_to :user
event belongs_to :category
event has_many :images

category has_many :events

image belongs_to :event

Controller
ApplicationController
Api::V1::AuthController
Api::V1::CategoriesController
Api::V1::EventsController
Api::V1::ImagesController
Api::V1::UsersController

User Account and Validation
JWT Authentication: Sign Up, Log In and Log Out.

API Database


4. React — Getting Started

I started with npx create-react-app storybook and npm install redux && npm install react-redux as for Redux state management. I learned that NPM packages do not allow upper case characters because unix filesystems are case-sensitive (as I previously tried Storybook, and 🤨 it failed). For whatever reason, I froze for quite sometime, not knowing where to get a start with my React app. I have decided to step back and brainstorm a file structuring diagram, which helped tremendously as I progressed through my code.

Alt Text

I started with my index.js file, and set up my Provider and store. Following best practice, I kept actions, reducers and store.js inside of the Redux folder. The App.js carries the first parent container for my ProfileContainer. This component becomes a portal once a user successfully signs in, and it will navigate the user to 3 container components, NavBar, EventViewList and DisplayContainer. The rests are presentational components and most of them are built as functional components which rely mainly on props. With all that said, I definitely spent a good chunk of time with file-naming, aligning file structures and folder hierarchy. On another note, Redux DevTools is a great tool that I set up in order to view Redux state.


5. ActionReducerNew State

connect() and Provider play a big role as part of React Redux middleware. Provider ensures that my React app can access data from the store, and connect() allows whichever component to specify which state and actions the app needs access to. I implemented combineReducers to consolidate all reducers and set the Redux state management.

export const fetchEvent = (eventId) => {
  return (dispatch) => {
    if (localStorage.getItem('token')) {
      let token = localStorage.getItem('token')

      fetch(`https://your-storybook.herokuapp.com/api/v1/events/${eventId}`, {
        headers: {
          'Authorization': `bearer ${token}`
        }
      })
        .then(resp => resp.json())
        .then(data => dispatch({ type: 'GET_EVENT', payload: data }))
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

One example of my actions would be the fetchEvent(eventId) that asynchronously fetches my back-end route, and dispatch a reducer to return a value.

function eventReducer(state = [], action) {
  switch (action.type) {
    case 'GET_EVENT':
      return action.payload.event

    default:
      return state
  }
}

export default eventReducer;
Enter fullscreen mode Exit fullscreen mode

I should be able to access the object value of event with mapStateToProps in any desirable component to display the current state of event entry. I have a total of 8 reducers from category, error, user, token and others under one rootReducer.


6. Nested Routes in React Router

ReactJS relies upon Client-Side routing to handle routing, fetching and displaying data in the browser. It is after all a Single-Page Application (SPA). While it benefits in speed, it also presents more design challenges. I tried my best to achieve proper RESTful routing.

import React from 'react';
import { Switch, Route } from 'react-router-dom';
...

export default function EventViewList({ categories, events, props, token, url, user }) {
  ...

  return (
    <div align='center'>
      <Switch>
        <Route path={`${url}/calendar/:eventId`} render={() => <InfiniteCalendar Component={withMultipleDates(Calendar)} interpolateSelection={defaultMultipleDateInterpolation} onSelect={date => renderSelectedEventDate(date)} selected={selectedDatesArray} />} />
        <Route path={`${url}/calendar`} render={() => <InfiniteCalendar Component={withMultipleDates(Calendar)} interpolateSelection={defaultMultipleDateInterpolation} onSelect={date => renderSelectedEventDate(date)} selected={selectedDatesArray} />} />
        <Route path={`${url}/map/:eventId`} render={(routerProps) => <MapView {...routerProps} events={events} />} />
        <Route path={`${url}/map`} render={(routerProps) => <MapView {...routerProps} events={events} />} />
        <Route path={`${url}/newentry`} render={() => <InfiniteCalendar selected={today} />} />
        <Route path={`${url}/photos/:eventId`} render={() => <PhotosView />} />
        <Route path={`${url}/photos`} render={() => <PhotosView />} />
        <Route path={`${url}/:categoryId/:eventId/edit`} render={(routerProps) => <CalendarView {...routerProps} categories={categories} events={events} token={token} user={user} />} />
        <Route path={`${url}/:categoryId/:eventId`} render={(routerProps) => <CalendarView {...routerProps} categories={categories} events={events} token={token} user={user} />} />
        <Route path={`${url}/:categoryId`} render={(routerProps) => <CalendarView {...routerProps} categories={categories} events={events} token={token} user={user} />} />
        <Route path={url} render={() => <InfiniteCalendar selected={today} />} />
      </Switch>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

The EventViewList component is my middle presentational component that displays various UI components in correspond to the left navigation bar. I would refer my EventViewList as an intermediary. As the user navigates through, my right presentational component, EventDisplay, will exhibit detailed information. Snippet below represents Route path ${url}/calendar/:eventId where calendar view displays propagated entry dates the user had previously recorded, and eventId will fetch the associated event entry from provided events state from Redux store.

Alt Text


7. Google Maps Platform APIs

I have decided to utilize google-maps-react and react-google-autocomplete NPM packages. Their documentation is pretty solid and provides a straightforward code implementation to my Storybook MVP needs. API can be retrieved from Google Developers Console, and I include Geocoding API, Maps JavaScript API and Places API. Once GoogleApiWrapper from 'google-maps-react' and PlacesAutocomplete from 'react-places-autocomplete' are imported to my Form component, the user can automatically submit an address and/or location from the autocomplete textfield. It should automatically send an API request to retrieve the location's latitude and longitude. Each location and its respective coordinates will be saved in the PostgreSQL database, and that's how I was able to collect an array of various coordinates and propagate them to a map view. I also learned how to save an API_KEY by adding REACT_APP_ to my API key in the .env file.

Alt Text


8. Material-UI and Lessons Learned

I had much fun perusing through Material-UI library. If given more time, I would love to develop Storybook mobile UI. Current project build is focused on browser desktop UI. There are a lot of customization theming that piques my design interest.

Anyhow... I am glad I had the chance to learn ReactJS/Redux, and it definitely speaks on its own popularity and demand. React provides a modular way to separate code and functionality in declarative writing structure, producing highly reusable and independent entities. I now feel comfortable with JSX syntax, container vs. presentational components, Redux state management, Client Routing and finally, implementing Google Maps API. Check out my GitHub repo!

GitHub logo fentybit / Storybook_frontend

The Minimum Viable Product (MVP) of Storybook app is to allow the user to log events, places, moods and self-reflect from various points of views (calendar, map, photos).

Storybook

Domain Modeling :: Digital Journaling
Welcome to my simplistic version of digital journaling app.

Back-End GitHub Repo

YouTube Demo

DEV Blog

About

I have been a long-advocate for having meaningful connection through self-reflection. While some journal apps I have seen simply record special memories and events, some focus more on mental health, mindfulness and self-care. I have decided to approach this app build with basic features of recorded events and necessary attributes.

The Minimum Viable Product (MVP) of Storybook app is to allow the user to log events, places, moods and self-reflect from various points of views (calendar, map, photos).

Features




Models
User, Event, Category, Image

user has_many :events

event belongs_to :user
event belongs_to :category
event has_many :images

category has_many :events

image belongs_to :event

Controller
ApplicationController
Api::V1::AuthController
Api::V1::CategoriesController
Api::V1::EventsController
Api::V1::ImagesController
Api::V1::UsersController

User Account and Validation
JWT Authentication: Sign Up, Log In and Log Out.

API Database

Alt Text


9. Build Status and Future Improvement

Storybook was completed in a 2-week timeframe from implementing Rails back-end, ReactJS front-end, Cloudinary API, Google Maps API and Material-UI library. I have several ideas as I progressed through building my MVP (Minimum Viable Product). Future cycle of product development as follows:

  • Search Bar. Over the time, the user will have many events, and it gets troublesome when the user needs to immediately access a specific event entry. A search bar to quickly type event title and access the journal entry would be useful.
  • Add Friend to model associations. I envision my app to emulate similar concept such as Instagram. Instead of creating a simple journaling app, what about a social journaling platform. Each user can personalize their privacy whether or not they'd like to share with their friends. Alt Text
  • Adding mood tracker. Current attribute vibe to capture my preliminary attempt of gathering user mood data on each event entry. I found a mood tracker API that I would love to integrate in future project build. User can view their journal entries based on Mood under View NavBar.
  • Current event entry only allows one image upload. User should be able to upload multiple images, insert GIF and video upload.
  • Create a toggle track for dark mode. 😎

Post Scriptum:
This is my Module 5 capstone project with Flatiron School. I believe one of the catalyst to becoming a good programmer is to welcome constructive criticism. Feel free to drop a message. 🙂

Keep Calm, and Code On.

External Sources:
ReactJS Documentation
Cloudinary
Google Maps React
React Google Autocomplete
React Infinite Calendar
Material-UI
Unsplash



fentybit | GitHub | Twitter | LinkedIn

Top comments (0)