Java Guide

In this project, you will required to have Java JDK, Maven and Lombok.

Click here to go to the repository. You can access all the app code and reuse it for your application.

Configuration for PKIX(SSLHandshake)

Go to Brick and click on the lock beside URL bar. Click on the Certificate -> Details then click on Copy to File.. and finally export the certificate as DER file and store it to your Java JDK Folder (e.g. C:\Program Files\Java\jdk-15.0.1\lib\security) with filename bricksandbox. Next, open your CMD and run as administrator, then run this command,

NOTE
Your file path may differ from the command below, you will need to change it to your own file path.

keytool - import -alias bricksandbox -keystore "C:\Program Files\Java\jdk-15.0.1\lib\security\cacerts" -file "C:\Program Files\Java\jdk-15.0.1\lib\security\bricksandbox.cer"

when you are prompt for password, by default it will be: changeit
when you are prompt for yes/no, type: yes

Steps to executing this program

In your terminal, you will have to go to the root folder of this project and type in the following:

mvn clean install //This will install, build, and execute the necessary packages or dependencies

once you finish the downloads and build all the necessary packages, in the terminal type the following:

java -jar target/money-save.war //this will execute the springframework builds.

Then in the terminal you will see that the moneysaveApplication is running, you can open your browser and go to:

http://localhost:8085/

Then if everything is successful, you will land on this page.

Required dependencies in pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
  <!--All repositories will be access from this-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
  <!--Configuration of project identifiers -->
    <groupId>com.brick.app</groupId>
    <artifactId>money-save</artifactId>
    <version>1.0.0</version>
    <name>money-save</name>
    <description>money-save app for mock client</description>
    
  <!--minimal/used java version-->
    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
    <!-- dependency for spring boot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    <!-- dependency for spring boot tomcat -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <!--for deployment uncomment this-->
            <!--<scope>provided</scope>-->
            <!--for deployment comment this-->
            <scope>compile</scope>
        </dependency>
    <!-- dependency for spring boot data jpa -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
    <!-- dependency for lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
            <!--<scope>provided</scope>-->
            <!--<optional>true</optional>-->
        </dependency>
    <!-- dependency for thymeleaf -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
        </dependency>
    <!-- dependency for codec -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.6</version>
        </dependency>
    <!-- dependency for http client -->
        <dependency>
            <groupId>commons-httpclient</groupId>
            <artifactId>commons-httpclient</artifactId>
            <version>3.1</version>
        </dependency>
    <!-- dependency for http components -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.11</version>
        </dependency>
        <!--H2 Database-->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.193</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-text</artifactId>
            <version>1.9</version>
        </dependency>


        <!-- dependency spring boot test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <!-- build to target for compilations -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <finalName>money-save</finalName>
    </build>
    <packaging>war</packaging>

</project>

Main java function

package com.onebrick.app.restclient;

//package to use SpringApplication
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

//Application class for main function
@SpringBootApplication
public class MoneySaveApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
    //spring boot to execute MoneySaveApplication
        SpringApplication.run(MoneySaveApplication.class, args);
    }
    //access the builds in .war package
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(MoneySaveApplication.class);
    }

}

Repository folder

AuthRepository.java

This class will perform authentication with ClientID and ClientSecret from users to retrieve (JWT) public-access-token

package com.onebrick.app.restclient.repositories;

//package to read json type
import com.fasterxml.jackson.databind.JsonNode;
//package for mapping object node
import com.fasterxml.jackson.databind.ObjectMapper;
//package for object node
import com.fasterxml.jackson.databind.node.ObjectNode;
//package for encoding and decoding with base64
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Service;
//package to access HTTP client
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

//function to get public access token from clientID and clientSecret
//classes at service level
@Service
public class AuthRepository {

    private static final String BASE_URL = "http://sandbox.onebrick.io/";
    //private static final String BASE_URL = "http://localhost:8080/";
    //change the value of XXXXX to your own ClientID
    private static final String CLIENT_ID = "XXXXX";
    //change the value of XXXXX to your own ClientSecret
    private static final String CLIENT_SECRET = "XXXXX";
    private static final ObjectMapper MAPPER = new ObjectMapper();
        
