summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Config.elm6
-rw-r--r--src/Header.elm34
-rw-r--r--src/Main.elm80
-rw-r--r--src/Pages/Feed.elm40
-rw-r--r--src/RemoteData.elm52
-rw-r--r--src/Types.elm16
-rw-r--r--src/Utils.elm42
7 files changed, 204 insertions, 66 deletions
diff --git a/src/Config.elm b/src/Config.elm
index ecd263b..397bc38 100644
--- a/src/Config.elm
+++ b/src/Config.elm
@@ -1,5 +1,7 @@
module Config exposing (ApiResource(..), makeApiUrl)
+import Utils exposing (templ)
+
baseUrl : String
baseUrl =
@@ -20,8 +22,8 @@ makeApiUrl res =
"{baseUrl}/posts?_sort=createdAt&_order=desc"
ApiLikePost postId ->
- "{baseUrl}/posts/{postId}"
- |> String.replace "{postId}" postId
+ "{baseUrl}/posts/{0}"
+ |> templ [ postId ]
ApiCompose ->
"{baseUrl}/posts"
diff --git a/src/Header.elm b/src/Header.elm
new file mode 100644
index 0000000..257b308
--- /dev/null
+++ b/src/Header.elm
@@ -0,0 +1,34 @@
+module Header exposing (..)
+
+import Css exposing (..)
+import Html.Styled exposing (Html, button, div, text)
+import Html.Styled.Attributes exposing (css)
+import Html.Styled.Events exposing (onClick)
+import Types exposing (Model, Msg(..))
+import Utils exposing (templ)
+
+
+styles =
+ { container =
+ css
+ [ displayFlex
+ , flexFlow2 row wrap
+ , justifyContent flexEnd
+ , width (pct 100)
+ , marginBottom (px 10)
+ ]
+ , logoutButton =
+ css
+ [ marginLeft (px 10)
+ , cursor pointer
+ ]
+ }
+
+
+headerView : Model -> List (Html Msg)
+headerView model =
+ [ div [ styles.container ]
+ [ div [] [ "Logged in as {0}" |> templ [ model.userData.name ] |> text ]
+ , button [ styles.logoutButton, onClick RequestLogout ] [ text "Logout" ]
+ ]
+ ]
diff --git a/src/Main.elm b/src/Main.elm
index 9d55d9c..9f5b699 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -3,19 +3,21 @@ port module Main exposing (..)
import Browser exposing (Document)
import Browser.Navigation exposing (Key)
import Config exposing (makeApiUrl)
+import Header exposing (headerView)
import Html.Styled exposing (..)
-import Html.Styled.Events exposing (onClick)
import Http
-import Json.Decode exposing (Decoder, field, int, list, map2, map5, string)
+import Json.Decode exposing (Decoder, field, int, list, map2, map6, string)
import Json.Encode
import Pages.Feed exposing (feedView)
+import RemoteData
import Task
import Time
-import Types exposing (Author, Model, Msg(..), Post)
+import Types exposing (Author, Model, Msg(..), Post, UserData)
import Url exposing (Url)
+import Utils exposing (httpErrorToString, listFlat)
-main : Program () Model Msg
+main : Program UserData Model Msg
main =
Browser.application
{ init = init
@@ -41,13 +43,18 @@ port requestLogout : () -> Cmd msg
-- INIT
-init : () -> Url -> Key -> ( Model, Cmd Msg )
-init _ _ _ =
- ( { posts = Nothing
- , now = Time.millisToPosix 0
+init : UserData -> Url -> Key -> ( Model, Cmd Msg )
+init userData _ key =
+ ( { now = Time.millisToPosix 0
+ , key = key
+ , userData = userData
+ , posts = RemoteData.Loading
, composeInputValue = ""
}
- , Cmd.batch [ getPosts, Task.perform SetNowPosix Time.now ]
+ , Cmd.batch
+ [ getPosts
+ , Task.perform SetNowPosix Time.now
+ ]
)
@@ -71,10 +78,10 @@ update msg model =
( model, getPosts )
GotPosts (Result.Ok posts) ->
- ( { model | posts = Just posts }, Cmd.none )
+ ( { model | posts = RemoteData.Success posts }, Cmd.none )
GotPosts (Result.Err httpErr) ->
- ( model
+ ( { model | posts = RemoteData.Failure httpErr }
, showAlert <| httpErrorToString httpErr
)
@@ -82,7 +89,7 @@ update msg model =
( model, likePost post )
LikedPost (Result.Ok post) ->
- ( { model | posts = Just <| replaceMatchingPost post (Maybe.withDefault [] model.posts) }
+ ( { model | posts = RemoteData.map (replaceMatchingPost post) model.posts }
, Cmd.none
)
@@ -105,7 +112,7 @@ update msg model =
ComposedPost (Result.Ok post) ->
( { model
- | posts = Maybe.map (\posts -> post :: posts) model.posts
+ | posts = RemoteData.map (\posts -> post :: posts) model.posts
, composeInputValue = ""
}
, Cmd.none
@@ -166,10 +173,11 @@ composePost model =
[ ( "content", Json.Encode.string model.composeInputValue )
, ( "createdAt", Json.Encode.int <| Time.posixToMillis model.now // 1000 )
, ( "likes", Json.Encode.int 0 )
+ , ( "userPictureUrl", Json.Encode.string model.userData.pictureUrl )
, ( "author"
, Json.Encode.object
- [ ( "id", Json.Encode.string "logged-in-user-id" )
- , ( "name", Json.Encode.string "Logged in user" )
+ [ ( "id", Json.Encode.string model.userData.email )
+ , ( "name", Json.Encode.string model.userData.name )
]
)
]
@@ -202,7 +210,7 @@ postsDecoder =
postDecoder : Decoder Post
postDecoder =
- map5 Post
+ map6 Post
(field "id" string)
(field "content" string)
(field "author"
@@ -213,6 +221,7 @@ postDecoder =
)
(field "createdAt" decodeTime)
(field "likes" int)
+ (field "userPictureUrl" string)
@@ -222,39 +231,8 @@ postDecoder =
view : Model -> Document Msg
view model =
{ title = "Shoob book"
- , body = button [ onClick RequestLogout ] [ text "Logout" ] :: feedView model |> List.map toUnstyled
+ , body =
+ [ headerView model, feedView model ]
+ |> listFlat
+ |> List.map toUnstyled
}
-
-
-
--- UTILS
-
-
-httpErrorToString : Http.Error -> String
-httpErrorToString err =
- case err of
- Http.BadUrl url ->
- "URL {0} is invalid" |> templ [ url ]
-
- Http.Timeout ->
- "Request has timed out"
-
- Http.NetworkError ->
- "Unable to reach the server, check your network connection"
-
- Http.BadStatus status ->
- "Server responded with status {0}" |> templ [ String.fromInt status ]
-
- Http.BadBody msg ->
- msg
-
-
-templ : List String -> String -> String
-templ rs original =
- let
- templElement_ : ( Int, String ) -> String -> String
- templElement_ ( index, r ) orig_ =
- String.replace ("{" ++ String.fromInt index ++ "}") r orig_
- in
- List.indexedMap Tuple.pair rs
- |> List.foldl templElement_ original
diff --git a/src/Pages/Feed.elm b/src/Pages/Feed.elm
index 824552a..7fbe98a 100644
--- a/src/Pages/Feed.elm
+++ b/src/Pages/Feed.elm
@@ -3,18 +3,16 @@ module Pages.Feed exposing (..)
import Css exposing (..)
import DateFormat.Relative exposing (relativeTime)
import Html.Styled exposing (..)
-import Html.Styled.Attributes exposing (css, placeholder, type_, value)
+import Html.Styled.Attributes exposing (css, placeholder, src, type_, value)
import Html.Styled.Events exposing (onClick, onInput, onSubmit)
+import RemoteData exposing (RemoteData(..))
import Time exposing (now)
import Types exposing (Model, Msg(..), Post)
styles =
{ page =
- css
- [ maxWidth (px 600)
- , margin2 (px 10) auto
- ]
+ css [ margin2 (px 10) auto ]
, postList =
css
[ overflow scroll
@@ -27,6 +25,16 @@ styles =
, backgroundColor (hex "#eee")
, marginBottom (px 10)
]
+ , postProfilePicture =
+ css
+ [ width (px 40)
+ , height (px 40)
+ ]
+ , postHeader =
+ css
+ [ display inlineBlock
+ , marginLeft (px 10)
+ ]
, postTitle =
css
[ margin (px 0) ]
@@ -50,9 +58,13 @@ styles =
postDiv : Time.Posix -> Post -> Html Msg
postDiv now post =
div [ styles.post ]
- [ h3 [ styles.postTitle ] [ text post.author.name ]
- , i []
- [ text <| relativeTime now post.createdAt
+ [ span []
+ [ img [ styles.postProfilePicture, src post.userPictureUrl ] [] ]
+ , span [ styles.postHeader ]
+ [ h3 [ styles.postTitle ] [ text post.author.name ]
+ , i []
+ [ text <| relativeTime now post.createdAt
+ ]
]
, p [] [ text post.content ]
, span []
@@ -65,12 +77,18 @@ postDiv now post =
postsDiv : Model -> Html Msg
postsDiv model =
case model.posts of
- Just posts ->
- div [ styles.postList ] <| List.map (postDiv model.now) posts
+ Initial ->
+ div [] []
- Nothing ->
+ Loading ->
div [] [ text "Loading posts..." ]
+ Failure _ ->
+ div [] []
+
+ Success posts ->
+ div [ styles.postList ] <| List.map (postDiv model.now) posts
+
composeDiv : Model -> Html Msg
composeDiv model =
diff --git a/src/RemoteData.elm b/src/RemoteData.elm
new file mode 100644
index 0000000..7173851
--- /dev/null
+++ b/src/RemoteData.elm
@@ -0,0 +1,52 @@
+module RemoteData exposing (..)
+
+import Http
+
+
+type RemoteData a
+ = Initial
+ | Loading
+ | Failure Http.Error
+ | Success a
+
+
+withDefault : a -> RemoteData a -> a
+withDefault default rd =
+ case rd of
+ Success value ->
+ value
+
+ _ ->
+ default
+
+
+map : (a -> b) -> RemoteData a -> RemoteData b
+map fn rd =
+ case rd of
+ Initial ->
+ Initial
+
+ Loading ->
+ Loading
+
+ Failure err ->
+ Failure err
+
+ Success value ->
+ Success (fn value)
+
+
+andThen : (a -> RemoteData b) -> RemoteData a -> RemoteData b
+andThen callback rd =
+ case rd of
+ Initial ->
+ Initial
+
+ Loading ->
+ Loading
+
+ Failure err ->
+ Failure err
+
+ Success value ->
+ callback value
diff --git a/src/Types.elm b/src/Types.elm
index ce487b6..d5068d6 100644
--- a/src/Types.elm
+++ b/src/Types.elm
@@ -1,13 +1,24 @@
module Types exposing (..)
+import Browser.Navigation exposing (Key)
import Http
+import RemoteData exposing (RemoteData)
import Time
type alias Model =
- { posts : Maybe (List Post)
+ { now : Time.Posix
+ , key : Key
+ , userData : UserData
+ , posts : RemoteData (List Post)
, composeInputValue : String
- , now : Time.Posix
+ }
+
+
+type alias UserData =
+ { name : String
+ , email : String
+ , pictureUrl : String
}
@@ -39,4 +50,5 @@ type alias Post =
, author : Author
, createdAt : Time.Posix
, likes : Int
+ , userPictureUrl : String
}
diff --git a/src/Utils.elm b/src/Utils.elm
new file mode 100644
index 0000000..3da2c74
--- /dev/null
+++ b/src/Utils.elm
@@ -0,0 +1,42 @@
+module Utils exposing (..)
+
+import Http
+
+
+
+-- UTILS
+
+
+httpErrorToString : Http.Error -> String
+httpErrorToString err =
+ case err of
+ Http.BadUrl url ->
+ "URL {0} is invalid" |> templ [ url ]
+
+ Http.Timeout ->
+ "Request has timed out"
+
+ Http.NetworkError ->
+ "Unable to reach the server, check your network connection"
+
+ Http.BadStatus status ->
+ "Server responded with status {0}" |> templ [ String.fromInt status ]
+
+ Http.BadBody msg ->
+ msg
+
+
+templ : List String -> String -> String
+templ rs original =
+ let
+ templElement_ : ( Int, String ) -> String -> String
+ templElement_ ( index, r ) orig_ =
+ String.replace (String.replace "n" (String.fromInt index) "{n}") r orig_
+ in
+ List.indexedMap Tuple.pair rs
+ |> List.foldl templElement_ original
+
+
+listFlat : List (List a) -> List a
+listFlat ll =
+ List.foldl (\acc l -> l ++ acc) [] ll