Building A Content Management System Using The MEAN Stack - 8 (Front-End Development)

in #utopian-io5 years ago (edited)

Repository

https://github.com/nodejs/node

What Will I Learn

The codebase for this tutorial is based on MEANie an open source content management system by Jason Watmore.

This is the eighth in the series of tutorials on building a content management system using the MEAN technology.

In the last tutorial we created the pages, redirects and account view for the admin area.

In this tutorial we will start work on the blog section of the CMS.

The blog front end has a number of modules for outputting posts, pages, contact form, post archive and some more.

In this tutorial we will learn how to set up the home view in order to output posts on the blog, we will also be learning how to create the various states needed for the other features of the blog section.

N.B;- LINK TO THE EARLIER TUTORIALS IN THIS SERIES CAN BE FOUND AT THE END OF THIS POST

Requirements

Difficulty

  • Intermediate

Tutorial Contents

We created a client folder to hold the code containing all front end functionalities. In the client folder we will add a new directory blog.

All the code in this tutorial and some subsequent ones will be added in this directory.

Custom CSS Styles

Before starting on the actual layout we need to add a css file containing the custom styling for this section.

To add the css file create a new directory named css in the blog directory and add the file style.css

In the style.css file add the following code

.section-header {
    height: 135px;
}

.nav-list {
    height: 52px  !important;
    margin-top: 18px  !important;
}

.nav-item {
    height: 52px  !important;
    margin-bottom: 10px;
    font-family: oswald;
}

.blog-header {
    height: 300px;
    background-image: url('../_content/images/blog-header.jpg');
    padding-top: 70px;
}

.section-body {
    margin-top: 30px;
    padding: 50px;
}

.side-widget {
    width: 100%;
}

.side-widget-content {
    width: 100%;
    padding-top: 20px;
    padding-bottom: 20px;
}

.side-widget-area {
    margin-bottom: 40px;
}

.post-area {
    font-family: oswald !important;
    font-size: 105%;
}

.post-publishdate {
    margin-left: 5px;
    margin-right: 5px;
    border-radius: 10px;
}

.post-tag-slug {
    width: 300px  !important;
    margin-left: 5px;
    margin-right: 5px;
    border-radius: 10px;
}

.post-tag {
    margin-left: 5px;
    margin-right: 5px;
    word-spacing: 15px;
}

.post-metadata {
    margin-left: 220px;
    margin-top: 20px;
    margin-bottom: 20px;
}

.post-title-area {
    margin-bottom: 20px;
}

.post-summary-area {
    margin-top: 20px;
}

.post-details-title {
    font-family: oswald;
}

.share-post-area {
    font-size: 20px;
}  

.archive-details {
    font-family: oswald;
}

.input-field {
    margin-top: 50px  !important;
    margin-bottom: 50px  !important;
}

.text-input {
    height: 200px;
}

The above CSS comprises of all the custom styling needed for this section of the application.

index.html File

We need to create the main file that will render all other templates in the blog front end.

The main file is named index.html and is created as a direct child in the blog directory.

The index.html file will contain the main blog code. Paste the following in the code

<!DOCTYPE html>

