Now a simple re-frame app

This commit is contained in:
Bryce Covert
2017-12-07 11:23:57 -08:00
parent 275119c362
commit 5b578c11e8
13 changed files with 618 additions and 40 deletions

2
.gitignore vendored
View File

@@ -8,3 +8,5 @@ pom.xml.asc
*.class
/.lein-*
/.nrepl-port
/resources/public/js/compiled
*.log

View File

@@ -1,3 +1,6 @@
FROM tomcat:9.0-jre8-alpine
RUN 'apk add poppler-utils'
COPY target/auto-ap.war /usr/local/tomcat
FROM openjdk:8-jre-alpine
RUN apk update
RUN apk add poppler
RUN apk add poppler-utils
COPY target/auto-ap-0.1.0-SNAPSHOT-standalone.jar /usr/local/
CMD java -jar /usr/local/auto-ap-0.1.0-SNAPSHOT-standalone.jar

View File

@@ -3,11 +3,71 @@
:url "http://example.com/FIXME"
:min-lein-version "2.0.0"
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.908"]
[reagent "0.7.0"]
[re-frame "0.10.2"]
[compojure "1.6.0"]
[secretary "1.2.3"]
[ring/ring-defaults "0.2.1"]
[ring/ring-json "0.4.0"]]
:plugins [[lein-ring "0.9.7"]]
[ring/ring-json "0.4.0"]
[ring "1.4.0"]
[yogthos/config "0.8"]]
:plugins [[lein-ring "0.9.7"]
[lein-cljsbuild "1.1.5"]]
:clean-targets ^{:protect false} ["resources/public/js/compiled" "target"]
:ring {:handler auto-ap.handler/app}
:source-paths ["src/clj"]
:figwheel {:css-dirs ["resources/public/css"]
:ring-handler auto-ap.handler/app}
:aliases {"dev" ["do" "clean"
["pdo" ["figwheel" "dev"]]]
"build" ["do" "clean"
["cljsbuild" "once" "min"]]}
:profiles
{:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
[ring/ring-mock "0.3.0"]]}})
{:dev
{:dependencies [[binaryage/devtools "0.9.4"]
[javax.servlet/servlet-api "2.5"]
[figwheel-sidecar "0.5.13"]
[com.cemerick/piggieback "0.2.2"]]
:plugins [[lein-figwheel "0.5.13"]
[lein-pdo "0.1.1"]]}}
:cljsbuild
{:builds
[{:id "dev"
:source-paths ["src/cljs"]
:figwheel {:on-jsload "auto-ap.core/mount-root"}
:compiler {:main auto-ap.core
:output-to "resources/public/js/compiled/app.js"
:output-dir "resources/public/js/compiled/out"
:asset-path "js/compiled/out"
:source-map-timestamp true
:preloads [devtools.preload]
:external-config {:devtools/config {:features-to-install :all}}
}}
{:id "min"
:source-paths ["src/cljs"]
:jar true
:compiler {:main auto-ap.core
:output-to "resources/public/js/compiled/app.js"
:optimizations :advanced
:closure-defines {goog.DEBUG false}
:pretty-print false}}
]}
:main auto-ap.server
:aot [auto-ap.server]
:uberjar-name "auto-ap.jar"
#_#_:prep-tasks [["cljsbuild" "once" "min"]"compile"])
;;

View File

