Migrates user page to SSR

This commit is contained in:
2023-10-12 21:55:37 -07:00
parent c00940fcbf
commit d9fec54062
16 changed files with 542 additions and 33 deletions

1
api-key.txt Normal file
View File

@@ -0,0 +1 @@
eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyIjoiQVBJIiwiZXhwIjoxNzgyNjYxMjQxLCJ1c2VyL3JvbGUiOiJhZG1pbiIsInVzZXIvbmFtZSI6IkFQSSJ9.Zt5kWBplVFSWaQ7GWud85s7B5ok8vEqIcww1AVZNmM26Y0O8m1-P_er9Mz1SLTX8sdBu4qFxqXk-9_ImS3Pl5A

View File

@@ -76,3 +76,22 @@
.min-h-content {
min-height: calc(100vh - 4em);
}
.select2 {
@apply text-xs !important;
}
.select2-dropdown {
@apply p-2 mb-6 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 text-xs !important;
}
.select2-selection__choice {
@apply bg-primary-100 dark:bg-primary-700 text-gray-900 dark:text-gray-200 !important;
}
.select2-search {
@apply h-4 !important;
}
.select2-selection {
@apply py-2.5 !important;
}

View File

@@ -1352,6 +1352,10 @@ input:checked + .toggle-bg {
width: 0.875rem;
}
.w-36 {
width: 9rem;
}
.w-4 {
width: 1rem;
}
@@ -1546,6 +1550,10 @@ input:checked + .toggle-bg {
flex-wrap: wrap;
}
.place-items-center {
place-items: center;
}
.items-start {
align-items: flex-start;
}
@@ -2476,6 +2484,82 @@ input:checked + .toggle-bg {
min-height: calc(100vh - 4em);
}
.select2 {
font-size: 0.75rem !important;
line-height: 1rem !important;
}
.select2-dropdown {
margin-bottom: 1.5rem !important;
border-radius: 0.5rem !important;
border-width: 1px !important;
--tw-border-opacity: 1 !important;
border-color: rgb(209 213 219 / var(--tw-border-opacity)) !important;
--tw-bg-opacity: 1 !important;
background-color: rgb(249 250 251 / var(--tw-bg-opacity)) !important;
padding: 0.5rem !important;
font-size: 0.75rem !important;
line-height: 1rem !important;
--tw-text-opacity: 1 !important;
color: rgb(17 24 39 / var(--tw-text-opacity)) !important;
}
.select2-dropdown:focus {
--tw-border-opacity: 1 !important;
border-color: rgb(0 156 234 / var(--tw-border-opacity)) !important;
--tw-ring-opacity: 1 !important;
--tw-ring-color: rgb(0 156 234 / var(--tw-ring-opacity)) !important;
}
:is(.dark .select2-dropdown) {
--tw-border-opacity: 1 !important;
border-color: rgb(75 85 99 / var(--tw-border-opacity)) !important;
--tw-bg-opacity: 1 !important;
background-color: rgb(55 65 81 / var(--tw-bg-opacity)) !important;
--tw-text-opacity: 1 !important;
color: rgb(255 255 255 / var(--tw-text-opacity)) !important;
}
:is(.dark .select2-dropdown)::-moz-placeholder {
--tw-placeholder-opacity: 1 !important;
color: rgb(156 163 175 / var(--tw-placeholder-opacity)) !important;
}
:is(.dark .select2-dropdown)::placeholder {
--tw-placeholder-opacity: 1 !important;
color: rgb(156 163 175 / var(--tw-placeholder-opacity)) !important;
}
:is(.dark .select2-dropdown:focus) {
--tw-border-opacity: 1 !important;
border-color: rgb(0 156 234 / var(--tw-border-opacity)) !important;
--tw-ring-opacity: 1 !important;
--tw-ring-color: rgb(0 156 234 / var(--tw-ring-opacity)) !important;
}
.select2-selection__choice {
--tw-bg-opacity: 1 !important;
background-color: rgb(228 240 213 / var(--tw-bg-opacity)) !important;
--tw-text-opacity: 1 !important;
color: rgb(17 24 39 / var(--tw-text-opacity)) !important;
}
:is(.dark .select2-selection__choice) {
--tw-bg-opacity: 1 !important;
background-color: rgb(73 109 28 / var(--tw-bg-opacity)) !important;
--tw-text-opacity: 1 !important;
color: rgb(229 231 235 / var(--tw-text-opacity)) !important;
}
.select2-search {
height: 1rem !important;
}
.select2-selection {
padding-top: 0.625rem !important;
padding-bottom: 0.625rem !important;
}
.hover\:scale-105:hover {
--tw-scale-x: 1.05;
--tw-scale-y: 1.05;

View File

@@ -1393,6 +1393,10 @@
:db/cardinality #:db{:ident :db.cardinality/one},
:db/doc "the id from the provider",
:db/ident :user/provider-id}
{:db/valueType #:db{:ident :db.type/instant},
:db/cardinality #:db{:ident :db.cardinality/one},
:db/doc "The last time the user logged in, defaulted to the last action they took",
:db/ident :user/last-login}
{:db/valueType #:db{:ident :db.type/ref},
:db/cardinality #:db{:ident :db.cardinality/one},
:db/doc "The role [:user :admin :none]",

View File

@@ -0,0 +1,14 @@
(->> (dc/q '[:find ?u (max ?lat)
:in $ $$
:where [?u :user/name]
[$$ ?u _ _ ?tx]
[?tx :db/txInstant ?lat]]
(dc/db conn)
(dc/history (dc/db conn)))
(map (fn [[u last-login]]
{:db/id u
:user/last-login last-login}))
(dc/transact conn)
deref)