<html>
    <head>
        <meta  charset="utf-8" />
        <title>MEAN-CMS - The MEAN Stack Blog</title>
        <link  rel="shortcut icon"  href="/admin/_content/images/logo.png"  type="image/png" />
        <link  rel="icon"  href="/admin/_content/images/logo.png"  type="image/png" />
        <link  rel="stylesheet"  href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
        <link  href="https://unpkg.com/[email protected]/dist/css/ionicons.min.css"  rel="stylesheet">
        <link  href="http://fonts.googleapis.com/css?family=Oswald:400,300,700"  rel="stylesheet">

        <style>
            <%- include('css/style.css') %>
        </style>
    </head>
    <body>
        <section  class="section-header black">
            <nav  class="container black">
                <a  href="#"  class="brand-logo"><span  class="yellow-text">MEAN</span>  <span  class="white-text">CMS</span></a>
                <br>
                <ul  class="center nav-list yellow">
                    <li><a  href="/"  class="black-text nav-item">Home</a></li>
                    <li><a  href="/archive"  class="black-text nav-item">Archive</a></li>
                    <li><a  href="/contact"  class="black-text nav-item">Contact</a></li>
                </ul>
            </nav>
        </section>

        <section  class="blog-header center center-align">
            <div  class="container">
                <h1  style="font-family: oswald"><span  class="yellow-text">Lorem </span><span  class="grey-text">Ipsum </span><span  class="black-text">Blog</span></h1>
            </div>
        </section>

        <section  class="section-body grey lighten-3"  ui-view>
            <div  class="row">
                <div  class="col l9 m9">
                    <% if(locals.templateUrl) { %>
                            <div  ng-non-bindable>
                                <%- include(locals.templateUrl) %>
                            </div>
                    <% } %>
                </div>
                <div  class="col l3 m3">
                    <div  class="col l12 m12 side-widget-area">
                        <ul  class="tabs">
                            <li  class="tab center side-widget"><a  href="#months-list"  class="active center-align yellow white-text">Months</a></li>
                        </ul>
                        <div  id="months-list"  class="center center-align side-widget-content white yellow-text">
                            <%- include('_partials/month-list.html') %>
                        </div>
                    </div>
                    <div  class="col l12 m12 side-widget-area">
                        <ul  class="tabs">
                            <li  class="tab center side-widget"><a  href="#months-list"  class="active center-align yellow white-text">Tags</a></li>
                        </ul>
                        <div  id="months-list"  class="center center-align side-widget-content white yellow-text">
                            <%- include('_partials/tag-list.html') %>
                        </div>
                    </div>
                    <div  class="col l12 m12 side-widget-area">
                        <ul  class="tabs">
                            <li  class="tab center side-widget"><a  href="#months-list"  class="active center-align yellow white-text">Helpful Links</a></li>
                        </ul>
                        <div  id="months-list"  class="center center-align side-widget-content white">
                            <ul>
                                <li><a  href="../admin"  target="_blank"  class="yellow-text">Admin Login</a></li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </section>
        <footer>
            <div  class="center center-align copyright black">
                <b><span  class="white-text">Powered By </span><a  href="#"  target="_blank"  class="yellow-text">Olatunde Oladunni - Web Application Developer</a></b>
            </div>
        </footer>

    (html comment removed:  syntax highlighter )
    <script  src="/_content/syntaxhighlighter/js/shCore.js"></script>
    <script  src="/_content/syntaxhighlighter/js/shBrushCSharp.js"></script>
    <script  src="/_content/syntaxhighlighter/js/shBrushCss.js"></script>
    <script  src="/_content/syntaxhighlighter/js/shBrushJScript.js"></script>
    <script  src="/_content/syntaxhighlighter/js/shBrushPlain.js"></script>
    <script  src="/_content/syntaxhighlighter/js/shBrushSql.js"></script>
    <script  src="/_content/syntaxhighlighter/js/shBrushVb.js"></script>
    <script  src="/_content/syntaxhighlighter/js/shBrushXml.js"></script>
    <script>
        SyntaxHighlighter.defaults['gutter'] = true;
        SyntaxHighlighter.defaults['smart-tabs'] = true;
        SyntaxHighlighter.defaults['auto-links'] = true;
        SyntaxHighlighter.defaults['collapse'] = false;
        SyntaxHighlighter.defaults['light'] = false;
        SyntaxHighlighter.defaults['tab-size'] = 4;
        SyntaxHighlighter.defaults['toolbar'] = true;
        SyntaxHighlighter.defaults['wrap-lines'] = true;
    </script>

    (html comment removed:  Minified JQuery )
    <script  src="https://code.jquery.com/jquery-3.3.1.min.js"  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="  crossorigin="anonymous"></script>
    
    (html comment removed:  Compiled and minified JavaScript )
    <script  src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>

    <script>
        $(document).ready(function(){
            $('.tabs').tabs();
        });
    </script>
    (html comment removed:  angular )
    <script  src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
    <script  src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.15/angular-ui-router.min.js"></script>

    (html comment removed:  meanie )
    <script  src="_dist/app.min.js"></script>
    
</body>

</html>

