aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoris Guyonvarch2015-07-21 23:25:58 +0200
committerJoris Guyonvarch2015-07-21 23:25:58 +0200
commit2a53fe50c62d4b7aec0f422998c743f68aa523c1 (patch)
treead32464c99668b477c4006146ec218c947bc9c8f /src
parenta271d6034bc4cc631a64476d25d21c83a701fa39 (diff)
downloadbudget-2a53fe50c62d4b7aec0f422998c743f68aa523c1.tar.gz
budget-2a53fe50c62d4b7aec0f422998c743f68aa523c1.tar.bz2
budget-2a53fe50c62d4b7aec0f422998c743f68aa523c1.zip
Adding the payment without reloading the page
Diffstat (limited to 'src')
-rw-r--r--src/client/Main.elm35
-rw-r--r--src/client/Model.elm8
-rw-r--r--src/client/Model/Message.elm8
-rw-r--r--src/client/Model/View/PaymentView.elm10
-rw-r--r--src/client/Native/Reads.js27
-rw-r--r--src/client/Reads.elm10
-rw-r--r--src/client/ServerCommunication.elm17
-rw-r--r--src/client/Update.elm13
-rw-r--r--src/client/Update/Payment.elm20
-rw-r--r--src/client/View/Events.elm19
-rw-r--r--src/client/View/Payments/Add.elm20
-rw-r--r--src/client/View/SignIn.elm17
-rw-r--r--src/server/Application.hs86
-rw-r--r--src/server/Design/Global.hs2
-rw-r--r--src/server/Main.hs3
-rw-r--r--src/server/Secure.hs23
16 files changed, 224 insertions, 94 deletions
diff --git a/src/client/Main.elm b/src/client/Main.elm
index 678d20e..fd0cec7 100644
--- a/src/client/Main.elm
+++ b/src/client/Main.elm
@@ -10,9 +10,11 @@ import Html exposing (Html)
import Http
import Task exposing (..)
+import Time exposing (..)
import Model exposing (Model, initialModel)
import Model.Payment exposing (Payments, paymentsDecoder)
+import Model.Message exposing (messageDecoder)
import Update exposing (Action(..), actions, updateModel)
import Update.SignIn exposing (..)
@@ -27,34 +29,41 @@ main : Signal Html
main = Signal.map renderPage model
model : Signal Model
-model = Signal.foldp updateModel initialModel actions.signal
+model = Signal.foldp updateModel (initialModel initialTime) update
--------------------------
+update : Signal Action
+update = Signal.mergeMany
+ [ Signal.map UpdateTime (Time.every 30)
+ , actions.signal
+ ]
+
+---------------------------------------
port signInError : Maybe String
---------------------------------------
-port fetchPayments : Task Http.Error ()
-port fetchPayments =
+port initialTime : Time
+
+---------------------------------------
+
+port initView : Task Http.Error ()
+port initView =
case signInError of
Just msg ->
Signal.send actions.address (SignInError msg)
Nothing ->
- getPayments
- |> flip Task.andThen reportSuccess
- |> flip Task.onError reportError
-
-reportSuccess : Payments -> Task x ()
-reportSuccess payments = Signal.send actions.address (GoPaymentView payments)
+ Task.map2 GoPaymentView getUserName getPayments
+ |> flip Task.andThen (Signal.send actions.address)
+ |> flip Task.onError (\_ -> Signal.send actions.address GoSignInView)
-reportError : Http.Error -> Task x ()
-reportError error = Signal.send actions.address GoSignInView
+getUserName : Task Http.Error String
+getUserName = Http.get messageDecoder "/userName"
getPayments : Task Http.Error Payments
getPayments = Http.get paymentsDecoder "/payments"
----------------------------------------------------
+---------------------------------------
port serverCommunicationsPort : Signal (Task Http.RawError ())
port serverCommunicationsPort =
diff --git a/src/client/Model.elm b/src/client/Model.elm
index 8005429..45fdf87 100644
--- a/src/client/Model.elm
+++ b/src/client/Model.elm
@@ -3,13 +3,17 @@ module Model
, initialModel
) where
+import Time exposing (Time)
+
import Model.View exposing (..)
type alias Model =
{ view : View
+ , currentTime : Time
}
-initialModel : Model
-initialModel =
+initialModel : Time -> Model
+initialModel initialTime =
{ view = LoadingView
+ , currentTime = initialTime
}
diff --git a/src/client/Model/Message.elm b/src/client/Model/Message.elm
new file mode 100644
index 0000000..9f21fd3
--- /dev/null
+++ b/src/client/Model/Message.elm
@@ -0,0 +1,8 @@
+module Model.Message
+ ( messageDecoder
+ ) where
+
+import Json.Decode exposing (..)
+
+messageDecoder : Decoder String
+messageDecoder = ("message" := string)
diff --git a/src/client/Model/View/PaymentView.elm b/src/client/Model/View/PaymentView.elm
index cea7d2e..8de005d 100644
--- a/src/client/Model/View/PaymentView.elm
+++ b/src/client/Model/View/PaymentView.elm
@@ -6,14 +6,16 @@ module Model.View.PaymentView
import Model.Payment exposing (Payments)
type alias PaymentView =
- { name : String
+ { userName : String
+ , name : String
, cost : String
, payments : Payments
}
-initPaymentView : Payments -> PaymentView
-initPaymentView payments =
- { name = ""
+initPaymentView : String -> Payments -> PaymentView
+initPaymentView userName payments =
+ { userName = userName
+ , name = ""
, cost = ""
, payments = payments
}
diff --git a/src/client/Native/Reads.js b/src/client/Native/Reads.js
new file mode 100644
index 0000000..52590f9
--- /dev/null
+++ b/src/client/Native/Reads.js
@@ -0,0 +1,27 @@
+Elm.Native.Reads = {};
+Elm.Native.Reads.make = function(localRuntime) {
+
+ localRuntime.Native = localRuntime.Native || {};
+ localRuntime.Native.Reads = localRuntime.Native.Reads || {};
+ if(localRuntime.Native.Reads.values) {
+ return localRuntime.Native.Reads.values;
+ }
+
+ var Result = Elm.Result.make(localRuntime);
+
+ function div(a, b)
+ {
+ return (a/b)|0;
+ }
+
+ function readInt(str) {
+ var number = Number(str);
+ return isNaN(number)
+ ? Result.Err("unable to parse '" + str + "' as a number")
+ : Result.Ok(number);
+ }
+
+ return localRuntime.Native.Reads.values = {
+ readInt: readInt
+ };
+};
diff --git a/src/client/Reads.elm b/src/client/Reads.elm
new file mode 100644
index 0000000..7bc1bbc
--- /dev/null
+++ b/src/client/Reads.elm
@@ -0,0 +1,10 @@
+module Reads
+ ( readInt
+ ) where
+
+
+import Native.Reads
+import Result exposing (Result)
+
+readInt : String -> Result String Int
+readInt = Native.Reads.readInt
diff --git a/src/client/ServerCommunication.elm b/src/client/ServerCommunication.elm
index d763e29..ccf63f2 100644
--- a/src/client/ServerCommunication.elm
+++ b/src/client/ServerCommunication.elm
@@ -5,18 +5,21 @@ module ServerCommunication
) where
import Signal
-import Task
-import Task exposing (Task)
+import Task as Task exposing (Task)
import Http
import Json.Decode exposing (..)
+import Date
+
+import Model.Message exposing (messageDecoder)
import Update as U
import Update.SignIn exposing (..)
+import Update.Payment as UP
type Communication =
NoCommunication
| SignIn String
- | AddPayment String String
+ | AddPayment String Int
| SignOut
serverCommunications : Signal.Mailbox Communication
@@ -47,7 +50,7 @@ getRequest communication =
Just
{ verb = "post"
, headers = []
- , url = "/payment/add?name=" ++ name ++ "&cost=" ++ cost
+ , url = "/payment/add?name=" ++ name ++ "&cost=" ++ (toString cost)
, body = Http.empty
}
SignOut ->
@@ -67,8 +70,8 @@ communicationToAction communication response =
U.NoOp
SignIn login ->
U.UpdateSignIn (ValidLogin login)
- AddPayment _ _ ->
- U.NoOp
+ AddPayment name cost ->
+ U.UpdatePayment (UP.AddPayment name cost)
SignOut ->
U.GoSignInView
else
@@ -86,7 +89,7 @@ decodeResponse : Http.Response -> (String -> U.Action) -> U.Action
decodeResponse response responseToAction =
case response.value of
Http.Text text ->
- case decodeString ("message" := string) text of
+ case decodeString messageDecoder text of
Ok x ->
responseToAction x
Err _ ->
diff --git a/src/client/Update.elm b/src/client/Update.elm
index f88a3a2..be7538a 100644
--- a/src/client/Update.elm
+++ b/src/client/Update.elm
@@ -4,6 +4,8 @@ module Update
, updateModel
) where
+import Time exposing (Time)
+
import Model exposing (Model)
import Model.Payment exposing (Payments)
import Model.View as V
@@ -15,10 +17,11 @@ import Update.Payment exposing (..)
type Action =
NoOp
+ | UpdateTime Time
| GoSignInView
| SignInError String
| UpdateSignIn SignInAction
- | GoPaymentView Payments
+ | GoPaymentView String Payments
| UpdatePayment PaymentAction
actions : Signal.Mailbox Action
@@ -29,10 +32,12 @@ updateModel action model =
case action of
NoOp ->
model
+ UpdateTime time ->
+ { model | currentTime <- time }
GoSignInView ->
{ model | view <- V.SignInView initSignInView }
- GoPaymentView payments ->
- { model | view <- V.PaymentView (initPaymentView payments) }
+ GoPaymentView userName payments ->
+ { model | view <- V.PaymentView (initPaymentView userName payments) }
SignInError msg ->
let signInView = { initSignInView | result <- Just (Err msg) }
in { model | view <- V.SignInView signInView }
@@ -45,6 +50,6 @@ updateModel action model =
UpdatePayment paymentAction ->
case model.view of
V.PaymentView paymentView ->
- { model | view <- V.PaymentView (updatePayment paymentAction paymentView) }
+ { model | view <- V.PaymentView (updatePayment model paymentAction paymentView) }
_ ->
model
diff --git a/src/client/Update/Payment.elm b/src/client/Update/Payment.elm
index 129ccde..136f0f9 100644
--- a/src/client/Update/Payment.elm
+++ b/src/client/Update/Payment.elm
@@ -3,6 +3,9 @@ module Update.Payment
, updatePayment
) where
+import Date
+
+import Model exposing (Model)
import Model.View.PaymentView exposing (..)
import Model.Payment exposing (..)
@@ -10,9 +13,10 @@ type PaymentAction =
UpdateName String
| UpdateCost String
| UpdatePayments Payments
+ | AddPayment String Int
-updatePayment : PaymentAction -> PaymentView -> PaymentView
-updatePayment action paymentView =
+updatePayment : Model -> PaymentAction -> PaymentView -> PaymentView
+updatePayment model action paymentView =
case action of
UpdateName name ->
{ paymentView | name <- name }
@@ -20,3 +24,15 @@ updatePayment action paymentView =
{ paymentView | cost <- cost }
UpdatePayments payments ->
{ paymentView | payments <- payments }
+ AddPayment name cost ->
+ let payment =
+ { creation = Date.fromTime model.currentTime
+ , name = name
+ , cost = cost
+ , userName = paymentView.userName
+ }
+ in { paymentView
+ | payments <- payment :: paymentView.payments
+ , name <- ""
+ , cost <- ""
+ }
diff --git a/src/client/View/Events.elm b/src/client/View/Events.elm
new file mode 100644
index 0000000..1eb9027
--- /dev/null
+++ b/src/client/View/Events.elm
@@ -0,0 +1,19 @@
+module View.Events
+ ( onSubmitPrevDefault
+ ) where
+
+import Signal
+import Json.Decode as Json
+import Html exposing (..)
+import Html.Events exposing (..)
+import Html.Attributes exposing (..)
+
+onSubmitPrevDefault : Signal.Address a -> a -> Attribute
+onSubmitPrevDefault address value =
+ onWithOptions
+ "submit"
+ { defaultOptions | preventDefault <- True }
+ Json.value
+ (\_ ->
+ Signal.message address value
+ )
diff --git a/src/client/View/Payments/Add.elm b/src/client/View/Payments/Add.elm
index f2230be..32010ef 100644
--- a/src/client/View/Payments/Add.elm
+++ b/src/client/View/Payments/Add.elm
@@ -2,20 +2,28 @@ module View.Payments.Add
( addPayment
) where
-import Html exposing (..)
+import Html as H exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
+import Reads exposing (readInt)
+import Result exposing (..)
-import ServerCommunication as SC
-import ServerCommunication exposing (serverCommunications)
+import ServerCommunication as SC exposing (serverCommunications)
import Update exposing (..)
import Update.Payment exposing (..)
+import View.Events exposing (onSubmitPrevDefault)
+
addPayment : String -> String -> Html
addPayment name cost =
- div
- [ class "add" ]
+ H.form
+ [ class "add"
+ , onSubmitPrevDefault serverCommunications.address
+ <| case readInt cost of
+ Ok number -> SC.AddPayment name number
+ Err _ -> SC.NoCommunication
+ ]
[ text "Name"
, input
[ value name
@@ -29,6 +37,6 @@ addPayment name cost =
]
[]
, button
- [ onClick serverCommunications.address (SC.AddPayment name cost) ]
+ [ type' "submit" ]
[ text "Add" ]
]
diff --git a/src/client/View/SignIn.elm b/src/client/View/SignIn.elm
index 02ee1bd..a45adc7 100644
--- a/src/client/View/SignIn.elm
+++ b/src/client/View/SignIn.elm
@@ -2,7 +2,7 @@ module View.SignIn
( renderSignIn
) where
-import Html exposing (..)
+import Html as H exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
@@ -16,20 +16,21 @@ import ServerCommunication exposing (serverCommunications)
import Model.View.SignInView exposing (..)
+import View.Events exposing (onSubmitPrevDefault)
+
renderSignIn : SignInView -> Html
renderSignIn signInView =
div
[ class "signIn" ]
- [ div
- [ class "form" ]
+ [ H.form
+ [ onSubmitPrevDefault serverCommunications.address (SC.SignIn signInView.login) ]
[ input
[ value signInView.login
, on "input" targetValue (Signal.message actions.address << UpdateSignIn << UpdateLogin)
- , onEnter serverCommunications.address (SC.SignIn signInView.login)
]
[]
, button
- [ onClick serverCommunications.address (SC.SignIn signInView.login) ]
+ []
[ text "Sign in" ]
]
, div
@@ -37,12 +38,6 @@ renderSignIn signInView =
[ signInResult signInView ]
]
-onEnter : Signal.Address a -> a -> Attribute
-onEnter address value =
- on "keydown"
- (Json.customDecoder keyCode (\code -> if code == 13 then Ok () else Err ""))
- (\_ -> Signal.message address value)
-
signInResult : SignInView -> Html
signInResult signInView =
case signInView.result of
diff --git a/src/server/Application.hs b/src/server/Application.hs
index 24342dc..59aa252 100644
--- a/src/server/Application.hs
+++ b/src/server/Application.hs
@@ -3,13 +3,15 @@
module Application
( signInAction
, validateSignInAction
+ , getUserName
+ , getPaymentsAction
+ , createPaymentAction
, signOutAction
+
, getIndexAction
, getUsersAction
- , getPaymentsAction
, addUserAction
, deleteUserAction
- , createPaymentAction
) where
import Web.Scotty
@@ -44,44 +46,6 @@ import View.Page (page)
import Mail
-getIndexAction :: ActionM ()
-getIndexAction = html page
-
-getUsersAction :: ActionM ()
-getUsersAction = do
- users <- liftIO $ runDb getUsers
- html . fromString . show $ users
-
-getPaymentsAction :: ActionM ()
-getPaymentsAction =
- Secure.loggedAction (\_ -> do
- payments <- liftIO $ runDb getPayments
- json payments
- )
-
-addUserAction :: Text -> Text -> ActionM ()
-addUserAction email name = do
- _ <- liftIO . runDb $ createUser email name
- status ok200
-
-deleteUserAction :: Text -> ActionM ()
-deleteUserAction email = do
- _ <- liftIO . runDb $ deleteUser email
- status ok200
-
-createPaymentAction :: Text -> Int -> ActionM ()
-createPaymentAction name cost =
- Secure.loggedAction (\login -> do
- maybeUser <- liftIO . runDb $ getUser login
- case maybeUser of
- Just user -> do
- _ <- liftIO . runDb $ createPayment (entityKey user) name cost
- return ()
- Nothing -> do
- status badRequest400
- status ok200
- )
-
signInAction :: Text -> ActionM ()
signInAction login =
if isValid (TE.encodeUtf8 login)
@@ -129,6 +93,26 @@ redirectError :: Text -> ActionM ()
redirectError msg =
redirect . TL.fromStrict . T.concat $ ["/?signInError=", msg]
+getUserName :: ActionM ()
+getUserName =
+ Secure.loggedAction (\user -> do
+ json . Message . userName . entityVal $ user
+ )
+
+getPaymentsAction :: ActionM ()
+getPaymentsAction =
+ Secure.loggedAction (\_ -> do
+ payments <- liftIO $ runDb getPayments
+ json payments
+ )
+
+createPaymentAction :: Text -> Int -> ActionM ()
+createPaymentAction name cost =
+ Secure.loggedAction (\user -> do
+ _ <- liftIO . runDb $ createPayment (entityKey user) name cost
+ return ()
+ )
+
signOutAction :: ActionM ()
signOutAction = do
LoginSession.delete
@@ -138,3 +122,25 @@ errorResponse :: Text -> ActionM ()
errorResponse msg = do
status badRequest400
json (Message msg)
+
+
+
+
+
+getIndexAction :: ActionM ()
+getIndexAction = html page
+
+getUsersAction :: ActionM ()
+getUsersAction = do
+ users <- liftIO $ runDb getUsers
+ html . fromString . show $ users
+
+addUserAction :: Text -> Text -> ActionM ()
+addUserAction email name = do
+ _ <- liftIO . runDb $ createUser email name
+ status ok200
+
+deleteUserAction :: Text -> ActionM ()
+deleteUserAction email = do
+ _ <- liftIO . runDb $ deleteUser email
+ status ok200
diff --git a/src/server/Design/Global.hs b/src/server/Design/Global.hs
index 9d096e4..1f35732 100644
--- a/src/server/Design/Global.hs
+++ b/src/server/Design/Global.hs
@@ -74,7 +74,7 @@ global = do
".signIn" ? do
- ".form" ? do
+ form ? do
let inputHeight = 50
width (px 500)
marginTop (px 50)
diff --git a/src/server/Main.hs b/src/server/Main.hs
index d534c4e..2ae319b 100644
--- a/src/server/Main.hs
+++ b/src/server/Main.hs
@@ -29,6 +29,9 @@ main = do
token <- param "token" :: ActionM Text
validateSignInAction token
+ get "/userName" $
+ getUserName
+
get "/payments" $
getPaymentsAction
diff --git a/src/server/Secure.hs b/src/server/Secure.hs
index 94ee8a9..1fef713 100644
--- a/src/server/Secure.hs
+++ b/src/server/Secure.hs
@@ -8,16 +8,31 @@ import Web.Scotty
import Network.HTTP.Types.Status (forbidden403)
-import Data.Text (Text)
+import Database.Persist (Entity)
+
+import Model.Database (User, runDb)
+import Model.User (getUser)
+
+import Control.Monad.IO.Class (liftIO)
+
+import qualified Data.Text as T
+import qualified Data.Text.IO as TIO
import qualified LoginSession
-loggedAction :: (Text -> ActionM ()) -> ActionM ()
+loggedAction :: (Entity User -> ActionM ()) -> ActionM ()
loggedAction action = do
maybeLogin <- LoginSession.get
case maybeLogin of
- Just login ->
- action login
+ Just login -> do
+ maybeUser <- liftIO . runDb $ getUser login
+ case maybeUser of
+ Just user ->
+ action user
+ Nothing -> do
+ status forbidden403
+ liftIO . TIO.putStrLn . T.concat $ ["Could not find the user which login is ", login]
+ html "Could not find a user from your login"
Nothing -> do
status forbidden403
html "You need to be logged in to perform this action"