tailwind ready

This commit is contained in:
Bryce
2023-06-13 22:05:58 -07:00
parent 3e4b963726
commit 1c245c6427
16 changed files with 454 additions and 243 deletions

View File

@@ -1,5 +1 @@
1. Make admin sidebar
2. Make transaction sidebar
3. Test power users
4. Maybe make dark mode persist 4. Maybe make dark mode persist
5. redirect when not authenticated

View File

@@ -1380,6 +1380,10 @@ input:checked + .toggle-bg {
flex: 1 1 0%; flex: 1 1 0%;
} }
.flex-none {
flex: none;
}
.flex-shrink { .flex-shrink {
flex-shrink: 1; flex-shrink: 1;
} }
@@ -1480,6 +1484,14 @@ input:checked + .toggle-bg {
flex-direction: column; flex-direction: column;
} }
.flex-wrap {
flex-wrap: wrap;
}
.content-start {
align-content: flex-start;
}
.items-start { .items-start {
align-items: flex-start; align-items: flex-start;
} }
@@ -1512,6 +1524,10 @@ input:checked + .toggle-bg {
justify-content: space-between; justify-content: space-between;
} }
.justify-items-start {
justify-items: start;
}
.gap-1 { .gap-1 {
gap: 0.25rem; gap: 0.25rem;
} }
@@ -1587,6 +1603,10 @@ input:checked + .toggle-bg {
border-color: rgb(243 244 246 / var(--tw-divide-opacity)); border-color: rgb(243 244 246 / var(--tw-divide-opacity));
} }
.justify-self-start {
justify-self: start;
}
.overflow-auto { .overflow-auto {
overflow: auto; overflow: auto;
} }
@@ -1797,6 +1817,21 @@ input:checked + .toggle-bg {
background-color: rgb(253 246 178 / var(--tw-bg-opacity)); background-color: rgb(253 246 178 / var(--tw-bg-opacity));
} }
.bg-green-200 {
--tw-bg-opacity: 1;
background-color: rgb(201 225 171 / var(--tw-bg-opacity));
}
.bg-blue-200 {
--tw-bg-opacity: 1;
background-color: rgb(153 215 247 / var(--tw-bg-opacity));
}
.bg-red-200 {
--tw-bg-opacity: 1;
background-color: rgb(255 154 154 / var(--tw-bg-opacity));
}
.bg-opacity-50 { .bg-opacity-50 {
--tw-bg-opacity: 0.5; --tw-bg-opacity: 0.5;
} }
@@ -2346,6 +2381,21 @@ input:checked + .toggle-bg {
background-color: rgb(255 255 255 / var(--tw-bg-opacity)); background-color: rgb(255 255 255 / var(--tw-bg-opacity));
} }
.hover\:bg-green-300:hover {
--tw-bg-opacity: 1;
background-color: rgb(175 211 130 / var(--tw-bg-opacity));
}
.hover\:bg-blue-300:hover {
--tw-bg-opacity: 1;
background-color: rgb(102 196 242 / var(--tw-bg-opacity));
}
.hover\:bg-red-300:hover {
--tw-bg-opacity: 1;
background-color: rgb(255 104 104 / var(--tw-bg-opacity));
}
.hover\:text-blue-600:hover { .hover\:text-blue-600:hover {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(0 125 187 / var(--tw-text-opacity)); color: rgb(0 125 187 / var(--tw-text-opacity));
@@ -2437,6 +2487,21 @@ input:checked + .toggle-bg {
--tw-ring-color: rgb(121 181 46 / var(--tw-ring-opacity)); --tw-ring-color: rgb(121 181 46 / var(--tw-ring-opacity));
} }
.focus\:ring-green-200:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(201 225 171 / var(--tw-ring-opacity));
}
.focus\:ring-blue-200:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(153 215 247 / var(--tw-ring-opacity));
}
.focus\:ring-red-200:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(255 154 154 / var(--tw-ring-opacity));
}
.group:hover .group-hover\:scale-110 { .group:hover .group-hover\:scale-110 {
--tw-scale-x: 1.1; --tw-scale-x: 1.1;
--tw-scale-y: 1.1; --tw-scale-y: 1.1;
@@ -2549,6 +2614,26 @@ input:checked + .toggle-bg {
background-color: rgb(99 49 18 / var(--tw-bg-opacity)); background-color: rgb(99 49 18 / var(--tw-bg-opacity));
} }
:is(.dark .dark\:bg-green-500) {
--tw-bg-opacity: 1;
background-color: rgb(121 181 46 / var(--tw-bg-opacity));
}
:is(.dark .dark\:bg-green-700) {
--tw-bg-opacity: 1;
background-color: rgb(73 109 28 / var(--tw-bg-opacity));
}
:is(.dark .dark\:bg-blue-700) {
--tw-bg-opacity: 1;
background-color: rgb(0 94 140 / var(--tw-bg-opacity));
}
:is(.dark .dark\:bg-red-700) {
--tw-bg-opacity: 1;
background-color: rgb(153 2 2 / var(--tw-bg-opacity));
}
:is(.dark .dark\:bg-opacity-80) { :is(.dark .dark\:bg-opacity-80) {
--tw-bg-opacity: 0.8; --tw-bg-opacity: 0.8;
} }
@@ -2662,6 +2747,16 @@ input:checked + .toggle-bg {
background-color: rgb(73 109 28 / var(--tw-bg-opacity)); background-color: rgb(73 109 28 / var(--tw-bg-opacity));
} }
:is(.dark .dark\:hover\:bg-blue-600:hover) {
--tw-bg-opacity: 1;
background-color: rgb(0 125 187 / var(--tw-bg-opacity));
}
:is(.dark .dark\:hover\:bg-red-600:hover) {
--tw-bg-opacity: 1;
background-color: rgb(204 2 2 / var(--tw-bg-opacity));
}
:is(.dark .dark\:hover\:text-blue-500:hover) { :is(.dark .dark\:hover\:text-blue-500:hover) {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(0 156 234 / var(--tw-text-opacity)); color: rgb(0 156 234 / var(--tw-text-opacity));

View File

@@ -1,8 +1,8 @@
(ns ingest-ml (ns ingest-ml
(:require [datomic.client.api :as dc] (:require [datomic.api :as dc]
[clojure.java.io :as io] [clojure.java.io :as io]
[clojure.data.csv :as csv] [clojure.data.csv :as csv]
[iol-ion.tx :refer [upsert-entity]] #_[iol-ion.tx :refer [upsert-entity]]
[auto-ap.datomic :refer [conn]])) [auto-ap.datomic :refer [conn]]))
(println "hi") (println "hi")
@@ -14,22 +14,21 @@
(defn reset-inference [] (defn reset-inference []
(doseq [p (->> (doseq [p (->>
(dc/q '[:find ?t (dc/q '[:find ?t
:where [?t :transaction/recommended-account]] :where [?t :transaction/recommended-account]]
(dc/db conn)) (dc/db conn))
(map (fn [[t]] (map (fn [[t]]
`(upsert-entity [:upsert-entity {:db/id t
~{:db/id t :transaction/recommended-account nil
:transaction/recommended-account nil :tranasction/vendor-confidence nil
:tranasction/vendor-confidence nil :transaction/account-confidence nil
:transaction/account-confidence nil :transaction/recommended-vendor nil}]))
:transaction/recommended-vendor nil})))
(partition-all 100))] (partition-all 100))]
(dc/transact conn {:tx-data p}))) @(dc/transact conn p)))
(defn read-inference [] (defn read-inference []
(with-open [reader (io/reader "/mnt/data/dev2/ml-test/inference-outcome.csv")] (with-open [reader (io/reader "data/inference-outcome.csv")]
(->> (csv/read-csv reader) (->> (csv/read-csv reader)
(into [] (into []
(comp (comp
@@ -43,7 +42,7 @@
(defn apply-inference [inference] (defn apply-inference [inference]
(doseq [p (->> inference (partition-all 100))] (doseq [p (->> inference (partition-all 100))]
(dc/transact conn {:tx-data p}))) @(dc/transact conn p)))
(defn check-applied-inference [] (defn check-applied-inference []
@@ -69,3 +68,20 @@
(dc/db conn)) (dc/db conn))
(shuffle) (shuffle)
(take 10)))) (take 10))))
(defn random-infer []
(let [n (rand-int 1000)
vs (into [] (map first (dc/q '[:find ?v :in $ :where [?v :vendor/name]] (dc/db conn))))
as (into [] (map first (dc/q '[:find ?v :in $ :where [?v :account/name]] (dc/db conn))))]
(->>
(dc/qseq {:query '[:find ?t
:where [?t :transaction/client]]
:args [(dc/db conn)]})
(map first)
(take n)
(map (fn [t]
{:db/id t
:transaction/recommended-account (rand-nth as)
:transaction/account-confidence (double (/ (double (rand-int 100)) 100.0))
:transaction/recommended-vendor (rand-nth vs)})))))

View File

@@ -0,0 +1,83 @@
;; This buffer is for Clojure experiments and evaluation.
;; Press C-j to evaluate the last expression.
;; You can also press C-u C-j to evaluate the expression and pretty-print its result.
(dc/pull (dc/db conn) [:db/id] [:client/code "NGAK"])
(user/init-repl)
(require '[auto-ap.datomic :refer [pull-attr pull-many]])
(csv/write-csv *out*
(let [clients (pull-many (dc/db conn) '[:db/id :client/code :client/locations] [[:client/code "NGAK"] [:client/code "NGOP"] #_#_[:client/code "NGRV"] [:client/code "NGE1"]])
account->numeric-code (into {} (seq (dc/q '[:find ?i ?n
:in $ [?a ...]
:where [?i ?a ?n]]
(dc/db conn) [:account/numeric-code :bank-account/numeric-code])))
account->name (let [lookup (into {} (seq (dc/q '[:find ?i ?n
:in $
:where [?i :account/name ?n]]
(dc/db conn))))]
(fn [x]
(or (lookup x) "Bank Account")))
account->location (into {} (seq (dc/q '[:find ?i ?l
:in $
:where [?i :account/location ?l]]
(dc/db conn))))
used-accounts (into #{} (for [c clients
d (dc/index-range (dc/db conn)
:journal-entry-line/client+account+location+date
[(:db/id c)]
[(inc (:db/id c))])
:let [[_ account] (:v d)]]
account))]
(for [client clients
:let [all-entries (->> (dc/index-pull (dc/db conn)
{:index :avet
:selector [:db/id :journal-entry-line/running-balance :journal-entry-line/client+account+location+date]
:start [:journal-entry-line/client+account+location+date [(:db/id client)]]
:reverse false
:limit 1})
(take-while (fn [curr]
(= (first (:journal-entry-line/client+account+location+date curr))
(:db/id client)))))
account-lookup (->> all-entries
(reduce
(fn [acc curr]
(let [[client account location date] (:journal-entry-line/client+account+location+date curr)
numeric-code (account->numeric-code account)]
(assoc acc (format "%d-%d-%s-%s" client numeric-code location (atime/unparse-local (clj-time.coerce/to-date-time date) atime/iso-date)) (:journal-entry-line/running-balance curr))))
(sorted-map)))]
a (sort-by account->numeric-code used-accounts)
:let [numeric-code (account->numeric-code a)]
l (or (account->location a) (:client/locations client))
delta (range -60 0)
:let [date (t/plus (c/to-date-time #inst "2023-06-12T08:00:00") (t/days delta))
date-str (atime/unparse-local date atime/iso-date)]
:when (and numeric-code (>= numeric-code 40000))]
(conj [(:client/code client) l numeric-code (account->name a) date-str] (with-precision 2
(double (.setScale (bigdec (or
(some->> (subseq account-lookup >= (format "%d-%d-%s-%s" (:db/id client) numeric-code l "2000-01-01") <= (format "%d-%d-%s-%s" (:db/id client) numeric-code l date-str))
last
last)
0.0))
2 java.math.RoundingMode/HALF_UP))))))
)
(dc/q '[:find (count ?x)
:where [?x :journal-entry-line/dirty true]]
(dc/db conn))
(let [clients (map first
(dc/q '[:find (pull ?c [:client/code :db/id])
:where [?c :client/code]]
(dc/db conn)))]
(doseq [[c i] (map vector clients (range))]
(let [db (dc/db conn)
accounts-needing-rebuild (auto-ap.ledger/accounts-needing-rebuild db (:db/id c))]
(when (seq accounts-needing-rebuild)
(println "C" c "needs" (count accounts-needing-rebuild) "built")
#_(refresh-running-balance-accounts accounts-needing-rebuild clients c i db)
#_(mu/log ::client-completed)))))
,

View File

@@ -20,16 +20,16 @@
(:jwt-secret env) (:jwt-secret env)
{:alg :hs512})) {:alg :hs512}))
(defn oauth [{{:strs [code]} :query-params {:strs [host]} :headers}] (defn oauth [{{:strs [code state]} :query-params {:strs [host]} :headers :as request}]
(try (try
(let [auth (-> "https://accounts.google.com/o/oauth2/token" (let [auth (-> "https://accounts.google.com/o/oauth2/token"
(http/post (http/post
{:form-params {"client_id" google-client-id {:form-params {"client_id" google-client-id
"client_secret" google-client-secret "client_secret" google-client-secret
"code" code "code" code
"redirect_uri" (str (:scheme env) "://" host "/api/oauth") "redirect_uri" (str (:scheme env) "://" host "/api/oauth")
"grant_type" "authorization_code"} "grant_type" "authorization_code"}
:as :json}) :as :json})
:body) :body)
token (:access_token auth) token (:access_token auth)
@@ -59,14 +59,13 @@
(let [jwt (jwt/sign auth (let [jwt (jwt/sign auth
(:jwt-secret env) (:jwt-secret env)
{:alg :hs512})] {:alg :hs512})]
{:status 301 {:status 301
:headers {"Location" (str "/?jwt=" jwt)} :headers {"Location" (str (or (not-empty state) "/") "?jwt=" jwt)}
:session {:identity (dissoc auth :exp)}}) :session {:identity (dissoc auth :exp)}})
{:status 401 {:status 401
:body "Couldn't authenticate"})) :body "Couldn't authenticate"}))
(catch Exception e (catch Exception e
(log/warn e ) (log/warn e)
{:status 401 {:status 401
:body (str "Couldn't authenticate " (.toString e))}))) :body (str "Couldn't authenticate " (.toString e))})))

View File

@@ -8,16 +8,17 @@
(defn wrap-secure [handler] (defn wrap-secure [handler]
(fn [request] (fn [request]
(cond (authenticated? request) (cond (authenticated? request)
(handler request) (handler request)
(get (:headers request) "hx-request")
{:status 401
:headers {"hx-redirect" (str "/login?"
(url/map->query {"redirect-to" (:uri request)}))}}
(get (:headers request) "hx-request") :else
{:status 401 {:status 302
:headers {"hx-redirect" "/login"}} :headers {"Location" (str "/login?"
(url/map->query {"redirect-to" (:uri request)}))}})))
:else
{:status 302
:headers {"Location" "/login" }})))
(defn wrap-admin [handler] (defn wrap-admin [handler]
(fn [request] (fn [request]
@@ -26,12 +27,14 @@
(do (do
(alog/warn ::unauthenticated) (alog/warn ::unauthenticated)
{:status 302 {:status 302
:headers {"Location" "/login"}})))) :headers {"Location" (str "/login?"
(url/map->query {"redirect-to" (:uri request)}))}}))))
(defn wrap-client-redirect-unauthenticated [handler] (defn wrap-client-redirect-unauthenticated [handler]
(fn [request] (fn [request]
(let [response (handler request)] (let [response (handler request)]
(if (= 401 (get response :status)) (if (= 401 (get response :status))
(-> response (-> response
(assoc-in [:headers "hx-redirect"] "/login/")) (assoc-in [:headers "hx-redirect"] (str "/login?"
(url/map->query {"redirect-to" (:uri request)}))))
response)))) response))))

View File

@@ -126,16 +126,17 @@ function initCompanyDropdown() {
(defn active-client [{:keys [identity params] :as request}] (defn active-client [{:keys [identity params] :as request}]
(let [client-id (some-> (or (:search-client params) (get params "search-client")) not-empty Long/parseLong)] (let [client-id (some-> (or (:search-client params) (get params "search-client")) not-empty Long/parseLong)]
(println (format "HERE CLIENT ID '%s'" client-id))
(when client-id (when client-id
(assert-can-see-client identity client-id)) (assert-can-see-client identity client-id))
(let [new-session (assoc (:session request) :client (let [new-session (assoc (:session request) :client
(when client-id (when client-id
(dc/pull (dc/db conn) [:db/id :client/name :client/code] client-id)))] (dc/pull (dc/db conn) [:db/id :client/name :client/code] client-id)))]
(assoc (assoc
(html-response (html-response
(dropdown {:client (:client new-session) (dropdown {:client (:client new-session)
:identity identity})) :identity identity}))
:session :session
new-session new-session
:headers :headers
{"hx-trigger" "clientSelected"})))) {"hx-trigger" "clientSelected"}))))

View File

@@ -150,69 +150,90 @@
(defn main-aside-nav- [] (defn main-aside-nav- []
[:ul {:class "space-y-2"} [:ul {:class "space-y-2"}
[:li [:li
(menu-button- {:icon svg/pie} (menu-button- {:icon svg/pie
"Dashboard")] :href "/"}
[:li "Dashboard")]
(menu-button- {:aria-controls "dropdown-invoices", [:li
:data-collapse-toggle "dropdown-invoices" (menu-button- {:aria-controls "dropdown-invoices",
:icon svg/accounting-invoice-mail} :data-collapse-toggle "dropdown-invoices"
"Invoices") :icon svg/accounting-invoice-mail}
(sub-menu- {:id "dropdown-invoices"} "Invoices")
(menu-button- {:href "http://google.com"} (sub-menu- {:id "dropdown-invoices"}
"All") (menu-button- {:href (bidi/path-for client-routes/routes
(menu-button- {:href "http://google.com"} :invoices)}
"Paid") "All")
(menu-button- {:href "http://google.com"} (menu-button- {:href (bidi/path-for client-routes/routes
"Unpaid") :paid-invoices)}
(menu-button- {:href "http://google.com"} "Paid")
"Voided")) (menu-button- {:href (bidi/path-for client-routes/routes
] :unpaid-invoices)}
[:li "Unpaid")
(menu-button- {:aria-controls "dropdown-sales", (menu-button- {:href (bidi/path-for client-routes/routes
:data-collapse-toggle "dropdown-sales" :voided-invoices)}
:icon svg/receipt-register-1} "Voided"))]
"Sales") [:li
(sub-menu- {:id "dropdown-sales"} (menu-button- {:aria-controls "dropdown-sales",
(menu-button- {:href "Sales"} "Sales") :data-collapse-toggle "dropdown-sales"
(menu-button- {:href "Sales"} "Expected Deposits") :icon svg/receipt-register-1}
(menu-button- {:href "Sales"} "Cash Shifts") "Sales")
(menu-button- {:href "Sales"} "Tenders"))] (sub-menu- {:id "dropdown-sales"}
[:li (menu-button- {:href (bidi/path-for client-routes/routes
(menu-button- {:aria-controls "dropdown-payments" :sales-orders)} "Sales")
:data-collapse-toggle "dropdown-payments" (menu-button- {:href (bidi/path-for client-routes/routes
:icon svg/payments} :expected-deposits)} "Expected Deposits")
"Payments") #_(menu-button- {:href "Sales"} "Cash Shifts")
(sub-menu- {:id "dropdown-payments"} #_(menu-button- {:href "Sales"} "Tenders"))]
(menu-button- {:href "Sales"} "All") [:li
(menu-button- {:href "Sales"} "Pending") (menu-button- {:aria-controls "dropdown-payments"
(menu-button- {:href "Sales"} "Cleared") :data-collapse-toggle "dropdown-payments"
(menu-button- {:href "Sales"} "Voided"))] :icon svg/payments}
"Payments")
(sub-menu- {:id "dropdown-payments"}
(menu-button- {:href (bidi/path-for client-routes/routes
:payments)} "All")
(menu-button- {:href (bidi/path-for client-routes/routes
:payments)} "Pending")
(menu-button- {:href (bidi/path-for client-routes/routes
:payments)} "Cleared")
(menu-button- {:href (bidi/path-for client-routes/routes
:payments)} "Voided"))]
[:li [:li
(menu-button- {:aria-controls "dropdown-transactions" (menu-button- {:aria-controls "dropdown-transactions"
:data-collapse-toggle "dropdown-transactions" :data-collapse-toggle "dropdown-transactions"
:icon svg/bank} :icon svg/bank}
"Transactions") "Transactions")
(sub-menu- {:id "dropdown-transactions"} (sub-menu- {:id "dropdown-transactions"}
(menu-button- {:href "Sales"} "All") (menu-button- {:href (bidi/path-for client-routes/routes
(menu-button- {:href "Sales"} "Unapproved") :transactions)} "All")
(menu-button- {:href "Sales"} "Client Review") (menu-button- {:href (bidi/path-for client-routes/routes
(menu-button- {:href "Sales"} "Approved") :unapproved-transactions)} "Unapproved")
(menu-button- {:href "Sales"} "Insights"))] (menu-button- {:href (bidi/path-for client-routes/routes
[:li :requires-feedback-transactions)} "Client Review")
(menu-button- {:aria-controls "dropdown-ledger" (menu-button- {:href (bidi/path-for client-routes/routes
:data-collapse-toggle "dropdown-ledger" :approved-transactions)} "Approved")
:icon svg/receipt} (menu-button- {:href (bidi/path-for ssr-routes/only-routes
"Ledger") :transaction-insights)} "Insights"))]
(sub-menu- {:id "dropdown-ledger"} [:li
(menu-button- {:href "Sales"} "Register") (menu-button- {:aria-controls "dropdown-ledger"
(menu-button- {:href "Sales"} "Profit & Loss") :data-collapse-toggle "dropdown-ledger"
(menu-button- {:href "Sales"} "Profit & Loss Detail") :icon svg/receipt}
(menu-button- {:href "Sales"} "Cash Flows") "Ledger")
(menu-button- {:href "Sales"} "Balance Sheet") (sub-menu- {:id "dropdown-ledger"}
(menu-button- {:href "Sales"} "External Ledger Import"))]]) (menu-button- {:href (bidi/path-for client-routes/routes
:ledger)} "Register")
(menu-button- {:href (bidi/path-for client-routes/routes
:profit-and-loss)} "Profit & Loss")
(menu-button- {:href (bidi/path-for client-routes/routes
:profit-and-loss-detail)} "Profit & Loss Detail")
(menu-button- {:href (bidi/path-for client-routes/routes
:cash-flows)} "Cash Flows")
(menu-button- {:href (bidi/path-for client-routes/routes
:balance-sheet)} "Balance Sheet")
(menu-button- {:href (bidi/path-for client-routes/routes
:external-import-ledger)} "External Ledger Import"))]])
(defn company-aside-nav- [] (defn company-aside-nav- []
@@ -301,3 +322,5 @@
(menu-button- {:href (bidi/path-for ssr-routes/only-routes (menu-button- {:href (bidi/path-for ssr-routes/only-routes
:admin-ezcater-xls) :admin-ezcater-xls)
:hx-boost "true"} "EZCater XLS Import"))]]) :hx-boost "true"} "EZCater XLS Import"))]])

View File

@@ -1,50 +1,45 @@
(ns auto-ap.ssr.components.navbar (ns auto-ap.ssr.components.navbar
(:require (:require
[auto-ap.datomic :refer [conn pull-attr]] [auto-ap.graphql.utils :refer [is-admin?]]
[auto-ap.client-routes :as client-routes2]
[auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.company-dropdown :as cd] [auto-ap.ssr.company-dropdown :as cd]
[auto-ap.ssr.components.buttons :refer [icon-button-]] [auto-ap.ssr.components.buttons :refer [icon-button-]]
[auto-ap.ssr.components.user-dropdown :as user-dropdown]
[auto-ap.ssr.svg :as svg] [auto-ap.ssr.svg :as svg]
[bidi.bidi :as bidi] [bidi.bidi :as bidi]))
[datomic.api :as dc]
[hiccup2.core :as hiccup]
[auto-ap.ssr.components.user-dropdown :as user-dropdown]))
(defn navbar- [{:keys [client identity]}] (defn navbar- [{:keys [client identity]}]
[:nav {:class "fixed z-30 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700"} [:nav {:class "fixed z-30 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700"}
[:div {:class "px-3 py-3 lg:px-5 lg:pl-3"} [:div {:class "px-3 py-3 lg:px-5 lg:pl-3"}
[:div {:class "flex items-center justify-between"} [:div {:class "flex items-center justify-between"}
[:div {:class "flex items-center justify-start"} [:div {:class "flex items-center justify-start"}
[:button { :aria-controls "left-nav", :id "left-nav-toggle" :type "button", :class "inline-flex items-center p-2 mt-2 ml-2 mr-2 text-sm text-gray-500 rounded-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"} [:button {:aria-controls "left-nav", :id "left-nav-toggle" :type "button", :class "inline-flex items-center p-2 mt-2 ml-2 mr-2 text-sm text-gray-500 rounded-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"}
[:span {:class "sr-only"} "Open sidebar"] [:span {:class "sr-only"} "Open sidebar"]
[:svg {:class "w-6 h-6", :aria-hidden "true", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"} [:svg {:class "w-6 h-6", :aria-hidden "true", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
[:path {:clip-rule "evenodd", :fill-rule "evenodd", :d "M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"}]]] [:path {:clip-rule "evenodd", :fill-rule "evenodd", :d "M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"}]]]
[:a {:href "/" :class "flex ml-2 md:mr-24"} [:a {:href "/" :class "flex ml-2 md:mr-24"}
[:img {:src "/img/logo-big2.png", :class "h-10 mr-16", :alt "Integreat logo"}] [:img {:src "/img/logo-big2.png", :class "h-10 mr-16", :alt "Integreat logo"}]]]
]
]
[:div {:class "flex items-center gap-4"} [:div {:class "flex items-center gap-4"}
[:button.mt-1.lg:w-96.relative.hidden.lg:block {:class "bg-gray-50 hover:bg-gray-200 dark:hover:bg-gray-700 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 w-full pl-10 py-4 pr-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 gap-4 "
:hx-get (bidi/path-for ssr-routes/only-routes
:search)
:hx-target "#modal-holder"
:hx-swap "outerHTML"}
[:div {:class "absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-500"}
[:div.w-4.h-4 svg/search]
[:span.ml-2 "Search"]]]
[:div {:class "hidden mr-3 -mb-1 sm:block"}
[:span]]
(icon-button-
{:id "toggleSidebarMobileSearch", :type "button", :class "p-2 text-gray-500 rounded-lg lg:hidden hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
:hx-get (bidi/path-for ssr-routes/only-routes
:search)
:hx-target "#modal-holder"
:hx-swap "outerHTML"}
svg/search)
(cd/dropdown {:client client :identity identity})
(when (is-admin? identity)
[:button.mt-1.lg:w-96.relative.hidden.lg:block {:class "bg-gray-50 hover:bg-gray-200 dark:hover:bg-gray-700 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 w-full pl-10 py-4 pr-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 gap-4 "
:hx-get (bidi/path-for ssr-routes/only-routes
:search)
:hx-target "#modal-holder"
:hx-swap "outerHTML"}
[:div {:class "absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-500"}
[:div.w-4.h-4 svg/search]
[:span.ml-2 "Search"]]])
[:div {:class "hidden mr-3 -mb-1 sm:block"}
[:span]]
(icon-button-
{:id "toggleSidebarMobileSearch", :type "button", :class "p-2 text-gray-500 rounded-lg lg:hidden hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
:hx-get (bidi/path-for ssr-routes/only-routes
:search)
:hx-target "#modal-holder"
:hx-swap "outerHTML"}
svg/search)
(cd/dropdown {:client client :identity identity})
(user-dropdown/dropdown {:identity identity}) (user-dropdown/dropdown {:identity identity})]]]])
]]]])

View File

@@ -20,7 +20,7 @@
:admin-history (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page))) :admin-history (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page)))
:admin-history-search (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page))) :admin-history-search (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/page)))
:admin-history-inspect (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/inspect))) :admin-history-inspect (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/inspect)))
:active-client (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin company-dropdown/active-client))) :active-client (wrap-client-redirect-unauthenticated (wrap-secure (wrap-secure company-dropdown/active-client)))
:company-dropdown-search-results :company-dropdown-search-results
(wrap-client-redirect-unauthenticated (wrap-secure company-dropdown/dropdown-search-results)) (wrap-client-redirect-unauthenticated (wrap-secure company-dropdown/dropdown-search-results))
:company (wrap-client-redirect-unauthenticated (wrap-secure company/page)) :company (wrap-client-redirect-unauthenticated (wrap-secure company/page))
@@ -35,11 +35,11 @@
:company-reports (wrap-client-redirect-unauthenticated (wrap-secure company-reports/page)) :company-reports (wrap-client-redirect-unauthenticated (wrap-secure company-reports/page))
:company-reports-table (wrap-client-redirect-unauthenticated (wrap-secure company-reports/table)) :company-reports-table (wrap-client-redirect-unauthenticated (wrap-secure company-reports/table))
:company-reports-delete (wrap-client-redirect-unauthenticated (wrap-admin company-reports/delete-report)) :company-reports-delete (wrap-client-redirect-unauthenticated (wrap-admin company-reports/delete-report))
:transaction-insights (wrap-client-redirect-unauthenticated (wrap-secure insights/page)) :transaction-insights (wrap-client-redirect-unauthenticated (wrap-admin insights/page))
:transaction-insight-table (wrap-client-redirect-unauthenticated (wrap-secure insights/insight-table)) :transaction-insight-table (wrap-client-redirect-unauthenticated (wrap-admin insights/insight-table))
:transaction-insight-rows (wrap-client-redirect-unauthenticated (wrap-secure insights/transaction-rows)) :transaction-insight-rows (wrap-client-redirect-unauthenticated (wrap-admin insights/transaction-rows))
:transaction-insight-approve (wrap-client-redirect-unauthenticated (wrap-secure insights/approve)) :transaction-insight-approve (wrap-client-redirect-unauthenticated (wrap-admin insights/approve))
:transaction-insight-explain (wrap-client-redirect-unauthenticated (wrap-secure insights/explain)) :transaction-insight-explain (wrap-client-redirect-unauthenticated (wrap-admin insights/explain))
:admin-ezcater-xls (wrap-client-redirect-unauthenticated (wrap-admin ezcater-xls/page)) :admin-ezcater-xls (wrap-client-redirect-unauthenticated (wrap-admin ezcater-xls/page))
:search (wrap-client-redirect-unauthenticated (wrap-secure search/dialog-contents))}) :search (wrap-client-redirect-unauthenticated (wrap-secure search/dialog-contents))})

View File

@@ -205,7 +205,7 @@
(defn page [{:keys [identity matched-route session] :as request}] (defn page [{:keys [identity matched-route session] :as request}]
(base-page (base-page
request request
(com/page {:nav (com/admin-aside-nav) (com/page {:nav (com/main-aside-nav)
:active-client (:client (:session request)) :active-client (:client (:session request))
:identity (:identity request) :identity (:identity request)
:app-params {:hx-get (bidi/path-for ssr-routes/only-routes :app-params {:hx-get (bidi/path-for ssr-routes/only-routes

View File

@@ -1,23 +1,21 @@
(ns auto-ap.views.components.layouts (ns auto-ap.views.components.layouts
(:require (:require
[auto-ap.events :as events] [auto-ap.events :as events]
[auto-ap.forms :as forms]
[auto-ap.forms.builder :as form-builder]
[auto-ap.routes :as routes] [auto-ap.routes :as routes]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.subs :as subs] [auto-ap.subs :as subs]
[auto-ap.views.components.modal :as modal] [auto-ap.views.components.modal :as modal]
[auto-ap.views.components.search :as search] [auto-ap.views.components.search :as search]
[auto-ap.views.components.vendor-dialog :as vendor-dialog] [auto-ap.views.components.vendor-dialog :as vendor-dialog]
[auto-ap.views.utils [auto-ap.views.utils
:refer [active-when :refer [active-when appearing dispatch-event-with-propagation login-url]]
appearing
dispatch-event-with-propagation
login-url]]
[bidi.bidi :as bidi] [bidi.bidi :as bidi]
[clojure.string :as str] [clojure.string :as str]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[reagent.core :as r] [reagent.core :as r]
[auto-ap.forms.builder :as form-builder] [vimsical.re-frame.cofx.inject :as inject]))
[vimsical.re-frame.cofx.inject :as inject]
[auto-ap.forms :as forms]))
(defn navbar-drop-down [{:keys [ class]} _] (defn navbar-drop-down [{:keys [ class]} _]
(let [!child (r/atom nil)] (let [!child (r/atom nil)]
@@ -47,19 +45,19 @@
(defn login-dropdown [] (defn login-dropdown []
(let [user (re-frame/subscribe [::subs/user])] (let [user (re-frame/subscribe [::subs/user])]
(if @user (if @user
[navbar-drop-down {:header [:span [:span.icon [:i.fa.fa-user] ] [navbar-drop-down {:header [:span [:span.icon [:i.fa.fa-user]]
[:span (:user/name @user)]] [:span (:user/name @user)]]
:id ::account} :id ::account}
[:div [:div
[:a {:class "navbar-item" [:a {:class "navbar-item"
:href (bidi/path-for routes/routes :reports)} "My company"] :href (bidi/path-for auto-ap.ssr-routes/only-routes :company)} "My company"]
[:a.dropdown-item {:on-click (dispatch-event-with-propagation [::vendor-dialog/started {}])} "New Vendor"] [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::vendor-dialog/started {}])} "New Vendor"]
[:a.dropdown-item {:on-click (dispatch-event-with-propagation [::vendor-dialog/edit {}])} "Edit Vendor"] [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::vendor-dialog/edit {}])} "Edit Vendor"]
(when (= "admin" (:user/role @user)) (when (= "admin" (:user/role @user))
[:a {:class "navbar-item" :href (bidi/path-for routes/routes :admin)} "Administration"]) [:a {:class "navbar-item" :href (bidi/path-for routes/routes :admin)} "Administration"])
[:hr {:class "navbar-divider"}] [:hr {:class "navbar-divider"}]
[:a.navbar-item {:on-click (fn [e] (.preventDefault e) (re-frame/dispatch [::events/logout]))} "Logout"]]] [:a.navbar-item {:on-click (fn [e] (.preventDefault e) (re-frame/dispatch [::events/logout]))} "Logout"]]]
[:a.navbar-item {:href login-url} "Login"]))) [:a.navbar-item {:href (login-url)} "Login"])))
(re-frame/reg-sub (re-frame/reg-sub
::client-search ::client-search

View File

@@ -1,7 +1,8 @@
(ns auto-ap.views.pages.login (ns auto-ap.views.pages.login
(:require (:require
[auto-ap.views.utils :refer [login-url]] [auto-ap.views.utils :refer [login-url]]
[re-frame.core :as re-frame])) [re-frame.core :as re-frame]
[cemerick.url :as url]))
(re-frame/reg-sub (re-frame/reg-sub
::logout-reason ::logout-reason
@@ -20,6 +21,6 @@
[:img {:src "/img/logo-big.png"}] [:img {:src "/img/logo-big.png"}]
[:div [:div
[:a.button.is-large.is-primary {:href login-url} "Login with Google"]]] [:a.button.is-large.is-primary {:href (doto (login-url (get (:query (url/url (.-location js/window))) "redirect-to")) println)} "Login with Google"]]]
[:p.has-text-gray [:p.has-text-gray
"Copyright Integreat 2018"]]]]]]) "Copyright Integreat 2018"]]]]]])

View File

@@ -17,6 +17,6 @@
[:a {:on-click (fn [] [:a {:on-click (fn []
(re-frame/dispatch-sync [::relogin]) (re-frame/dispatch-sync [::relogin])
true) true)
:href login-url} :href (login-url)}
"here"] "here"]
" to try again."]]) " to try again."]])

View File

@@ -21,47 +21,47 @@
user @(re-frame/subscribe [::subs/user]) user @(re-frame/subscribe [::subs/user])
client @(re-frame/subscribe [::subs/client])] client @(re-frame/subscribe [::subs/client])]
[:div [:div
[:div [:p.menu-label "Type"] [:div [:p.menu-label "Type"]
[:ul.menu-list [:ul.menu-list
[:li.menu-item [:li.menu-item
[:a.item {:href (bidi/path-for routes/routes :transactions) [:a.item {:href (bidi/path-for routes/routes :transactions)
:class [(active-when ap = :transactions)]} :class [(active-when ap = :transactions)]}
[:span {:class "icon" :style {:font-size "25px"}}] [:span {:class "icon" :style {:font-size "25px"}}]
[:span {:class "name"} "All"]]] [:span {:class "name"} "All"]]]
[:li.menu-item [:li.menu-item
[:a.item {:href (bidi/path-for routes/routes :unapproved-transactions) [:a.item {:href (bidi/path-for routes/routes :unapproved-transactions)
:class [(active-when ap = :unapproved-transactions)]} :class [(active-when ap = :unapproved-transactions)]}
[:span {:class "icon icon-task-list-text-1" :style {:font-size "25px"}}] [:span {:class "icon icon-task-list-text-1" :style {:font-size "25px"}}]
[:span {:class "name"} "Unapproved"]]] [:span {:class "name"} "Unapproved"]]]
[:li.menu-item [:li.menu-item
[:a.item {:href (bidi/path-for routes/routes :requires-feedback-transactions) [:a.item {:href (bidi/path-for routes/routes :requires-feedback-transactions)
:class [(active-when ap = :requires-feedback-transactions)]} :class [(active-when ap = :requires-feedback-transactions)]}
[:span {:class "icon icon-task-list-question" :style {:font-size "25px"}}] [:span {:class "icon icon-task-list-question" :style {:font-size "25px"}}]
[:span {:class "name"} "Client Review"]]] [:span {:class "name"} "Client Review"]]]
[:li.menu-item [:li.menu-item
[:a.item {:href (bidi/path-for routes/routes :approved-transactions) [:a.item {:href (bidi/path-for routes/routes :approved-transactions)
:class [(active-when ap = :approved-transactions)]} :class [(active-when ap = :approved-transactions)]}
[:span {:class "icon icon-task-list-check-1" :style {:font-size "25px"}}] [:span {:class "icon icon-task-list-check-1" :style {:font-size "25px"}}]
[:span {:class "name"} "Approved"]]] [:span {:class "name"} "Approved"]]]
[:li.menu-item [:li.menu-item
[:a.item {:href (bidi/path-for routes/routes :excluded-transactions) [:a.item {:href (bidi/path-for routes/routes :excluded-transactions)
:class [(active-when ap = :excluded-transactions)]} :class [(active-when ap = :excluded-transactions)]}
[:span {:class "icon icon-task-list-disable" :style {:font-size "25px"}}] [:span {:class "icon icon-task-list-disable" :style {:font-size "25px"}}]
[:span {:class "name"} "Excluded"]]] [:span {:class "name"} "Excluded"]]]
(when @(re-frame/subscribe [::subs/is-admin?])
[:li.menu-item [:li.menu-item
[:a.item {:href (bidi/path-for ssr-routes/only-routes :transaction-insights) [:a.item {:href (bidi/path-for ssr-routes/only-routes :transaction-insights)
:class [(active-when ap = :transaction-insights)]} :class [(active-when ap = :transaction-insights)]}
[:span {:class "icon icon-task-list-disable" :style {:font-size "25px"}}] [:span {:class "icon icon-task-list-disable" :style {:font-size "25px"}}]
[:span {:class "name"} "Insights"]]] [:span {:class "name"} "Insights"]]])]]
]]
(when client (when client
[:<> [:<>
[:p.menu-label "Bank Account"] [:p.menu-label "Bank Account"]
@@ -71,7 +71,6 @@
:value @(re-frame/subscribe [::data-page/filter data-page :bank-account]) :value @(re-frame/subscribe [::data-page/filter data-page :bank-account])
:bank-accounts @(re-frame/subscribe [::subs/bank-accounts])}]]]) :bank-accounts @(re-frame/subscribe [::subs/bank-accounts])}]]])
[:p.menu-label "Financial Account"] [:p.menu-label "Financial Account"]
[:div [:div
[search-backed-typeahead {:search-query (fn [i] [search-backed-typeahead {:search-query (fn [i]
@@ -79,7 +78,7 @@
{:query i {:query i
:client-id (:id client)} :client-id (:id client)}
[:name :id]]) [:name :id]])
:entity->text (fn [x ] (str (:numeric-code x) " - " (:name x))) :entity->text (fn [x] (str (:numeric-code x) " - " (:name x)))
:type "typeahead-v3" :type "typeahead-v3"
:on-change #(re-frame/dispatch [::data-page/filter-changed data-page :account (some-> % (select-keys [:name :id :numeric-code]))]) :on-change #(re-frame/dispatch [::data-page/filter-changed data-page :account (some-> % (select-keys [:name :id :numeric-code]))])
:value @(re-frame/subscribe [::data-page/filter data-page :account])}]] :value @(re-frame/subscribe [::data-page/filter data-page :account])}]]
@@ -94,13 +93,11 @@
:on-change #(re-frame/dispatch [::data-page/filter-changed data-page :vendor %]) :on-change #(re-frame/dispatch [::data-page/filter-changed data-page :vendor %])
:value @(re-frame/subscribe [::data-page/filter data-page :vendor])}]] :value @(re-frame/subscribe [::data-page/filter data-page :vendor])}]]
[:p.menu-label "Amount"] [:p.menu-label "Amount"]
[:div [:div
[number-filter [number-filter
{:on-change-event [::data-page/filter-changed data-page :amount-range] {:on-change-event [::data-page/filter-changed data-page :amount-range]
:value @(re-frame/subscribe [::data-page/filter data-page :amount-range])}]] :value @(re-frame/subscribe [::data-page/filter data-page :amount-range])}]]
[:p.menu-label "Date Range"] [:p.menu-label "Date Range"]
[:div [:div
@@ -108,20 +105,19 @@
{:on-change-event [::data-page/filter-changed data-page :date-range] {:on-change-event [::data-page/filter-changed data-page :date-range]
:value @(re-frame/subscribe [::data-page/filter data-page :date-range])}]] :value @(re-frame/subscribe [::data-page/filter data-page :date-range])}]]
[:p.menu-label "Location"] [:p.menu-label "Location"]
[:div.field [:div.field
[:div.control [:input.input {:placeholder "SC" [:div.control [:input.input {:placeholder "SC"
:style {:width "3em"} :style {:width "3em"}
:value @(re-frame/subscribe [::data-page/filter data-page :location]) :value @(re-frame/subscribe [::data-page/filter data-page :location])
:on-change (dispatch-value-change [::data-page/filter-changed data-page :location ])} ]]] :on-change (dispatch-value-change [::data-page/filter-changed data-page :location])}]]]
[:p.menu-label "Description"] [:p.menu-label "Description"]
[:div [:div
[:div.field [:div.field
[:div.control [:input.input {:placeholder "CHECK 123 ABC" [:div.control [:input.input {:placeholder "CHECK 123 ABC"
:value @(re-frame/subscribe [::data-page/filter data-page :description]) :value @(re-frame/subscribe [::data-page/filter data-page :description])
:on-change (dispatch-value-change [::data-page/filter-changed data-page :description])} ]]]] :on-change (dispatch-value-change [::data-page/filter-changed data-page :description])}]]]]
(when-let [exact-match-id @(re-frame/subscribe [::data-page/filter data-page :exact-match-id])] (when-let [exact-match-id @(re-frame/subscribe [::data-page/filter data-page :exact-match-id])]
[:div [:div
[:p.menu-label "Specific Transaction"] [:p.menu-label "Specific Transaction"]
@@ -129,7 +125,6 @@
[:button.delete.is-small {:on-click [:button.delete.is-small {:on-click
(dispatch-event [::data-page/filter-changed data-page :exact-match-id nil])}]]]) (dispatch-event [::data-page/filter-changed data-page :exact-match-id nil])}]]])
(when (= "admin" (:user/role user)) (when (= "admin" (:user/role user))
[:<> [:<>

View File

@@ -57,10 +57,16 @@
(when (apply f (into [active-page] rest)) " is-active")) (when (apply f (into [active-page] rest)) " is-active"))
(def login-url (defn login-url
(let [client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com" ([] (login-url nil))
redirect-uri (js/encodeURI (str (.-origin (.-location js/window)) "/api/oauth"))] ([next]
(str "https://accounts.google.com/o/oauth2/auth?access_type=online&client_id=" client-id "&redirect_uri=" redirect-uri "&response_type=code&max_auth_age=0&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile")))
(let [client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com"
redirect-uri (js/encodeURI (str (.-origin (.-location js/window)) "/api/oauth"
))]
(str "https://accounts.google.com/o/oauth2/auth?access_type=online&client_id=" client-id "&redirect_uri=" redirect-uri "&response_type=code&max_auth_age=0&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile"
(when next
(str "&state=" (js/encodeURIComponent next)))))))
(defn dispatch-value-change [event] (defn dispatch-value-change [event]
(fn [e] (fn [e]
@@ -251,7 +257,7 @@
(defn query-params [] (defn query-params []
(reduce-kv (reduce-kv
(fn [result k v] (fn [result k v]
(assoc result (keyword k) (edn/read-string v))) (assoc result (keyword k) (try (edn/read-string v) (catch js/Error e v))))
{} {}
(:query (cemerick.url/url (.-location js/window))))) (:query (cemerick.url/url (.-location js/window)))))