In the index.html file we use MaterializeCSS and ionicons for layout and icons respectively.

The font used was pulled from the Google fonts API and is known as Oswald. It was pulled in with the line

<link  href="http://fonts.googleapis.com/css?family=Oswald:400,300,700"  rel="stylesheet">

We also included the custom css style added earlier by adding the following code in the <head></head> tag

<style>
    <%- include('css/style.css') %>
</style>

Instead of using the common <link /> element to import the css file we used the ejs directive <%- include('css/style.css') %>

ejs is a templating framework that allows javascript code be embedded with html code and we'll use it through out this section to infuse js codes with the layout.

In the body of our index.html file we first create the navigation bar enclosed in the <section class="section-header black"></section> element.

The class attribute value black gives the entire navigation area a black background color.

The menu items in the in the navigation list are added as an unordered list.

There are three menu items in total and each is enclosed in a

<li><a  href=""  class="black-text nav-item"></a></li>

The first list item links to the home page which is represented by href="/". / is the default route set for the homepage in the app state.

The second list item links to the archive page. The attribute value /archive is the route url set for the archive page in the app state.

The third list item links to the contact page. The attribute value /contact is the route url set for the archive page in the app state.

The main view for our app where other templates for this section will be rendered is enclosed in the element

<section  class="section-body grey lighten-3"  ui-view></section>

The angular ui-router directive ui-view indicates that this area will render all the views included in this section of the application.

angular ui-router is imported towards the bottom of the index.html file using the following line of code

<script  src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.15/angular-ui-router.min.js"></script>

In the main view area we create a new row with two columns.

The first column will output all the templates dynamically through the following code

<% if(locals.templateUrl) { %>
    <div  ng-non-bindable>
        <%- include(locals.templateUrl) %>
    </div>
<% } %>

The code above checks firstly checks if the view being requested exists in the app state.

If the view exists the template to that view is rendered inside the <div ng-non-bindable></div> element.

The second column in the main view serves as a sidebar and it contains the following

  • A list of posts filtered by the months they were created.
  • A list of tags used in the posts on the blog
  • Some utility links

All the constituents of the sidebar is contained in the element

<div  class="col l12 m12 side-widget-area"></div>

All the constituents are separated and each is rendered through a materializecss tab element.

In the first tab we have the list of post filtered by their months of creation. The following code will render the filtered post list

<%- include('_partials/month-list.html') %>

In the second tab we have a list of tags used for the posts on the blog and it is rendered through the following code.

<%- include('_partials/tag-list.html') %>

In the third tab we have the utility links section which contains only one link at the moment. The link leads to the admin login page which is indicated through href="../admin" in the <a></a> element.

/admin is the route url set for the admin area of the application in the app state.

At this point a screenshot of the homepage would look like the one below.


The index.html file is still bare for a reason. The reason is because other templates needed for the file to display correctly hasn't been included in our application.

In order to fill the index.html file we need to add the view templates for the blog features.

Before adding the view templates we will create the application state for the ui-router in order to register the routes for the different views.

Application State(app.js)

Create a new directory _dist and add the file app.js in the directory. The code in this file will manage the different states for all the views in this application.

Paste the following code in the file.