View File

@@ -31,7 +31,8 @@
:where [?e :user/provider ?provider]
[?e :user/provider-id ?provider-id]]
(dc/db conn) provider provider-id))
result @(dc/transact conn [[:upsert-entity (cond-> (assoc new-user :db/id (or user-id "user"))
result @(dc/transact conn [[:upsert-entity (cond-> (assoc new-user :db/id (or user-id "user")
:user/last-login (java.util.Date.))
(not user-id) (assoc :user/role :user-role/none)
is-first-user? (assoc :user/role :user-role/admin))]])
user-id (or user-id (get-in result [:tempids "user"]))]

View File

@@ -1,15 +1,19 @@
(ns auto-ap.ssr.company
(:require
[auto-ap.datomic :refer [conn]]
[auto-ap.datomic :refer [conn pull-attr]]
[auto-ap.datomic.clients :refer [full-read]]
[auto-ap.solr :as solr]
[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]]
[bidi.bidi :as bidi]
[cemerick.url :as url]
[clojure.set :as set]
[clojure.string :as str]
[config.core :refer [env]]
[datomic.api :as dc]
[auto-ap.ssr-routes :as ssr-routes]
[bidi.bidi :as bidi]))
[ring.middleware.json :refer [wrap-json-response]]))
(defn please-select-client-screen* []
[:div.grid.grid-cols-3
@@ -66,3 +70,19 @@
(main-content* {:client (:client request)}))
"My Company"))
(defn search [{:keys [clients query-params]}]
(let [valid-client-ids (set (map :db/id clients))
name-like-ids (when (not-empty (get query-params "q"))
(set (map (comp #(Long/parseLong %) :id)
(solr/query solr/impl "clients"
{"query" (format "_text_:(%s*)" (str/upper-case (solr/escape (get query-params "q"))))
"fields" "id"
"limit" 300}))))
valid-clients (for [n name-like-ids
:when (valid-client-ids n)]
{"id" n "text" (pull-attr (dc/db conn) :client/name n)}
)]
{:body {"results" valid-clients}}))
(def search (wrap-json-response search))

View File

@@ -28,6 +28,7 @@
(def money-input inputs/money-input-)
(def date-input inputs/date-input-)
(def select inputs/select-)
(def typeahead inputs/typeahead-)
(def field inputs/field-)
(def left-aside aside/left-aside-)

View File

@@ -1,17 +1,50 @@
(ns auto-ap.ssr.components.inputs
(:require [hiccup2.core :as hiccup]))
(:require [hiccup2.core :as hiccup]
[auto-ap.ssr.svg :as svg]
[bidi.bidi :as bidi]))
(defn select- [params & children]
(into
[:select {:class "bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
[:select {:class (str (:class params) " bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500")
:name (:name params)}
(cond->>
(map (fn [[k v]]
[:option {:value k :selected (= v (:value params))} v])
[:option {:value k :selected (= k (:value params))} v])
(:options params))
(:allow-blank? params) (conj [:option {:value "" :selected (not (:value params))} ""]))]
children))
(defn typeahead- [params]
[:div {:class (:class params)}
[:select (-> params
(dissoc :url)
(assoc :width ""))
(for [[k v] (:value params)]
[:option {:value k :selected true} v]
)
[:script {:lang "javascript"}
(hiccup/raw (format "$(document).ready(function() {$(\"#%s\").select2({ajax: {url: '%s', dataType: 'json'}, minimumInputLength: 4, placeholder: \"%s\"})})"
(:id params)
(:url params)
(or (:placeholder params)
"Type to search")))]]])
(defn typeahead-results- [{:keys [options]}]
[:ul
(for [{:keys [id name]} options]
[:li
[:div {:class "flex items-center pl-2 rounded hover:bg-green-100 dark:hover:bg-green-600"}
[:a {:href "#"
:class "w-full py-2 ml-2 text-sm font-medium text-gray-900 rounded dark:text-gray-300"
#_#_:hx-put (bidi/path-for ssr-routes/only-routes
:active-client
:request-method :put)
:hx-target "#company-dropdown"
:hx-headers (format "{\"x-clients\": \"[%d]\"}" id)
:hx-swap "outerHTML"
:hx-trigger "click"}
name]]])])
(defn use-size [size]
(if (= :small size)
(str " " "text-xs p-2")

View File

@@ -1,24 +1,26 @@
(ns auto-ap.ssr.core
(:require
[auto-ap.routes.ezcater-xls :as ezcater-xls]
[auto-ap.routes.utils
:refer [wrap-admin wrap-client-redirect-unauthenticated wrap-secure]]
[auto-ap.ssr.admin.history :as history]
[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.company.plaid :as company-plaid]
[auto-ap.ssr.search :as search]
[auto-ap.ssr.company :as company]
[auto-ap.ssr.company-dropdown :as company-dropdown]
[auto-ap.ssr.company.company-1099 :as company-1099]
[auto-ap.ssr.company.plaid :as company-plaid]
[auto-ap.ssr.company.reports :as company-reports]
[auto-ap.ssr.company.yodlee :as company-yodlee]
[auto-ap.ssr.invoice.glimpse :as invoice-glimpse]
[auto-ap.ssr.pos.sales-orders :as pos-sales]
[auto-ap.ssr.pos.refunds :as pos-refunds]
[auto-ap.ssr.pos.expected-deposits :as pos-expected-deposits]
[auto-ap.ssr.pos.cash-drawer-shifts :as pos-cash-drawer-shifts]
[auto-ap.ssr.pos.expected-deposits :as pos-expected-deposits]
[auto-ap.ssr.pos.refunds :as pos-refunds]
[auto-ap.ssr.pos.sales-orders :as pos-sales]
[auto-ap.ssr.pos.tenders :as pos-tenders]
[auto-ap.routes.ezcater-xls :as ezcater-xls]
[auto-ap.ssr.company :as company]))
[auto-ap.ssr.search :as search]
[auto-ap.ssr.transaction.insights :as insights]
[auto-ap.ssr.users :as users]
[ring.middleware.json :refer [wrap-json-response]]))
;; from auto-ap.ssr-routes, because they're shared
@@ -31,7 +33,9 @@
:admin-history-inspect (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin history/inspect)))
:active-client (wrap-client-redirect-unauthenticated (wrap-secure (wrap-secure company-dropdown/active-client)))
:company-dropdown-search-results
(wrap-client-redirect-unauthenticated (wrap-secure company-dropdown/dropdown-search-results))
(wrap-client-redirect-unauthenticated (wrap-secure (wrap-json-response company-dropdown/dropdown-search-results {})))
:company-search
(wrap-client-redirect-unauthenticated (wrap-secure company/search))
: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))
@@ -66,5 +70,6 @@
(into pos-expected-deposits/key->handler)
(into pos-tenders/key->handler)
(into pos-cash-drawer-shifts/key->handler)
(into pos-refunds/key->handler)))
(into pos-refunds/key->handler)
(into users/key->handler)))

