keechma-login

0.1.0-SNAPSHOT


Example application with the login workflow.

dependencies

org.clojure/clojure
1.7.0
org.clojure/clojurescript
1.7.170
org.clojure/core.async
0.2.374
keechma
0.1.0-SNAPSHOT
(this space intentionally left almost blank)
 
(ns keechma-login.components.session
  (:require [keechma.ui-component :as ui]))

Renders the login screen

(defn render-login
  [ctx]
  [:button.btn.btn-primary
    {:on-click #(ui/send-command ctx :login)}
    "Login to your Trello account"])

Renders the main component of the :main application

(defn render-main-app
  [main-app]
  [(:main-component main-app)])

This component is rendered by the session application. - If the session app's :main-component is nil it will render the login screen - If the session app has :main-component set render the main component of the :main app

(defn render
  [ctx]
  (fn []
    (let [main-app-sub (ui/subscription ctx :main-app)
          main-app @main-app-sub]
      [:div.container>div.row>div.col-xs-12>div.jumbotron
       (if main-app
         (render-main-app main-app)
         (render-login ctx))])))
(def component (ui/constructor
                {:renderer render
                 :subscription-deps [:main-app]
                 :topic :login}))
 
(ns keechma-login.components.trello
  (:require [keechma.ui-component :as ui]))

Renders the user info based on the loaded data.

(defn render-current-user
  [ctx current-user]
  [:div
   [:h1 (str "Hello " (:fullName current-user))]
   [:img {:src (str "https://trello-avatars.s3.amazonaws.com/" (:avatarHash current-user) "/30.png")}]
   [:br]
   [:a {:href (ui/url ctx {:session-action "logout"})} "Logout"]])

Resolves the :current-user subscription if it's nil it shows the "Loading..." string, otherwise it renders the current user info

(defn render
  [ctx]
  (fn []
    (let [current-user-sub (ui/subscription ctx :current-user)
          current-user @current-user-sub]
      (if current-user
        (render-current-user ctx current-user)
        [:div "Loading user info..."]))))
(def component (ui/constructor
                {:renderer render
                 :subscription-deps [:current-user]}))
 
(ns keechma-login.core
  (:require [keechma.app-state :as app-state]
            [keechma-login.apps.session :as session]))
(enable-console-print!)
(defonce running-app (clojure.core/atom))

Helper function that starts the application.

(defn start-app!
  []
  (reset! running-app (app-state/start! session/app)))

Helper function that restarts the application whenever the code is hot reloaded.

(defn restart-app!
  []
  (let [current @running-app]
    (if current
      (app-state/stop! current start-app!)
      (start-app!))))
(restart-app!)
(defn on-js-reload []
  ;; optionally touch your app-state to force rerendering depending on
  ;; your application
  ;; (swap! app-state update-in [:__figwheel_counter] inc))
 
(ns keechma-login.subscriptions
  (:require-macros [reagent.ratom :refer [reaction]]))
(defn current-user [app-db]
  ;; Returns the current user. Called by the `main` app
  (reaction
   (get-in @app-db [:kv :current-user])))
(defn main-app [app-db]
  ;; Returns the main application. Called by the `session` app
  (reaction
   (get-in @app-db [:kv :main-app])))
 
(ns keechma-login.controllers.logout
  (:require [keechma.controller :as controller]
            [keechma.app-state :as app-state]))

When the user lands on the logout url, and this controller is started, it will immediately redirect the user to the frontpage URL.

Logout is handled by the stop function which will stop the main application and remove it from the session app's application state.

This will cause the session app to render the login screen.

(defrecord  Controller []
  controller/IController
  (params [_ route-params]
    (when (= (get-in route-params [:data :session-action]) "logout")
      true))
  (start [this params app-db]
    (controller/redirect this {})
    app-db)
  (stop [_ params app-db]
    (let [kv-store (:kv app-db)
          main-app (:main-app kv-store)]
      (app-state/stop! main-app)
      (assoc app-db :kv (dissoc kv-store :main-app)))))
 
(ns keechma-login.controllers.trello
  (:require [keechma.controller :as controller]))
(defn load-user-info [app-db-atom]
  (.get (.-members js/Trello) "me"
        (fn [user-info]
          (swap! app-db-atom assoc-in [:kv :current-user] (js->clj user-info :keywordize-keys true)))))
(defrecord Controller []
  controller/IController
  (params [_ _] true)
  (handler [_ app-db-atom _ _]
    (load-user-info app-db-atom)))
 
(ns keechma-login.controllers.login
  (:require [keechma.controller :as controller]
            [keechma.app-state :as app-state]))

Starts the main application when the user is suceessfully logged in.

(defn start-app!
  [main-app app-db-atom]
  (let [started-app (app-state/start! main-app false)]
    (swap! app-db-atom assoc-in [:kv :main-app] started-app)))

Logs in the user.

(defn login
  [main-app app-db-atom _]
  (let [success-cb (partial start-app! main-app app-db-atom)]
    (.authorize js/Trello #js {:type "popup"
                               :name "Keechma Trello example"
                               :success success-cb})))

Handles the user login.

When the user clicks the "Login" button it will use the Trello client to create the session.

After that it will start the main application and save the reference to it in the session app's application state.

This allows the session app to render the main application instead of the login screen.

(defrecord  Controller [main-app]
  controller/IController
  (params [_ route-params] true)
  (handler [this app-db-atom in-chan _]
    (let [main-app (:main-app this)]
      (controller/dispatcher app-db-atom in-chan
                             {:login (partial login main-app)}))))
 
(ns keechma-login.apps.main
  (:require [keechma-login.controllers.trello :as trello]
            [keechma-login.components.trello :as trello-c]
            [keechma-login.subscriptions :refer [current-user]]))
(def app {:routes ["session/:session-action"]
          :controllers {:trello (trello/->Controller)}
          :components {:main trello-c/component}
          :subscriptions {:current-user current-user}})
 
(ns keechma-login.apps.session
  (:require [keechma-login.controllers.login :as login]
            [keechma-login.controllers.logout :as logout]
            [keechma-login.components.session :as session-c]
            [keechma-login.subscriptions :as subs]
            [keechma-login.apps.main :as main]))
(def app {:routes ["session/:session-action"]
          :controllers {:login (login/->Controller main/app)
                        :logout (logout/->Controller)}
          :components {:main session-c/component}
          :subscriptions {:main-app subs/main-app}
          :html-element (.getElementById js/document "app")})