(function () {
    'use strict';

    angular
        .module('app', ['ui.router', 'ngMessages'])
        .config(config)
        .run(run);

    function  config($locationProvider, $stateProvider, $urlRouterProvider, $httpProvider) {
        // set variable to control behaviour on initial app load
        window.initialLoad  =  true;
        
        $locationProvider.html5Mode(true);

        // default route
        $urlRouterProvider.otherwise("/");
        
        $stateProvider
            .state('home', {
                url:  '/?:page',
                templateUrl:  function (stateParams) {
                    return  window.initialLoad  ?  null  :
                        '/?xhr=1'  + (stateParams.page  ?  '&page='  +  stateParams.page  :  '');
                }
            })
            .state('post-details', {
                url:  '/post/:year/:month/:day/:slug',
                templateUrl:  function (stateParams) {
                    return  window.initialLoad  ?  null  :
                        '/post/'  +  stateParams.year  +  '/'  +  stateParams.month  +  '/'  +  stateParams.day  +  '/'  +  stateParams.slug  +  '?xhr=1';
                }
            })
            .state('posts-for-tag', {
                url:  '/posts/tag/:tag',
                templateUrl:  function (stateParams) {
                    return  window.initialLoad  ?  null  :
                        '/posts/tag/'  +  stateParams.tag  +  '?xhr=1';
                }
            })
            .state('posts-for-month', {
                url:  '/posts/:year/:month',
                templateUrl:  function (stateParams) {
                    return  window.initialLoad  ?  null  :
                        '/posts/'  +  stateParams.year  +  '/'  +  stateParams.month  +  '?xhr=1';
                }
            })
            .state('page-details', {
                url:  '/page/:slug',
                templateUrl:  function (stateParams) {
                    return  window.initialLoad  ?  null  :
'/page/'  +  stateParams.slug  +  '?xhr=1';
                }
            })
            .state('archive', {
                url:  '/archive',
                templateUrl:  '/archive?xhr=1'
            })
            .state('contact', {
                url:  '/contact',
                templateUrl:  '/contact?xhr=1',
                controller:  'Contact.IndexController',
                controllerAs:  'vm'
            })
            .state('contact-thanks', {
                url:  '/contact-thanks',
                templateUrl:  '/contact-thanks?xhr=1'
            });
  
        // mark all requests from angular as ajax requests
        $httpProvider.defaults.headers.common['X-Requested-With'] =  'XMLHttpRequest';
    }

    function  run($rootScope, $timeout, $location, $window) {
        // initialise google analytics  
        $window.ga  &&  $window.ga('create', 'UA-30211492-1', 'auto');

        $rootScope.$on('$stateChangeSuccess', function () {
            // hide mobile nav
            $rootScope.showNav  =  false;

            // track pageview
            var  urlWithoutHash  =  $location.url().split('#')[0];
            $window.ga  &&  $window.ga('send', 'pageview', urlWithoutHash);

            // jump to top of page if not initial page load
            if (!window.initialLoad) {
                document.body.scrollTop  =  document.documentElement.scrollTop  =  0;
            }
 
            $timeout(function () {
                // run syntax highlighter plugin
                SyntaxHighlighter.highlight();
            });

            window.initialLoad  =  false;
        });
    }
    
})();

We register the blog application with angular.module() and include the directives ui.router and ngMessages

ui.router specifies that the application is using angular ui-router module for routing operations in the application.

The ngMessages directive will show or hide messages based on the state of the object each message listens to in the application.

We have a function config() which is used to configure the states for the different routes and views.

In the config() function we first of all set the initialLoad property of the window object to true. This will help control the behavior of the application whenever the application loads for the first time.

We also set the html5mode method of the $locationProvider object to true which will grant the application access to the HTML history API.

We set the default route for the application using $urlRouterProvider.otherwise("/"); which is the page that displays by default when no views are requested for by the application.

Using the $stateProvider object we add the states for the different views of the application.

Each state and its properties are inserted in the state() method.

We first of all create the state for the homepage view. The name of the state is home.

The url for the home view is set to '/?:page'.

We have a templateUrl for the home view which is generated by a function.

The function uses a ternary operator to check the value of window.initialLoad which we set earlier. If it returns true the function returns a null object but if it returns false the function returns an extended url which includes a string and parameters relating to the page being viewed.

The next state is for the view that displays each post detail like the post body.

The url for this state is set to /post/:year/:month/:day/:slug which comprises of the year, month and day that particular post was made in combination with the post slug.

The next state /posts/tag/:tag uses post tags to filter through the posts and displays post created under a specifically requested tag.

The requested tag will be specified in the :tag section of the url.

After the posts-for-tag we have the posts-for-month state with its url set to /posts/:year/:month.

posts-for-month will only display posts created in a specific year and a specific month, both parameters are specified with :year and :month respectively.

page-details will output the main content of any requested page.

The url for page-details is /page/:slug with :slug being the slug associated with that specific page in the database.

The archive state will display the archive view for all the posts on the blog on one page.

The url for archive is set to /archive.

contact will output the view for the view for the contact from. Its route url is set to /contact.

