diff --git a/.gitignore b/.gitignore index 9c635a9..0826505 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,7 @@ npm-debug.log # The directory NPM downloads your dependencies sources to. /assets/node_modules/ -.elixir_ls \ No newline at end of file +.elixir_ls +.DS_Store +.vcode +.idea diff --git a/README.md b/README.md index f12b8e8..1b019ad 100644 --- a/README.md +++ b/README.md @@ -8,47 +8,95 @@ Goals: - Detailed error messages and documentation. - A focus on robustness and production-level performance. +## Table of contents -### Why Use ExUssd? +- [Why Use ExUssd](#why-use-exussd) +- [Documentation](#documentation) +- [Installation](#installation) +- [Configuration](#configuration) +- [Usage](#usage) +- [Contribution](#contribution) +- [Contributors](#contributors) +- [Licence](#licence) + + +## Why Use ExUssd? ExUssd lets you create simple, flexible, and customizable USSD interface. Under the hood ExUssd uses Elixir Registry to create and route individual USSD session. https://user-images.githubusercontent.com/23293150/124460086-95ebf080-dd97-11eb-87ab-605f06291563.mp4 +## Documentation +The docs can be found at [https://hexdocs.pm/ex_ussd](https://hexdocs.pm/ex_ussd). + ## Installation -To add ExUssd to your application application. +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `ex_ussd` to your list of dependencies in `mix.exs`: + ```elixir defp deps do [ - {:ex_ussd, "~> 1.0.0"} + {:ex_ussd, "~> 1.0.0-rc-1"} ] end ``` -### Let's create a custom global values +## Configuration + +Add to your `config.exs` ```elixir # config/config.exs # TODO: This are example values, replace them with your own config :ex_ussd, nav: [ - ExUssd.Nav.new(type: :home, name: "HOME", match: "00", reverse: true, orientation: :vertical), - ExUssd.Nav.new(type: :back, name: "BACK", match: "0", right: 1), - ExUssd.Nav.new(type: :next, name: "MORE", match: "98") + ExUssd.Nav.new(type: :home, name: "HOME", match: "00", reverse: true, orientation: :vertical), + ExUssd.Nav.new(type: :back, name: "BACK", match: "0", right: 1), + ExUssd.Nav.new(type: :next, name: "MORE", match: "98") ], - delimiter: ").", - default_error: "invalid input,try again\n" + delimiter: ").", + default_error: "invalid input,try again\n" ``` ## Usage +### Settable Fields + +* **`:data`** Set data to pass through to next menu. N/B - ExUssd menu are stateful unless using ExUssd.new/2 with `:name` and `:resolve` as arguments; + ```elixir + data = %{name: "John Doe"} + # stateful + menu + |> ExUssd.set(data: data) + |> ExUssd.add(ExUssd.new("Check Balance", &check_balance/2)) + + # stateless + menu + |> ExUssd.add(ExUssd.new(data: data, name: "Check Balance", resolve: &check_balance/2)) + ``` + +* **`:delimiter`** Set's menu style delimiter. Default- `:` +* **`:default_error`** Default error shown on invalid input +* **`:error`** Set custom error message +* **`:name`** Sets the name of the menu +* **`:nav`** Its used to create a new ExUssd Nav menu +* **`:orientation`** Sets the menu orientation. Available option; + - `:horizontal` - Left to right. Blog/articles style menu + - `vertical` - Top to bottom(default) +* **`:resolve`** Navigates(invokes the next `ussd_init/2`) to the next menu +* **`:should_close`** Indicate whether to USSD session should end or continue +* **`:show_navigation`** Set show navigation menu. Default - `true` +* **`:split`** Set menu batch size. Default - 7 +* **`:title`** Set menu title + + ### ExUssd Callbacks ExUssd provides you with 3 callbacks -* **`ussd_init/2`** +* **`ussd_init/2`** It's invoked once when the user navigates to that particular menu * **`ussd_callback/3`** @@ -57,10 +105,12 @@ ExUssd provides you with 3 callbacks * **`ussd_after_callback/3`** It's an optional callback that is invoked after `ussd_callback/3` is invoked. + +#### Example Create a new module: ```elixir -defmodule Api.HomeResolver do +defmodule ApiWeb.HomeResolver do use ExUssd def ussd_init(menu, _) do ExUssd.set(menu, title: "Enter your PIN") @@ -68,9 +118,7 @@ defmodule Api.HomeResolver do def ussd_callback(menu, payload, %{attempt: attempt}) do if payload.text == "5555" do - menu - |> ExUssd.set(title: "You have Entered the Secret Number, 5555") - |> ExUssd.set(should_close: true) + ExUssd.set(menu, resolve: &success_menu/2) else ExUssd.set(menu, error: "Wrong PIN, attempt #{attempt}/3\n") end @@ -81,15 +129,21 @@ defmodule Api.HomeResolver do |> ExUssd.set(title: "Account is locked, you have entered the wrong PIN 3 times") |> ExUssd.set(should_close: true) end + + def success_menu(menu, _) do + menu + |> ExUssd.set(title: "You have Entered the Secret Number, 5555") + |> ExUssd.set(should_close: true) + end end ``` -Lets test the different ExUssd callbacks with `ExUssd.to_string/3` +Let's test the different ExUssd callbacks with `ExUssd.to_string/3` create menu ```elixir -menu = ExUssd.new(name: "PIN", resolve: Api.HomeResolver) +menu = ExUssd.new(name: "PIN", resolve: ApiWeb.HomeResolver) ``` **`ussd_init/2`** @@ -120,6 +174,53 @@ iex> ExUssd.to_string(menu, :ussd_after_callback, [payload: %{text: "42", attemp }} ``` +### ExUssd Menu List +```elixir + defmodule HomeResolver do + use ExUssd + + def product_a(menu, _payload), do: menu |> ExUssd.set(title: "selected product a") + def product_b(menu, _payload), do: menu |> ExUssd.set(title: "selected product b") + def product_c(menu, _payload), do: menu |> ExUssd.set(title: "selected product c") + + def account(%{data: %{account_type: :personal}} = menu, _payload) do + menu + |> ExUssd.set(name: "personal Account") + |> ExUssd.set(resolve: &personal_account/2) + end + + def account(%{data: %{account_type: :business}} = menu, _payload) do + menu + |> ExUssd.set(name: "business Account") + |> ExUssd.set(resolve: &business_account/2) + end + + def check_balance(%{data: %{account_type: account_type}} = menu, _payload) do + if (account_type == :personal) do + menu + |> ExUssd.set(resolve: &personal_account_balance/2) + else + menu + |> ExUssd.set(resolve: &business_account_balance/2) + end + end + + def home(menu, _payload) do + data = %{user_name: "john_doe", account_type: :personal} + menu + |> ExUssd.set(title: "Welcome") + |> ExUssd.set(data: data) + |> ExUssd.add(ExUssd.new(name: "Product A", resolve: &product_a/2)) + |> ExUssd.add(ExUssd.new(name: "Product B", resolve: &product_b/2)) + |> ExUssd.add(ExUssd.new(name: "Product C", resolve: &product_c/2)) + |> ExUssd.add(ExUssd.new(&account/2)) + |> ExUssd.add(ExUssd.new("Check Balance", &check_balance/2)) + |> ExUssd.add(ExUssd.new(name: "Enter Pin", resolve: __MODULE__)) + end + end +``` + + ## Contribution If you'd like to contribute, start by searching through the [issues](https://github.com/beamkenya/ex_ussd/issues) and [pull requests](https://github.com/beamkenya/ex_ussd/pulls) to see whether someone else has raised a similar idea or question. diff --git a/lib/ex_ussd.ex b/lib/ex_ussd.ex index 5fb4a14..febd270 100644 --- a/lib/ex_ussd.ex +++ b/lib/ex_ussd.ex @@ -77,13 +77,13 @@ defmodule ExUssd do end end - defdelegate new(opts), to: ExUssd.Op - defdelegate new(name, function), to: ExUssd.Op - defdelegate set(menu, opts), to: ExUssd.Op defdelegate add(menu, child), to: ExUssd.Op defdelegate add(menu, menus, opts), to: ExUssd.Op defdelegate end_session(opts), to: ExUssd.Op defdelegate goto(opts), to: ExUssd.Op + defdelegate new(opts), to: ExUssd.Op + defdelegate new(name, function), to: ExUssd.Op + defdelegate set(menu, opts), to: ExUssd.Op defdelegate to_string(menu, opts), to: ExUssd.Op defdelegate to_string(menu, atom, opts), to: ExUssd.Op end diff --git a/lib/ex_ussd/op.ex b/lib/ex_ussd/op.ex index 10b16cc..cbb3c3b 100644 --- a/lib/ex_ussd/op.ex +++ b/lib/ex_ussd/op.ex @@ -17,7 +17,8 @@ defmodule ExUssd.Op do :data, :resolve, :orientation, - :name + :name, + :nav ] @doc """ @@ -185,7 +186,7 @@ defmodule ExUssd.Op do ``` Note: ExUssd.new callback function will be called multiple times to get the `name` value. - + It's advisable to only set the `name` and `resolve` values on the callback like so reduce the side effects. ```elixir @@ -340,7 +341,7 @@ defmodule ExUssd.Op do ## Example ```elixir iex> menu = ExUssd.new(name: "home", resolve: fn menu, _payload -> menu |> ExUssd.set(title: "Welcome") end) - + iex> ExUssd.to_string(menu, []) {:ok, %{menu_string: "Welcome", should_close: false}} diff --git a/mix.exs b/mix.exs index a1e289e..d7d4986 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule ExUssd.MixProject do def project do [ app: :ex_ussd, - version: "1.0.0", + version: "1.0.0-rc-1", elixir: "~> 1.12", start_permanent: Mix.env() == :prod, description: "ExUssd lets you create simple, flexible, and customizable USSD interface.",