@@ -1,37 +1,331 @@
<!doctype html>
<html lang="en">
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/dropzone@5.2.0/dist/dropzone.min.js"></script>
<script src="http://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.1/css/bulma.css" />
<style>.dz-error-mark { display:none} .dz-details {display:none}
form { border: 3px solid lightgray; padding: 25px; width: 100%;}
.dz-success-mark {display:none} </style>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Auto AP</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,700" rel="stylesheet">
<script src="https://cdn.auth0.com/js/lock/10.24/lock.min.js"></script>
<!-- Bulma Version 0.6.0 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.0/css/bulma.min.css" integrity="sha256-HEtF7HLJZSC3Le1HcsWbz1hDYFPZCqDhZa9QsCgVUdw=" crossorigin="anonymous" />
<style>
.dz-error-mark { display:none} .dz-details {display:none}
.dz-success-mark {display:none}
.dz-image {display:none}
form { width: 100% }
html,body {
font-family: 'Open Sans', serif;
font-size: 14px;
line-height: 1.5;
height: 100%;
background-color: #fff;
}
.left-nav {
width: 300px;
}
.nav.is-dark {
background-color: #232B2D;
color: #F6F7F7;
}
.nav.is-dark .nav-item a, .nav.is-dark a.nav-item {
color: #F6F7F7;
}
.nav.is-dark .nav-item a.button.is-default {
color: #F6F7F7;
background-color: transparent;
border-width: 2px;
}
.nav.menu {
border-bottom: 1px solid #e1e1e1;
}
.nav.menu .nav-item .icon-btn {
border: 3px solid #B7C6C9;
border-radius: 90px;
padding: 5px 7px;
color: #B7C6C9;
}
.nav.menu .nav-item.is-active .icon-btn {
color: #2EB398;
border: 3px solid #2EB398;
}
.nav.menu .nav-item .icon-btn .fa {
font-size: 20px;
color: #B7C6C9;
}
.nav.menu .nav-item.is-active .icon-btn .fa {
color: #2EB398;
}
.aside {
display:block;
background-color: #F9F9F9;
border-right: 1px solid #DEDEDE;
}
.messages {
display:block;
background-color: #fff;
border-right: 1px solid #DEDEDE;
}
.message {
display:block;
background-color: #fff;
}
.aside .compose {
height: 95px;
margin:0 -10px;
padding: 25px 30px;
}
.aside .compose .button {
color: #F6F7F7;
}
.aside .compose .button .compose {
font-size: 14px;
font-weight: 700;
}
.aside .main {
padding: 40px;
color: #6F7B7E;
}
.aside .title {
color: #6F7B7E;
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
}
.aside .main .item {
display: block;
padding: 10px 0;
color: #6F7B7E;
}
.aside .main .item.active {
background-color: #F1F1F1;
margin: 0 -50px;
padding-left: 50px;
}
.aside .main .item:active,.aside .main .item:hover {
background-color: #F2F2F2;
margin: 0 -50px;
padding-left: 50px;
}
.aside .main .icon {
font-size: 19px;
padding-right: 30px;
color: #A0A0A0;
}
.aside .main .name {
font-size: 15px;
color: #5D5D5D;
font-weight: 500;
}
.messages {
padding: 40px 20px;
}
.message {
padding: 40px 20px;
}
.messages .action-buttons {
padding: 0;
margin-top: -20px;
}
.message .action-buttons {
padding: 0;
margin-top: -5px;
}
.action-buttons .control.is-grouped {
display: inline-block;
margin-right: 30px;
}
.action-buttons .control.is-grouped:last-child {
margin-right: 0;
}
.action-buttons .control.is-grouped .button:first-child {
border-radius: 5px 0 0 5px;
}
.action-buttons .control.is-grouped .button:last-child {
border-radius: 0 5px 5px 0;
}
.action-buttons .control.is-grouped .button {
margin-right: -5px;
border-radius: 0;
}
.pg {
display: inline-block;
top:10px;
}
.action-buttons .pg .title {
display: block;
margin-top: 0;
padding-top: 0;
margin-bottom: 3px;
font-size:12px;
color: #AAAAA;
}
.action-buttons .pg a{
font-size:12px;
color: #AAAAAA;
text-decoration: none;
}
.is-grouped .button {
background-image: linear-gradient(#F8F8F8, #F1F1F1);
}
.is-grouped .button .fa {
font-size: 15px;
color: #AAAAAA;
}
.inbox-messages .card {
width: 100%;
}
.inbox-messages strong {
color: #5D5D5D;
}
.inbox-messages .msg-check {
padding: 0 20px;
}
.inbox-messages .msg-subject {
padding: 10px 0;
color: #5D5D5D;
}
.inbox-messages .msg-attachment {
float:right;
}
.inbox-messages .msg-snippet {
padding: 5px 20px 0px 5px;
}
.inbox-messages .msg-subject .fa {
font-size: 14px;
padding:3px 0;
}
.inbox-messages .msg-timestamp {
float: right;
padding: 0 20px;
color: #5D5D5D;
}
.message-preview .avatar {
display: inline-block;
}
.message-preview .top .address {
display: inline-block;
padding: 0 20px;
}
.avatar img {
width: 40px;
border-radius: 50px;
border: 2px solid #999;
padding: 2px;
}
.address .name {
font-size: 16px;
font-weight: bold;
}
.address .email {
font-weight: bold;
color: #B6C7D1;
}
.card.active {
background-color:#F5F5F5;
}
</style>
</head>
<body>
<div id="app" class="container">
<h1>Invoice Parsing Demo</h1>
<form action="/pdf-upload" id="my-dropzone">
<h3>Drop invoice pdfs here</h3>
<input type="file" name="file" style="display:none"/>
</form>
<h2 style="display:none">Found invoices:</h2>
<ul>
</ul>
<div id="app">
<nav class="navbar has-shadow">
<div class="container">
<div class="navbar-brand">
<a class="navbar-item" href="../">
<h1>Auto-ap</h1>
</a>
</div>
</div>
</nav>
<div class="has-text-centered hero is-fullheight is-vertically-centered" id="mail-app">
<div class="is-vertically-centered">
<h1 class="title"><i class="fa fa-spin fa-spinner"></i></h1>
</div>
</div>
<footer class="footer">
<div class="container">
<div class="content has-text-centered">
<p>
<strong>Auto-AP</strong>
by <a href="https://github.com/">Integreat</a>.
</p>
<p>
<a class="icon" href="https://github.com/dansup/bulma-templates">
<i class="fa fa-github"></i>
</a>
</p>
</div>
</div>
</footer>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.min.js" integrity="sha256-5CEXP4Sh+bwJYBngjYYh2TEev9kTDwcjw60jZatTHtY=" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Faker/3.1.0/faker.min.js" integrity="sha256-QHdJObhDO++VITP6S4tMlDHRWMaUOk+s/xWIRgF/YY0=" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.15.1/moment.min.js" integrity="sha256-4PIvl58L9q7iwjT654TQJM+C/acEyoG738iL8B8nhXg=" crossorigin="anonymous"></script>
<script src="http://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/dropzone@5.2.0/dist/dropzone.min.js"></script>
<script>
/*
$(function() {
var lock = new Auth0Lock(
"twbXfoLvL0tKTR6GWORoM-ss51wM1zXZ",
"app82488100.auth0.com",
{
rememberLastLogin: false,
socialButtonStyle: "big",
oidcConformant: true,
auth: {
audience: "https://app82488100.auth0.com/api/v2/",
params: {scope: 'openid email profile'},
responseType: "token",
redirect: true,
redirectUrl: "http://localhost:3000",
},
languageDictionary: {"title":"Auto AP"},
language: "en",
theme: {"primaryColor":"#3A99D8"}
} );
$(".login").click(function(e) {
e.preventDefault();
lock.show();
});
lock.on("authenticated", function(result, other) {
console.log(result.accessToken, other);
lock.getUserInfo(result.accessToken, function(err, profile) {
$(".login").text(profile.nickname);
console.log(profile);
})
});
var myDropzone = new Dropzone("#my-dropzone");
myDropzone.on("success", function(file, a) {
$("h2").show();
$(".found-invoices").show();
JSON.parse(a).map(function(x) {
$("ul").append($("<li>").text(x));
console.log(x);
var tr = $("<tr>");
tr.append($("<td>").text(x['customer-identifier']));
tr.append($("<td>").text(x['invoice-number']));
tr.append($("<td>").text(x['date']));
tr.append($("<td>").text(x['total']));
$("tbody").append(tr);
});
});
});
*/
</script>
</div>
<script src="js/compiled/app.js"></script>
<script>auto_ap.core.init();</script>
</body>
</html>

View File

@@ -9,15 +9,18 @@
[ring.middleware.json :refer [wrap-json-response]]))
(defroutes app-routes
(GET "/hi" [] "hello")
(GET "/" [] (response/resource-response "index.html" {:root "public"}))
(POST "/pdf-upload"
{{ files "file"} :params :as params}
(let [{:keys [filename tempfile]} (second files)]
(io/copy tempfile (io/file "resources" "public" filename))
(for [{:keys [total date invoice-number customer-identifier]} (parse/parse-file (str "resources/public/" filename))]
(do
(println (str "An invoice #" invoice-number " on " date " for " total))
(str "An invoice for customer " customer-identifier " #" invoice-number " on " date " for " total )))))
(println tempfile)
#_(io/copy tempfile (io/file "resources" "public" filename))
(for [{:keys [total date invoice-number customer-identifier]} (parse/parse-file (.getPath tempfile))]
{"customer-identifier" customer-identifier
"invoice-number" invoice-number
"date" date
"total" total})))
(route/resources "/")
(route/not-found "Not Found"))

