aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/Product.elm
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/Product.elm')
-rw-r--r--frontend/src/Product.elm302
1 files changed, 302 insertions, 0 deletions
diff --git a/frontend/src/Product.elm b/frontend/src/Product.elm
new file mode 100644
index 0000000..0ea0ce1
--- /dev/null
+++ b/frontend/src/Product.elm
@@ -0,0 +1,302 @@
1module Product exposing (..)
2
3import Browser
4import Browser.Navigation as Nav
5import Html exposing (..)
6import Html.Attributes exposing (..)
7import Html.Events exposing (..)
8import Http
9import Json.Decode as D
10import Json.Encode as Encode
11import Url
12import Url.Parser as P exposing ((</>), Parser, int, oneOf, s, string)
13
14
15type SubmitStatus
16 = SubmitSuccess
17 | SubmitFail
18 | Submitting
19 | NotSubmitted
20
21
22type alias Product =
23 { id : Int
24 , name : String
25 , kind : Maybe String
26 , price : Float
27 , description : Maybe String
28 }
29
30
31emptyProduct =
32 Product -1 "" Nothing 0 Nothing
33
34
35type alias Rating =
36 { commentDate : String
37 , commentText : Maybe String
38 , customerName : String
39 , productName : String
40 , stars : Int
41 }
42
43
44type alias Model =
45 { pageStatus : Status
46 , listing : Product
47 , ratings : List Rating
48 , ratingStars : Int
49 , ratingText : String
50 , addRatingStatus : SubmitStatus
51 }
52
53
54type Status
55 = Loading
56 | Loaded
57 | NotLoaded
58
59
60type Msg
61 = ListingLoaded (Result Http.Error Product)
62 | RatingsLoaded (Result Http.Error (List Rating))
63 | FetchProduct Int
64 | FetchRatings Int
65 | AddRatingStars Int
66 | AddRatingComment String
67 | AddRatingPressed
68 | AddRatingSuccess (Result Http.Error ())
69 | AddRatingFail
70 | AddToCartSuccess (Result Http.Error ())
71 | AddToCartPressed
72
73
74init : Model
75init =
76 Model NotLoaded emptyProduct [] 0 "" NotSubmitted
77
78
79update : Msg -> Model -> ( Model, Cmd Msg )
80update msg model =
81 case msg of
82 ListingLoaded res ->
83 case res of
84 Ok s ->
85 ( { model | listing = s, pageStatus = Loaded }, Cmd.none )
86
87 Err e ->
88 let
89 _ =
90 Debug.log "error" e
91 in
92 ( { model | pageStatus = NotLoaded }, Cmd.none )
93
94 RatingsLoaded res ->
95 case res of
96 Ok s ->
97 ( { model | ratings = s, pageStatus = Loaded }, Cmd.none )
98
99 Err e ->
100 let
101 _ =
102 Debug.log "error" e
103 in
104 ( { model | pageStatus = NotLoaded }, Cmd.none )
105
106 FetchProduct id ->
107 ( { model | pageStatus = Loading }, fetchListing id )
108
109 FetchRatings id ->
110 ( { model | pageStatus = Loading }, fetchRatings id )
111
112 AddRatingStars i ->
113 ( { model | ratingStars = i }, Cmd.none )
114
115 AddRatingComment s ->
116 ( { model | ratingText = s }, Cmd.none )
117
118 AddRatingPressed ->
119 ( { model | addRatingStatus = Submitting }
120 , submitRating model
121 )
122
123 AddRatingSuccess res ->
124 case res of
125 Ok _ ->
126 ( { model | addRatingStatus = SubmitSuccess }, fetchRatings model.listing.id )
127
128 Err _ ->
129 ( { model | addRatingStatus = SubmitFail }, Cmd.none )
130
131 AddRatingFail ->
132 ( { model | addRatingStatus = SubmitFail }, Cmd.none )
133
134 AddToCartPressed ->
135 ( model, addToCart model )
136
137 AddToCartSuccess _ ->
138 ( model, Cmd.none )
139
140
141decodeProduct : D.Decoder Product
142decodeProduct =
143 D.map5 Product
144 (D.field "id" D.int)
145 (D.field "name" D.string)
146 (D.field "kind" (D.nullable D.string))
147 (D.field "price" D.float)
148 (D.field "description" (D.nullable D.string))
149
150
151decodeRating : D.Decoder Rating
152decodeRating =
153 D.map5 Rating
154 (D.field "comment_date" D.string)
155 (D.field "comment_text" (D.nullable D.string))
156 (D.field "customer_name" D.string)
157 (D.field "product_name" D.string)
158 (D.field "stars" D.int)
159
160
161decodeRatings : D.Decoder (List Rating)
162decodeRatings =
163 D.list decodeRating
164
165
166fetchListing : Int -> Cmd Msg
167fetchListing id =
168 let
169 _ =
170 Debug.log "err" <| "fetching listing " ++ String.fromInt id
171 in
172 Http.get
173 { url = "http://127.0.0.1:7878/product/" ++ String.fromInt id
174 , expect = Http.expectJson ListingLoaded decodeProduct
175 }
176
177
178fetchRatings : Int -> Cmd Msg
179fetchRatings id =
180 let
181 _ =
182 Debug.log "err" <| "fetching ratings " ++ String.fromInt id
183 in
184 Http.get
185 { url = "http://127.0.0.1:7878/product/reviews/" ++ String.fromInt id
186 , expect = Http.expectJson RatingsLoaded decodeRatings
187 }
188
189
190encodeRatingForm : Model -> Encode.Value
191encodeRatingForm model =
192 Encode.object
193 [ ( "product_id", Encode.int model.listing.id )
194 , ( "stars", Encode.int model.ratingStars )
195 , ( "comment_text", Encode.string model.ratingText )
196 ]
197
198
199submitRating : Model -> Cmd Msg
200submitRating model =
201 let
202 _ =
203 Debug.log "err" <| "submitting rating for" ++ String.fromInt model.listing.id
204 in
205 Http.riskyRequest
206 { method = "POST"
207 , headers = []
208 , url = "http://127.0.0.1:7878/rating/add"
209 , body = model |> encodeRatingForm |> Http.jsonBody
210 , expect = Http.expectWhatever AddRatingSuccess
211 , timeout = Nothing
212 , tracker = Nothing
213 }
214
215
216addToCart : Model -> Cmd Msg
217addToCart model =
218 let
219 _ =
220 Debug.log "err" <| "adding to cart: " ++ String.fromInt model.listing.id
221 in
222 Http.riskyRequest
223 { method = "POST"
224 , headers = []
225 , url = "http://127.0.0.1:7878/cart/add"
226 , body = Http.stringBody "applcation/json" <| String.fromInt <| model.listing.id
227 , expect = Http.expectWhatever AddToCartSuccess
228 , timeout = Nothing
229 , tracker = Nothing
230 }
231
232
233viewStatus : Status -> String
234viewStatus s =
235 case s of
236 Loading ->
237 "Loading"
238
239 Loaded ->
240 "Ready!"
241
242 NotLoaded ->
243 "Not loaded ..."
244
245
246viewProduct : Product -> Html Msg
247viewProduct p =
248 div []
249 [ text p.name
250 , text <| Maybe.withDefault "" p.kind
251 , text <| Maybe.withDefault "" p.description
252 , text <| String.fromFloat p.price
253 ]
254
255
256viewRating : Rating -> Html Msg
257viewRating r =
258 div []
259 [ text <| r.customerName ++ " posted on "
260 , text <| r.commentDate ++ " "
261 , text <| Maybe.withDefault "" r.commentText
262 , text <| " Stars: " ++ String.fromInt r.stars
263 ]
264
265
266viewInput : String -> String -> String -> (String -> msg) -> Html msg
267viewInput t p v toMsg =
268 input [ type_ t, placeholder p, value v, onInput toMsg ] []
269
270
271viewStars : Html Msg
272viewStars =
273 ul []
274 (List.map
275 (\i -> button [ onClick (AddRatingStars i) ] [ text <| String.fromInt i ])
276 [ 0, 1, 2, 3, 4, 5 ]
277 )
278
279
280view : Model -> Html Msg
281view model =
282 case model.pageStatus of
283 Loading ->
284 div [] [ text <| viewStatus Loading ]
285
286 _ ->
287 div []
288 [ div [] [ viewProduct model.listing ]
289 , ul [] (List.map viewRating model.ratings)
290 , div [] [ text "Add Rating: " ]
291 , div []
292 [ viewStars
293 , viewInput "text" "Enter Comment Text" model.ratingText AddRatingComment
294 , button [ onClick AddRatingPressed ] [ text "Submit Rating" ]
295 ]
296 , div []
297 [ button [ onClick AddToCartPressed ] [ text "Add To Cart" ]
298 ]
299 , div []
300 [ a [ href "/catalog" ] [ text "Back to catalog" ]
301 ]
302 ]