Skip to content

Commit

Permalink
clinic: refactor routing logic to lift effectful state out of views
Browse files Browse the repository at this point in the history
  • Loading branch information
ashutoshgngwr committed Oct 27, 2023
1 parent 15e77ad commit 5063ef1
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 103 deletions.
7 changes: 4 additions & 3 deletions clinic/src/cljs/clinic/components.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
(defn page []
(let [this (r/current-component)
props (r/props this)
logout-enabled (props :logout-enabled)
logout-handler (props :on-logout-click #())]
title-href (:title-href props)
logout-enabled (:logout-enabled props)
logout-handler (get props :on-logout-click #())]
(into [:main {:class ["flex" "flex-col gap-12 md:gap-16"
"w-full max-w-4xl"
"mx-auto p-8 md:p-12"]}
[:header {:class ["flex" "flex-row" "gap-12"]}
[:a {:href "/"} [heading-1 "Acme Orthopedic Clinic"]]
[:a {:href title-href} [heading-1 "Acme Orthopedic Clinic"]]
(when logout-enabled
[:<>
[:div {:class "flex-grow"}]
Expand Down
25 changes: 20 additions & 5 deletions clinic/src/cljs/clinic/router.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,30 @@
#(-> (bidi/match-route routes %)
(assoc :query-params (u/query-params %)))))

(def path-for (partial bidi/path-for routes))

(defn start! []
(pushy/start! history))

(defn replace-token! [token]
(pushy/replace-token! history token))
(rf/reg-event-fx ::set-current-view
(fn [{db :db} [_ view-id params]]
{:db (assoc db ::current-view {::id view-id ::params params})
:dispatch [::on-current-view-changed]}))

(defn set-token! [token]
(pushy/set-token! history token))
(rf/reg-sub ::current-view get-in)

(rf/reg-fx ::set-token
(fn router-set-token-effect [token]
(set-token! token)))
(pushy/set-token! history token)))

(rf/reg-fx ::replace-token
(fn router-replace-token-effect [token]
(pushy/replace-token! history token)))

(rf/reg-event-fx ::set-token
(fn [_ [_ token]]
{::set-token token}))

(rf/reg-event-fx ::replace-token
(fn [_ [_ token]]
{::replace-token token}))
16 changes: 14 additions & 2 deletions clinic/src/cljs/clinic/utils.cljs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
(ns clinic.utils
(:require [clojure.spec.alpha :as s]))
(:require [clojure.spec.alpha :as s]
[clojure.string :as string]))

(defn form-data->map
"Converts DOM FormData to a Clojure map. Also keywordizes keys in the
Expand Down Expand Up @@ -27,7 +28,18 @@
(defn query-params
"Returns a keywordized map of query parameters in the given `url`."
[url]
(->> (js/URL. url "http://dummy")
(->> (js/URL. url
(-> js/window
(.-location)
(.-origin)))
(.-searchParams)
(map (fn [[k v]] [(keyword k) v]))
(into {})))

(defn url [path query-params]
(->> query-params
(map #(str (-> % (first) (name))
"="
(-> % (second) (js/encodeURIComponent))))
(string/join "&")
(str path "?")))
48 changes: 24 additions & 24 deletions clinic/src/cljs/clinic/views/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,36 @@
[clinic.views.view-patient :as view-patient]
[re-frame.core :as rf]))

(def ^:private views {::router/home home/root
::router/create-patient create-patient/root
::router/view-patient view-patient/root
::router/search-patients list-patients/root})

(def ^:private titles {::router/home "Home"
::router/create-patient "Add Patient"
::router/view-patient "Patient Info"
::router/search-patients "Search Patients"})

(rf/reg-fx ::set-window-title
(fn [title]
(set! (.-title js/document)
(-> title
(or "Page Not Found")
(str " - Acme Clinic")))))

(rf/reg-event-fx ::router/set-current-view
(fn [{db :db} [_ view-id params]]
{:db (assoc db ::current-view {::id view-id ::params params})
::set-window-title (titles view-id)}))

(rf/reg-sub ::current-view :-> ::current-view)
(rf/reg-event-fx ::router/on-current-view-changed
(fn [{db :db} _]
(let [{view-id ::router/id
params ::router/params} (::router/current-view db)]
(case view-id
::router/home {::set-window-title "Home"}
::router/create-patient {::set-window-title "Add Patient"}
::router/view-patient {::set-window-title "Patient Info"
:dispatch [::view-patient/fetch-patient params]}
::router/search-patients {::set-window-title "Search Patients"
:dispatch [::list-patients/fetch-patients params]}))))

(defn root []
(let [current-role (user-role/get)
current-view (rf/subscribe [::current-view])]
(fn []
[components/page {:logout-enabled @current-role
:on-logout-click #(do (user-role/clear)
(router/replace-token! "/"))}
[(get views (::id @current-view) not-found/root)
(::params @current-view)]])))
(let [current-role @(user-role/get)
current-view-id @(rf/subscribe [::router/current-view ::router/id])]
[components/page {:title-href (router/path-for ::router/home)
:logout-enabled current-role
:on-logout-click #(do (user-role/clear)
(rf/dispatch [::router/replace-token
(router/path-for ::router/home)]))}
[(case current-view-id
::router/home home/root
::router/create-patient create-patient/root
::router/view-patient view-patient/root
::router/search-patients list-patients/root
not-found/root)]]))
4 changes: 1 addition & 3 deletions clinic/src/cljs/clinic/views/create_patient.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
(rf/reg-event-fx ::submit-form-data-success
(fn [{db :db} [_ result]]
{:db (assoc db ::submit-form {::loading false})
::router/set-token (str "/patients/" (result :id))}))
::router/set-token (router/path-for ::router/view-patient :id (result :id))}))

