-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #50 from melange-re/order-with-promo
'Order with promo' chapter
- Loading branch information
Showing
32 changed files
with
2,461 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
let stringToDate = s => | ||
// add "T00:00" to make sure the date is in local time | ||
s ++ "T00:00" |> Js.Date.fromString; | ||
|
||
let dateToString = d => | ||
Printf.sprintf( | ||
"%4.0f-%02.0f-%02.0f", | ||
Js.Date.getFullYear(d), | ||
Js.Date.getMonth(d) +. 1., | ||
Js.Date.getDate(d), | ||
); | ||
|
||
[@react.component] | ||
let make = (~date: Js.Date.t, ~onChange: Js.Date.t => unit) => { | ||
<input | ||
type_="date" | ||
required=true | ||
value={dateToString(date)} | ||
onChange={evt => evt |> RR.getValueFromEvent |> stringToDate |> onChange} | ||
/>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
// #region initial | ||
let items: Order.t = [ | ||
Sandwich(Portabello), | ||
Sandwich(Unicorn), | ||
Sandwich(Ham), | ||
Sandwich(Turducken), | ||
Hotdog, | ||
Burger({lettuce: true, tomatoes: true, onions: 3, cheese: 2, bacon: 6}), | ||
Burger({lettuce: false, tomatoes: false, onions: 0, cheese: 0, bacon: 0}), | ||
Burger({lettuce: true, tomatoes: false, onions: 1, cheese: 1, bacon: 1}), | ||
Burger({lettuce: false, tomatoes: false, onions: 1, cheese: 0, bacon: 0}), | ||
Burger({lettuce: false, tomatoes: false, onions: 0, cheese: 1, bacon: 0}), | ||
]; | ||
|
||
[@react.component] | ||
let make = () => { | ||
let (date, setDate) = | ||
RR.useStateValue(Js.Date.fromString("2024-05-28T00:00")); | ||
|
||
<div> | ||
<h1> {RR.s("Order confirmation")} </h1> | ||
<DateInput date onChange=setDate /> | ||
<h2> {RR.s("Order")} </h2> | ||
<Order items date /> | ||
</div>; | ||
}; | ||
// #endregion initial | ||
|
||
// #region datasets | ||
let burger = | ||
Item.Burger.{ | ||
lettuce: false, | ||
tomatoes: false, | ||
onions: 0, | ||
cheese: 0, | ||
bacon: 0, | ||
}; | ||
|
||
let datasets = { | ||
[ | ||
( | ||
"No burgers", | ||
Item.[ | ||
Sandwich(Unicorn), | ||
Hotdog, | ||
Sandwich(Ham), | ||
Sandwich(Turducken), | ||
Hotdog, | ||
], | ||
), | ||
( | ||
"5 burgers", | ||
{ | ||
[ | ||
Burger({...burger, tomatoes: true}), | ||
Burger({...burger, lettuce: true}), | ||
Burger({...burger, bacon: 2}), | ||
Burger({...burger, cheese: 3, onions: 9, tomatoes: true}), | ||
Burger({...burger, onions: 2}), | ||
]; | ||
}, | ||
), | ||
( | ||
"1 burger with at least one of every topping", | ||
[ | ||
Hotdog, | ||
Burger({ | ||
lettuce: true, | ||
tomatoes: true, | ||
onions: 1, | ||
cheese: 2, | ||
bacon: 3, | ||
}), | ||
Sandwich(Turducken), | ||
], | ||
), | ||
( | ||
"All sandwiches", | ||
[ | ||
Sandwich(Ham), | ||
Hotdog, | ||
Sandwich(Portabello), | ||
Sandwich(Unicorn), | ||
Hotdog, | ||
Sandwich(Turducken), | ||
], | ||
), | ||
]; | ||
}; | ||
// #endregion datasets | ||
|
||
/** | ||
let datasets': list((string, list(Item.t))) = [ | ||
// #region burger-expression | ||
{ | ||
let burger = | ||
Item.Burger.{ | ||
lettuce: false, | ||
tomatoes: false, | ||
onions: 0, | ||
cheese: 0, | ||
bacon: 0, | ||
}; | ||
( | ||
"5 burgers", | ||
{ | ||
[ | ||
Burger({...burger, tomatoes: true}), | ||
Burger({...burger, lettuce: true}), | ||
Burger({...burger, bacon: 2}), | ||
Burger({...burger, cheese: 3, onions: 9, tomatoes: true}), | ||
Burger({...burger, onions: 2}), | ||
]; | ||
}, | ||
); | ||
}, | ||
// #endregion burger-expression | ||
]; | ||
*/ | ||
ignore(make); | ||
|
||
// #region refactor | ||
[@react.component] | ||
let make = () => { | ||
let (date, setDate) = | ||
RR.useStateValue(Js.Date.fromString("2024-05-28T00:00")); | ||
|
||
<div> | ||
<h1> {RR.s("Order Confirmation")} </h1> | ||
<DateInput date onChange=setDate /> | ||
<h2> {RR.s("Order")} </h2> | ||
{datasets | ||
|> List.map(((label, items)) => { | ||
<div key=label> <h3> {RR.s(label)} </h3> <Order items date /> </div> | ||
}) | ||
|> RR.list} | ||
</div>; | ||
}; | ||
// #endregion refactor | ||
|
||
ignore(make); | ||
|
||
// #region date-and-order | ||
module DateAndOrder = { | ||
[@react.component] | ||
let make = (~label: string, ~items: list(Item.t)) => { | ||
let (date, setDate) = | ||
RR.useStateValue(Js.Date.fromString("2024-05-28T00:00")); | ||
|
||
<div> | ||
<h2> {RR.s(label)} </h2> | ||
<DateInput date onChange=setDate /> | ||
<Order items date /> | ||
</div>; | ||
}; | ||
}; | ||
// #endregion date-and-order | ||
|
||
// #region make | ||
[@react.component] | ||
let make = () => { | ||
<div> | ||
<h1> {RR.s("Order Confirmation")} </h1> | ||
{datasets | ||
|> List.map(((label, items)) => <DateAndOrder key=label label items />) | ||
|> RR.list} | ||
</div>; | ||
}; | ||
// #endregion make |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
type error = | ||
| InvalidCode | ||
| ExpiredCode; | ||
|
||
let getDiscountFunction = (code, _date) => { | ||
switch (code) { | ||
| "FREE" => Ok(_items => Ok(0.0)) | ||
| _ => Error(InvalidCode) | ||
}; | ||
}; | ||
|
||
let getSandwichHalfOff = (~date as _: Js.Date.t, _items: list(Item.t)) => | ||
Error(`MissingSandwichTypes([])); | ||
|
||
type sandwichTracker = { | ||
portabello: bool, | ||
ham: bool, | ||
unicorn: bool, | ||
turducken: bool, | ||
}; | ||
|
||
let _ = | ||
(~tracker, ~date, ~items) => { | ||
let _ = | ||
// #region missing-sandwich-types | ||
switch (tracker) { | ||
| {portabello: true, ham: true, unicorn: true, turducken: true} => | ||
let total = | ||
items | ||
|> ListLabels.fold_left(~init=0.0, ~f=(total, item) => | ||
total +. Item.toPrice(item, ~date) | ||
); | ||
Ok(total /. 2.0); | ||
| tracker => | ||
let missing = | ||
[ | ||
tracker.portabello ? "" : "portabello", | ||
tracker.ham ? "" : "ham", | ||
tracker.unicorn ? "" : "unicorn", | ||
tracker.turducken ? "" : "turducken", | ||
] | ||
|> List.filter((!=)("")); | ||
Error(`MissingSandwichTypes(missing)); | ||
}; | ||
// #endregion missing-sandwich-types | ||
(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
open Fest; | ||
|
||
let june3 = Js.Date.fromString("2024-06-03T00:00"); | ||
|
||
module SandwichHalfOff = { | ||
// #region not-all-sandwiches | ||
test("Not all sandwiches, return Error", () => | ||
expect | ||
|> deepEqual( | ||
Discount.getSandwichHalfOff( | ||
~date=june3, | ||
[ | ||
Sandwich(Unicorn), | ||
Hotdog, | ||
Sandwich(Portabello), | ||
Sandwich(Ham), | ||
], | ||
), | ||
Error(`MissingSandwichTypes(["turducken"])), | ||
) | ||
); | ||
// #endregion not-all-sandwiches | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
let node = ReactDOM.querySelector("#root"); | ||
switch (node) { | ||
| None => | ||
Js.Console.error("Failed to start React: couldn't find the #root element") | ||
| Some(root) => | ||
let root = ReactDOM.Client.createRoot(root); | ||
ReactDOM.Client.render(root, <Demo />); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
module Burger = { | ||
type t = { | ||
lettuce: bool, | ||
onions: int, | ||
cheese: int, | ||
tomatoes: bool, | ||
bacon: int, | ||
}; | ||
}; | ||
|
||
module Sandwich = { | ||
type t = | ||
| Portabello | ||
| Ham | ||
| Unicorn | ||
| Turducken; | ||
}; | ||
|
||
type t = | ||
| Sandwich(Sandwich.t) | ||
| Burger(Burger.t) | ||
| Hotdog; | ||
|
||
let toPrice = (~date as _: Js.Date.t, _t: t) => 0.; | ||
|
||
let toEmoji = (_t: t) => ""; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
type t = list(Item.t); | ||
|
||
module OrderItem = { | ||
[@react.component] | ||
let make = (~item as _: Item.t) => <div />; | ||
}; | ||
|
||
module Style = { | ||
let order = ""; | ||
let total = ""; | ||
|
||
// #region promo-class | ||
let promo = [%cx | ||
{| | ||
border-top: 1px solid gray; | ||
text-align: right; | ||
vertical-align: top; | ||
|} | ||
]; | ||
// #endregion promo-class | ||
}; | ||
|
||
// #region make | ||
[@react.component] | ||
let make = (~items: t, ~date: Js.Date.t) => { | ||
let (discount, setDiscount) = RR.useStateValue(0.0); | ||
|
||
let subtotal = | ||
items | ||
|> ListLabels.fold_left(~init=0., ~f=(acc, order) => | ||
acc +. Item.toPrice(order, ~date) | ||
); | ||
|
||
<table className=Style.order> | ||
<tbody> | ||
{items | ||
|> List.mapi((index, item) => | ||
<OrderItem key={"item-" ++ string_of_int(index)} item /> | ||
) | ||
|> RR.list} | ||
<tr className=Style.total> | ||
<td> {RR.s("Subtotal")} </td> | ||
<td> {subtotal |> RR.currency} </td> | ||
</tr> | ||
<tr> | ||
<td> {RR.s("Promo code")} </td> | ||
<td> <Promo items date onApply=setDiscount /> </td> | ||
</tr> | ||
<tr className=Style.total> | ||
<td> {RR.s("Total")} </td> | ||
<td> {subtotal -. discount |> RR.currency} </td> | ||
</tr> | ||
</tbody> | ||
</table>; | ||
}; | ||
// #endregion make | ||
|
||
let _ = | ||
(setDiscount, items, date) => { | ||
<> | ||
// #region set-promo-class | ||
<tr className=Style.promo> | ||
<td> {RR.s("Promo code")} </td> | ||
<td> <Promo items date onApply=setDiscount /> </td> | ||
</tr> | ||
// #endregion set-promo-class | ||
</>; | ||
}; |
Oops, something went wrong.