The contact state also includes a controller module named Contact.IndexController.

We are going to set all common http request headers in the application to ajax requests and that will be achieved through the following line

$httpProvider.defaults.headers.common['X-Requested-With'] =  'XMLHttpRequest';

Partials

The partials section will contain some files needed by the main view to output certain states of the app.

The partials files will be used to set the layout behavior of some area of the main view including the

  • Posts filtered by their month of creation
  • Full list of posts
  • Posts filtered by their common tags

For the partials create a directory _partials.

Month List(month-list.html)

In the _partials directory create a new file month-list.html.

This file will contain the code that will control the layout of posts filtered by their creation year and month.

Paste the following code in the file

<ul  class="month-list">
    <% years.forEach(function(year) { %>
        <li>
            <%= year.value %>
            <ul>
                <% year.months.forEach(function(month) { %>
                    <li>
                        <a  href="/posts/<%= year.value%>/<%= month.value %>"><%= month.name %></a> (<%= month.postCount %>)
                    </li>
                <% }) %>
            </ul>
        </li>
    <% }) %>
</ul>

All the items from this file is enclosed in an unordered list.

Each list item in the list is displayed by firstly outputting the year from which the posts will be extracted, this is achieved with the line <%= year.value %>.

This is followed by another unordered list and for each list item in this list we have a link element with the href attribute "/posts/<%= year.value%>/<%= month.value %>".

The href attribute above links to a list of posts associated with each month dynamically with each month url values determined by year.value and month.value depending on the year the link was clicked under.

Post List (post-list.html)

This file contains the layout code for displaying all blog posts on the home view of the application.

In the _partials directory create a new file post-list.html.

Paste the following code in the file

<div  class="row">
    <% posts.forEach(function(post) { %>
        <div  class="post-area">
            <div  class="row center">
                <div  class="col l12 m12 center center-align post-title-area">
                    <a  href="<%= post.url %>">
                        <h5  class="center-align"><%= post.title %></h5>
                    </a>
                </div>
                <div  class="post-metadata">
                    <div  class="col l3 m3 post-publishdate yellow black-text center-align">
                        <%= post.publishDateFormatted %>
                    </div>
                    <div  class="col l8 m8 yellow black-text center-align post-tag-slug">
                        <span  class="left">Tags:</span>
                        <% var postTags = post.tags %>
                        <% postTags.forEach(function(tag) { %>
                            <span  class="post-tag"><%= tag %></span>
                        <% }) %>
                    </div>
                </div>
                <div  class="col l12 m12 post-summary-area">
                    <%= post.summary %>
                </div>
            </div>
        </div>
    <% }) %>
</div>

<% if(locals.pager && locals.pager.endPage > 1) { %>
    <div  class="post-pager">
        <ul  class="pagination">
            <% if(locals.pager.currentPage > 1) { %>
                <li  class="first"><a  href="?page=1">First</a></li>
                <li  class="previous"><a  href="?page=<%= locals.pager.currentPage - 1 %>">Previous</a></li>
            <% } else { %>
                <li  class="first disabled"><a>First</a></li>
                <li  class="previous disabled"><a>Previous</a></li>
            <% } %>
            <% for(var page = locals.pager.startPage; page <= locals.pager.endPage; page++) { %>
                <li  class="<%= page === locals.pager.currentPage ? 'active' : '' %>">
                    <a  href="?page=<%= page %>"><%= page %></a>
                </li>
            <% } %>

            <% if(locals.pager.currentPage < locals.pager.totalPages) { %>
                <li  class="next"><a  href="?page=<%= locals.pager.currentPage + 1 %>">Next</a></li>
                <li  class="last"><a  href="?page=<%= locals.pager.totalPages %>">Last</a></li>
            <% } else { %>
                <li  class="next disabled"><a>Next</a></li>
                <li  class="last disabled"><a>Last</a></li>
            <% } %>
        </ul>
    </div>
<% } %>

All the contents of these post list view is enclosed in <div></div> tag which will create a new row with different columns.

For each post to be displayed in the row we add another row, the first column to be displayed in this new row will contain the post title which will link to the post details.

The code for displaying the post title is

