diff --git a/project.clj b/project.clj index 722a3e6d..8581919d 100644 --- a/project.clj +++ b/project.clj @@ -84,7 +84,8 @@ :jvm-opts ["-Dconfig=config/dev.edn" "--add-modules" "java.xml.bind"]} :uberjar {:prep-tasks [["cljsbuild" "once" "min"] "compile"]} :provided {:dependencies [[org.clojure/clojurescript "1.10.238"] - [reagent "0.7.0"] + [reagent "0.7.0" ] + [cljsjs/react-transition-group "2.4.0-0"] [re-frame "0.10.2"] [com.andrewmcveigh/cljs-time "0.5.2"]]}} diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index 7e3e480a..ca540a41 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -602,7 +602,11 @@ ([id q v] (println "executing graphql query" id q v) (try - (time (simplify (execute schema q v {:id id}))) + (let [result (time (simplify (execute schema q v {:id id})))] + (when (seq (:errors result)) + (throw (ex-info "GraphQL error" {:result result}))) + result) + (catch Exception e (if-let [v (:validation-error (ex-data e))] (println "validation error" v) diff --git a/src/clj/auto_ap/graphql/clients.clj b/src/clj/auto_ap/graphql/clients.clj index a49fa14a..47f5f7e8 100644 --- a/src/clj/auto_ap/graphql/clients.clj +++ b/src/clj/auto_ap/graphql/clients.clj @@ -14,7 +14,7 @@ :in ['$ '?code] :where ['[?id :client/code ?code]]} :args [(d/db (d/connect uri)) code]})) - (throw (ex-info "Client is not unique" {:validation-error "Client is not unique"})))) + (throw (ex-info "Client is not unique" {:validation-error (str "Client code '" code "' is not unique.")})))) (defn edit-client [context {:keys [edit_client new_bank_accounts] :as args} value] (assert-admin (:id context)) diff --git a/src/clj/auto_ap/routes/graphql.clj b/src/clj/auto_ap/routes/graphql.clj index faae2d6f..59394d88 100644 --- a/src/clj/auto_ap/routes/graphql.clj +++ b/src/clj/auto_ap/routes/graphql.clj @@ -21,7 +21,15 @@ :body (pr-str (ql/query (:identity r) (query-params "query") variables )) :headers {"Content-Type" "application/edn"}}) (catch Exception e - {:status 400 - :body (pr-str {:data (merge {:message (.getMessage e)} (ex-data e))}) - :headers {"Content-Type" "application/edn"}})))) + (if-let [result (:result (ex-data e))] + {:status 400 + :body (pr-str result) + :headers {"Content-Type" "application/edn"}} + (if-let [message (:validation-error (ex-data e) )] + {:status 400 + :body (pr-str {:errors [(merge {:message message} (ex-data e))]}) + :headers {"Content-Type" "application/edn"}} + {:status 500 + :body (pr-str {:errors [(merge {:message (.getMessage e)} (ex-data e))]}) + :headers {"Content-Type" "application/edn"}})))))) wrap-secure)) diff --git a/src/cljs/auto_ap/effects.cljs b/src/cljs/auto_ap/effects.cljs index 8c0c8217..711247fc 100644 --- a/src/cljs/auto_ap/effects.cljs +++ b/src/cljs/auto_ap/effects.cljs @@ -148,7 +148,7 @@ (when on-error (->> response :body - :data + :errors (dates->date-times) (conj on-error) (re-frame/dispatch))) diff --git a/src/cljs/auto_ap/views/components/layouts.cljs b/src/cljs/auto_ap/views/components/layouts.cljs index 4fbf5258..7f29dfc9 100644 --- a/src/cljs/auto_ap/views/components/layouts.cljs +++ b/src/cljs/auto_ap/views/components/layouts.cljs @@ -1,5 +1,7 @@ (ns auto-ap.views.components.layouts (:require + [cljsjs.react-transition-group] + [reagent.core :as reagent] [re-frame.core :as re-frame] [bidi.bidi :as bidi] [auto-ap.routes :as routes] @@ -8,7 +10,8 @@ [auto-ap.views.utils :refer [active-when active-when= login-url dispatch-event]] [auto-ap.views.components.vendor-dialog :refer [vendor-dialog]])) - +(def css-transition-group + (reagent/adapt-react-class js/ReactTransitionGroup.CSSTransition)) (defn login-dropdown [] (let [user (re-frame/subscribe [::subs/user]) menu (re-frame/subscribe [::subs/menu])] @@ -82,7 +85,16 @@ [:a {:class "icon", :href "https://github.com/dansup/bulma-templates"} [:i {:class "fa fa-github"}]]]]]]) -(defn side-bar-layout [{:keys [side-bar main ap bottom right-side-bar]}] +(defn appearing-side-bar [{:keys [visible?]} & children ] + (let [final-state (reagent/atom visible?)] + (fn [{:keys [visible?]} & children] + [css-transition-group {:in visible? :class-names {:exitDone "bounce animated" :exit "fadeOutRight animated" :enter "fadeInRight animated"} :timeout 300 :onEnter (fn [] (reset! final-state true )) :onExited (fn [] (reset! final-state false))} + (if (or @final-state visible?) + [:div.aside + [:div.sub-main children]] + [:div])]))) + +(defn side-bar-layout [{:keys [side-bar main ap bottom right-side-bar right-side-bar-visible?]}] (let [ap @(re-frame/subscribe [::subs/active-page]) client @(re-frame/subscribe [::subs/client])] [:div @@ -94,10 +106,7 @@ [:div {:class "column messages hero is-fullheight", :id "message-feed"} ^{:key (str "active-page-" (:name client))} [:div.inbox-messages main]] - (when right-side-bar - [:aside.fadeInRight.animated {:class "column aside is-narrow menu hero is-fullheight" :style {:animation-duration "0.75s"}} - [:div.sub-main - right-side-bar]])] + [appearing-side-bar {:visible? right-side-bar-visible?} right-side-bar]] [footer] bottom [:div#dz-hidden]])) diff --git a/src/cljs/auto_ap/views/pages/admin/clients.cljs b/src/cljs/auto_ap/views/pages/admin/clients.cljs index 1c782de9..e36abd27 100644 --- a/src/cljs/auto_ap/views/pages/admin/clients.cljs +++ b/src/cljs/auto_ap/views/pages/admin/clients.cljs @@ -2,6 +2,7 @@ (:require-macros [cljs.core.async.macros :refer [go]] [clojure.string :as str]) (:require [re-frame.core :as re-frame] + [reagent.core :as reagent] [clojure.spec.alpha :as s] [clojure.string :as str] @@ -17,6 +18,15 @@ [auto-ap.routes :as routes] [bidi.bidi :as bidi])) + + +(re-frame/reg-sub + ::loading-class + (fn [db [_ x]] + (if (get-in db [::loading x]) + "is-loading" + ""))) + (re-frame/reg-event-fx ::new (fn [{:keys [db]} [_ client-id]] @@ -35,7 +45,9 @@ (fn [{:keys [db]} _] (let [edited-client (-> (:client @(re-frame/subscribe [::subs/admin])) (dissoc :location))] - {:db (assoc-in db [:admin :client :saving?] true) + {:db (-> db + (assoc-in [::loading ::save-client] true) + (assoc-in [:admin :client :error] nil)) :graphql {:token (-> db :user) :query-obj {:venia/operation {:operation/type :mutation @@ -56,22 +68,27 @@ (fn [{:keys [db]} _] (let [new-client (-> (:new-client @(re-frame/subscribe [::subs/admin])) (dissoc :location))] - {:db (assoc-in db [:admin :client :saving?] true) - :graphql - {:token (-> db :user) - :query-obj {:venia/operation {:operation/type :mutation - :operation/name "EditClient"} - :venia/queries [{:query/data [:edit-client - {:edit-client - ;; TODO - hard code fields we want - (-> new-client - (update :bank-accounts #(seq (into % (map (fn [ba] (dissoc ba :is-new?)) (:new-bank-accounts new-client))))) - (dissoc :new-bank-accounts)) - } - [:id :name :code :email [:address [:street1 :street2 :city :state :zip]] [:bank-accounts [:id :number :check-number :name :code :bank-code :bank-name :routing]]] - ]}]} - :on-success [::save-complete] - :on-error [::save-error]}}))) + (if (s/valid? ::entity/client new-client) + + {:db (-> db (assoc-in [::loading ::save-client] true) + (assoc-in [:admin :client :error] nil)) + :graphql + {:token (-> db :user) + :query-obj {:venia/operation {:operation/type :mutation + :operation/name "EditClient"} + :venia/queries [{:query/data [:edit-client + {:edit-client + ;; TODO - hard code fields we want + (-> new-client + (update :bank-accounts #(seq (into % (map (fn [ba] (dissoc ba :is-new?)) (:new-bank-accounts new-client))))) + (dissoc :new-account) + (dissoc :new-bank-accounts)) + } + [:id :name :code :email [:address [:street1 :street2 :city :state :zip]] [:bank-accounts [:id :number :check-number :name :code :bank-code :bank-name :routing]]] + ]}]} + :on-success [::save-complete] + :on-error [::save-error]}} + {:db db})))) (re-frame/reg-event-fx ::save-complete @@ -81,6 +98,7 @@ {:dispatch [::events/modal-completed :auto-ap.views.pages.admin.clients/edit] :db (-> db (assoc-in [:admin :adding-client?] false) + (assoc-in [::loading ::save-client] nil) (update :admin dissoc :new-client) @@ -89,10 +107,10 @@ (re-frame/reg-event-db ::save-error - (fn [db [_ client]] + (fn [db [_ result]] (-> db - (assoc-in [:admin :client :saving?] false) - (assoc-in [:admin :client :error] true)))) + (assoc-in [::loading ::save-client] false) + (assoc-in [:admin :client :error] (:message (first result)))))) (re-frame/reg-event-db ::change @@ -352,179 +370,187 @@ (defn new-client-form [] - (let [new-client (:new-client @(re-frame/subscribe [::subs/admin]))] - [:div - [:form - [:section.section {:style {:padding-bottom "0.75em" :padding-top "0.75em"}} - [:h1.title "Add client"] - [:div.field - [:label.label "Name"] - [:div.control - [bind-field - [:input.input {:type "text" - :field :name - :spec ::entity/name - :event ::change-new - :subscription new-client}]]]] + (let [new-client (:new-client @(re-frame/subscribe [::subs/admin])) + error (:error (:client @(re-frame/subscribe [::subs/admin])))] + [:div + [:form + [:section.section {:style {:padding-bottom "0.75em" :padding-top "0.75em"}} + [:h1.title "Add client"] + [:div.field + [:label.label "Name"] + [:div.control + [bind-field + [:input.input {:type "text" + :field :name + :spec ::entity/name + :event ::change-new + :subscription new-client}]]]] - [:div.field - [:label.label "Client code"] - [:div.control - [bind-field - [:input.input {:type "code" - :field :code - :spec ::entity/code - :event ::change-new - :subscription new-client}]]]] + [:div.field + [:label.label "Client code"] + [:div.control + [bind-field + [:input.input {:type "code" + :field :code + :spec ::entity/code + :event ::change-new + :subscription new-client}]]]] - [:div.field - [:label.label "Email"] - [:div.control - [bind-field - [:input.input {:type "email" - :field :email - :spec ::entity/email - :event ::change-new - :subscription new-client}]]]] + [:div.field + [:label.label "Email"] + [:div.control + [bind-field + [:input.input {:type "email" + :field :email + :spec ::entity/email + :event ::change-new + :subscription new-client}]]]] - [:div.field - [:label.label "Locations"] - [:div.control - [:div.field.has-addons - [:p.control - [bind-field - [:input.input {:type "text" - :field :location - :event ::change-new - :subscription new-client}]]] - [:p.control [:button.button.is-primary {:on-click (dispatch-event [::add-new-location])} "Add"]]] - [:ul - (for [location (:locations new-client)] - ^{:key location} [:li location ])]]] - ] + [:div.field + [:label.label "Locations"] + [:div.control + [:div.field.has-addons + [:p.control + [bind-field + [:input.input {:type "text" + :field :location + :event ::change-new + :subscription new-client}]]] + [:p.control [:button.button.is-primary {:on-click (dispatch-event [::add-new-location])} "Add"]]] + [:ul + (for [location (:locations new-client)] + ^{:key location} [:li location ])]]] + ] - [:section.section {:style {:padding-bottom "0.75em" :padding-top "0.75em"}} - [:h2.subtitle "Address"] - [address-field {:field [:address] - :event ::change-new - :subscription new-client}]] - - [:section.section {:style {:padding-bottom "0.75em" :padding-top "0.75em" :background "hsl(0, 0%, 96%)"}} - [:h2.subtitle "Add bank account"] - [:label.label "General"] - [horizontal-field - nil - [:div.control - [:p.help "Account Code"] - [:div.field.has-addons.is-extended - [:p.control [:a.button.is-static (:code new-client) "-" ]] - [:p.control - [bind-field - [:input.input {:type "code" - :field [:new-account :code] - :spec ::entity/code - :event ::change-new - :subscription new-client}]]]]] - - [:div.control - [:p.help "Nickname"] - [bind-field - [:input.input {:placeholder "BOA Checking #1" - :type "text" - :field [:new-account :name] + [:section.section {:style {:padding-bottom "0.75em" :padding-top "0.75em"}} + [:h2.subtitle "Address"] + [address-field {:field [:address] :event ::change-new - :subscription new-client}]]]] + :subscription new-client}]] - [:label.label "Bank"] - [horizontal-field - nil - [:div.control - [:p.help "Bank Name"] - [bind-field - [:input.input {:placeholder "Bank of America" - :type "text" - :field [:new-account :bank-name] - :event ::change-new - :subscription new-client}]]] - [:div.control - [:p.help "Routing #"] - [bind-field - [:input.input {:placeholder "104819123" - :type "text" - :field [:new-account :routing] - :event ::change-new - :subscription new-client}]]] - [:div.control - [:p.help "Bank code"] - [bind-field - [:input.input {:placeholder "12/10123" - :type "text" - :field [:new-account :bank-code] - :event ::change-new - :subscription new-client}]]]] + [:section.section {:style {:padding-bottom "0.75em" :padding-top "0.75em" :background "hsl(0, 0%, 96%)"}} + [:h2.subtitle "Add bank account"] + [:label.label "General"] + [horizontal-field + nil + [:div.control + [:p.help "Account Code"] + [:div.field.has-addons.is-extended + [:p.control [:a.button.is-static (:code new-client) "-" ]] + [:p.control + [bind-field + [:input.input {:type "code" + :field [:new-account :code] + :spec ::entity/code + :event ::change-new + :subscription new-client}]]]]] - [:label.label "Checking account"] - [horizontal-field - nil - [:div.control - [:p.help "Account #"] - [bind-field - [:input.input {:placeholder "123456789" - :type "text" - :field [:new-account :number] - :event ::change-new - :subscription new-client}]]] - [:div.control - [:p.help "Check Number"] - [bind-field - [:input.input {:placeholder "10000" - :type "text" - :field [:new-account :check-number] - :event ::change-new - :subscription new-client}]]]] + [:div.control + [:p.help "Nickname"] + [bind-field + [:input.input {:placeholder "BOA Checking #1" + :type "text" + :field [:new-account :name] + :event ::change-new + :subscription new-client}]]]] - [:div.field - [:label.label "Yodlee Account"] - [:div.control - [bind-field - [:input.input {:placeholder "Yodlee Account #" - :type "text" - :field [:new-account :yodlee-account-id] - :event ::change-new - :subscription new-client}]]] - ] - [:div.field - [:div.control - (println (s/explain-data ::entity/bank-account (:new-account new-client))) - [:button.button.is-primary {:disabled (if (and (doto (s/valid? ::entity/bank-account (:new-account new-client)) println) - (not ((set (map :code (:new-bank-accounts new-client))) - (str (:code new-client) "-" (-> new-client :new-account :code))))) - "" - "disabled") - :on-click (dispatch-event [::add-new-new-bank-account])} "Add"]]]] + [:label.label "Bank"] + [horizontal-field + nil + [:div.control + [:p.help "Bank Name"] + [bind-field + [:input.input {:placeholder "Bank of America" + :type "text" + :field [:new-account :bank-name] + :event ::change-new + :subscription new-client}]]] + [:div.control + [:p.help "Routing #"] + [bind-field + [:input.input {:placeholder "104819123" + :type "text" + :field [:new-account :routing] + :event ::change-new + :subscription new-client}]]] + [:div.control + [:p.help "Bank code"] + [bind-field + [:input.input {:placeholder "12/10123" + :type "text" + :field [:new-account :bank-code] + :event ::change-new + :subscription new-client}]]]] - [:section.section {:style {:padding-bottom "0.75em" :padding-top "0.75em"}} - [:h2.subtitle "Bank Accounts"] - [:div.field - nil - [:div.control - [:ul - - (for [{:keys [code name number check-number id]} (:bank-accounts new-client)] - ^{:key id} [:li code ": " name]) - (for [[index {:keys [name code number check-number]}] (map vector (range) (:new-bank-accounts new-client))] - ^{:key index} [:li [:strong "* " code ": " name] [:button.button {:on-click (dispatch-event [::remove-new-bank-account index])} [:span.icon [:i.fa.fa-times]]]])]]]] - - #_(when (:saving? new-client) [:div.is-overlay {:style {"backgroundColor" "rgba(150,150,150, 0.5)"}}]) - (println (s/explain-data ::entity/client new-client)) - [:submit.button.is-large.is-primary {:disabled (if (s/valid? ::entity/client new-client) - "" - "disabled") - :on-click (dispatch-event [::save-new-client])} "Save"]]])) + [:label.label "Checking account"] + [horizontal-field + nil + [:div.control + [:p.help "Account #"] + [bind-field + [:input.input {:placeholder "123456789" + :type "text" + :field [:new-account :number] + :event ::change-new + :subscription new-client}]]] + [:div.control + [:p.help "Check Number"] + [bind-field + [:input.input {:placeholder "10000" + :type "text" + :field [:new-account :check-number] + :event ::change-new + :subscription new-client}]]]] + + [:div.field + [:label.label "Yodlee Account"] + [:div.control + [bind-field + [:input.input {:placeholder "Yodlee Account #" + :type "text" + :field [:new-account :yodlee-account-id] + :event ::change-new + :subscription new-client}]]] + ] + [:div.field + [:div.control + #_(println (s/explain-data ::entity/bank-account (:new-account new-client))) + [:button.button.is-primary {:disabled (if (and (doto (s/valid? ::entity/bank-account (:new-account new-client)) println) + (not ((set (map :code (:new-bank-accounts new-client))) + (str (:code new-client) "-" (-> new-client :new-account :code))))) + "" + "disabled") + :on-click (dispatch-event [::add-new-new-bank-account])} "Add"]]]] + + [:section.section {:style {:padding-bottom "0.75em" :padding-top "0.75em"}} + [:h2.subtitle "Bank Accounts"] + [:div.field + nil + [:div.control + [:ul + + (for [{:keys [code name number check-number id]} (:bank-accounts new-client)] + ^{:key id} [:li code ": " name]) + (for [[index {:keys [name code number check-number]}] (map vector (range) (:new-bank-accounts new-client))] + ^{:key index} [:li [:strong "* " code ": " name] [:button.button {:on-click (dispatch-event [::remove-new-bank-account index])} [:span.icon [:i.fa.fa-times]]]])]]]] + + #_(when (:saving? new-client) [:div.is-overlay {:style {"backgroundColor" "rgba(150,150,150, 0.5)"}}]) + #_(println (s/explain-data ::entity/client new-client)) + + (when error + [:div.notification.is-warning.animated.fadeInUp + error] + ) + + [:submit.button.is-large.is-primary {:disabled (if (s/valid? ::entity/client new-client) + "" + "disabled") + :on-click (dispatch-event [::save-new-client]) + :class (str @(re-frame/subscribe [::loading-class ::save-client]) (when error " animated shake"))} "Save"]]])) (defn admin-clients-page [] (let [{:keys [adding-client?]} @(re-frame/subscribe [::subs/admin])] [side-bar-layout {:side-bar [admin-side-bar {}] :main [admin-clients-content] - :right-side-bar (when adding-client? - [new-client-form])}])) + :right-side-bar-visible? adding-client? + :right-side-bar [new-client-form]}]))