(rf/reg-event-db ::submit-form-data-failure
(fn [db [_ {error-code :status}]]
Expand Down Expand Up @@ -48,12 +48,10 @@
loading? (rf/subscribe [::submit-form ::loading])
error-code (rf/subscribe [::submit-form ::error-code])]
(fn []
(prn @loading? @error-code)
[:section {:class ["flex" "flex-col" "gap-12"]}
[components/heading-2 "Add a Patient"]
[:form {:ref (partial reset! form-ref)
:method "POST"
:action "/api/v1/patients/"
:class ["w-full" "flex" "flex-col" "gap-4"]
:on-blur #(do (swap! touched? conj (-> %
(.-target)
Expand Down
7 changes: 4 additions & 3 deletions clinic/src/cljs/clinic/views/home.cljs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
(ns clinic.views.home
(:require [clinic.components :as components]
[clinic.router :as router]
[clinic.user-role.core :as user-role]))

(defn- role-selector []
Expand All @@ -26,15 +27,15 @@

(defn- nurse-fn-list []
(let [list-item #(vector :li
[:a {:href %2
[:a {:href (router/path-for %2)
:class ["text-blue-600" "hover:underline"]}
%1])]

[:section {:class ["flex" "flex-col" "gap-8"]}
[components/heading-2 "Operations"]
[:ol {:class ["list-decimal" "list-inside"]}
[list-item "Add patient" "/patients/new"]
[list-item "Search Patients" "/patients"]]]))
[list-item "Add patient" ::router/create-patient]
[list-item "Search Patients" ::router/search-patients]]]))

(defn root []
(let [current-role (user-role/get)]
Expand Down
94 changes: 51 additions & 43 deletions clinic/src/cljs/clinic/views/list_patients.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,63 @@
(:require [ajax.core :as ajax]
[clinic.components :as components]
[clinic.router :as router]
[clinic.utils :as u]
[re-frame.core :as rf]
[reagent.core :as r]))

(rf/reg-event-db ::fetch-patients-success
(fn [db [_ phone page result]]
(assoc-in db [::patients phone page] {::loading false
::data result})))
(fn [db [_ result]]
(assoc db ::patients {::loading false ::data result})))

(rf/reg-event-db ::fetch-patients-failure
(fn [db [_ phone page {error-code :status}]]
(assoc-in db [::patients phone page] {::loading false
::error-code error-code})))
(fn [db [_ {error-code :status}]]
(assoc db ::patients {::loading false ::error-code error-code})))

(rf/reg-event-fx ::fetch-patients
(fn [{db :db} [_ phone page]]
{:db (assoc-in db [::patients phone page] {::loading true})
:http-xhrio {:method :get
:uri (str "/api/v1/patients/")
:params (cond-> {:count 10
:offset (* 10 (dec page))}
phone (assoc :phone phone))
:response-format (ajax/json-response-format {:keywords? true})
:on-success [::fetch-patients-success phone page]
:on-failure [::fetch-patients-failure phone page]}}))
(fn [{db :db} [_ {:keys [phone page] :or {page "1"}}]]
(let [page-num (parse-long page)]
{:db (assoc db ::patients {::loading true})
:http-xhrio {:method :get
:uri (str "/api/v1/patients/")
:params (cond-> {:count 10
:offset (* 10 (dec page-num))}
phone (assoc :phone phone))
:response-format (ajax/json-response-format {:keywords? true})
:on-success [::fetch-patients-success]
:on-failure [::fetch-patients-failure]}})))

(rf/reg-sub ::patients get-in)

(defn- patient-row []
(let [{:keys [index patient]} (r/props (r/current-component))]
[:tr {:class [(if (odd? index) "bg-gray-50" "bg-white")
"hover:bg-gray-100" "hover:cursor-pointer"]
:on-click #(router/set-token! (str "/patients/" (:id patient)))}
:on-click #(rf/dispatch [::router/set-token
(router/path-for ::router/view-patient :id (:id patient))])}
[:td {:class ["px-6" "py-2"]} (inc index)]
[:td {:class ["px-6" "py-2"]} (:first-name patient) " " (:last-name patient)]
[:td {:class ["px-6" "py-2"]} (:birth-date patient)]
[:td {:class ["px-6" "py-2"]} (:phone patient)]]))

