Undoes old way of doing forms in favor of context version. Much easier

This commit is contained in:
2022-07-17 17:32:47 -07:00
parent a17f587ff3
commit b6962c5bfa
17 changed files with 782 additions and 1393 deletions

View File

@@ -4,16 +4,16 @@
[auto-ap.subs :as subs]
[auto-ap.views.components.layouts :refer [side-bar]]
[auto-ap.views.components.typeahead :refer [typeahead-v3]]
[auto-ap.views.utils :refer [dispatch-event multi-field]]
[auto-ap.views.utils :refer [dispatch-event multi-field with-user]]
[clojure.spec.alpha :as s]
[clojure.string :as str]
[re-frame.core :as re-frame]))
[re-frame.core :as re-frame]
[auto-ap.forms.builder :as form-builder]
[vimsical.re-frame.cofx.inject :as inject]))
(def types [:dividend :expense :asset :liability :equity :revenue])
(def applicabilities [:global :optional :customized])
(re-frame/reg-sub
::request
:<- [::forms/form ::form]
@@ -51,103 +51,99 @@
(re-frame/reg-event-fx
::edited
[(forms/triggers-saved ::form :upsert-account)]
(fn [{:keys [db]} [_ {:keys [upsert-account]}]]))
(fn [_ [_ _]]))
(re-frame/reg-event-db
::add-client-override
[(forms/in-form ::form)]
(fn [form]
(update form :data (fn [data]
(-> data
(update :client-overrides conj (:new-client-override data))
(dissoc :new-client-override))))))
(re-frame/reg-event-fx
::saving
(fn [{:keys [db]} _]
[with-user (re-frame/inject-cofx ::inject/sub [::request]) ]
(fn [{:keys [user] ::keys [request]} _]
(when @(re-frame/subscribe [::can-submit])
(let [{{:keys [id type name numeric-code account-set]} :data :as data} @(re-frame/subscribe [::forms/form ::form])]
{:db (forms/loading db ::form )
:graphql
{:token (-> db :user)
(let [_ @(re-frame/subscribe [::forms/form ::form])]
{:graphql
{:owns-state {:single ::form}
:token user
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "UpsertAccount"}
:venia/queries [{:query/data [:upsert-account
{:account @(re-frame/subscribe [::request])}
{:account request}
[:id :type :name :account-set :numeric-code :location :applicability [:client-overrides [:name :id [:client [:id :name]]]]]]}]}
:on-success [::edited]
:on-error [::forms/save-error ::form]}}))))
(def account-form (forms/vertical-form {:can-submit [::can-submit]
:change-event [::forms/change ::form]
:submit-event [::saving]
:id ::form}))
(defn form [_]
(let [{error :error account :data } @(re-frame/subscribe [::forms/form ::form])
{:keys [form-inline field field-holder raw-field error-notification submit-button]} account-form]
^{:key (:id account)}
(let [{account :data } @(re-frame/subscribe [::forms/form ::form])]
[side-bar {:on-close (dispatch-event [::forms/form-closing ::form])}
(form-inline {:title (if (:id account)
"Edit account"
"Add account")}
[:<>
[form-builder/builder {:can-submit [::can-submit]
:change-event [::forms/change ::form]
:submit-event [::saving]
:id ::form}
[form-builder/section {:title (if (:id account)
"Edit account"
"Add account")}
[form-builder/field
"Account Set"
[:input.input {:type "text"
:field :account-set
:disabled (boolean (:id account))
:spec ::entity/account-set}]]
(field "Account Set"
[:input.input {:type "text"
:field :account-set
:disabled (boolean (:id account))
:spec ::entity/account-set}])
[form-builder/field
"Code"
[:input.input {:type "text"
:field :numeric-code
:disabled (boolean (:id account))
:spec ::entity/numeric-code}]]
(field "Code"
[:input.input {:type "text"
:field :numeric-code
:disabled (boolean (:id account))
:spec ::entity/numeric-code}])
[form-builder/field
"Name"
[:input.input {:type "text"
:field :name
:spec ::entity/name}]]
(field "Name"
[:input.input {:type "text"
:field :name
:spec ::entity/name}])
(field-holder "Account Type"
[:div.select
(raw-field
[:select {:type "select"
:field :type
:spec (set types)}
(map (fn [l]
[:option {:value (name l)} (str/capitalize (name l))]) types)])])
[form-builder/vertical-control
"Account Type"
[:div.select
[form-builder/raw-field
[:select {:type "select"
:field :type
:spec (set types)}
(map (fn [l]
[:option {:value (name l)} (str/capitalize (name l))]) types)]]]]
(field "Location"
[:input.input.known-field.location {:type "text"
:field :location
:spec ::entity/location}])
[form-builder/field
"Location"
[:input.input.known-field.location {:type "text"
:field :location
:spec ::entity/location}]]
[:h2.subtitle "Client"]
(field-holder "Applicability"
[:div.select
(raw-field
[:select {:type "select"
:field :applicability
:spec (set applicabilities)}
(map (fn [l]
[:option {:value (name l)} (str/capitalize (name l))]) applicabilities)])])
(field "Customizations"
[multi-field {:type "multi-field"
:field [:client-overrides]
:template [[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients])
:style {:width "13em"}
:entity->text :name
:type "typeahead-v3"
:field [:client]}]
[:input.input {:type "text"
:style {:width "15em"}
:placeholder "Bubblegum"
:field [:name]}]
]}])
(error-notification)
[form-builder/section {:title "Client"}
[:h2.subtitle "Client"]
[form-builder/vertical-control
"Applicability"
[:div.select
[form-builder/raw-field
[:select {:type "select"
:field :applicability
:spec (set applicabilities)}
(map (fn [l]
[:option {:value (name l)} (str/capitalize (name l))]) applicabilities)]]]]
[form-builder/field
"Customizations"
[multi-field {:type "multi-field"
:field [:client-overrides]
:template [[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients])
:style {:width "13em"}
:entity->text :name
:type "typeahead-v3"
:field [:client]}]
[:input.input {:type "text"
:style {:width "15em"}
:placeholder "Bubblegum"
:field [:name]}]
]}]]]
[form-builder/error-notification]
(submit-button "Save")])]))
[form-builder/submit-button "Save"]]]]))

