diff --git a/config/dev.edn b/config/dev.edn index 9a3c54dd..f79b29ef 100644 --- a/config/dev.edn +++ b/config/dev.edn @@ -1,5 +1,6 @@ {:db {:server "localhost"} :scheme "http" + :base-url "http://localhost:3449" :solr-uri "http://localhost:8983" :solr-impl :solr :client-config {:server-type :dev-local @@ -24,21 +25,22 @@ :yodlee-cobrand-name "restserver" :yodlee-cobrand-login "sbCobda48aa19712a83c3ca4e935dd5e5d46b1a" :yodlee-cobrand-password "0a07ea32-1b5d-461b-ad0f-2752cdd77602" - :yodlee-user-login "sbMemda48aa19712a83c3ca4e935dd5e5d46b1a4" + :yodlee-user-login "G7T9kiwaG8rMiykdV4pckmQnfj4OM2pf" :yodlee-user-password "sbMemda48aa19712a83c3ca4e935dd5e5d46b1a4#123" :yodlee-base-url "https://developer.api.yodlee.com/ysl" :yodlee-app "10003600" - :yodlee-fastlink "https://node.developer.yodlee.com/authenticate/restserver/?channelAppName=restserver" + :yodlee-fastlink "https://fl4.sandbox.yodlee.com/authenticate/restserver/fastlink" :run-web? true :run-background? true :dd-env "dev" :dd-service "integreat-app" - :yodlee2-admin-user "e02b38f9-9865-4264-8e4f-6a5ac2c500b0_ADMIN" + :yodlee2-admin-user "50ec7e57-297d-4970-941e-1cb07b8dcb4e_ADMIN" :yodlee2-integreat-user "integreat-main" - :yodlee2-client-id "l6sUyK2NEq3mwopISHlFGWUcJ1U8OUQd" - :yodlee2-client-secret "wZQHoGEkv5AGG2ZH" - :yodlee2-base-url "https://development.api.yodlee.com/ysl" - :yodlee2-fastlink "https://fl4.preprod.yodlee.com/authenticate/USDevexPreProd2-195/fastlink/?channelAppName=usdevexpreprod2" + :yodlee2-test-user "sbMem5f61421aba2161" + :yodlee2-client-id "G7T9kiwaG8rMiykdV4pckmQnfj4OM2pf" + :yodlee2-client-secret "8I0mmq1wmAWSSpr9" + :yodlee2-base-url "https://sandbox.api.yodlee.com/ysl" + :yodlee2-fastlink "https://fl4.sandbox.yodlee.com/authenticate/restserver/fastlink" :square-config {"NGE1" {:square-location "SCX0Y8CTGM1S0", diff --git a/config/prod-cloud.edn b/config/prod-cloud.edn index 44b742d7..db5449f8 100644 --- a/config/prod-cloud.edn +++ b/config/prod-cloud.edn @@ -1,5 +1,6 @@ {:scheme "https" :db-name "prod-mirror2" + :base-url "https://prod-cloud.app.integreatconsult.com" :solr-uri "http://solr-prod-cloud.local:8983" :solr-impl :solr :datomic-url "datomic:ddb://us-east-1/iol-dev/dev" diff --git a/config/prod.edn b/config/prod.edn index ed829225..6629a8cd 100644 --- a/config/prod.edn +++ b/config/prod.edn @@ -1,5 +1,6 @@ {:db {:server "database"} :datomic-url "datomic:ddb://us-east-1/integreat/integreat-prod" + :base-url "https://app.integreatconsult.com" :solr-uri "http://solr-prod.local:8983" :solr-impl :solr :scheme "https" @@ -23,7 +24,6 @@ :yodlee-proxy-port 8888 :run-background? false :run-web? true - :yodlee2-admin-user "93398522-412b-470d-8400-3691392b12fb_ADMIN" :yodlee2-integreat-user "integreat-main" :yodlee2-client-id "3AATcwfPsWP1rP9oDoo4HvZhtaroGVcA" :yodlee2-client-secret "cXTBmKbGfkaBFIpM" diff --git a/deps.edn b/deps.edn deleted file mode 100644 index f8b3bdca..00000000 --- a/deps.edn +++ /dev/null @@ -1,8 +0,0 @@ -{:paths ["iol_ion/src/" "resources"] - :deps {com.cognitect/anomalies {:mvn/version "0.1.12"} - com.datomic/client-cloud {:mvn/version "1.0.123"} - com.datomic/ion {:mvn/version "1.0.62"} - clj-time/clj-time {:mvn/version "0.15.2"} - org.clojure/clojure {:mvn/version "1.10.1"} - org.clojure/data.json {:mvn/version "0.2.6"}} - :mvn/repos {"datomic-cloud" {:url "s3://datomic-releases-1fc2183a/maven/releases"}}} diff --git a/resources/input.css b/resources/input.css index 9027f220..be241fc7 100644 --- a/resources/input.css +++ b/resources/input.css @@ -68,3 +68,7 @@ .fade-out { opacity: 1.0; } + +.min-h-content { + min-height: calc(100vh - 4em); +} diff --git a/resources/public/output.css b/resources/public/output.css index ed0087b4..4015199c 100644 --- a/resources/public/output.css +++ b/resources/public/output.css @@ -1473,6 +1473,10 @@ input:checked + .toggle-bg { justify-content: space-between; } +.gap-1 { + gap: 0.25rem; +} + .gap-2 { gap: 0.5rem; } @@ -2183,6 +2187,10 @@ input:checked + .toggle-bg { opacity: 1.0; } +.min-h-content { + min-height: calc(100vh - 4em); +} + .hover\:scale-105:hover { --tw-scale-x: 1.05; --tw-scale-y: 1.05; @@ -2614,6 +2622,10 @@ input:checked + .toggle-bg { border-radius: 0.5rem; } + .sm\:p-6 { + padding: 1.5rem; + } + .sm\:py-5 { padding-top: 1.25rem; padding-bottom: 1.25rem; diff --git a/src/clj/auto_ap/routes/exports.clj b/src/clj/auto_ap/routes/exports.clj index 441f2e2a..994bbe4a 100644 --- a/src/clj/auto_ap/routes/exports.clj +++ b/src/clj/auto_ap/routes/exports.clj @@ -267,8 +267,9 @@ )) (into [["Vendor Name" "Address" "City" "State" "Zip" "Terms" "Account" "Account Code"]]))] {:body - (into (list) - data)}))) + (into [] + data) + :headers {"content-disposition" "attachment; filename=\"vendors.csv\""}}))) (defn export-ledger [{:keys [identity query-params]}] (let [start-date (or (some-> (query-params "start-date") @@ -426,7 +427,7 @@ "expected-deposit/" {#"export/?" {:get :export-expected-deposits}} "clients/" {#"export/?" {:get :export-clients}} "vendors/" {#"export/?" {:get :export-vendors} - "/company" {#"export/?" {:get :export-company-vendors}}} + "company/" {#"export" {:get :export-company-vendors}}} "ledger/" {#"export/?" {:get :export-ledger}} "accounts/" {#"export/?" {:get :export-accounts}} "transactions/" {#"export/?" {:get :export-transactions} diff --git a/src/clj/auto_ap/ssr/company.clj b/src/clj/auto_ap/ssr/company.clj new file mode 100644 index 00000000..1b9dd19d --- /dev/null +++ b/src/clj/auto_ap/ssr/company.clj @@ -0,0 +1,66 @@ +(ns auto-ap.ssr.company + (:require + [auto-ap.datomic :refer [conn]] + [auto-ap.datomic.clients :refer [full-read]] + [auto-ap.ssr.components :as com] + [auto-ap.ssr.svg :as svg] + [auto-ap.ssr.ui :refer [base-page]] + [cemerick.url :as url] + [config.core :refer [env]] + [datomic.api :as dc] + [auto-ap.ssr-routes :as ssr-routes] + [bidi.bidi :as bidi])) + +(defn please-select-client-screen* [] + [:div.grid.grid-cols-3 + (com/content-card {} + [:div.col-span-1.p-4 {:class "p-4 sm:p-6"} + [:h3 {:class "mb-4 text-xl font-semibold dark:text-white"} + "Please select a company"] + ])]) + +(defn main-content* [{:keys [client]}] + (if-not client + (please-select-client-screen*) + (let [client (dc/pull (dc/db conn) full-read (:db/id client))] + [:div.grid.grid-cols-3.gap-4 + (com/content-card {} + [:div.col-span-1.p-4 {:class "p-4 sm:p-6"} + [:h3 {:class "mb-4 text-xl font-semibold dark:text-white"} + (:client/name client)] + (when-let [address (-> client :client/address)] + [:div.flex.flex-col.gap-1.text-lg.dark:text-white.text-gray-700 + [:p (-> address :address/street1)] + [:p (-> address :address/street2)] + [:p (-> address :address/city) " " + (-> address :address/state) ", " + (-> address :address/zip)]])] + ) + (com/content-card {} + [:div.col-span-1.p-4 {:class "p-4 sm:p-6"} + [:h3 {:class "mb-4 text-xl font-semibold dark:text-white"} + "Downloads"] + [:a {:href (str (assoc (url/url (str (:base-url env) "/api/vendors/company/export")) + :query {"client" (:client/code client)}))} + (com/button {:color :primary} + "Download vendor list" + (com/button-icon {} svg/download))]])]))) + +(defn page [{:keys [identity matched-route] :as request}] + (base-page + request + (com/page {:nav (com/company-aside-nav) + :active-client (:client (:session request)) + :identity (:identity request) + :app-params { + :hx-get (bidi/path-for ssr-routes/only-routes + :company) + :hx-trigger "clientSelected from:body" + :hx-swap "outerHTML swap:300ms"}} + (com/breadcrumbs {} + [:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "My Company"]) + (main-content* {:client (:client (:session request))})) + nil)) + diff --git a/src/clj/auto_ap/ssr/company/company_1099.clj b/src/clj/auto_ap/ssr/company/company_1099.clj index e580c6bb..948aa434 100644 --- a/src/clj/auto_ap/ssr/company/company_1099.clj +++ b/src/clj/auto_ap/ssr/company/company_1099.clj @@ -230,91 +230,93 @@ (let [vendor (dc/pull (dc/db conn) '[* {:vendor/legal-entity-1099-type [:db/ident] :vendor/legal-entity-tin-type [:db/ident]}] (Long/parseLong (:vendor-id (:params request))))] ;; TODO perms (html-response - (com/modal {} - [:form {:hx-post (str (bidi/path-for ssr-routes/only-routes - :company-1099-vendor-save - :request-method :post - :vendor-id (Long/parseLong (:vendor-id (:params request))))) - :hx-target "#vendor-table" - :hx-swap "outerHTML swap:300ms"} - [:fieldset {:class "hx-disable"} - (com/modal-card {} - [:div.flex [:div.p-2 "Vendor 1099 Info"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600 (:vendor/name vendor)]] - [:div.space-y-6 - [:div.grid.grid-cols-6.gap-4 - [:h4.text-xl.border-b.col-span-6 "Address"] - [:div.col-span-6 - (com/field {:label "Street 1"} - (com/text-input {:name (path->name [:vendor/address :address/street1]) - :value (-> vendor :vendor/address :address/street1) - :placeholder "1700 Pennsylvania Ave" - :autofocus true}))] - [:div.col-span-6 - (com/field {:label "Street 2"} - (com/text-input {:name (path->name [:vendor/address :address/street2]) - :value (-> vendor :vendor/address :address/street2) - :placeholder "Suite 200"}))] - [:div.col-span-3 - (com/field {:label "City"} - (com/text-input {:name (path->name [:vendor/address :address/city]) - :value (-> vendor :vendor/address :address/city) - :placeholder "Cupertino"}))] - [:div.col-span-1 - (com/field {:label "State"} - (com/text-input {:name (path->name [:vendor/address :address/state]) - :value (-> vendor :vendor/address :address/state) - :placeholder "CA"}))] - [:div.col-span-2 - (com/field {:label "Zip"} - (com/text-input {:name (path->name [:vendor/address :address/zip]) - :value (-> vendor :vendor/address :address/zip) - :placeholder "98102"}))] - [:h4.text-xl.border-b.col-span-6 "Legal Entity"] - [:div.col-span-6 - (com/field {:label "Legal Entity Name"} - (com/text-input {:name (path->name [:vendor/legal-entity-name]) - :value (-> vendor :vendor/legal-entity-name) - :placeholder "Good Restaurant LLC"}))] - [:div.col-span-6.text-center " - OR -"] - [:div.col-span-2 - (com/field {:label "First Name"} - (com/text-input {:name (path->name [:vendor/legal-entity-first-name]) - :value (-> vendor :vendor/legal-entity-first-name) - :placeholder "John"}))] - [:div.col-span-2 - (com/field {:label "Middle Name"} - (com/text-input {:name (path->name [:vendor/legal-entity-middle-name]) - :value (-> vendor :vendor/legal-entity-middle-name) - :placeholder "C."}))] - [:div.col-span-2 - (com/field {:label "Last Name"} - (com/text-input {:name (path->name [:vendor/legal-entity-last-name]) - :value (-> vendor :vendor/legal-entity-last-name) - :placeholder "Riley"}))] - [:div.col-span-2 - (com/field {:label "TIN"} - (com/text-input {:name (path->name [:vendor/legal-entity-tin]) - :value (-> vendor :vendor/legal-entity-tin) - :placeholder "John"}))] - [:div.col-span-2 - (com/field {:label "TIN Type"} - (com/select {:name (path->name [:vendor/legal-entity-tin-type]) - :allow-blank? true - :value (some-> vendor :vendor/legal-entity-tin-type :db/ident name) - :options [["ein" "EIN"] - ["ssn" "SSN"]]}))] - [:div.col-span-2 - (com/field {:label "1099 Type"} - (com/select {:name (path->name [:vendor/legal-entity-1099-type]) - :allow-blank? true - :value (some-> vendor :vendor/legal-entity-1099-type :db/ident name) - :options [["none" "None"] - ["misc" "Misc"] - ["landlord" "Landlord"]]}))] - [:div.col-span-6 - (com/button {:color :primary} - "Save")]]] - [:div])]])))) + (com/modal + {} + [:form {:hx-post (str (bidi/path-for ssr-routes/only-routes + :company-1099-vendor-save + :request-method :post + :vendor-id (Long/parseLong (:vendor-id (:params request))))) + :hx-target "#vendor-table" + :hx-swap "outerHTML swap:300ms"} + [:fieldset {:class "hx-disable"} + (com/modal-card + {} + [:div.flex [:div.p-2 "Vendor 1099 Info"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600 (:vendor/name vendor)]] + [:div.space-y-6 + [:div.grid.grid-cols-6.gap-4 + [:h4.text-xl.border-b.col-span-6 "Address"] + [:div.col-span-6 + (com/field {:label "Street 1"} + (com/text-input {:name (path->name [:vendor/address :address/street1]) + :value (-> vendor :vendor/address :address/street1) + :placeholder "1700 Pennsylvania Ave" + :autofocus true}))] + [:div.col-span-6 + (com/field {:label "Street 2"} + (com/text-input {:name (path->name [:vendor/address :address/street2]) + :value (-> vendor :vendor/address :address/street2) + :placeholder "Suite 200"}))] + [:div.col-span-3 + (com/field {:label "City"} + (com/text-input {:name (path->name [:vendor/address :address/city]) + :value (-> vendor :vendor/address :address/city) + :placeholder "Cupertino"}))] + [:div.col-span-1 + (com/field {:label "State"} + (com/text-input {:name (path->name [:vendor/address :address/state]) + :value (-> vendor :vendor/address :address/state) + :placeholder "CA"}))] + [:div.col-span-2 + (com/field {:label "Zip"} + (com/text-input {:name (path->name [:vendor/address :address/zip]) + :value (-> vendor :vendor/address :address/zip) + :placeholder "98102"}))] + [:h4.text-xl.border-b.col-span-6 "Legal Entity"] + [:div.col-span-6 + (com/field {:label "Legal Entity Name"} + (com/text-input {:name (path->name [:vendor/legal-entity-name]) + :value (-> vendor :vendor/legal-entity-name) + :placeholder "Good Restaurant LLC"}))] + [:div.col-span-6.text-center " - OR -"] + [:div.col-span-2 + (com/field {:label "First Name"} + (com/text-input {:name (path->name [:vendor/legal-entity-first-name]) + :value (-> vendor :vendor/legal-entity-first-name) + :placeholder "John"}))] + [:div.col-span-2 + (com/field {:label "Middle Name"} + (com/text-input {:name (path->name [:vendor/legal-entity-middle-name]) + :value (-> vendor :vendor/legal-entity-middle-name) + :placeholder "C."}))] + [:div.col-span-2 + (com/field {:label "Last Name"} + (com/text-input {:name (path->name [:vendor/legal-entity-last-name]) + :value (-> vendor :vendor/legal-entity-last-name) + :placeholder "Riley"}))] + [:div.col-span-2 + (com/field {:label "TIN"} + (com/text-input {:name (path->name [:vendor/legal-entity-tin]) + :value (-> vendor :vendor/legal-entity-tin) + :placeholder "John"}))] + [:div.col-span-2 + (com/field {:label "TIN Type"} + (com/select {:name (path->name [:vendor/legal-entity-tin-type]) + :allow-blank? true + :value (some-> vendor :vendor/legal-entity-tin-type :db/ident name) + :options [["ein" "EIN"] + ["ssn" "SSN"]]}))] + [:div.col-span-2 + (com/field {:label "1099 Type"} + (com/select {:name (path->name [:vendor/legal-entity-1099-type]) + :allow-blank? true + :value (some-> vendor :vendor/legal-entity-1099-type :db/ident name) + :options [["none" "None"] + ["misc" "Misc"] + ["landlord" "Landlord"]]}))] + [:div.col-span-6 + (com/button {:color :primary} + "Save")]]] + [:div])]])))) (defn vendor-table [request] (html-response (table* request) @@ -323,11 +325,13 @@ (defn page [{:keys [identity matched-route] :as request}] (base-page request - (com/page {:nav (com/company-aside-nav) + (com/page {:nav (com/company-aside-nav) :active-client (:client (:session request)) - :identity (:identity request)} + :identity (:identity request)} (com/breadcrumbs {} - [:a {:href "#"} "My Company"] - [:a {:href "#"} "1099 Vendor Info"]) + [:a {:href (bidi/path-for ssr-routes/only-routes + :company)} "My Company"] + [:a {:href (bidi/path-for ssr-routes/only-routes + :company-1099)} "1099 Vendor Info"]) (table* request)) nil)) diff --git a/src/clj/auto_ap/ssr/company/reports.clj b/src/clj/auto_ap/ssr/company/reports.clj index 62a502fa..9c0a93e4 100644 --- a/src/clj/auto_ap/ssr/company/reports.clj +++ b/src/clj/auto_ap/ssr/company/reports.clj @@ -110,8 +110,10 @@ :active-client (:client (:session request)) :identity (:identity request)} (com/breadcrumbs {} - [:a {:href "#"} "My Company"] - [:a {:href "#"} "Reports"]) + [:a {:href (bidi/path-for ssr-routes/only-routes + :company)} "My Company"] + [:a {:href {:href (bidi/path-for ssr-routes/only-routes + :company-reports)}} "Reports"]) (table* {:client (:client (:session request)) :start (some-> (or (get query-params "start") (get hx-query-params "start")) not-empty (Long/parseLong )) :per-page (some-> (or (get query-params "per-page") (get hx-query-params "per-page")) not-empty (Long/parseLong )) diff --git a/src/clj/auto_ap/ssr/company/yodlee.clj b/src/clj/auto_ap/ssr/company/yodlee.clj new file mode 100644 index 00000000..0e736793 --- /dev/null +++ b/src/clj/auto_ap/ssr/company/yodlee.clj @@ -0,0 +1,168 @@ +(ns auto-ap.ssr.company.yodlee + (:require + [auto-ap.datomic :refer [conn]] + [auto-ap.datomic.yodlee2 :as yodlee2] + [auto-ap.graphql.utils :refer [is-admin?]] + [auto-ap.ssr-routes :as ssr-routes] + [auto-ap.ssr.components :as com] + [auto-ap.ssr.svg :as svg] + [auto-ap.ssr.ui :refer [base-page]] + [auto-ap.ssr.utils :refer [html-response]] + [auto-ap.time :as atime] + [auto-ap.yodlee.core2 :as yodlee] + [bidi.bidi :as bidi] + [config.core :refer [env]] + [datomic.api :as dc] + [hiccup2.core :as hiccup])) + +(defn row* [{:keys [flash? yodlee-provider-account identity delete-after-settle?]}] + (com/data-grid-row + {:class (when flash? + "live-added") + "_" (hiccup/raw (when delete-after-settle?" on htmx:afterSettle wait 400ms then remove me"))} + (com/data-grid-cell + {} + (:yodlee-provider-account/id yodlee-provider-account)) + (com/data-grid-cell + {} + (when-let [status (:yodlee-provider-account/status yodlee-provider-account)] + (com/pill {:color (if (not= status "SUCCESS") + :yellow + :primary) } + status))) + (com/data-grid-cell + {} + (when-let [status (:yodlee-provider-account/detailed-status yodlee-provider-account)] + status) + ) + + (com/data-grid-cell + {} + (atime/unparse-local (:yodlee-provider-account/last-updated yodlee-provider-account) + atime/normal-date)) + (com/data-grid-cell + {} + [:ul + (for [a (:yodlee-provider-account/accounts yodlee-provider-account)] + [:li (:yodlee-account/name a) " - " (:yodlee-account/number a) #_[:div.tag (->$ (:available-balance a))]])]) + (com/data-grid-right-stack-cell + {} + (when (is-admin? identity) + [:form + [:input {:type :hidden :name "id" :value (:db/id yodlee-provider-account)}] + (com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes + :company-yodlee-provider-account-refresh) + :hx-target "closest tr"} + svg/refresh)]) + #_(when (is-admin? identity) + [:form + [:input {:type :hidden :name "id" :value (:db/id report)}] + (com/icon-button {:hx-delete (str (bidi/path-for ssr-routes/only-routes + :company-reports-delete + :request-method :delete)) + :hx-target "closest tr"} + svg/trash)])))) + +(defn table* [{:keys [identity start per-page client flash-id]}] + (let [start (or start 0) + per-page (or per-page 30) + [yodlee-provider-accounts total] (yodlee2/get-graphql {:id identity + :start start + :per-page per-page + :client-id (:db/id client) + :sort nil})] + [:div + (com/data-grid-card {:id "yodlee-table" + :title "Yodlee Accounts" + :entity-name "Yodlee accounts" + :route :company-yodlee-table + :start start + :per-page per-page + :total total + :action-buttons [(com/button {:color :primary + :on-click "openFastlink()" + :hx-get (bidi/path-for ssr-routes/only-routes + :company-yodlee-fastlink-dialog) + :hx-target "#modal-holder"} + (com/button-icon {} svg/refresh) + "Link new account")] + :rows (for [yodlee-provider-account yodlee-provider-accounts] + (row* {:yodlee-provider-account yodlee-provider-account + :flash? (= flash-id + (:db/id yodlee-provider-account)) + :identity identity})) + :headers [(com/data-grid-header {} "Provider Account") + (com/data-grid-header {} "Status") + (com/data-grid-header {} "Detailed Status") + (com/data-grid-header {} "Last Updated") + (com/data-grid-header {:class "hidden md:table-cell"} "Accounts") + (com/data-grid-header {})]})])) + +(def default-read '[:db/id + :yodlee-provider-account/last-updated + :yodlee-provider-account/status + :yodlee-provider-account/id + :yodlee-provider-account/detailed-status + {:yodlee-provider-account/accounts [:yodlee-account/name :yodlee-account/number] + :yodlee-provider-account/client [:client/code]}]) + +(defn refresh-provider-account [{:keys [form-params identity]}] + (let [provider-account (dc/pull (dc/db conn) default-read (some-> (get form-params "id") not-empty Long/parseLong))] + (yodlee/refresh-provider-account (:client/code (:yodlee-provider-account/client provider-account)) + (:yodlee-provider-account/id provider-account)) + (html-response + (row* {:yodlee-provider-account provider-account + :flash? true + :identity identity})))) + +(defn fastlink-dialog [{:keys [session]}] + (html-response + (com/modal + {} + (com/modal-card + {} + [:div.flex [:div.p-2 "Yodlee Fastlink"] ] + [:div + [:div#fa-spot] + [:script {:lang "text/javascript"} + (hiccup/raw + (format " +fastlink.open({fastLinkURL: '%s', + accessToken: '%s', + params: {'configName': 'Aggregation'}}, + 'fa-spot'); + +" (:yodlee2-fastlink env) (yodlee/get-access-token (:client/code (:client session)))))] + ] + [:div])))) + + +(defn table [{:keys [query-params hx-query-params identity session] :as request}] + (html-response (table* {:client (:client (:session request)) + :start (some-> (or (get query-params "start") (get hx-query-params "start")) not-empty (Long/parseLong )) + :per-page (some-> (or (get query-params "per-page") (get hx-query-params "per-page")) not-empty (Long/parseLong )) + :identity identity + :session session}) + :headers {"hx-push-url" (str "?start=" (get (:query-params request) "start"))})) + +(defn page [{:keys [identity matched-route query-params :hx-query-params session] :as request}] + (base-page + request + (com/page {:nav (com/company-aside-nav) + :active-client (:client (:session request)) + :identity (:identity request)} + (com/breadcrumbs {} + [:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "My Company"] + + [:a {:href (bidi/path-for ssr-routes/only-routes + :company-yodlee)} + "Yodlee"] + ) + (table* {:client (:client session) + :start (some-> (or (get query-params "start") (get hx-query-params "start")) not-empty (Long/parseLong )) + :per-page (some-> (or (get query-params "per-page") (get hx-query-params "per-page")) not-empty (Long/parseLong )) + :identity identity + :session session})) + nil)) diff --git a/src/clj/auto_ap/ssr/components/aside.clj b/src/clj/auto_ap/ssr/components/aside.clj index f486f88c..cb470b81 100644 --- a/src/clj/auto_ap/ssr/components/aside.clj +++ b/src/clj/auto_ap/ssr/components/aside.clj @@ -188,20 +188,23 @@ (defn company-aside-nav- [] [:ul {:class "space-y-2"} + [:li + (menu-button- {:icon svg/vendors + :href (bidi/path-for ssr-routes/only-routes + :company)} + "My Company")] - [:li + [:li (menu-button- {:icon svg/report :href (bidi/path-for ssr-routes/only-routes :company-reports)} "Reports")] - [:li - (menu-button- {:icon svg/bank} + [:li + (menu-button- {:icon svg/bank + :href (bidi/path-for ssr-routes/only-routes + :company-yodlee)} "Yodlee Link")] - [:li - (menu-button- {:icon svg/vendors} - "Vendors")] - - [:li + [:li (menu-button- {:icon svg/government-building :href (bidi/path-for ssr-routes/only-routes :company-1099)} diff --git a/src/clj/auto_ap/ssr/components/buttons.clj b/src/clj/auto_ap/ssr/components/buttons.clj index 17b5f90e..40198207 100644 --- a/src/clj/auto_ap/ssr/components/buttons.clj +++ b/src/clj/auto_ap/ssr/components/buttons.clj @@ -5,9 +5,11 @@ [:div.h-4.w-4 i]) (defn button- [params & children] - [:button { :class (cond-> "text-white focus:ring-4 font-bold rounded-lg text-sm px-5 py-2.5 text-center mr-2 inline-flex items-center hover:scale-105 transition duration-100 justify-center" - (= :secondary (:color params)) (str " bg-blue-500 hover:bg-blue-600 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700") - (= :primary (:color params)) (str " bg-green-500 hover:bg-green-600 focus:ring-green-300 dark:bg-green-600 dark:hover:bg-green-700 "))} + [:button (update params + :class #(cond-> % + true (str " text-white focus:ring-4 font-bold rounded-lg text-sm px-5 py-2.5 text-center mr-2 inline-flex items-center hover:scale-105 transition duration-100 justify-center") + (= :secondary (:color params)) (str " bg-blue-500 hover:bg-blue-600 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700") + (= :primary (:color params)) (str " bg-green-500 hover:bg-green-600 focus:ring-green-300 dark:bg-green-600 dark:hover:bg-green-700 "))) [:div.htmx-indicator.flex.items-center (svg/spinner {:class "inline w-4 h-4 text-white"}) [:div.ml-3 "Loading..."]] diff --git a/src/clj/auto_ap/ssr/components/dialog.clj b/src/clj/auto_ap/ssr/components/dialog.clj index dc8e9635..8a8bacae 100644 --- a/src/clj/auto_ap/ssr/components/dialog.clj +++ b/src/clj/auto_ap/ssr/components/dialog.clj @@ -4,7 +4,7 @@ (defn modal- [params & children] [:div [:div#modal-holder { :tabindex "-1", :class "fixed top-0 left-0 right-0 z-50 w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full flex justify-center hidden" :aria-hidden true - "_" (hiccup/raw "on closeModal transition <#modal-holder .modal-content /> opacity to 0.0 over 300ms then call hideModal()")} + "_" (hiccup/raw "on closeModal transition <#modal-holder .modal-content /> opacity to 0.0 over 300ms then call hideModal() ")} [:div {:class "relative w-full max-w-2xl max-h-full"} (into [:div#modal-content] children)] @@ -22,7 +22,7 @@ }, onHide: function() { - modal_element.outerHTML='
'; + modal_element.outerHTML='
'; }, }; var curModal = new Modal(modal_element, modal_options); diff --git a/src/clj/auto_ap/ssr/components/page.clj b/src/clj/auto_ap/ssr/components/page.clj index 1f312c4d..232691e6 100644 --- a/src/clj/auto_ap/ssr/components/page.clj +++ b/src/clj/auto_ap/ssr/components/page.clj @@ -1,27 +1,17 @@ (ns auto-ap.ssr.components.page - (:require [auto-ap.ssr.components.navbar :refer [navbar-]] - [auto-ap.ssr.components.aside :refer [left-aside-]] - [hiccup2.core :as hiccup])) + (:require + [auto-ap.ssr.components.aside :refer [left-aside-]] + [auto-ap.ssr.components.navbar :refer [navbar-]])) -(defn page- [{:keys [nav page-specific active-client identity]} & children] - [:div#app +(defn page- [{:keys [nav page-specific active-client identity app-params] :or {app-params {}}} & children] + [:div#app app-params (navbar- {:client active-client :identity identity}) [:div.flex.pt-16.overflow-hidden (left-aside- {:nav nav :page-specific page-specific}) - - [:div#main-content {:class "relative w-full h-full lg:pl-64 overflow-y-auto px-4 bg-gray-100 dark:bg-gray-900"} + [:div#main-content {:class "relative w-full h-full lg:pl-64 overflow-y-auto px-4 bg-gray-100 dark:bg-gray-900 min-h-content "} (into [:div.p-4] children)]] - - - - - #_[:div#modal-holder.hidden - {"_" (hiccup/raw "on click trigger closeDialog")} - - [:div {:class "bg-gray-900 bg-opacity-50 dark:bg-opacity-80 fixed inset-0 z-40" - }]] [:div#modal-holder]]) diff --git a/src/clj/auto_ap/ssr/core.clj b/src/clj/auto_ap/ssr/core.clj index 70725b2a..b3447b53 100644 --- a/src/clj/auto_ap/ssr/core.clj +++ b/src/clj/auto_ap/ssr/core.clj @@ -6,10 +6,12 @@ [auto-ap.ssr.auth :as auth] [auto-ap.ssr.transaction.insights :as insights] [auto-ap.ssr.company.company-1099 :as company-1099] + [auto-ap.ssr.company.yodlee :as company-yodlee] [auto-ap.ssr.search :as search] [auto-ap.ssr.company-dropdown :as company-dropdown] [auto-ap.ssr.company.reports :as company-reports] - [auto-ap.routes.ezcater-xls :as ezcater-xls])) + [auto-ap.routes.ezcater-xls :as ezcater-xls] + [auto-ap.ssr.company :as company])) ;; from auto-ap.ssr-routes, because they're shared @@ -21,10 +23,15 @@ :active-client (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin company-dropdown/active-client))) :company-dropdown-search-results (wrap-client-redirect-unauthenticated (wrap-secure company-dropdown/dropdown-search-results)) + :company (wrap-client-redirect-unauthenticated (wrap-secure company/page)) :company-1099 (wrap-client-redirect-unauthenticated (wrap-secure company-1099/page)) :company-1099-vendor-table (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-table)) :company-1099-vendor-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-dialog)) :company-1099-vendor-save (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-save)) + :company-yodlee (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/page)) + :company-yodlee-table (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/table)) + :company-yodlee-fastlink-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/fastlink-dialog)) + :company-yodlee-provider-account-refresh (wrap-client-redirect-unauthenticated (wrap-admin company-yodlee/refresh-provider-account)) :company-reports (wrap-client-redirect-unauthenticated (wrap-secure company-reports/page)) :company-reports-table (wrap-client-redirect-unauthenticated (wrap-secure company-reports/reports-table)) :company-reports-delete (wrap-client-redirect-unauthenticated (wrap-admin company-reports/delete-report)) diff --git a/src/clj/auto_ap/yodlee/core2.clj b/src/clj/auto_ap/yodlee/core2.clj index 9437647c..04dd1ce5 100644 --- a/src/clj/auto_ap/yodlee/core2.clj +++ b/src/clj/auto_ap/yodlee/core2.clj @@ -13,7 +13,8 @@ [clj-time.coerce :as coerce] [datomic.api :as dc] [auto-ap.datomic :refer [conn]] - [auto-ap.datomic.clients :as d-clients])) + [auto-ap.datomic.clients :as d-clients] + [clojure.string :as str])) ;; switch all of this to use tokens instead of passing around client codes, particularly because the codes ;; need to be tweaked for repeats (defn client-code->login [client-code] @@ -61,7 +62,7 @@ (client/post (merge {:headers (assoc base-headers "loginName" (:yodlee2-admin-user env) "Content-Type" "application/x-www-form-urlencoded") - :body (str "clientId=" (:yodlee2-client-id env) " &secret=" (:yodlee2-client-secret env)) + :body (str "clientId=" (:yodlee2-client-id env) "&secret=" (:yodlee2-client-secret env)) :as :json} other-config) ) @@ -75,11 +76,13 @@ (log/info "logging in as " client-code) (-> (str (:yodlee2-base-url env) "/auth/token") (client/post (merge {:headers (assoc base-headers - "loginName" (if (<= (count client-code) 3) - (str client-code client-code) - client-code) + "loginName" (if (:yodlee2-test-user env) + (:yodlee2-test-user env) + (if (<= (count client-code) 3) + (str client-code client-code) + client-code)) "Content-Type" "application/x-www-form-urlencoded") - :body (str "clientId=" (:yodlee2-client-id env) " &secret=" (:yodlee2-client-secret env)) + :body (str "clientId=" (:yodlee2-client-id env) "&secret=" (:yodlee2-client-secret env)) :as :json} other-config) ) @@ -278,10 +281,6 @@ provider-accounts) vals))) - - - - (defn delete-provider-account [client-code id] (let [cob-session (login-user (client-code->login client-code))] diff --git a/src/cljc/auto_ap/ssr_routes.cljc b/src/cljc/auto_ap/ssr_routes.cljc index 83db9fbb..cbfa37e4 100644 --- a/src/cljc/auto_ap/ssr_routes.cljc +++ b/src/cljc/auto_ap/ssr_routes.cljc @@ -13,15 +13,20 @@ ["/approve/" [#"\d+" :transaction-id]] {:post :transaction-insight-approve} ["/rows/" [#"\d+" :after]] {:get :transaction-insight-rows} ["/explain/" [#"\d+" :transaction-id]] {:get :transaction-insight-explain}}} - "company" {"/dropdown" :company-dropdown-search-results - "/active" {:put :active-client} - "/1099" :company-1099 - "/1099/table" {:get :company-1099-vendor-table} - "/1099/vendor-dialog" {["/" [#"\d+" :vendor-id]] {:get :company-1099-vendor-dialog + "company" {"" :company + "/dropdown" :company-dropdown-search-results + "/active" {:put :active-client} + "/1099" :company-1099 + "/1099/table" {:get :company-1099-vendor-table} + "/1099/vendor-dialog" {["/" [#"\d+" :vendor-id]] {:get :company-1099-vendor-dialog :post :company-1099-vendor-save}} - "/reports" {"" {:get :company-reports - :delete :company-reports-delete}} - "/reports/table" :company-reports-table + "/reports" {"" {:get :company-reports + :delete :company-reports-delete} + "/table" :company-reports-table} + "/yodlee" {"" {:get :company-yodlee} + "/table" {:get :company-yodlee-table} + "/fastlink" {:get :company-yodlee-fastlink-dialog} + "/refresh" {:put :company-yodlee-provider-account-refresh}} }}) diff --git a/test/clj/auto_ap/import/transactions_test.clj b/test/clj/auto_ap/import/transactions_test.clj index 64b9536f..87f123ce 100644 --- a/test/clj/auto_ap/import/transactions_test.clj +++ b/test/clj/auto_ap/import/transactions_test.clj @@ -16,8 +16,7 @@ (def base-transaction #:transaction {:date #inst "2020-01-02T00:00:00-08:00" :raw-id "1" - :id #_{:clj-kondo/ignore [:unresolved-var]} - (di/sha-256 "1") + :id (di/sha-256 "1") :amount 12.0 :description-original "original-description" :status "POSTED"