(defn root []
(let [props (r/props (r/current-component))
page (parse-long (get props :page "1"))
phone (:phone props)
loading? (rf/subscribe [::patients phone page ::loading])
patients (rf/subscribe [::patients phone page ::data])
error-code (rf/subscribe [::patients phone page ::error-code])]
(rf/dispatch [::fetch-patients phone page])
(let [params @(rf/subscribe [::router/current-view ::router/params])
page (parse-long (get params :page "1"))
phone (:phone params)
loading? @(rf/subscribe [::patients ::loading])
patients @(rf/subscribe [::patients ::data])
error-code @(rf/subscribe [::patients ::error-code])]
[:section {:class ["flex" "flex-col" "gap-8"]}
[:div {:class ["flex" "flex-row" "self-center" "items-center" "gap-6"]}
[components/heading-2 "Search Patients"]
[:form {:class ["flex" "flex-row" "self-center" "items-center" "gap-6"]
:on-submit #(do (.preventDefault %)
(let [phone (-> js/document
(.getElementById "phone")
(.-value))]
(when-not (empty? phone)
(rf/dispatch [::router/set-token
(-> ::router/search-patients
(router/path-for)
(u/url {:phone phone}))]))))}
[:input {:id "phone"
:name "phone"
:placeholder "Search by phone"
Expand All @@ -58,26 +68,22 @@
"rounded" "py-2.5" "px-4" "leading-tight"
"focus:outline-none" "focus:bg-white"
"focus:border-gray-500"]}]
[:button {:class ["bg-blue-600" "hover:bg-blue-800" "text-white"
"font-bold" "py-2" "px-4" "rounded-full"]
:on-click #(-> js/document
(.getElementById "phone")
(.-value)
((partial str "/patients?phone="))
(router/set-token!))}
"Search"]]
[:input {:type "submit"
:value "Search"
:class ["bg-blue-600" "hover:bg-blue-800" "text-white"
"font-bold" "py-2" "px-4" "rounded-full"]}]]

(cond
@loading?
loading?
[components/spinner {:class ["block" "self-center" "w-8" "h-8" "m-16" "text-blue-600"]}]

@error-code
error-code
[components/danger-alert "There was an error while fetching patient data. Please try again!"]

(empty? @patients)
(empty? patients)
[:p {:class ["self-center" "text-center"]} "No patients found matching this criteria!"]

@patients
patients
[:<>
[:table {:class ["w-full" "table-auto" "self-center" "text-center"]}
[:thead
Expand All @@ -88,16 +94,18 @@
[:th {:class ["px-6" "py-2"]} "Phone Number"]]]
(into [:tbody] (map-indexed #(do [patient-row {:index %1
:patient %2}])
@patients))]
patients))]
[:div {:class ["flex" "flex-row" "justify-center" "gap-8"]}
[:a {:class ["text-blue-600" "hover:underline"
(when (<= page 1) "invisible")]
:href (cond-> (str "/patients?page=" (dec page))
phone (str "&phone=" phone))}
:href (-> (router/path-for ::search-patients)
(u/url (cond-> {:page (dec page)}
phone (assoc :phone phone))))}
"Prev"]
[:p {:class ["font-medium"]} "Page" " " page]
[:a {:class ["text-blue-600" "hover:underline"
(when (< (count @patients) 10) "invisible")]
:href (cond-> (str "/patients?page=" (inc page))
phone (str "&phone=" phone))}
(when (< (count patients) 10) "invisible")]
:href (-> (router/path-for ::search-patients)
(u/url (cond-> {:page (inc page)}
phone (assoc :phone phone))))}
"Next"]]])]))
30 changes: 10 additions & 20 deletions clinic/src/cljs/clinic/views/view_patient.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,21 @@


