Skip to content

Commit

Permalink
Merge pull request #50 from melange-re/order-with-promo
Browse files Browse the repository at this point in the history
'Order with promo' chapter
  • Loading branch information
feihong authored Aug 6, 2024
2 parents 7a48ad2 + 116671d commit d058e26
Show file tree
Hide file tree
Showing 32 changed files with 2,461 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default defineConfig({
{ text: 'Discounts Using Lists', link: '/discounts-lists/' },
{ text: 'Promo Codes', link: '/promo-codes/' },
{ text: 'Promo Component', link: '/promo-component/' },
{ text: 'Order with Promo', link: '/order-with-promo/' },
]
}
],
Expand Down
21 changes: 21 additions & 0 deletions docs/order-with-promo/DateInput.re
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}
/>;
};
169 changes: 169 additions & 0 deletions docs/order-with-promo/Demo.re
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
47 changes: 47 additions & 0 deletions docs/order-with-promo/Discount.re
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
();
};
23 changes: 23 additions & 0 deletions docs/order-with-promo/DiscountTests.re
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
};
8 changes: 8 additions & 0 deletions docs/order-with-promo/Index.re
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 />);
};
26 changes: 26 additions & 0 deletions docs/order-with-promo/Item.re
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) => "";
68 changes: 68 additions & 0 deletions docs/order-with-promo/Order.re
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
</>;
};
Loading

0 comments on commit d058e26

Please sign in to comment.