    //function to get the publicToken()
    public JsonNode getPublicToken() throws Exception {
        //JsonNode to store the json data retrieved
        JsonNode result = MAPPER.createObjectNode();
        try {
            //client to acces sandbox
            var client = HttpClientBuilder.create().build();
            //request to perform GET method
            var request = new HttpGet(BASE_URL + "/v1/auth/token");
            //authentication header
            var authHeader = CLIENT_ID + ":" + CLIENT_SECRET;
            //encoded authentication
            var encHeader = Base64.encodeBase64(authHeader.getBytes(StandardCharsets.ISO_8859_1));
            authHeader ="Basic " + new String(encHeader);
            request.setHeader("Authorization", authHeader);
            //execute Get Request
            var response = client.execute(request);
            var node = MAPPER.readValue(response.getEntity().getContent(), ObjectNode.class);
            //if condition to store the data
            if(node.has("data")) {
                result = node.get("data");
            }
        } catch (Exception e) {
            throw new Exception(e);
        }
        return result;//return json data with public access token from Brick API
    }
}

Example of the return value from AuthRepository class will be like this JSON data.

{
    "status": 200,
    "message": "OK",
    "data": {
        "access_token": "public-sandbox-b70bcf83-87a1-4f2c-8f2a-16d48021413a"
    }
}

UserAccessMappingRepository.java

This class will store the mapped value of user access data. It will retrieve from UserAccessMapping class.

package com.onebrick.app.restclient.repositories;

//import entities class from local path 
import com.onebrick.app.restclient.entities.UserAccessMapping;
//package to use data JPA repository
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

//interface using data JPA to access data from UserAccessMapping class in entities folder
//persistence layer
@Repository
public interface UserAccessMappingRepository extends JpaRepository<UserAccessMapping, Integer> {
    //save the userAccessMapping in JPA repository
    UserAccessMapping findBySessionId(String sessionId);
}

Entities folder

UserAccessMapping.java

This class will set the value for user access variables, and will store value into H2 Database. This class will be called in repositories/UserAccessMappingRepository.java and controller/HomeController.java

package com.onebrick.app.restclient.entities;
//package from lombok get and set function
import lombok.Getter;
import lombok.Setter;
//package for persistence to be stored in jpa
import javax.persistence.*;

@Entity
//set table name
@Table(name = "user_access_mapping")
//get and set for variables
@Getter
@Setter
public class UserAccessMapping {
    //id column in table h2
    @Id
    @GeneratedValue
    private int id;
        
    //set column user_access_token in table h2
    @Column(name = "user_access_token")
    //variable to get and set user_access_token
    private String userAccessToken;
        
    //set column session_id in table h2
    @Column(name = "session_id")
    //variable to get and set sessionId
    private String sessionId;
        
    //set column is_jenius in table h2
    @Column(name = "is_jenius")
    //variable to get and set isJenius
    private boolean isJenius;
        
    //lob annotation to keep in binary format
    @Lob
    //set column transactions in table h2
    @Column(name = "transactions", length = 100000)
    //variable to get and set transaction
    private String transactions;

    //lob annotation to keep in binary format
    @Lob
    //set column accounts in table h2
    @Column(name = "accounts", length = 100000)
    //variable to get and set accounts
    private String accounts;
}

Model Folder

Transaction.java

This class is to store transaction details from a user's account. This class will be called in controller/HomeController.java.

package com.onebrick.app.restclient.models;
//package to read json data to get each specific value
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
//package from lombok get and set function
import lombok.Getter;
import lombok.Setter;
//package to get time
import java.sql.Timestamp;

//set get and set function
@Getter
@Setter
//condition to ignore JSON properties that are not identified
@JsonIgnoreProperties(ignoreUnknown = true)
public class Transaction {
    //unique id for each rows of data
    @JsonProperty("id")
    private long id;
    
    //store the value for account_id
    @JsonProperty("account_id")
    private String accountId;

    //store the value for category_id
    @JsonProperty("category_id")
    private long categoryId;
    
    //store the value for subcategory_id
    @JsonProperty("subcategory_id")
    private long subcategoryId;

    //store the value for merchant_id
    @JsonProperty("merchant_id")
    private long merchantId;

    //store the value for location_country_id
    @JsonProperty("location_country_id")
    private long countryId;
  
        //store the value for location_city_id
    @JsonProperty("location_city_id")
    private long cityId;