View File

@@ -184,7 +184,6 @@
valid-clients (->> valid-clients
(take 20)
set)]
(println "VALID CLIENTS ARE" valid-clients)
(handler (assoc request :trimmed-clients valid-clients)))))
(defn table-route [grid-spec]

View File

@@ -9,10 +9,7 @@
pull-many
query2]]
[auto-ap.datomic.sales-orders :as d-sales]
[auto-ap.graphql.utils :refer [extract-client-ids]]
[auto-ap.query-params :as query-params]
[auto-ap.routes.utils
:refer [wrap-client-redirect-unauthenticated wrap-secure]]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.components :as com]
[auto-ap.ssr.grid-page-helper :as helper]
@@ -22,8 +19,7 @@
[auto-ap.time :as atime]
[bidi.bidi :as bidi]
[clj-time.coerce :as c]
[datomic.api :as dc]
[malli.core :as m]))
[datomic.api :as dc]))
(defn filters [request]
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
@@ -150,7 +146,6 @@
true
(merge-query {:query {:find ['?sort-default '?e]}}))]
(clojure.pprint/pprint query)
(cond->> (query2 query)
true (apply-sort-3 query-params)
true (apply-pagination query-params))))

View File

@@ -21,15 +21,12 @@
[:title (str "Integreat | " page-name)]
[:link {:href "/css/font.min.css", :rel "stylesheet"}]
[:link {:rel "icon" :type "image/png" :href "/favicon.png"}]
#_[:link {:rel "stylesheet", :href "/css/react-datepicker.min.inc.css"}]
[:link {:rel "stylesheet", :href "/output.css"}]
[:script {:src "https://code.jquery.com/jquery-3.7.1.min.js"}]
[:script {:src "https://unpkg.com/hyperscript.org@0.9.7/dist/_hyperscript.min.js"}]
[:script {:src "https://unpkg.com/@popperjs/core@2.11.8/dist/umd/popper.min.js"}]
[:script {:src "https://cdn.plaid.com/link/v2/stable/link-initialize.js"}]
#_[:script {:src "https://unpkg.com/htmx.org@1.8.4"
:integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV"
:crossorigin= "anonymous"}]
[:script {:src "https://unpkg.com/htmx.org@1.9.6/dist/htmx.min.js"
:crossorigin= "anonymous"}]
[:script {:src "https://unpkg.com/htmx.org/dist/ext/debug.js"}]
@@ -38,7 +35,8 @@
[:link {:rel "stylesheet" :href "https://cdn.jsdelivr.net/npm/vanillajs-datepicker@1.1.4/dist/css/datepicker.min.css"}]
[:script {:type "text/javascript" :src "https://cdn.jsdelivr.net/npm/vanillajs-datepicker@1.1.4/dist/js/datepicker-full.min.js"}]
[:script {:type "text/javascript", :src "https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/autoComplete.min.js"}]
[:script {:type "text/javascript", :src "https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"}]
[:link {:rel "stylesheet" :href "https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css"}]
[:script {:src "https://unpkg.com/dropzone@5.9.3/dist/min/dropzone.min.js"}]
[:link {:rel "stylesheet" :href "https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" :type "text/css"}]
[:style

View File

@@ -0,0 +1,331 @@
(ns auto-ap.ssr.users
(:require
[auto-ap.datomic
:refer [add-sorter-fields
apply-pagination
apply-sort-3
conn
merge-query
pull-many
query2]]
[auto-ap.query-params :as query-params]
[auto-ap.routes.auth :as auth]
[auto-ap.routes.utils :refer [wrap-admin]]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.components :as com]
[auto-ap.ssr.grid-page-helper :as helper]
[auto-ap.ssr.svg :as svg]
[auto-ap.ssr.utils :refer [html-response]]
[auto-ap.time :as atime]
[bidi.bidi :as bidi]
[buddy.sign.jwt :as jwt]
[clj-http.client :as client]
[clojure.string :as str]
[config.core :refer [env]]
[datomic.api :as dc]))
(defn filters [request]
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
"hx-get" (bidi/path-for ssr-routes/only-routes
:user-table)
"hx-target" "#user-table"
"hx-indicator" "#user-table"}
[:fieldset.space-y-6
(com/field {:label "Name"}
(com/text-input {:name "name"
:id "name"
:class "hot-filter"
:value (:name (:parsed-query-params request))
:placeholder "Johnny Testerson"
:size :small}))
(com/field {:label "Email"}
(com/text-input {:name "email"
:id "email"
:class "hot-filter"
:value (:name (:parsed-query-params request))
:placeholder "hello@friend.com"
:size :small}))
(com/field {:label "Role"}
(com/radio {:size :small
:name "role"
:options [{:value ""
:content "All"}
{:value "admin"
:content "Admin"}
{:value "power-user"
:content "Power user"}
{:value "manager"
:content "Manager"}
{:value "user"
:content "User"}
{:value "none"
:content "None"}]}))]])
(def default-read '[:db/id
:user/name
:user/email
:user/profile-image-url
[:user/last-login :xform clj-time.coerce/from-date]
{[:user/role :xform iol-ion.query/ident] [:db/ident]
:user/clients [:client/code :db/id :client/locations :client/name]}])
(defn fetch-ids [db request]
(let [query-params (:parsed-query-params request)
query (cond-> {:query {:find []
:in '[$ ]
:where '[]}
:args [db ]}
(:sort query-params) (add-sorter-fields {"name" ['[?e :user/name ?un]
'[(clojure.string/upper-case ?un) ?sort-name]]
"email" ['[(get-else $ ?e :user/email "") ?sort-email]]
"role" ['[?e :user/role ?r]
'[?r :db/ident ?ri]
'[(name ?ri) ?sort-role]]
"last-login" ['[?e :user/last-login ?sort-last-login]]}
query-params)
(some->> query-params :name not-empty)
(merge-query {:query {:find []
:in ['?ns]
:where ['[?e :user/name ?sn]
'[(clojure.string/upper-case ?sn) ?upper-sn]
'[(clojure.string/includes? ?upper-sn ?ns)]]}
:args [(str/upper-case (:name query-params))]})
(some->> query-params :email not-empty)
(merge-query {:query {:find []
:in ['?es]
:where ['[?e :user/email ?se]
'[(clojure.string/upper-case ?se) ?upper-se]
'[(clojure.string/includes? ?upper-se ?es)]]}
:args [(str/upper-case (:email query-params))]})
(some->> query-params :role)
(merge-query {:query {:find []
:in ['?r]
:where ['[?e :user/role ?r]
'[?r :db/ident ?ri]]}
:args [(some->> query-params :role)]})
true
(merge-query {:query {:find ['?sort-default '?e]
:where ['[?e :user/name ?un]
'[(clojure.string/upper-case ?un) ?sort-default]]}}))]
(cond->> (query2 query)
true (apply-sort-3 query-params)
true (apply-pagination query-params))))
(defn hydrate-results [ids db _]
(let [results (->> (pull-many db default-read ids)
(group-by :db/id))
refunds (->> ids
(map results)
(map first))]
refunds))
(defn fetch-page [request]
(let [db (dc/db conn)
{ids-to-retrieve :ids matching-count :count} (fetch-ids db request)]
[(->> (hydrate-results ids-to-retrieve db request))
matching-count]))
(defn role->pill [role]
(com/pill {:color (cond (= :user-role/admin role)
:primary
(= :user-role/manager role)
:secondary
(= :user-role/power-user role)
:secondary
(= :user-role/user role)
:yellow
:else
:red)}
(name role)))
(defn user->client-pills [user]
[:div.flex.space-x-2
(for [{:client/keys [code]} (take 3 (:user/clients user))]
(com/pill {:color :primary}
code)
)
(let [remainder (- (count (:user/clients user)) 3)]
(when (> remainder 0)
(com/pill {:color :white}
(format "%d more" remainder))))])
(def grid-page
(helper/build {:id "user-table"
:nav (com/admin-aside-nav)
:page-specific-nav filters
:fetch-page fetch-page
:parse-query-params (comp
(query-params/parse-key :role #(query-params/parse-keyword "user-role" %))
(query-params/parse-key :total-gte query-params/parse-double)
(query-params/parse-key :total-lte query-params/parse-double)
(helper/default-parse-query-params grid-page))
:row-buttons (fn [request entity]
[(com/button {:hx-post (str (bidi/path-for ssr-routes/only-routes
:user-impersonate))
:hx-vals (format "{\"user-id\": \"%s\"}" (:db/id entity))} "Impersonate")
(com/icon-button {:hx-get (str (bidi/path-for ssr-routes/only-routes
:user-edit-dialog
:user-id (:db/id entity)))
:hx-target "#modal-holder"
:hx-swap "outerHTML"}
svg/pencil)])
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
:admin)}
"Admin"]
[:a {:href (bidi/path-for ssr-routes/only-routes
:users)}
"Users"]]
:title "Users"
:entity-name "User"
:route :user-table
:headers [{:key "name"
:name "Name"
:sort-key "name"
:render (fn [user]
[:div.flex.space-x-2.place-items-center
(when-let [profile-image (:user/profile-image-url user) ]
[:div.rounded-full.overflow-hidden.w-8.h-8.display-inline
[:img {:src profile-image }]])
[:span.inline-block ](:user/name user)])}
{:key "email"
:name "Email"
:sort-key "email"
:render #(-> % :user/email)}
{:key "role"
:name "Role"
:sort-key "role"
:render #(some-> % :user/role role->pill)}
{:key "last-login"
:name "Last login"
:sort-key "last-login"
:render #(some-> % (:user/last-login) (atime/unparse-local atime/standard-time))}
{:key "clients"
:name "Clients"
:render user->client-pills}
]}))
(def row* (partial helper/row* grid-page))
(def table* (partial helper/table* grid-page))
(defn impersonate [request]
(let [user (some-> request
:form-params
(get "user-id")
not-empty
Long/parseLong
(#(dc/pull (dc/db conn) default-read %))) ]
{:status 200
:headers {"hx-redirect" (str "/?jwt=" (jwt/sign (auth/user->jwt user "FAKE_TOKEN")
(:jwt-secret env)
{:alg :hs512}))
}
:session {:identity (dissoc (auth/user->jwt user "FAKE_TOKEN")
:exp)}}))
(defn user-edit-save [{:keys [form-params] :as request}]
(let [user (some-> request
:params
:user-id
not-empty
Long/parseLong
(#(dc/pull (dc/db conn) default-read %)))
new-clients (map #(Long/parseLong %)
(cond-> (get form-params "clients")
(string? (get form-params "clients")) vector))
_ @(dc/transact conn [
[:upsert-entity {:db/id (:db/id user)
:user/role (keyword "user-role" (get form-params "role"))
:user/clients new-clients}]])
user (some-> request
:params
:user-id
not-empty
Long/parseLong
(#(dc/pull (dc/db conn) default-read %)))]
(html-response
(row* identity user {:flash? true})
:headers {"hx-trigger" "closeModal"
"hx-retarget" (format "#user-table tr[data-id=\"%d\"]" (:db/id user))})))
(defn user-edit-dialog [request]
(let [user (some-> request
:params
:user-id
not-empty
Long/parseLong
(#(dc/pull (dc/db conn) default-read %)))]
(html-response
(com/modal
{}
[:form {:hx-post (str (bidi/path-for ssr-routes/only-routes
:user-edit-save
:request-method :post
:user-id (:db/id user )))
:hx-swap "outerHTML swap:300ms"}
[:fieldset {:class "hx-disable"}
(com/modal-card
{}
[:div.flex [:div.p-2 "User"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600 (:user/name user)]]
[:div.space-y-6
(com/field {:label "Role"}
(com/select {:name "role"
:class "w-36"
:id "role"
:value (name (:user/role user))
:options [["none" "None"]
["power-user" "Power user"]
["manager" "Manager"]
["admin" "Admin"]
["user" "User"]]
:size :small}))
(com/field {:label "Clients"}
(com/typeahead {:name "clients"
:class "w-full"
:multiple "multiple"
:url (bidi/path-for ssr-routes/only-routes
:company-search)
:id "role"
:value (map
(fn [client]
[(:db/id client) (:client/name client)])
(:user/clients user))
#_#_:value (name (:user/role user))
#_#_:options [["none" "None"]
["power-user" "Power user"]
["manager" "Manager"]
["admin" "Admin"]
["user" "User"]]
:size :small}))
(com/button {:color :primary}
"Save")
]
[:div])]]))))
(def key->handler
{:users (wrap-admin (helper/page-route grid-page))
:user-table (wrap-admin (helper/table-route grid-page))
:user-edit-save (wrap-client-redirect-unauthenticated (wrap-admin user-edit-save))
:user-edit-dialog (wrap-client-redirect-unauthenticated (wrap-admin user-edit-dialog))
:user-impersonate (wrap-client-redirect-unauthenticated (wrap-admin impersonate))})

View File

@@ -13,6 +13,11 @@
#"/search/?" :admin-history-search
["/" [#"\d+" :entity-id] #"/?"] :admin-history
["/inspect/" [#"\d+" :entity-id] #"/?"] :admin-history-inspect}
"/user" {"" :users
"/table" :user-table
"/impersonate" :user-impersonate
[[#"\d+" :user-id] "/edit"] {:get :user-edit-dialog
:post :user-edit-save}}
"/ezcater-xls" :admin-ezcater-xls}
"transaction" {"/insights" {"" :transaction-insights
"/table" :transaction-insight-table
@@ -33,6 +38,7 @@
"company" {"" :company
"/dropdown" :company-dropdown-search-results
"/search" :company-search
"/active" {:put :active-client}
"/1099" :company-1099
"/1099/table" {:get :company-1099-vendor-table}

View File

@@ -22,8 +22,6 @@
::impersonate
[with-user]
(fn [{:keys [db user]} [_ impersonate-jwt]]
(js/alert "HI")
{:http {:method "GET"
:uri (str "/impersonate?jwt=" impersonate-jwt)
:on-success [::impersonated impersonate-jwt]}}))