+# Prerequisites:
+- Python 3.8.5
+- Flask v1.1.2 `pip3 install flask`
+# Usage:
+- run app.py with `python3 app.py`
+- Open your choice of browser and navigate to `localhost:5000`
-Thanks for taking the time to do our front-end / full-stack practical coding challenge.
-The objective of this challenge is to evaluate your domain knowledge in front-end / full-stack development: code organization, style and best practices.
-
-# Overview:
-The main challenge will be to build a simple user management tool that will perform basic CRUD operations on a user. Please use mockups as a reference
-about look of the application. There are no business rules & guidelines other than to show us what you’re truly made of. It can be as simple or as complex as you want it to be.
-
-### Prerequisites
-There are none :) Our main solutions stack include but not limited to Kotlin, Java, AngularJS, Flutter, Spring...
-Feel free to use any languages and technologies you are comfortable with.
-
-## Mockups
-
-
-
-
-
-
-### Front-end:
-- For API please use https://reqres.in/.
-
-### Back-end:
-- The API should be similar to https://reqres.in/, performing basic operations for user.
-
-_We'll be happy if you cover application with tests._
-
-### Submission Guidelines
-- Please fork the repo and then submit a Pull Request when you are done.
-- Instructions must be provided to run the application, install any dependencies, and any other information needed.
-- Please use version control and make sure we can see the history of how you went about it, rather than just uploading the complete project to GitHub.
-
-## Questions? ###
-Please feel free to reach out and ask any questions while you are working on a solution.
-Send your questions to [tech@chimaera.my](mailto:tech@chimaera.my).
-
-
-Good luck!
diff --git a/app.py b/app.py
new file mode 100644
index 0000000..6344b96
--- /dev/null
+++ b/app.py
@@ -0,0 +1,50 @@
+from flask import Flask, request, render_template, jsonify, make_response
+import requests
+
+app = Flask(__name__)
+app.debug = True
+
+@app.route('/')
+def hello_world():
+ return render_template('home.html')
+
+@app.route("/api/create_user", methods=['POST'])
+def create_user():
+ user_first_name = request.args.get('first_name')
+ user_last_name = request.args.get('last_name')
+ user_email = request.args.get('email')
+
+ url = "https://reqres.in/api/users"
+ response = requests.post(url, data = {"first_name": user_first_name, "last_name": user_last_name, "email": user_email} )
+ print(response.text)
+ return jsonify(response.text)
+
+@app.route("/api/delete_user", methods=['POST'])
+def delete_user():
+ user_id = request.args.get('userid')
+ url = "https://reqres.in/api/users/{}".format(user_id)
+ response = requests.delete(url)
+ return jsonify(response.text)
+
+@app.route("/api/update_user", methods=['POST'])
+def update_user():
+ user_id = request.args.get('user_id')
+ user_first_name = request.args.get('first_name')
+ user_last_name = request.args.get('last_name')
+ user_email = request.args.get('email')
+
+ url = "https://reqres.in/api/users/{}".format(user_id)
+ response = requests.patch(url, data = {"first_name": user_first_name, "last_name": user_last_name, "email": user_email})
+ return jsonify(response.text)
+
+@app.route("/api/get_user", methods=['GET'])
+def get_user():
+ url = "https://reqres.in/api/users?per_page=100"
+ response = requests.get(url)
+ if response.ok:
+ return jsonify(response.text)
+ else:
+ abort(400)
+
+if __name__ == '__main__':
+ app.run()
\ No newline at end of file
diff --git a/static/css/custom.css b/static/css/custom.css
new file mode 100644
index 0000000..cee4bde
--- /dev/null
+++ b/static/css/custom.css
@@ -0,0 +1,172 @@
+body {
+ background-color: #B3E5FC;
+ border-radius: 10px
+}
+
+.user-image {
+ width: 128px;
+ height: 128px;
+}
+
+.page-item {
+ cursor:pointer;
+}
+
+.card {
+ width: 400px;
+ border: none;
+ border-radius: 10px;
+ background-color: #fff
+}
+
+.stats {
+ background: #f2f5f8 !important;
+ color: #000 !important
+}
+
+.articles {
+ font-size: 10px;
+ color: #a1aab9
+}
+
+.number1 {
+ font-weight: 500
+}
+
+.followers {
+ font-size: 10px;
+ color: #a1aab9
+}
+
+.number2 {
+ font-weight: 500
+}
+
+.rating {
+ font-size: 10px;
+ color: #a1aab9
+}
+
+.number3 {
+ font-weight: 500
+}
+
+.modal-confirm {
+ color: #636363;
+ width: 400px;
+ margin: 30px auto;
+}
+.modal-confirm .modal-content {
+ padding: 20px;
+ border-radius: 5px;
+ border: none;
+ text-align: center;
+ font-size: 14px;
+}
+.modal-confirm .modal-header {
+ border-bottom: none;
+ position: relative;
+}
+.modal-confirm h4 {
+ text-align: center;
+ font-size: 26px;
+ margin: 30px 0 -10px;
+}
+.modal-confirm .close {
+ position: absolute;
+ top: -5px;
+ right: -2px;
+}
+.modal-confirm .modal-body {
+ color: #999;
+}
+.modal-confirm .modal-footer {
+ border: none;
+ text-align: center;
+ border-radius: 5px;
+ font-size: 13px;
+ padding: 10px 15px 25px;
+}
+.modal-confirm .modal-footer a {
+ color: #999;
+}
+.modal-confirm .icon-box {
+ width: 80px;
+ height: 80px;
+ margin: 0 auto;
+ border-radius: 50%;
+ z-index: 9;
+ text-align: center;
+ border: 3px solid #f15e5e;
+}
+.modal-confirm .icon-box i {
+ color: #f15e5e;
+ font-size: 46px;
+ display: inline-block;
+ margin-top: 13px;
+}
+
+.modal-confirm .btn {
+ color: #fff;
+ border-radius: 4px;
+ background: #60c7c1;
+ text-decoration: none;
+ transition: all 0.4s;
+ line-height: normal;
+ min-width: 120px;
+ border: none;
+ min-height: 40px;
+ border-radius: 3px;
+ margin: 0 5px;
+ outline: none !important;
+}
+.modal-confirm .btn-info {
+ background: #c1c1c1;
+}
+.modal-confirm .btn-info:hover, .modal-confirm .btn-info:focus {
+ background: #a8a8a8;
+}
+.modal-confirm .btn-danger {
+ background: #f15e5e;
+}
+.modal-confirm .btn-danger:hover, .modal-confirm .btn-danger:focus {
+ background: #ee3535;
+}
+.trigger-btn {
+ display: inline-block;
+ margin: 100px auto;
+}
+
+.modal-confirm .icon-box-1 {
+ width: 80px;
+ height: 80px;
+ margin: 0 auto;
+ border-radius: 50%;
+ z-index: 9;
+ text-align: center;
+ border: 3px solid #7FFF00;
+}
+.modal-confirm .icon-box-1 i {
+ color: #7FFF00;
+ font-size: 46px;
+ display: inline-block;
+ margin-top: 13px;
+}
+
+.btn-label {
+ position: relative;
+ left: -12px;
+ display: inline-block;
+ padding: 6px 12px;
+ background: rgba(0, 0, 0, 0.15);
+ border-radius: 3px 0 0 3px;
+}
+
+.btn-labeled {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.btn {
+ margin-bottom: 10px;
+}
\ No newline at end of file
diff --git a/static/js/home.js b/static/js/home.js
new file mode 100644
index 0000000..4274fe7
--- /dev/null
+++ b/static/js/home.js
@@ -0,0 +1,193 @@
+var userList;
+
+$( document ).ready(function() {
+ $.ajax({
+ type: "GET",
+ url: "api/get_user",
+ async: false,
+ success: function(response) {
+ userList = JSON.parse(response)
+ fillUsers(userList);
+ }
+ });
+});
+
+$(document).on('click', '.btn-delete', function(){
+ $('.delete-confirm').attr('userid', $(this).attr('userid'));
+ showModal('deleteModal');
+});
+
+$(document).on('click', '.btn-new', function(){
+ $('.delete-confirm').attr('userid', $(this).attr('userid'));
+ hideAlert('newModalAlert');
+ showModal('newModal');
+});
+
+$(document).on('click', '.btn-new-save', function(){
+
+ var userFirstName = $("#new-user-name").val().split(' ').slice(0, -1).join(' ');
+ var userLastName = $("#new-user-name").val().split(' ').slice(-1).join(' ');
+ var userEmail = $("#new-user-email").val();
+
+ if(!(validateEmail(userEmail))) {
+ showAlert('newModalAlert', "Sorry, something seems to be wrong with the email!");
+ return;
+ }
+
+ $.ajax({
+ type: "POST",
+ url: "api/create_user",
+ data: {first_name: userFirstName, last_name: userLastName, email: userEmail},
+ success: function(response) {
+ responseJson = JSON.parse(response);
+ if(responseJson.hasOwnProperty('id')) {
+ userList.data.push({id: parseInt(responseJson.id), email: userEmail, first_name: userFirstName, last_name: userLastName, avatar:"https://st.depositphotos.com/1779253/5140/v/950/depositphotos_51405259-stock-illustration-male-avatar-profile-picture-use.jpg"});
+ fillUsers(userList);
+ hideModal('newModal');
+ showModal('successNewModal');
+ } else {
+ showAlert('newModalAlert', "Sorry something went wrong!");
+ }
+ }
+ });
+});
+
+$(document).on('click', '.btn-edit', function(){
+ var userId = $(this).attr('userid');
+ $('.btn-edit-save').attr('userid', userId);
+ var index = getUserIndex(userId);
+ var userName = `${userList.data[index].first_name} ${userList.data[index].last_name}`;
+ var userEmail = userList.data[index].email
+
+ $('#editModalLabel').text(`Editing ${userName}`);
+ $('#user-name').val(userName);
+ $('#user-email').val(userEmail);
+
+ showModal('editModal');
+});
+
+
+$(document).on('click', '.delete-confirm', function(){
+ var userId = $(this).attr('userid');
+
+ $.ajax({
+ type: "POST",
+ url: "api/delete_user",
+ data: {userid : userId},
+ success: function(response) {
+ console.log('deleted');
+ var index = getUserIndex(userId);
+ if(index > -1) {
+ userList.data.splice(index, 1);
+ fillUsers(userList);
+ hideModal('deleteModal');
+ showModal('successDeleteModal');
+ }
+ }
+ });
+
+});
+
+
+$(document).on('click', '.btn-edit-save', function(){
+ var userId = $(this).attr('userid');
+ var userFirstName = $("#user-name").val().split(' ').slice(0, -1).join(' ');
+ var userLastName = $("#user-name").val().split(' ').slice(-1).join(' ');
+ var userEmail = $("#user-email").val();
+
+ if(!(validateEmail(userEmail))){
+ showAlert('editModalAlert', "Sorry, something seems to be wrong with the email!");
+ return;
+ }
+
+ $.ajax({
+ type: "POST",
+ url: "api/update_user",
+ data: {user_id : userId, first_name: userFirstName, last_name: userLastName, email: userEmail},
+ success: function(response) {
+ console.log('Updated');
+ var index = getUserIndex(userId);
+ if(index > -1) {
+ userList.data[index].first_name = userFirstName;
+ userList.data[index].last_name = userLastName;
+ userList.data[index].email = userEmail;
+ fillUsers(userList);
+ hideModal('editModal');
+ showModal('successEditModal');
+ }
+ }
+ });
+});
+
+
+function getUserIndex(userId){
+ var index = userList.data.findIndex(function(user, i){
+ return user.id == userId;
+ });
+
+ return index;
+}
+
+function fillUsers(userList) {
+ var userPage = $(".users");
+ var userTemplateRow = $(".user-row-template").clone();
+
+ userPage.empty();
+
+ $.each(userList.data, function(k,v) {
+
+ var userTemplate = userTemplateRow.find('.user-template').first();
+
+ userTemplate.find('.user-image').attr('src', v.avatar);
+ userTemplate.find('.user-name').text(v.first_name + " " +v.last_name);
+ userTemplate.find('.user-mail').text(v.email);
+ userTemplate.find('.btn-edit').attr('userid', v.id);
+ userTemplate.find('.btn-delete').attr('userid', v.id);
+
+ userTemplate.removeClass('user-template invisible');
+
+ if(userTemplateRow.find('.user-template').length == 0) {
+ userTemplateRow.removeClass("user-row-template invisible");
+ userPage.append(userTemplateRow);
+ userTemplateRow = $(".user-row-template").clone();
+ }
+ });
+
+ if(userTemplateRow.find('.user-template').length != 2) {
+ userTemplateRow.removeClass("user-row-template invisible");
+ userPage.append(userTemplateRow);
+ }
+
+}
+
+function showModal(modalId) {
+ var modal = document.getElementById(modalId);
+ var bsModal = new bootstrap.Modal(modal);
+ bsModal.show();
+};
+
+function hideModal(modalId) {
+ var modal = document.getElementById(modalId)
+ var bsModal = bootstrap.Modal.getInstance(modal)
+ console.log(bsModal);
+ bsModal.hide();
+};
+
+function showAlert(alertId, message) {
+ $('#'+alertId).text(message);
+ $('#'+alertId).attr('style', '');
+};
+
+function hideAlert(alertId) {
+ $('#'+alertId).text("");
+ $('#'+alertId).attr('style', 'display: none');
+};
+
+function validateEmail(email)
+{
+ if (/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(email)) {
+ return true;
+ } else {
+ return false;
+ }
+};
\ No newline at end of file
diff --git a/static/js/jquery.js b/static/js/jquery.js
new file mode 100644
index 0000000..fc6c299
--- /dev/null
+++ b/static/js/jquery.js
@@ -0,0 +1,10881 @@
+/*!
+ * jQuery JavaScript Library v3.6.0
+ * https://jquery.com/
+ *
+ * Includes Sizzle.js
+ * https://sizzlejs.com/
+ *
+ * Copyright OpenJS Foundation and other contributors
+ * Released under the MIT license
+ * https://jquery.org/license
+ *
+ * Date: 2021-03-02T17:08Z
+ */
+( function( global, factory ) {
+
+ "use strict";
+
+ if ( typeof module === "object" && typeof module.exports === "object" ) {
+
+ // For CommonJS and CommonJS-like environments where a proper `window`
+ // is present, execute the factory and get jQuery.
+ // For environments that do not have a `window` with a `document`
+ // (such as Node.js), expose a factory as module.exports.
+ // This accentuates the need for the creation of a real `window`.
+ // e.g. var jQuery = require("jquery")(window);
+ // See ticket #14549 for more info.
+ module.exports = global.document ?
+ factory( global, true ) :
+ function( w ) {
+ if ( !w.document ) {
+ throw new Error( "jQuery requires a window with a document" );
+ }
+ return factory( w );
+ };
+ } else {
+ factory( global );
+ }
+
+// Pass this if window is not defined yet
+} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
+// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
+// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
+// enough that all such attempts are guarded in a try block.
+"use strict";
+
+var arr = [];
+
+var getProto = Object.getPrototypeOf;
+
+var slice = arr.slice;
+
+var flat = arr.flat ? function( array ) {
+ return arr.flat.call( array );
+} : function( array ) {
+ return arr.concat.apply( [], array );
+};
+
+
+var push = arr.push;
+
+var indexOf = arr.indexOf;
+
+var class2type = {};
+
+var toString = class2type.toString;
+
+var hasOwn = class2type.hasOwnProperty;
+
+var fnToString = hasOwn.toString;
+
+var ObjectFunctionString = fnToString.call( Object );
+
+var support = {};
+
+var isFunction = function isFunction( obj ) {
+
+ // Support: Chrome <=57, Firefox <=52
+ // In some browsers, typeof returns "function" for HTML