    //store the value for outlet_outlet_id
    @JsonProperty("outlet_outlet_id")
    private long outletId;

    //store the value for amount
    @JsonProperty("amount")
    private Double amount;

    //store the value for date and set with property yyyy-mm-dd
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    @JsonProperty("date")
    private Timestamp date;

    //store the value for description
    @JsonProperty("description")
    private String description;

    //store the value for status
    @JsonProperty("status")
    private String status;

    //store the value for direction
    @JsonProperty("direction")
    private String direction;
}

This is the sample of Transaction in JSON format

{
    "status": 200,
    "message": "OK",
    "data": [
        {
            "id": 9744,
            "account_id": "jwYhjuy4tQZx8sZj3oBrg9fSqvzsR41F==",
            "category_id": 24,
            "subcategory_id": 141,
            "merchant_id": 8,
            "location_country_id": 0,
            "location_city_id": 0,
            "outlet_outlet_id": 0,
            "amount": 600000.0,
            "date": "2020-06-29",
            "description": "ATM-MP SA CWD XMD S1AW1MJK /7774759936/ATM-RCOASIAAFRK 4616993200225278 RCOASIAAFRK",
            "status": "CONFIRMED",
            "direction": "out"
        },
        {
            "id": 9743,
            "account_id": "jwYhjuy4tQZx8sZj3oBrg9fSqvzsR41F==",
            "category_id": 22,
            "subcategory_id": 129,
            "merchant_id": 0,
            "location_country_id": 0,
            "location_city_id": 0,
            "outlet_outlet_id": 0,
            "amount": 1000.0,
            "date": "2020-06-29",
            "description": "CA/SA UBP DR/CR-ATM UBP60116073701FFFFFF085755130021 50000 S1AW1MJK /7774759939/ATM-RCOASIAAFRK P085755130021",
            "status": "CONFIRMED",
            "direction": "out"
        }
     ]
}

Controller Folder

HomeController.java

Classes to store Balance, User Access, Account Details from user's Financial Institution account.

//Balance class to store account's balance details
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
class Balance {
    private String currency;
    private double availableBalance;
    private double currentBalance;
    private Timestamp timestamp;
}

//userAccess class to store the accessToken and its transactions
@Data
@NoArgsConstructor
class UserAccess {
    private String accessToken;
    private String transactions;
    //private String accounts;
}

//UserAccountDetail class to store the account's details
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
class UserAccountDetail {
    private String accountId;
    private String accountHolder;
    private String accountNumber;
    private BalanceDetail balances;
        //set default value if there is no account details
    UserAccountDetail() {
        accountHolder = "John Doe";
        accountNumber = "987-0675-789";
        accountId = "qwerty//++--==";
        balances = new BalanceDetail(100987.0, 100987.0);
    }
}

//store balance detail value only the current and available balance
@Data
@NoArgsConstructor
@AllArgsConstructor
class BalanceDetail {
    private double available;
    private double current;
}

getMockTransaction(string) function is to get the transaction and store it into mapper.

//function to store the transaction list
private List<Transaction> getMockTransaction(String userToken) throws Exception {
            //create new object mapper
        var mapper = new ObjectMapper();
            //startdate to find the transaction starting date
        var startDate = setStartDate(new Date());
            //enddate for the end of date range of the transaction
        var endDate = parseDate(new Date());
            //link to access the transaction using Brick API
        var url = BASE_URL + "v1/transaction/list?from=" + startDate +"&to=" + endDate;
        var client = HttpClientBuilder.create().build();
        var request = new HttpGet(url);
            //request header for the http
        request.setHeader("Authorization", "Bearer " + userToken);
        var response = client.execute(request);
        var node = mapper.readValue(response.getEntity().getContent(), ObjectNode.class);
        return mapper.convertValue(node.get("data"), new TypeReference<ArrayList<Transaction>>(){}); //return the value of transaction into mapper
    }

getMockBalance(string) function is to get the balance and store it into mapper.

private Balance getMockBalance(String userToken) throws Exception {
            //create new object mapper
        var mapper = new ObjectMapper();
            //link to the account balance
        var url = BASE_URL + "v1/account/balance";
        var client = HttpClientBuilder.create().build();
        var request = new HttpGet(url);
            //request header for the http
        request.setHeader("Authorization", "Bearer " + userToken);
        var response = client.execute(request);
        var node = mapper.readValue(response.getEntity().getContent(), ObjectNode.class);
        return mapper.convertValue(node.get("data"), Balance.class);//return the value of balance into mapper
    }

