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
- User Story and Model Associations
- Rails API Back-End
- Routes, Controllers and Serializers
- React — Getting Started
Action
→Reducer
→New State
- Nested Routes in React Router
- Google Maps Platform APIs
- Material-UI and Lessons Learned
- 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. 🥺
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
.
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
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
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
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
I believe that's all I have for my back-end! I have attached my GitHub repo below.
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.
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
eventbelongs_to
:category
eventhas_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.
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. Action
→ Reducer
→ New 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 }))
}
}
}
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;
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>
)
}
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.
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.
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!
fentybit / Storybook_frontend
The Minimum Viable Product (MVP) of Storybook app is to allow 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.
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
eventbelongs_to
:category
eventhas_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
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. - 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 onMood
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
Top comments (0)