<a  href="<%= post.url %>">
    <h5  class="center-align"><%= post.title %></h5>
</a>

<%= post.url %> will provide the link to the post details while <%= post.title %> will supply the text for the post title.

In another column we output the date the post was published through the <%= post.publishDateFormatted %> directive.

The next column displays the tags associated with the post gotten through the following code

<% var postTags = post.tags %>
    <% postTags.forEach(function(tag) { %>
    <span  class="post-tag"><%= tag %></span>
<% }) %>

The code above creates adds every individual post tag in a span tag in rder to define their behavior with css.

The last column on the row outputs the post summary through the <%= post.summary %>.

We also have a section for the pagination which sets the layout to make the post appear in a sequence of pages with each page containing the same number of posts as the last one.

The code for the pagination is wrapped in an if statement which checks if the value for the variables locals.pager and locals.pager.endPage is greater than 1.

locals.pagerrepresents the current page being displayed while locals.pager.endPage represents the page where the post list ends.

If both evaluations are true a list will be displayed and in the list we have another if statement which checks if the value of locals.pager.currentPage is greater.

locals.pager.currentPage also represents the current page being displayed and if the value is greater than one the list below will be displayed

<li  class="first"><a  href="?page=1">First</a></li>
<li  class="previous"><a  href="?page=<%= locals.pager.currentPage - 1 %>">Previous</a></li>

href="?page=1" links to the first page on the list of pages containing the post list while href="?page=<%= locals.pager.currentPage - 1 %> links to the page directly before the current page.

In any other case the list will be displayed only this time it will be unclickable due to it containing the disabled class attribute.

Tag List (tag-list.html)

In order to set the layout for posts filtered by their common tags create a new file in the _partials directory and add the file tag-list.html

<ol  class="tag-list left-align">
    <% tags.forEach(function(tag) { %>
        <li>
            <a  href="/posts/tag/<%= tag.slug %>"><%= tag.text %></a>
        </li>
    <% }) %>
</ol>

In this file we have an ordered list and for each item of the ordered list and for each list item we output a link that leads to a view that displays the post associated with the listed tags.

In our next tutorial we will work on completing the home view in order to finally display all the contents of the _partials directory on the homepage.

Curriculum

  1. Building A Content Management System Using The MEAN Stack - 1 (Create Server, Config File and Helper Modules)

  2. Building A Content Management System Using The MEAN Stack - 2(Create Controller Modules 1)

  3. Building A Content Management System Using The MEAN Stack - 3 (Create Controller Modules 2)

  4. Building A Content Management System Using The MEAN Stack - 4 (Create Services Modules)

  5. Building A Content Management System Using The MEAN Stack - 5 (Front-End Development)

  6. Building A Content Management System Using The MEAN Stack - 6 (Front-End Development)

  7. Building A Content Management System Using The MEAN Stack - 7 (Front-End Development

Proof Of Work Done

https://github.com/olatundeee/mean-cms

Sort:  

Thank you for your contribution @gotgame.
We have reviewed your tutorial and suggested the following points:

  • The CSS code is not relevant to put in your tutorial. The reader when going to your github can visualize the CSS that you constructed.

  • Your tutorial is a bit confusing. Just explain the practical part and it is quite important in a tutorial to explain the theory about what will develop.

We are waiting for your next tutorial with our suggestions. Thank you for your work.

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Thank you for your review, @portugalcoin!

So far this week you've reviewed 6 contributions. Keep up the good work!

Hi @gotgame!

Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your post is eligible for our upvote, thanks to our collaboration with @utopian-io!
Feel free to join our @steem-ua Discord server

Hello! Your post has been resteemed and upvoted by @ilovecoding because we love coding! Keep up good work! Consider upvoting this comment to support the @ilovecoding and increase your future rewards! ^_^ Steem On!

Reply !stop to disable the comment. Thanks!

Hey, @gotgame!

Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

As a follower of @followforupvotes this post has been randomly selected and upvoted! Enjoy your upvote and have a great day!

Coin Marketplace

STEEM 0.36
TRX 0.12
JST 0.039
BTC 70181.59
ETH 3549.53
USDT 1.00
SBD 4.74