getMockUserDetail(string) function is to get the user account details and store it into mapper.

private UserAccountDetail getMockUserDetail(String userToken) throws Exception {
            //create new object mapper
        var mapper = new ObjectMapper();
            //link to the account list
        var url = BASE_URL + "v1/account/list";
        var client = HttpClientBuilder.create().build();
        var request = new HttpGet(url);
            //request header for the http
        request.setHeader("Authorization", "Bearer " + userToken);
        var response = client.execute(request);
        var node = mapper.readValue(response.getEntity().getContent(), ObjectNode.class);
        UserAccountDetail[] accounts = mapper.convertValue(node.get("data"), UserAccountDetail[].class);
            //condition when there is no account in the list
        if(accounts.length == 0) {
            return new UserAccountDetail();
        //condition when account list exist
        } else {
            return accounts[0];
        }
    }

parseDate(date) function to set the date for transaction date range.

private String parseDate(Date date) {
        var df = new SimpleDateFormat("yyyy-MM-dd");
        return df.format(date);
    }

    private String setStartDate(Date date) {
        var cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(Calendar.MONTH, -1);
        return parseDate(cal.getTime());
    }

Controller class that calls repository and entities classes, all POST and GET request to Brick API happens in HomeController class.

@Autowired
    //create AuthRepository object
    private AuthRepository authRepository;
    @Autowired
    //create UserAccessMappingRepository object
    private UserAccessMappingRepository userAccessMappingRepository;
    //create ObjectMapper object
        private ObjectMapper mapper;
        //base url of Brick API
    private static final String BASE_URL = "http://sandbox.onebrick.io/";
    //private static final String BASE_URL = "http://localhost:8080/";
    //private static final String REDIRECT = "https://demo.onebrick.io/result";
        //redirect page to result file in local directory
    private static final String REDIRECT = "http://localhost:8085/result";
        
        //check server
    @RequestMapping(value = "/ping", produces = MediaType.APPLICATION_JSON_VALUE)
    public @ResponseBody ResponseEntity<String> ping() {
        return ResponseEntity.ok("Server is up and running");
    }
        //First function to get the index page when project is executed
    @RequestMapping("/")
    public String index(Model model) throws Exception {
        //token to store public_access_token
        var token = authRepository.getPublicToken();
        //add to model 
        model.addAttribute("server", BASE_URL + "v1/index");
        model.addAttribute("token", token.get("access_token").textValue());
        model.addAttribute("redirect", REDIRECT);
        return "start";
    }

        //function called when brick API request for POST method
    @RequestMapping(value = "/result", method = RequestMethod.POST, consumes = "application/json")
    public ResponseEntity<String> postResult(@RequestBody UserAccess userAccess) throws Exception {
        //create a random alhpanumeric string
        String randomString = RandomStringUtils.randomAlphanumeric(20);
        var newMapping = new UserAccessMapping();
        //mapping the value of session id from random
        newMapping.setSessionId(randomString);
        //map the user_access_token when received from successfull login of bank account
        newMapping.setUserAccessToken(userAccess.getAccessToken());
        if(userAccess.getTransactions() != null) {
            newMapping.setJenius(true);
            newMapping.setTransactions(userAccess.getTransactions());
        }
        userAccessMappingRepository.save(newMapping);
        var url = REDIRECT + "?sessionId=" + randomString;
        return new ResponseEntity<>(url, HttpStatus.OK);
    }

        //function called when brick API request for GET method
    @RequestMapping(value = "/result", method = RequestMethod.GET)
    public String result(@RequestParam String sessionId, Model model) throws Exception {
        //call the classes to store the values
        List<Transaction> transactions;
        Balance balance;
        UserAccountDetail detail;
        //get the session id
        var session = userAccessMappingRepository.findBySessionId(sessionId);
        var token = authRepository.getPublicToken();

        //condition to check for Jenius bank account method
        if(session.isJenius()) {
            transactions = scaffoldJeniusTransactions(session.getTransactions());
            detail = new UserAccountDetail(); //scaffoldJeniusAccounts(session.getAccounts()).get(0);
            //set Balance and currency from data retrieve with jenius account
            balance = new Balance();
            balance.setAvailableBalance(detail.getBalances().getAvailable());
            balance.setCurrentBalance(detail.getBalances().getCurrent());
            balance.setCurrency("IDR");
            balance.setTimestamp(Timestamp.from(Instant.now()));
        } else {
            //set Balance and currency from data retrieve with non-jenius account
            transactions = getMockTransaction(session.getUserAccessToken());
            balance = getMockBalance(session.getUserAccessToken());
            detail = getMockUserDetail(session.getUserAccessToken());
        }
        
      //add value into model
      detail.setAccountHolder(WordUtils.capitalizeFully(detail.getAccountHolder()));
        model.addAttribute("items", transactions);
        model.addAttribute("balance", balance);
        model.addAttribute("detail", detail);
        model.addAttribute("server", BASE_URL + "v1/index");
        model.addAttribute("token", token.get("access_token").textValue());
        model.addAttribute("redirect", REDIRECT);
        return "result"; //map the model
    }