View File

@@ -0,0 +1,9 @@
(ns auto-ap.server
(:require [auto-ap.handler :refer [app]]
[config.core :refer [env]]
[ring.adapter.jetty :refer [run-jetty]])
(:gen-class))
(defn -main [& args]
(let [port (Integer/parseInt (or (env :port) "3000"))]
(run-jetty app {:port port :join? false})))

View File

@@ -0,0 +1,4 @@
(ns auto-ap.config)
(def debug?
^boolean goog.DEBUG)

View File

@@ -0,0 +1,23 @@
(ns auto-ap.core
(:require [reagent.core :as reagent]
[re-frame.core :as re-frame]
[auto-ap.events :as events]
[auto-ap.views :as views]
[auto-ap.config :as config]))
(defn dev-setup []
(when config/debug?
(enable-console-print!)
(println "dev mode")))
(defn mount-root []
(re-frame/clear-subscription-cache!)
(reagent/render [views/main-panel]
(.getElementById js/document "app")))
(defn ^:export init []
(re-frame/dispatch-sync [::events/initialize-db])
(dev-setup)
(mount-root))

5
src/cljs/auto_ap/db.cljs Normal file
View File

@@ -0,0 +1,5 @@
(ns auto-ap.db)
(def default-db
{:company {:name "Campbell brewery"}
:invoices #{}})

View File

@@ -0,0 +1,13 @@
(ns auto-ap.events
(:require [re-frame.core :as re-frame]
[auto-ap.db :as db]))
(re-frame/reg-event-db
::initialize-db
(fn [_ _]
db/default-db))
(re-frame/reg-event-db
::imported-invoices
(fn [db [_ new-invoices]]
(update-in db [:invoices] into new-invoices)))

View File

@@ -0,0 +1,13 @@
(ns auto-ap.subs
(:require [re-frame.core :as re-frame]))
(re-frame/reg-sub
::name
(fn [db]
(:name (:company db))))
(re-frame/reg-sub
::invoices
(fn [db]
(:invoices db)))

149
src/cljs/auto_ap/views.cljs Normal file
View File

@@ -0,0 +1,149 @@
(ns auto-ap.views
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[auto-ap.subs :as subs]
[auto-ap.events :as events]))
(def dropzone
(with-meta
(fn []
[:form {:action "/pdf-upload" :class ".dropzone"}
[:div {:class "card"}
[:div {:class "card-header"}
[:span {:class "card-header-title"} "Upload Invoices"]]
[:div {:class "card-content"}
[:span {:class "icon"}
[:i {:class "fa fa-cloud-download"}]]
"Drop invoice pdfs here"
[:input {:type "file", :name "file", :style {:display "none"}}]]]])
{:component-did-mount (fn [this]
(-> (js/$ (reagent/dom-node this))
(.dropzone (clj->js {:init (fn []
(this-as t
(.on t "success" (fn [_ files]
(re-frame/dispatch [::events/imported-invoices (js->clj (.parse js/JSON files))])
))))
:url "/pdf-upload"}))))})
)
(defn main-panel []
(let [name (re-frame/subscribe [::subs/name])
invoices (re-frame/subscribe [::subs/invoices])]
(println name)
[:div
[:nav {:class "navbar has-shadow"}
[:div {:class "container"}
[:div {:class "navbar-brand"}
[:a {:class "navbar-item", :href "../"}
[:h1 (str "Auto-ap - " @name)]]
[:div {:class "navbar-burger burger", :data-target "navMenu"}
[:span]
[:span]
[:span]]]
[:div {:id "navMenu", :class "navbar-menu"}
[:div {:class "navbar-end"}
[:div {:class "navbar-item has-dropdown is-active"}
[:a {:class "navbar-link login"} ]
[:div {:class "navbar-dropdown", :style {:display "none"}}
[:a {:class "navbar-item"} ]
[:a {:class "navbar-item"} ]
[:a {:class "navbar-item"} ]
[:hr {:class "navbar-divider"}]
[:div {:class "navbar-item"} ]]]]]]]
[:div {:class "columns", :id "mail-app"}
[:aside {:class "column is-narrow aside hero is-fullheight"}
[:div.left-nav
[:div {:class "compose has-text-centered"}
[:a {:class "button is-danger is-block is-bold"}
[:span {:class "compose"} "New Invoice"]]]
[:div {:class "main"}
[:a {:href "#", :class "item active"}
[:span {:class "icon"}
[:i {:class "fa fa-inbox"}]]
[:span {:class "name"} "Upload\n Invoices"]]
[:a {:href "#", :class "item"}
[:span {:class "icon"}
[:i {:class "fa fa-star"}]]
[:span {:class "name"} "Unpaid Invoices"]]
[:a {:href "#", :class "item"}
[:span {:class "icon"}
[:i {:class "fa fa-envelope-o"}]]
[:span {:class "name"} "Paid Invoices"]]]]]
[:div {:class "column messages hero is-fullheight", :id "message-feed"}
[:div {:class "inbox-messages"}
[dropzone]
[:div {:class "section"}]
[:div {:class "card found-invoices", :style {:display (if (seq @invoices) "block" "none")}}
[:div {:class "card-header"}
[:span {:class "card-header-title"} "Found Invoices"]]
[:div {:class "card-content"}
[:table {:class "table", :style {:width "100%"}}
[:thead
[:tr
[:th "Customer"]
[:th "Invoice #"]
[:th "Date"]
[:th "Amount"]]]
[:tbody (for [{:strs [customer-identifier invoice-number date total]} @invoices]
^{:key (str customer-identifier "-" invoice-number)}
[:tr
[:td customer-identifier]
[:td invoice-number]
[:td date]
[:td total]])]]]]]]]
[:footer {:class "footer"}
[:div {:class "container"}
[:div {:class "content has-text-centered"}
[:p
[:strong "Auto-AP"]"by "
[:a {:href "https://github.com/"} "Integreat"]"."]
[:p
[:a {:class "icon", :href "https://github.com/dansup/bulma-templates"}
[:i {:class "fa fa-github"}]]]]]]]))
;;
;; <nav class="navbar has-shadow">
;; <div class="container">
;; <div class="navbar-brand">
;; <a class="navbar-item" href="../">
;; <h1>Auto-ap</h1>
;; </a>
;;
;; <div class="navbar-burger burger" data-target="navMenu">
;; <span></span>
;; <span></span>
;; <span></span>
;; </div>
;; </div>
;;
;; <div id="navMenu" class="navbar-menu">
;; <div class="navbar-end">
;; <div class="navbar-item has-dropdown is-active">
;; <a class="navbar-link login">
;; Login
;; </a>
;;
;; <div class="navbar-dropdown" style="display:none">
;; <a class="navbar-item">
;; Dashboard
;; </a>
;; <a class="navbar-item">
;; Profile
;; </a>
;; <a class="navbar-item">
;; Settings
;; </a>
;; <hr class="navbar-divider">
;; <div class="navbar-item">
;; Logout
;; </div>
;; </div>
;; </div>
;; </div>
;; </div>
;; </div>
;; </nav>