summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJan Tuomi <jan.tuomi@valuemotive.com>2021-10-29 15:22:52 +0300
committerJan Tuomi <jan.tuomi@valuemotive.com>2021-10-29 15:22:52 +0300
commita972058d20b773f4488df51babb40156c5a335e1 (patch)
tree80b8e18462134da7e5d00a2a32fc25bfd5855667 /src
parentf4dacee4908300f63200864b9f089874152a568c (diff)
Add feed
Diffstat (limited to 'src')
-rw-r--r--src/Config.elm23
-rw-r--r--src/Main.elm136
-rw-r--r--src/Pages/Feed.elm95
-rw-r--r--src/Types.elm19
4 files changed, 247 insertions, 26 deletions
diff --git a/src/Config.elm b/src/Config.elm
new file mode 100644
index 0000000..c3df2b0
--- /dev/null
+++ b/src/Config.elm
@@ -0,0 +1,23 @@
+module Config exposing (ApiResource(..), makeApiUrl)
+
+
+baseUrl : String
+baseUrl =
+ "http://localhost:3000"
+
+
+type ApiResource
+ = ApiPosts
+ | ApiLikePost String
+
+
+makeApiUrl : ApiResource -> String
+makeApiUrl res =
+ String.replace "{baseUrl}" baseUrl <|
+ case res of
+ ApiPosts ->
+ "{baseUrl}/posts"
+
+ ApiLikePost postId ->
+ "{baseUrl}/posts/{postId}"
+ |> String.replace "{postId}" postId
diff --git a/src/Main.elm b/src/Main.elm
index 54352d6..d359eaa 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -2,8 +2,15 @@ module Main exposing (..)
import Browser exposing (Document)
import Browser.Navigation exposing (Key)
+import Config exposing (makeApiUrl)
import Html.Styled exposing (..)
-import Types exposing (DisplayableError(..), Model, Msg(..))
+import Http
+import Json.Decode exposing (Decoder, field, int, list, map2, map5, string)
+import Json.Encode
+import Pages.Feed exposing (feedView)
+import Task
+import Time
+import Types exposing (Author, DisplayableError(..), Model, Msg(..), Post)
import Url exposing (Url)
@@ -13,7 +20,7 @@ main =
{ init = init
, view = view
, update = update
- , subscriptions = \_ -> Sub.none
+ , subscriptions = subscriptions
, onUrlRequest = \_ -> NoOp
, onUrlChange = \_ -> NoOp
}
@@ -25,18 +32,11 @@ main =
init : () -> Url -> Key -> ( Model, Cmd Msg )
init _ _ _ =
- ( { posts =
- [ { id = "123"
- , content = "foobar"
- , author =
- { id = "456"
- , name = "George Technoman"
- }
- }
- ]
- , displayError = NoError
+ ( { posts = []
+ , displayError = DNoError
+ , now = Time.millisToPosix 0
}
- , Cmd.none
+ , Cmd.batch [ getPosts, Task.perform SetNowPosix Time.now ]
)
@@ -50,11 +50,108 @@ update msg model =
NoOp ->
( model, Cmd.none )
- FetchPostsSuccess posts ->
+ SetNowPosix now ->
+ ( { model | now = now }, Cmd.none )
+
+ GetPosts ->
+ ( model, getPosts )
+
+ GotPosts (Result.Ok posts) ->
( { model | posts = posts }, Cmd.none )
- FetchPostsFailure error ->
- ( { model | displayError = error }, Cmd.none )
+ GotPosts (Result.Err httpErr) ->
+ ( { model | displayError = DHttpError httpErr }
+ , Cmd.none
+ )
+
+ LikePost post ->
+ ( model, likePost post )
+
+ LikedPost (Result.Ok post) ->
+ ( { model | posts = replaceMatchingPost post model.posts }
+ , Cmd.none
+ )
+
+ LikedPost (Result.Err httpErr) ->
+ ( { model | displayError = DHttpError httpErr }
+ , Cmd.none
+ )
+
+
+replaceMatchingPost : Post -> List Post -> List Post
+replaceMatchingPost post posts =
+ List.map
+ (\p ->
+ if p.id == post.id then
+ post
+
+ else
+ p
+ )
+ posts
+
+
+getPosts : Cmd Msg
+getPosts =
+ Http.get
+ { url = makeApiUrl Config.ApiPosts
+ , expect = Http.expectJson GotPosts postsDecoder
+ }
+
+
+likePost : Post -> Cmd Msg
+likePost post =
+ Http.request
+ { method = "PATCH"
+ , headers = []
+ , url = makeApiUrl (Config.ApiLikePost post.id)
+ , body =
+ Http.jsonBody
+ (Json.Encode.object
+ [ ( "likes", Json.Encode.int (post.likes + 1) ) ]
+ )
+ , expect = Http.expectJson LikedPost postDecoder
+ , timeout = Nothing
+ , tracker = Nothing
+ }
+
+
+subscriptions : Model -> Sub Msg
+subscriptions _ =
+ Time.every 5000 SetNowPosix
+
+
+
+-- JSON
+
+
+decodeTime : Decoder Time.Posix
+decodeTime =
+ int
+ |> Json.Decode.andThen
+ (\ms ->
+ Json.Decode.succeed <| Time.millisToPosix (ms * 1000)
+ )
+
+
+postsDecoder : Decoder (List Post)
+postsDecoder =
+ list postDecoder
+
+
+postDecoder : Decoder Post
+postDecoder =
+ map5 Post
+ (field "id" string)
+ (field "content" string)
+ (field "author"
+ (map2 Author
+ (field "id" string)
+ (field "name" string)
+ )
+ )
+ (field "createdAt" decodeTime)
+ (field "likes" int)
@@ -64,10 +161,5 @@ update msg model =
view : Model -> Document Msg
view model =
{ title = "Shoob book"
- , body = [ body model ] |> List.map toUnstyled
+ , body = feedView model |> List.map toUnstyled
}
-
-
-body : Model -> Html Msg
-body model =
- main_ [] (List.map (\post -> div [] [ text post.id ]) model.posts)
diff --git a/src/Pages/Feed.elm b/src/Pages/Feed.elm
new file mode 100644
index 0000000..a848200
--- /dev/null
+++ b/src/Pages/Feed.elm
@@ -0,0 +1,95 @@
+module Pages.Feed exposing (..)
+
+import Css exposing (..)
+import DateFormat.Relative exposing (relativeTime)
+import Html.Styled exposing (..)
+import Html.Styled.Attributes exposing (css)
+import Html.Styled.Events exposing (onClick)
+import Time exposing (now)
+import Types exposing (DisplayableError(..), Model, Msg(..), Post)
+
+
+styles =
+ { page =
+ css
+ [ maxWidth (px 600)
+ , margin2 (px 10) auto
+ ]
+ , post =
+ css
+ [ padding2 (px 15) (px 20)
+ , backgroundColor (hex "#eee")
+ ]
+ , postTitle =
+ css
+ [ margin (px 0) ]
+ , postLikeButton =
+ css
+ [ marginRight (px 5)
+ , cursor pointer
+ ]
+ }
+
+
+derrorToHtml : DisplayableError -> Html Msg
+derrorToHtml derror =
+ case derror of
+ DNoError ->
+ text ""
+
+ DHttpError httpErr ->
+ text <| Debug.toString httpErr
+
+
+derrorDiv : Model -> Html Msg
+derrorDiv model =
+ div []
+ [ derrorToHtml model.displayError ]
+
+
+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
+ ]
+ , p [] [ text post.content ]
+ , span []
+ [ button [ styles.postLikeButton, onClick (LikePost post) ] [ text "❤️ Like" ]
+ , text <| String.fromInt post.likes
+ ]
+ ]
+
+
+postsDiv : Model -> Html Msg
+postsDiv model =
+ div [] <| List.map (postDiv model.now) model.posts
+
+
+headerSection : Model -> Html msg
+headerSection _ =
+ header []
+ [ h1 [] [ text "Shoob book" ]
+ ]
+
+
+mainSection : Model -> Html Msg
+mainSection model =
+ main_ [] <|
+ [ derrorDiv model
+ , postsDiv model
+ ]
+
+
+body : Model -> Html Msg
+body model =
+ div [ styles.page ]
+ [ headerSection model
+ , mainSection model
+ ]
+
+
+feedView : Model -> List (Html Msg)
+feedView model =
+ [ body model ]
diff --git a/src/Types.elm b/src/Types.elm
index de1cccf..02e1257 100644
--- a/src/Types.elm
+++ b/src/Types.elm
@@ -1,21 +1,30 @@
module Types exposing (..)
+import Http
+import Time
+
type alias Model =
{ posts : List Post
, displayError : DisplayableError
+ , now : Time.Posix
}
type Msg
= NoOp
- | FetchPostsSuccess (List Post)
- | FetchPostsFailure DisplayableError
+ -- TIME
+ | SetNowPosix Time.Posix
+ -- POSTS
+ | GetPosts
+ | GotPosts (Result Http.Error (List Post))
+ | LikePost Post
+ | LikedPost (Result Http.Error Post)
type DisplayableError
- = Error String
- | NoError
+ = DHttpError Http.Error
+ | DNoError
type alias Author =
@@ -28,4 +37,6 @@ type alias Post =
{ id : String
, content : String
, author : Author
+ , createdAt : Time.Posix
+ , likes : Int
}