application.properties

//server port for starting the application
server.port=8085
//database server
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
//username and password for H2 Database
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true

HTML pages

Start.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Money Save</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="icon" type="image/png" th:href="@{/images/favicon.png}"/>
    <link rel="stylesheet" media="screen" th:href="@{/stylesheets/main.css}"/>
    <link rel="stylesheet" media="screen" th:href="@{/stylesheets/bootstrap.min.css}"/>
    <link rel="stylesheet" media="screen" th:href="@{/stylesheets/permission.css}"/>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="javascripts/bootstrap.min.js"></script>
    <script src="javascripts/popup.js"></script>
    <script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<div class="center">
    <div class="content-box">
        <div></div>

        <div class="brick-logo"><img src="images/money-save-logo.png"class="img-fluid"></div>
        <h2 class="header">Money Save App</h2>
        <p>
            Welcome to Brick's PFM application. Please think of Money Save as your own application UI when using this demo.
        </p>

        <!--<a class="btn btn-warning" type='button' href="#"
           onclick="@{${server} + 'v1/launch?redirect_url=http://localhost:8085/user-access/retrieve&token=' + ${token}}">
            Connect Your Bank<a/>-->
        <p></p>
        <a href="#" class="btn btn-warning" type="button" th:data-server="${server}" th:data-token="${token}" th:data-url="${redirect}"
           th:onclick="openLink(this.getAttribute('data-server'), this.getAttribute('data-token'), this.getAttribute('data-url'))">Add a bank account</a>
    </div>
    <footer class="py-5">
        <div class="container">
            <p class="m-0 text-center">This application is for demonstrative purposes only. We do not store your data at any
                time.</p>
        </div>
    </footer>
</div>
</body>
</html>

Result.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <title>Money Save App</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="icon" type="image/png" th:href="@{/images/favicon.png}" />
    <link rel="stylesheet" media="screen" th:href="@{/stylesheets/main.css}" />
    <link rel="stylesheet" media="screen" th:href="@{/stylesheets/bootstrap.min.css}" />
    <link rel="stylesheet" media="screen" th:href="@{/stylesheets/permission.css}" />
    <link rel="stylesheet" media="screen" th:href="@{/stylesheets/result.css}" />

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="javascripts/bootstrap.min.js"></script>
    <script src="javascripts/popup.js"></script>
    <script src="javascripts/sorttable.js"></script>
    <script src="https://unpkg.com/feather-icons"></script>
</head>