View File

@@ -3,13 +3,16 @@
[auto-ap.entities.transaction-rule :as entity]
[auto-ap.events :as events]
[auto-ap.forms :as forms]
[auto-ap.forms.builder :as form-builder]
[auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.views.components.level :refer [left-stack]]
[auto-ap.views.components.button-radio :refer [button-radio]]
[auto-ap.views.components.expense-accounts-field
:as expense-accounts-field
:refer [expense-accounts-field]]
[auto-ap.views.components.layouts :as layouts]
[auto-ap.views.components.money-field :refer [money-field]]
[auto-ap.views.components.typeahead :refer [typeahead-v3]]
[auto-ap.views.components.typeahead.vendor
:refer [search-backed-typeahead]]
@@ -29,7 +32,6 @@
::default-note
:<- [::forms/form ::form]
(fn [{{:keys [client description amount-lte amount-gte dom-lte dom-gte]} :data}]
(str/join " - " (filter (complement str/blank?)
[(:code client)
description
@@ -185,18 +187,21 @@
:on-success [::changed [:vendor-preferences]]}]})))
(re-frame/reg-event-db
::changed
(forms/change-handler ::form
(fn [data field value]
(cond (and (= [:vendor-preferences] field)
value
(expense-accounts-field/can-replace-with-default? (:accounts data)))
[[:accounts] (expense-accounts-field/default-account (:accounts data)
(:default-account value)
(:total data)
[])]
:else
[]))))
::changed
(forms/change-handler ::form
(fn [data field value]
(cond (and (= [:vendor-preferences] field)
value
(expense-accounts-field/can-replace-with-default? (:accounts data)))
[[:accounts] (expense-accounts-field/default-account (:accounts data)
(:default-account value)
(:total data)
[])]
(= [:client] field)
[[:bank-account] nil]
:else
[]))))
(re-frame/reg-event-fx
::saving
@@ -223,9 +228,8 @@
(re-frame/reg-event-fx
::updated
[(forms/triggers-stop ::form)]
(fn [{:keys [db]} [_ {:keys [rule-saved]} result]]
{:db (forms/start-form db ::form {:client @(re-frame/subscribe [::subs/client])})
:dispatch (conj rule-saved (:upsert-transaction-rule result))}))
(fn [{:keys [db]} [_ {:keys [rule-saved]} _]]
{:db (forms/start-form db ::form {:client @(re-frame/subscribe [::subs/client])})}))
(re-frame/reg-event-fx
::succeeded-test
@@ -238,10 +242,6 @@
;; VIEWS
(def rule-form (forms/vertical-form {:can-submit [::can-submit]
:change-event [::changed]
:submit-event [::saving ]
:id ::form}))
(re-frame/reg-event-fx
::mounted
@@ -263,131 +263,127 @@
(defn form-contents [params]
[layouts/side-bar {:on-close (dispatch-event [::forms/form-closing ::form ])}
(let [{:keys [data id]} @(re-frame/subscribe [::forms/form ::form])
{:keys [form-inline field raw-field error-notification submit-button ]} rule-form
default-note @(re-frame/subscribe [::default-note])
test-state @(re-frame/subscribe [::status/single ::test])]
^{:key id}
(form-inline (assoc params :title "New Transaction Rule")
[:<>
(let [{:keys [data id]} @(re-frame/subscribe [::forms/form ::form])
default-note @(re-frame/subscribe [::default-note])
test-state @(re-frame/subscribe [::status/single ::test])]
[form-builder/builder {:can-submit [::can-submit]
:change-event [::changed]
:submit-event [::saving ]
:id ::form}
[form-builder/section {:title "Transaction Rule"}
[form-builder/field {:required? true}
"Client"
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients])
:auto-focus true
:entity->text :name
:type "typeahead-v3"
:field [:client]
:spec ::entity/client}]]
(field "Client"
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients])
:auto-focus true
:entity->text :name
:type "typeahead-v3"
:field [:client]
:spec ::entity/client}])
[form-builder/field
"Bank account"
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/real-bank-accounts-for-client (:client data)])
:entity->text :name
:type "typeahead-v3"
:field [:bank-account]
:spec ::entity/bank-account}]]
(with-meta
(field "Bank account"
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/real-bank-accounts-for-client (:client data)])
:entity->text :name
:type "typeahead-v3"
:field [:bank-account]
:spec ::entity/bank-account}])
;; TODO this forces unmounting when client changes, since it is an "uncontorlled" input
{:key (str "client-" (:id (:client data)))})
#_[form-builder/field
"Yodlee Merchant"
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/yodlee-merchants])
:entity->text #(str (:name %) " - " (:yodlee-id %))
:type "typeahead-v3"
:field [:yodlee-merchant]}]]
(field "Yodlee Merchant"
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/yodlee-merchants])
:entity->text #(str (:name %) " - " (:yodlee-id %))
:type "typeahead-v3"
:field [:yodlee-merchant]}])
[form-builder/field
[:span "Description (" [:a {:href "https://regex101.com" :target "_new"} "regex tester"] ")" ]
[:input.input {:type "text"
:field [:description]
:spec ::entity/description}]]
(field [:span "Description (" [:a {:href "https://regex101.com" :target "_new"} "regex tester"] ")" ]
[:input.input {:type "text"
:field [:description]
:spec ::entity/description}])
[:div.field
[:p.help "Amount"]
[left-stack
[form-builder/raw-field
[money-field {:type "money"
:placeholder ">="
:field [:amount-gte]
:spec ::entity/amount-gte}]]
"-"
[form-builder/raw-field
[money-field {:type "money"
:placeholder "<="
:field [:amount-lte]
:spec ::entity/amount-lte}]]]]
[:div.field
[:p.help "Amount"]
[:div.control
[:div.columns
[:div.column
(raw-field
[:input.input {:type "number"
:placeholder ">="
:field [:amount-gte]
:spec ::entity/amount-gte
:step "0.01"}])]
[:div.column
(raw-field
[:input.input {:type "number"
:placeholder "<="
:field [:amount-lte]
:spec ::entity/amount-lte
:step "0.01"}])]]]]
[:div.field
[:p.help "Day of Month"]
[:div.control
[:div.columns
[:div.column
[form-builder/raw-field
[:input.input {:type "number"
:placeholder ">="
:field [:dom-gte]
:spec ::entity/dom-gte
:precision 0
:step "1"}]]]
[:div.column
[form-builder/raw-field
[:input.input {:type "number"
:placeholder "<="
:field [:dom-lte]
:spec ::entity/dom-lte
:precision 0
:step "1"}]]]]]]
[:div.field
[:p.help "Day of Month"]
[:div.control
[:div.columns
[:div.column
(raw-field
[:input.input {:type "number"
:placeholder ">="
:field [:dom-gte]
:spec ::entity/dom-gte
:precision 0
:step "1"}])]
[:div.column
(raw-field
[:input.input {:type "number"
:placeholder "<="
:field [:dom-lte]
:spec ::entity/dom-lte
:precision 0
:step "1"}])]]]]
[:h2.title.is-4 "Outcomes"]
[:h2.title.is-4 "Outcomes"]
[form-builder/field "Assign Vendor"
[search-backed-typeahead {:search-query (fn [i]
[:search_vendor
{:query i}
[:name :id]])
:type "typeahead-v3"
:field [:vendor]}]]
(field "Assign Vendor"
[search-backed-typeahead {:search-query (fn [i]
[:search_vendor
{:query i}
[:name :id]])
:type "typeahead-v3"
:field [:vendor]}])
[form-builder/raw-field
[expense-accounts-field {:type "expense-accounts"
:descriptor "account asssignment"
:percentage-only? true
:client (:client data)
:locations (into ["Shared"] @(re-frame/subscribe [::subs/locations-for-client-or-bank-account (:id (:client data)) (:id (:bank-account data))]))
:max 100
:field [:accounts]}]]
(with-meta
(field nil
[expense-accounts-field {:type "expense-accounts"
:descriptor "account asssignment"
:percentage-only? true
:client (:client data)
:locations (into ["Shared"] @(re-frame/subscribe [::subs/locations-for-client-or-bank-account (:id (:client data)) (:id (:bank-account data))]))
:max 100
:field [:accounts]}])
{:key (str (some-> data :vendor :id str) "-" (some-> data :client :id str))})
[form-builder/field
"Approval Status"
[button-radio
{:type "button-radio"
:field [:transaction-approval-status]
:options [[:unapproved "Unapproved"]
[:requires-feedback "Client Review"]
[:approved "Approved"]
[:excluded "Excluded from Ledger"]]}]]
(field "Approval Status"
[button-radio
{:type "button-radio"
:field [:transaction-approval-status]
:options [[:unapproved "Unapproved"]
[:requires-feedback "Client Review"]
[:approved "Approved"]
[:excluded "Excluded from Ledger"]]}])
[form-builder/field "Note"
[:input.input {:type "text"
:field [:note]
:placeholder default-note
:spec (s/nilable ::entity/note)}]]
(field "Note"
[:input.input {:type "text"
:field [:note]
:placeholder default-note
:spec (s/nilable ::entity/note)}])
[:div.is-divider]
(error-notification)
[:div.columns
[:div.column
[:a.button.is-medium.is-fullwidth.is-outlined {:on-click (dispatch-event [::test-clicked])
:disabled (status/disabled-for test-state)
:class (status/class-for test-state)}
"Test Rule"]]
[:div.column
(submit-button "Save")]]]))])
[:div.is-divider]
[form-builder/error-notification]
[:div.columns
[:div.column
[:a.button.is-medium.is-fullwidth.is-outlined {:on-click (dispatch-event [::test-clicked])
:disabled (status/disabled-for test-state)
:class (status/class-for test-state)}
"Test Rule"]]
[:div.column
[form-builder/submit-button {:class ["is-fullwidth"]}
"Save"]]]]])])
(defn form [_]
(r/create-class

View File

@@ -1,49 +1,14 @@
(ns auto-ap.views.pages.admin.users.form
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[clojure.string :as str]
[auto-ap.subs :as subs]
[auto-ap.events :as events]
[auto-ap.entities.clients :as entity]
[auto-ap.views.components.address :refer [address-field]]
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
[auto-ap.views.components.layouts :refer [side-bar-layout]]
[auto-ap.views.utils :refer [login-url dispatch-value-change bind-field horizontal-field dispatch-event with-user]]
[auto-ap.views.components.grid :as grid]
[auto-ap.utils :refer [by replace-if]]
[cljs.reader :as edn]
[auto-ap.routes :as routes]
[bidi.bidi :as bidi]
[auto-ap.status :as status]
[auto-ap.forms :as forms]
[auto-ap.views.components.modal :as modal]))
(re-frame/reg-sub
::can-submit
(fn [db]
true))
(re-frame/reg-event-db
::changed
(forms/change-handler ::form
(fn [data field value]
[])))
(re-frame/reg-event-db
::add-client
[(forms/in-form ::form)]
(fn [form [_ d]]
(let [client (get @(re-frame/subscribe [::subs/clients-by-id])
(get-in form [:data :adding-client]))]
(update-in form [:data :clients] conj client ))))
(re-frame/reg-event-db
::remove-client
[(forms/in-form ::form)]
(fn [form [_ d]]
(update-in form [:data :clients] #(filter (fn [c] (not= (:id c) d)) %))))
(:require
[auto-ap.entities.clients :as entity]
[auto-ap.forms :as forms]
[auto-ap.forms.builder :as form-builder]
[auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.views.components.modal :as modal]
[auto-ap.views.components.typeahead :refer [typeahead-v3]]
[auto-ap.views.utils :refer [dispatch-event multi-field with-user]]
[re-frame.core :as re-frame]))
(re-frame/reg-event-fx
::saving
@@ -56,7 +21,7 @@
:operation/name "EditUser"}
:venia/queries [{:query/data [:edit-user
{:edit-user (-> (:data db)
(update :clients #(map :id %))
(update :clients #(map (comp :id :client) %))
(select-keys #{:id :name :clients :role}))}
[:id :name :role [:clients [:id :name]]]]}]}
:on-success [::saved]}}))
@@ -64,66 +29,54 @@
(re-frame/reg-event-fx
::saved
(forms/triggers-stop ::form)
(fn [{:keys [db]} [_ {:keys [edit-user]}]]
(fn [_ _]
{:dispatch [::modal/modal-closed]}))
(def user-form (forms/vertical-form {:submit-event [::saving]
:change-event [::changed]
:can-submit [::can-submit]
:id ::form}))
(defn form []
(let [{:keys [data active? error id]} @(re-frame/subscribe [::forms/form ::form])
{:keys [form-inline field raw-field error-notification submit-button]} user-form]
(form-inline {}
[:<>
(field "Name"
[:input.input {:type "text"
:field [:name]
:spec ::entity/name}])
[:div.field
[:p.help "Role"]
[:div.control
[:div.select
[raw-field
[:select {:type "select"
:field [:role]}
[:option {:value ":none"} "None"]
[:option {:value ":user"} "User"]
[:option {:value ":manager"} "Manager"]
[:option {:value ":power_user"} "Power User"]
[:option {:value ":admin"} "Admin"]]]]]]
(when (#{":user" ":manager" ":power_user"} (:role data))
[:div.field
[:p.help "Clients"]
[:div.control
[:div.field.has-addons
[:div.control
[:div.select
[raw-field
[:select {:type "select"
:field [:adding-client]}
[:option]
(let [used-clients (set (map :id (:clients data)))]
(for [{:keys [id name] :as client} @(re-frame/subscribe [::subs/clients])
:when (not (used-clients id))]
^{:key id} [:option {:value id} name]))]]]]
[:p.control
[:button.button.is-primary {:on-click (dispatch-event [::add-client])} "Add"]]]
(let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form])
clients @(re-frame/subscribe [::subs/clients])]
[form-builder/builder {:submit-event [::saving]
:id ::form}
[form-builder/field
"Name"
[:input.input {:type "text"
:field [:name]
:spec ::entity/name}]]
[:div.field
[:p.help "Role"]
[:div.control
[:div.select
[form-builder/raw-field
[:select {:type "select"
:field [:role]}
[:option {:value ":none"} "None"]
[:option {:value ":user"} "User"]
[:option {:value ":manager"} "Manager"]
[:option {:value ":power_user"} "Power User"]
[:option {:value ":admin"} "Admin"]]]]]]
(when (#{":user" ":manager" ":power_user"} (:role data))
[form-builder/field
"Client"
[multi-field {:type "multi-field"
:field [:clients]
:template [[typeahead-v3 {:entities clients
:entity->text :name
:style {:width "13em"}
:type "typeahead-v3"
:field [:client]}]]}]])
[form-builder/hidden-submit-button]]))
[:ul
(for [{:keys [id name]} (:clients data)]
^{:key id} [:li name [:a.icon {:on-click (dispatch-event [::remove-client id])} [:i.fa.fa-times ]]])]]])])))
(re-frame/reg-event-fx
::editing
(fn [{:keys [db]} [_ d]]
{:db (-> db
(forms/start-form ::form d))
:dispatch [::modal/modal-requested {:title (str "Edit user " (:name d))
:body [form]
:cancel? false
:confirm {:value "Save"
{:db (-> db
(forms/start-form ::form (update d :clients #(map (fn [x] {:client x}) %))))
:dispatch [::modal/modal-requested {:title (str "Edit user " (:name d))
:body [form]
:cancel? false
:confirm {:value "Save"
:status-from [::status/single ::form]
:class "is-primary"
:on-click (dispatch-event [::saving])
:class "is-primary"
:on-click (dispatch-event [::saving])
:close-event [::status/completed ::form]}}]}))

View File

@@ -7,59 +7,51 @@
[auto-ap.views.components.typeahead.vendor
:refer [search-backed-typeahead]]
[auto-ap.views.utils :refer [dispatch-event]]
[re-frame.core :as re-frame]))
[re-frame.core :as re-frame]
[auto-ap.forms.builder :as form-builder]))
(re-frame/reg-sub
::can-submit
:<- [::forms/form ::form]
(fn [{:keys [data]}]
(println data)
(and (:from data)
(:to data))))
(def merge-form (forms/vertical-form {:submit-event [::save]
:change-event [::forms/change ::form]
:can-submit [::can-submit]
:id ::form}))
(defn form []
(let [_ @(re-frame/subscribe [::forms/form ::form])
{:keys [form-inline field]} merge-form]
(form-inline {}
[:<>
(field "Form Vendor (will be deleted)"
[search-backed-typeahead {:search-query (fn [i]
[:search_vendor
{:query i}
[:name :id]])
:type "typeahead-v3"
:auto-focus true
:field [:from]}])
[form-builder/builder {:submit-event [::save]
:can-submit [::can-submit]
:id ::form}
[form-builder/field "Form Vendor (will be deleted)"
[search-backed-typeahead {:search-query (fn [i]
[:search_vendor
{:query i}
[:name :id]])
:type "typeahead-v3"
:auto-focus true
:field [:from]}]]
(field "To Vendor"
[search-backed-typeahead {:search-query (fn [i]
[:search_vendor
{:query i}
[:name :id]])
:type "typeahead-v3"
:field [:to]}])])))
[form-builder/field "To Vendor"
[search-backed-typeahead {:search-query (fn [i]
[:search_vendor
{:query i}
[:name :id]])
:type "typeahead-v3"
:field [:to]}]]
[form-builder/hidden-submit-button]])
(re-frame/reg-event-fx
::show
(fn [{:keys [db]} _]
{:dispatch [::modal/modal-requested {:title "Merge Vendors"
:body [form]
{:dispatch [::modal/modal-requested {:title "Merge Vendors"
:body [form]
:confirm {:value "Merge"
:status-from [::status/single ::form]
:class "is-primary"
:on-click (dispatch-event [::save])
:can-submit [::can-submit]
:close-event [::status/completed ::form]}}]
:db (forms/start-form db ::form {})}
))
:db (forms/start-form db ::form {})}))
(re-frame/reg-event-fx
::complete

View File

@@ -1,415 +0,0 @@
(ns auto-ap.views.pages.admin.yodlee
(:require [re-frame.core :as re-frame]
[auto-ap.forms :as forms]
[reagent.core :as reagent]
[clojure.string :as str]
[cljs-time.format :as f]
[cljs-time.core :as time]
[auto-ap.subs :as subs]
[auto-ap.events :as events]
[auto-ap.entities.clients :as entity]
[auto-ap.views.components.layouts :refer [side-bar-layout]]
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
[auto-ap.views.components.address :refer [address-field]]
[auto-ap.views.utils :refer [login-url dispatch-event dispatch-value-change bind-field horizontal-field str->date date->str with-user]]
[auto-ap.views.components.modal :as modal]
[auto-ap.status :as status]
[cljs.reader :as edn]
[auto-ap.routes :as routes]
[bidi.bidi :as bidi]))
(re-frame/reg-sub
::authentication
(fn [db]
(-> db ::yodlee :authentication)))
(re-frame/reg-sub
::can-submit
(fn [db]
true))
(re-frame/reg-sub
::loading?
(fn [db]
(-> db ::yodlee :loading?)))
(re-frame/reg-sub
::accounts
(fn [db]
(-> db ::yodlee :accounts)))
(re-frame/reg-sub
::accounts-loading?
(fn [db]
(-> db ::yodlee :accounts-loading?)))
(re-frame/reg-sub
::provider-accounts-loading?
(fn [db]
(-> db ::provider-accounts-loading?)))
(re-frame/reg-sub
::provider-accounts
(fn [db]
(-> db ::provider-accounts)))
(re-frame/reg-event-fx
::authenticate-with-yodlee
(fn [{:keys [db]} _]
{:db (assoc-in db [::yodlee :loading?] true)
:http {:token (:user db)
:method :get
:headers {"Content-Type" "application/edn"}
:uri (str "/api/yodlee/fastlink")
:on-success [::authenticated]
:on-error [::save-error]}}))
(re-frame/reg-event-fx
::mounted
(fn [{:keys [db]} _]
{:db (-> db
(assoc ::yodlee {:provider-accounts-loading? true})
(assoc ::save-error nil)
(assoc ::provider-accounts [])
(assoc ::provider-accounts-loading? true))
:http {:token (:user db)
:method :get
:headers {"Content-Type" "application/edn"}
:uri (str "/api/yodlee/provider-accounts")
:on-success [::got-provider-accounts]
:on-error [::save-error]}}))
(re-frame/reg-event-fx
::kicked
(fn [{:keys [db]} [_ id state]]
{:dispatch [::mounted]}))
(re-frame/reg-event-fx
::kicked
(fn [{:keys [db]} [_ id state]]
{:dispatch [::mounted]}))
(re-frame/reg-event-fx
::kick
(fn [{:keys [db]} [_ id]]
{:http {:token (:user db)
:method :post
:headers {"Content-Type" "application/edn"}
:uri (str "/api/yodlee/provider-accounts/" id)
:on-success [::kicked id :kicked]
:on-error [::kicked id :errored]}}))
(re-frame/reg-event-fx
::got-accounts
(fn [{:keys [db]} [_ accounts]]
{:db (-> db
(assoc-in [::yodlee :accounts] accounts)
(assoc-in [::yodlee :accounts-loading?] false))}))
(re-frame/reg-event-fx
::got-provider-accounts
(fn [{:keys [db]} [_ accounts]]
{:db (-> db
(assoc-in [::provider-accounts] accounts)
(assoc-in [::provider-accounts-loading?] false))}))
(re-frame/reg-event-fx
::authenticated
(fn [{:keys [db]} [_ authentication]]
{:db (-> db
(assoc-in [::yodlee :authentication] authentication)
(assoc-in [::yodlee :loading?] false))}))
(re-frame/reg-event-fx
::authenticated-mfa
(fn [{:keys [db]} [_ provider-account-id authentication]]
{:db (-> db
(assoc-in [::yodlee :authentication] authentication)
(assoc-in [::yodlee :loading?] false)
(forms/stop-form [::mfa-form provider-account-id]))}))
(re-frame/reg-event-fx
::save-error
(fn [{:keys [db]} [_ authentication]]
{:db (assoc db ::load-error "error")}))
(defn yodlee-link-button []
[:div
(let [authentication @(re-frame/subscribe [::authentication])
loading? @(re-frame/subscribe [::loading?])]
(if authentication
[:div
"Authentication successful!"
[:form {:action (:url authentication) :method "POST"}
[:input {:type "hidden"
:name "rsession"
:value (:session authentication)}]
[:input {:type "hidden"
:name "token"
:value (:token authentication)}]
[:input {:type "hidden"
:name "app"
:value (:app authentication)}]
[:input {:type "hidden"
:name "redirectReq"
:value "true"}]
[:button.button.is-primary [:span [:span.icon [:i.fa.fa-external-link]] " Go to yodlee"]]]]
[:button.button.is-primary {:class (if loading? "is-loading" "") :on-click (dispatch-event [::authenticate-with-yodlee])} "Authenticate with Yodlee"]))])
(defn yodlee-date->date [d]
(try
(some-> d
(str->date (:date-time-no-ms f/formatters))
)
(catch js/Error e
nil)))
(defn yodlee-date->str [d]
(try
(or (some-> d
(str->date (:date-time-no-ms f/formatters))
date->str)
"N/A")
(catch js/Error e
"N/A")))
(defn yodlee-accounts-table [accounts]
(let [bank-accounts @(re-frame/subscribe [::bank-accounts-by-yodlee-account-id])]
[:div
[:table.table
[:thead
[:tr
[:th "Account Name"]
[:th "Account Number"]
[:th "Yodlee Account Number"]
[:th "Balance"]
[:th "Yodlee Status"]
[:th "Usage"]]]
[:tbody
(for [account accounts]
^{:key (:id account)} [:tr
[:td (:accountName account)]
[:td (:accountNumber account)]
[:td (:id account)]
[:td.has-text-right (:amount (:balance account))]
[:td (str/join ", " (map :additionalStatus (:dataset account)))]
[:td
(when-let [bank-accounts (get bank-accounts (:id account))]
[:div.tags
(for [bank-account bank-accounts]
^{:key (:id bank-account)}
[:div.tag (:name bank-account) " (" (:code bank-account) ")"])])]
])]]]))
(re-frame/reg-event-fx
::reauthenticate-mfa
[with-user ]
(fn [{:keys [user db]} [_ provider-account-id ]]
{:db (forms/loading db [::mfa-form provider-account-id])
:http {:token user
:method :post
:headers {"Content-Type" "application/edn"}
:uri (str "/api/yodlee/reauthenticate/" provider-account-id )
:body {"loginForm"
{"row"
(->> (get-in db [::forms/forms [::mfa-form provider-account-id]])
:data
:login
(sort-by (fn [[k v]] k))
(map second)
(map (fn [row]
{"field"
(mapv (fn [[k v]]
{"id" k
"value" v})
row)})))}
"field"
(mapv (fn [[k v]]
{"id" k
"value" v})
(:mfa (:data (get-in db [::forms/forms [::mfa-form provider-account-id]]))))}
:on-success [::authenticated-mfa provider-account-id]
:on-error [::forms/save-error [::mfa-form provider-account-id] ]}}))
(re-frame/reg-event-fx
::provider-account-refreshed
(fn [{:keys [db]} [_ i result]]
{:db (assoc-in db [::provider-accounts] result)
:dispatch [::forms/form-closing [::refresh-provider-account i]]}))
(re-frame/reg-event-fx
::refresh-provider-account
[with-user ]
(fn [{:keys [user db]} [_ provider-account-id ]]
{:db (forms/loading db [::refresh-provider-account provider-account-id])
:http {:token user
:method :post
:headers {"Content-Type" "application/edn"}
:uri (str "/api/yodlee/provider-accounts/refresh/" provider-account-id )
:body {}
:on-success [::provider-account-refreshed provider-account-id]
:on-error [::forms/save-error [::refresh-provider-account provider-account-id] ]}}))
(re-frame/reg-event-fx
::provider-account-deleted
(fn [{:keys [db]} [_ i result]]
{:db (assoc-in db [::provider-accounts] result)
:dispatch-n [[::forms/form-closing [::refresh-provider-account i]]
[::modal/modal-closed ]]}))
(re-frame/reg-event-fx
::delete-provider-account
[with-user ]
(fn [{:keys [user db]} [_ provider-account-id ]]
{:http {:token user
:method :post
:owns-state {:single ::delete-provider-account}
:headers {"Content-Type" "application/edn"}
:uri (str "/api/yodlee/provider-accounts/delete/" provider-account-id )
:body {}
:on-success [::provider-account-deleted provider-account-id]
:on-error [::forms/save-error [::delete-provider-account provider-account-id] ]}}))
(re-frame/reg-event-fx
::delete-requested
[with-user]
(fn [{:keys [user db]} [_ account-id]]
{:dispatch
[::modal/modal-requested {:title "Delete Provider account "
:body [:div "Are you sure you want to delete provider account " account-id "?"]
:confirm {:value "Delete provider account"
:status-from [::status/single ::delete-provider-account]
:class "is-danger"
:on-click (dispatch-event [::delete-provider-account account-id])
:close-event [::status/completed ::delete-provider-account]}
:cancel? true}]}))
(defn delete-button [account-id]
[:button.button
{:on-click (dispatch-event [::delete-requested account-id])}
[:span.icon [:i.fa.fa-times]]])
(re-frame/reg-sub
::bank-accounts-by-yodlee-account-id
:<- [::subs/bank-accounts]
(fn [bank-accounts]
(group-by :yodlee-account-id bank-accounts)))
(defn yodlee-provider-accounts-table []
(let [bank-accounts @(re-frame/subscribe [::bank-accounts-by-yodlee-account-id])]
(if @(re-frame/subscribe [::provider-accounts-loading?])
[:div "Loading..."]
[:div.columns
[:div.column.is-half
(doall
(for [account @(re-frame/subscribe [::provider-accounts])
:let [{:keys [error status] :as g} @(re-frame/subscribe [::forms/form [::refresh-provider-account (:id account)]])
total-usages (mapcat (comp bank-accounts :id) (:accounts account))]]
^{:key (:id account)}
[:div.card {:style {:margin-bottom "1em"}}
[:div.card-header
[:div.card-header-title "Provider account " (:id account)]
[:div.card-header-icon
(when (seq total-usages)
[:div.tags
[:div.tag.is-primary (count total-usages) " usages"]])]
[:div.card-header-icon
[delete-button (:id account)]]
[:div.card-header-icon
(cond
(= :loading status) [:button.button.is-disabled.is-loading [:i.fa.fa-refresh]]
error [:button.button.is-disabled [:span.icon [:i.fa.fa-exclamation-triangle]]]
:else
[:button.button
{:on-click (dispatch-event [::refresh-provider-account (:id account)])}
[:span.icon [:i.fa.fa-refresh]]])]]
[:div.card-content
(if (> (some-> (-> account :dataset first :lastUpdated)
(yodlee-date->date )
(time/interval (time/now))
(time/in-days ))
1)
[:div.notification.is-info.is-light
[:div.level
[:div.level-left
[:div.level-item
[:p
"This account was last updated on "
(yodlee-date->str (-> account :dataset first :lastUpdated))
", and last attempted "
(yodlee-date->str (-> account :dataset first :lastUpdateAttempt))
"."]]]
[:div.level-right [:button.button.is-success {:on-click (dispatch-event [::kick (:id account)] )} "Sync yodlee with bank" ]]]
])
[yodlee-accounts-table (:accounts account)]
(if (not= (-> account :dataset first :additionalStatus)
"AVAILABLE_DATA_RETRIEVED")
[:div
[:div.notification.is-info.is-warning
[:div.level
[:div.level-left
[:div.level-item
"This provider account's status is '"
(-> account :dataset first :additionalStatus)
"'. If this is in error, it might help to try reauthenticating by filling out the form below."]]]]
(let [{error :error account-data :data } @(re-frame/subscribe [::forms/form [::mfa-form (:id account)]])
change-event [::forms/change [::mfa-form (:id account)]]
{:keys [form-inline field field-holder raw-field error-notification submit-button]} (forms/vertical-form {:can-submit [::can-submit]
:change-event change-event
:submit-event [::reauthenticate-mfa (:id account)]
:id [::mfa-form (:id account)]} )]
(form-inline {:title "Reauthenticate"}
[:<>
(error-notification)
(doall
(for [[row i] (map vector (-> account :loginForm last :row) (range))
f (:field row)
:let [options (map :optionValue (:option f))]]
^{:key (:id f)}
[:div
(field (:label row)
[:input.input {:type "text" :field [:login i (:id f)]}])
(if (seq options)
[:ul
(for [o options]
^{:key o}
[:li [:pre o]])])]))
(doall
(for [f (-> account :field)]
^{:key (:id f)}
(field (:label f)
[:input.input {:type "text" :mfa [:form (:id f)] :value (-> f :field first :value)}])))
(submit-button "Reauthenticate")]))])]]))]])))
(defn admin-yodlee-content []
[(with-meta
(fn []
[:div
[:h1.title "Yodlee provider accounts"]
[yodlee-provider-accounts-table]
[yodlee-link-button]])
{:component-did-mount (fn []
(re-frame/dispatch [::mounted]))})])
#_(defn admin-yodlee-page []
[side-bar-layout {:side-bar [admin-side-bar {}]
:main [admin-yodlee-content]}])