(rf/reg-event-db ::fetch-patient-success
(fn [db [_ patient-id result]]
(assoc-in db
[::patient patient-id]
{::loading false
::data result})))
(fn [db [_ result]]
(assoc db ::patient {::loading false ::data result})))

(rf/reg-event-db ::fetch-patient-failure
(fn [db [_ patient-id {error-code :status}]]
(assoc-in db
[::patient patient-id]
{::loading false
::error-code error-code})))
(fn [db [_ {error-code :status}]]
(assoc db ::patient {::loading false ::error-code error-code})))

(rf/reg-event-fx ::fetch-patient
(fn [{db :db} [_ patient-id]]
(fn [{db :db} [_ {patient-id :id}]]
{:db (assoc-in db [::patient patient-id] {::loading true})
:http-xhrio {:method :get
:uri (str "/api/v1/patients/" patient-id)
:response-format (ajax/json-response-format {:keywords? true})
:on-success [::fetch-patient-success patient-id]
:on-failure [::fetch-patient-failure patient-id]}}))
:on-success [::fetch-patient-success]
:on-failure [::fetch-patient-failure]}}))

(rf/reg-sub ::patient get-in)

Expand Down Expand Up @@ -57,13 +51,9 @@
"Unknown"))

(defn root []
(let [patient-id (-> (r/current-component)
(r/props)
(:id))
loading? @(rf/subscribe [::patient patient-id ::loading])
patient @(rf/subscribe [::patient patient-id ::data])
error-code @(rf/subscribe [::patient patient-id ::error-code])]
(rf/dispatch [::fetch-patient patient-id])
(let [loading? @(rf/subscribe [::patient ::loading])
patient @(rf/subscribe [::patient ::data])
error-code @(rf/subscribe [::patient ::error-code])]
[:section {:class ["flex" "flex-col"]}
(cond
loading? [components/spinner {:class ["block" "self-center" "w-8" "h-8" "m-16" "text-blue-600"]}]
Expand Down

0 comments on commit 5063ef1

Please sign in to comment.