Add frontend auth example

This commit is contained in:
Juho Teperi 2019-01-28 22:16:41 +02:00
parent 71e83818b1
commit 51d24007e7
8 changed files with 240 additions and 0 deletions

View file

@ -0,0 +1,13 @@
# reitit-frontend + controllers example
## Usage
```clj
> lein figwheel
```
Go with browser to http://localhost:3449
## License
Copyright © 2018 Metosin Oy

View file

@ -0,0 +1 @@
../../../modules/reitit-core

View file

@ -0,0 +1 @@
../../../modules/reitit-frontend

View file

@ -0,0 +1 @@
../../../modules/reitit-schema

View file

@ -0,0 +1,54 @@
(defproject frontend "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.10.0"]
[ring-server "0.5.0"]
[reagent "0.8.1"]
[ring "1.7.1"]
[compojure "1.6.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.439"]
[metosin/reitit "0.2.13"]
[metosin/reitit-schema "0.2.13"]
[metosin/reitit-frontend "0.2.13"]
;; Just for pretty printting the match
[fipp "0.6.14"]]
:plugins [[lein-cljsbuild "1.1.7"]
[lein-figwheel "0.5.18"]]
:source-paths []
:resource-paths ["resources" "target/cljsbuild"]
:profiles {:dev {:dependencies [[binaryage/devtools "0.9.10"]]}}
:cljsbuild
{:builds
[{:id "app"
:figwheel true
:source-paths ["src"]
:watch-paths ["src" "checkouts/reitit-frontend/src"]
:compiler {:main "frontend.core"
:asset-path "/js/out"
:output-to "target/cljsbuild/public/js/app.js"
:output-dir "target/cljsbuild/public/js/out"
:source-map true
:optimizations :none
:pretty-print true
:preloads [devtools.preload]}}
{:id "min"
:source-paths ["src"]
:compiler {:output-to "target/cljsbuild/public/js/app.js"
:output-dir "target/cljsbuild/public/js"
:source-map "target/cljsbuild/public/js/app.js.map"
:optimizations :advanced
:pretty-print false}}]}
:figwheel {:http-server-root "public"
:server-port 3449
:nrepl-port 7002
;; Server index.html for all routes for HTML5 routing
:ring-handler backend.server/handler})

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>Reitit frontend example</title>
</head>
<body>
<div id="app"></div>
<script src="/js/app.js"></script>
</body>
</html>

View file

@ -0,0 +1,11 @@
(ns backend.server
(:require [clojure.java.io :as io]
[ring.util.response :as resp]
[ring.middleware.content-type :as content-type]))
(def handler
(-> (fn [request]
(or (resp/resource-response (:uri request) {:root "public"})
(-> (resp/resource-response "index.html" {:root "public"})
(resp/content-type "text/html"))))
content-type/wrap-content-type))

View file

@ -0,0 +1,149 @@
(ns frontend.core
(:require [reagent.core :as r]
[reitit.frontend :as rf]
[reitit.frontend.easy :as rfe]
[reitit.frontend.controllers :as rfc]
[reitit.coercion :as rc]
[reitit.coercion.schema :as rsc]
[schema.core :as s]
[fipp.edn :as fedn]))
(defonce state (r/atom {:user nil}))
(defn home-page []
[:div
[:h2 "Welcome to frontend"]
[:p "Look at console log for controller calls."]])
(defn item-page [match]
(let [{:keys [path query]} (:parameters match)
{:keys [id]} path]
[:div
[:ul
[:li [:a {:href (rfe/href ::item {:id 1})} "Item 1"]]
[:li [:a {:href (rfe/href ::item {:id 2} {:foo "bar"})} "Item 2"]]]
(if id
[:h2 "Selected item " id])
(if (:foo query)
[:p "Optional foo query param: " (:foo query)])]))
(defn login-done [user]
(swap! state assoc :user user))
(defn login [user]
;; In real app one would send API call here to create session or retrieve token or something
;; and the callback would update app-state
(js/setTimeout #(login-done user) 250))
(defn logout []
(swap! state assoc :user nil))
(defn login-view []
(let [form (r/atom {})]
(fn []
[:div
[:form
{:on-submit (fn [e]
(.preventDefault e)
(if (and (:username @form)
(:password @form))
(login @form)))}
[:label "Username"]
[:input
{:default-value ""
:on-change #(swap! form assoc :username %)}]
[:label "Password"]
[:input
{:default-value ""
:on-change #(swap! form assoc :password %)}]
[:button
{:type "submit"}
"Login"]]])))
(defn about-page []
[:div
[:p "This view is public."]])
(defn main-view []
(let [{:keys [user match]} @state
route-data (:data match)]
[:div
[:ul
[:li [:a {:href (rfe/href ::frontpage)} "Frontpage"]]
[:li [:a {:href (rfe/href ::about)} "About (public)"]]
[:li [:a {:href (rfe/href ::item-list)} "Item list"]]
(if user
[:li [:a {:on-click (fn [e]
(.preventDefault e)
(logout))
:href "#"}
"Logout"]])]
;; If user is authenticated
;; or if this route has been defined as public, else login view
(if (or user
(:public? route-data))
(if match
(let [view (:view route-data)]
[view match]))
[login-view])
[:pre (with-out-str (fedn/pprint @state))]]))
(defn log-fn [& params]
(fn [_]
(apply js/console.log params)))
(def routes
(rf/router
["/"
[""
{:name ::frontpage
:view home-page
:controllers [{:start (log-fn "start" "frontpage controller")
:stop (log-fn "stop" "frontpage controller")}]}]
["about"
{:name ::about
:view about-page
:public? true}]
["items"
;; Shared data for sub-routes
{:view item-page
:controllers [{:start (log-fn "start" "items controller")
:stop (log-fn "stop" "items controller")}]}
[""
{:name ::item-list
:controllers [{:start (log-fn "start" "item-list controller")
:stop (log-fn "stop" "item-list controller")}]}]
["/:id"
{:name ::item
:parameters {:path {:id s/Int}
:query {(s/optional-key :foo) s/Keyword}}
:controllers [{:params (fn [match]
(:path (:parameters match)))
:start (fn [params]
(js/console.log "start" "item controller" (:id params)))
:stop (fn [params]
(js/console.log "stop" "item controller" (:id params)))}]}]]]
{:data {:controllers [{:start (log-fn "start" "root-controller")
:stop (log-fn "stop" "root controller")}]
:coercion rsc/coercion
:public? false}}))
(defn init! []
(rfe/start!
routes
(fn [new-match]
(swap! state (fn [state]
(if new-match
(if (:user state)
(assoc state :match (assoc new-match :controllers (rfc/apply-controllers (:controllers (:match state)) new-match)))
(assoc state :match new-match))))))
{:use-fragment true})
(r/render [main-view] (.getElementById js/document "app")))
(init!)