<body>
    <div id="root">
        <div>
            <section id="navbar-margin" style="padding: 0;">
                <div id="background-header" class="container-fluid">
                    <div class="container">
                        <div class="row">
                            <div id="logo" class="col-lg-3" style="padding-top:100px">
                                <img src="/images/money-save-logo.png" class="img-fluid">
                            </div>
                            <div id="info" class="col-lg-9">
                                <div class="row">
                                    <div class="col-lg-6 hello-box">
                                        <h3>Hello!</h3>
                                        <!-- <h3>[[${detail.accountHolder}]]</h3> -->
                                    </div>
                                    <div class="col-lg-6 bank-box">
                                        <h3>[[${detail.accountNumber}]]</h3>
                                        <h4><span th:text="${#calendars.format(#calendars.createNow(), 'dd MMM yyyy')}"></span>
                                            <!--<span class="account-details">
                                                <span>
                                                    <span
                                                        class="account-details-separator">/</span><span>10000000</span></span><span><span
                                                        class="account-details-separator">/</span><span>01-21-31</span></span></span>-->
                                        </h4>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="container">
                    <div>
                        <div class="row">
                            <div id="sidebar" class="col-lg-3">
                                <div class="linked-accounts">
                                    <p style="text-align:center">You have <span>1</span> account <br> linked to Money Save.</p>
                                    <h6 class="list-group-title text-center">Add bank account</h6>
                                    <div class="text-center">
                                        <a href="#" id="connect-bank" class="btn btn-piggy-small text-center" type="button" th:data-server="${server}" th:data-token="${token}" th:data-url="${redirect}"
                                           th:onclick="openLink(this.getAttribute('data-server'), this.getAttribute('data-token'), this.getAttribute('data-url'))">Connect Your Bank</a>
                                        <!-- <button type="button" id="connect-bank" class="btn btn-piggy-small text-center">Connect your bank</button> -->
                                    </div>
                                 </div>
                            </div>
                            <div class="col-lg-9">
                                <div class="card mt-4">
                                    <div class="row">
                                        <div class="col col-lg-6">
                                            <h5>Balance</h5>
                                        </div>
                                        <div class="col col-lg-6 text-right last-update">
                                            <p>Updated <span class="timer"></span></p>
                                        </div>
                                    </div>
                                    <div>
                                        <div>
                                            <div id="balance" class="row double-col">
                                                <div class="col col-lg-6 text-center">
                                                    <span class="positive-val">[[${balance.currency}]] [[${#numbers.formatDecimal(detail.balances.available, 0, 'COMMA', 0, 'POINT')}]]</span>Available Balance
                                                </div>
                                                <div class="col col-lg-6 text-center">
                                                    <span class="positive-val">[[${balance.currency}]] [[${#numbers.formatDecimal(detail.balances.current, 0, 'COMMA', 0, 'POINT')}]]</span>Current Balance
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                                <div class="card mt-4">
                                    <div class="row">
                                        <div class="col col-lg-6">
                                            <h5>Transactions</h5>
                                        </div>
                                        <div class="col col-lg-6 text-right last-update">
                                            <p>Updated <span class="timer"></span></p>
                                        </div>
                                    </div>
                                    <div>
                                        <div>
                                            <div id="transactions" class="row">
                                                <table class="table table-hover table-responsive sortable" style="font-size: 13px;">
                                                    <thead>
                                                        <tr>
                                                            <th>Time</th>
                                                            <th>Description</th>
                                                            <th>Status</th>
                                                            <th>Amount</th>
                                                        </tr>
                                                    </thead>
                                                    <tbody>
                                                        <th:block th:each="item : ${items}">
                                                            <tr>
                                                                <td th:text="${#dates.format(item.date, 'dd-MM-yyyy')}"></td>
                                                                <td th:text="${item.description}"></td>
                                                                <td th:text="${item.status}"></td>
                                                                <td th:text="${'IDR ' + #numbers.formatDecimal(item.amount, 0, 'COMMA', 0, 'POINT')}"></td>
                                                            </tr>
                                                        </th:block>
                                                    </tbody>
                                                </table>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </section>
        </div>
    </div>
    <script type="text/javascript">
        var el = '.timer';
            var start = Math.round(new Date().getTime() / 1000);
            var cDisplay = $(el);
            var format = function (t) {
                var hours = Math.floor(t / 3600),
                    minutes = Math.floor(t / 60 % 60),
                    seconds = Math.floor(t % 60),
                    arr = [];
                if (hours > 0) {
                    arr.push(hours == 1 ? '1 hr ago' : hours + 'hrs ago');
                }
                if (minutes > 0 || hours > 0) {
                    arr.push(minutes > 1 ? minutes + ' mins ago' : minutes + ' min ago');
                }
                if (seconds > 0 || minutes > 0 || hours > 0) {
                    arr.push(seconds > 1 ? seconds + ' secs ago' : seconds + ' sec ago');
                }
                cDisplay.html(arr.join(' '));
            };
            setInterval(function () {
                format(new Date().getTime() / 1000 - start);
            }, 1000);
    </script>
</body>
</html>

Did this page help you?