` element.
-
- ```elixir
- <:caption>
- Posts
-
- ```
- """
-
- slot :col,
- required: true,
- doc: """
- For each column to render, add one `<:col>` element.
-
- ```elixir
- <:col :let={post} label="Name" field={:name} col_style="width: 20%;">
- <%= post.name %>
-
- ```
-
- Any additional assigns will be added as attributes to the `` elements.
-
- """ do
- attr :label, :any, doc: "The content for the header column."
-
- attr :field, :atom,
- doc: """
- The field name for sorting. If set and the field is configured as sortable
- in the resource, the column header will be clickable, allowing the user to
- sort by that column. If the field is not marked as sortable or if the
- `field` attribute is omitted or set to `nil` or `false`, the column header
- will not be clickable.
- """
-
- attr :directions, :any,
- doc: """
- An optional 2-element tuple used for custom ascending and descending sort
- behavior for the column, i.e. `{:asc_nils_last, :desc_nils_first}`
- """
-
- attr :col_style, :string,
- doc: """
- If set, a `` element is rendered and the value of the
- `col_style` assign is set as `style` attribute for the `` element of
- the respective column. You can set the `width`, `background`, `border`,
- and `visibility` of a column this way.
- """
-
- attr :col_class, :string,
- doc: """
- If set, a `` element is rendered and the value of the
- `col_class` assign is set as `class` attribute for the `` element of
- the respective column. You can set the `width`, `background`, `border`,
- and `visibility` of a column this way.
- """
-
- attr :class, :string,
- doc: """
- Additional classes to add to the `| ` and ` | ` element. Will be merged with the
- `thead_attr_attrs` and `tbody_td_attrs` attributes.
- """
-
- attr :thead_th_attrs, :list,
- doc: """
- Additional attributes to pass to the ` | ` element as a static keyword
- list. Note that these attributes will override any conflicting
- `thead_th_attrs` that are set at the table level.
- """
-
- attr :th_wrapper_attrs, :list,
- doc: """
- Additional attributes for the `` element that wraps the
- header link and the order direction symbol. Note that these attributes
- will override any conflicting `th_wrapper_attrs` that are set at the table
- level.
- """
-
- attr :tbody_td_attrs, :any,
- doc: """
- Additional attributes to pass to the ` | ` element. May be provided as a
- static keyword list, or as a 1-arity function to dynamically generate the
- list using row data. Note that these attributes will override any
- conflicting `tbody_td_attrs` that are set at the table level.
- """
- end
-
- slot :action,
- doc: """
- The slot for showing user actions in the last table column. These columns
- do not receive the `row_click` attribute.
-
-
- ```elixir
- <:action :let={user}>
- <.link navigate={~p"/users/\#{user}"}>Show
-
- ```
- """ do
- attr :label, :string, doc: "The content for the header column."
-
- attr :show, :boolean,
- doc: "Boolean value to conditionally show the column. Defaults to `true`."
-
- attr :hide, :boolean,
- doc: "Boolean value to conditionally hide the column. Defaults to `false`."
-
- attr :col_style, :string,
- doc: """
- If set, a `` element is rendered and the value of the
- `col_style` assign is set as `style` attribute for the `` element of
- the respective column. You can set the `width`, `background`, `border`,
- and `visibility` of a column this way.
- """
-
- attr :col_class, :string,
- doc: """
- If set, a `` element is rendered and the value of the
- `col_class` assign is set as `class` attribute for the `` element of
- the respective column. You can set the `width`, `background`, `border`,
- and `visibility` of a column this way.
- """
-
- attr :class, :string,
- doc: """
- Additional classes to add to the `| ` and ` | ` element. Will be merged with the
- `thead_attr_attrs` and `tbody_td_attrs` attributes.
- """
-
- attr :thead_th_attrs, :list,
- doc: """
- Any additional attributes to pass to the ` | ` as a keyword list.
- """
-
- attr :tbody_td_attrs, :any,
- doc: """
- Any additional attributes to pass to the ` | `. Can be a keyword list or
- a function that takes the current row item as an argument and returns a
- keyword list.
- """
- end
-
- slot :foot,
- doc: """
- You can optionally add a `foot`. The inner block will be rendered inside
- a `tfoot` element.
-
-
- <:foot>
- | Total: <%= @total %> |
-
-
- """
-
- def table_pagify(%{meta: %Meta{}, path: nil, on_sort: nil}) do
- raise PathOrJSError, component: :table
- end
-
- def table_pagify(%{meta: nil} = assigns) do
- assigns =
- assigns
- |> assign(id: Map.get(assigns, :id, "table"))
- |> assign(meta: %Meta{})
- |> assign(on_sort: %JS{})
-
- table_pagify(assigns)
- end
-
- def table_pagify(%{error: true, opts: opts} = assigns) do
- assigns =
- assign(assigns, :opts, Table.merge_opts(opts))
-
- ~H"""
- {@opts[:error_content]}
- """
- end
-
- def table_pagify(%{meta: meta, opts: opts} = assigns) do
- assigns =
- assigns
- |> assign(:opts, Table.merge_opts(opts))
- |> assign_new(:id, fn -> table_id(meta.resource) end)
-
- ~H"""
- <%= if !@loading and empty?(@items) do %>
- {@opts[:no_results_content]}
- <% else %>
- <%= if @opts[:container] do %>
- "_container"} {@opts[:container_attrs]}>
-
-
- <% else %>
-
- <% end %>
- <% end %>
- """
- end
-
- defp empty?(items)
- defp empty?([]), do: true
- defp empty?(%Phoenix.LiveView.LiveStream{inserts: [], deletes: []}), do: true
- defp empty?(_), do: false
-
- defp table_id(nil), do: "sortable_table"
-
- defp table_id(resource) do
- module_name = resource |> Module.split() |> List.last() |> Macro.underscore()
- module_name <> "_table"
- end
-
- @doc """
- Converts a AshPagify struct into a keyword list that can be used as a query with
- Phoenix verified routes or route helper functions.
-
- ## Encoded parameters
-
- The following parameters are encoded as strings:
-
- - `:search`
- - `:scopes`
- - `:filter_form`
- - `:order_by`
- - `:limit`
- - `:offset`
-
- ## Default parameters
-
- Default parameters for the limit, scopes, filter_form and order parameters
- are omitted. The defaults are determined by calling `AshPagify.Misc.get_option/3`.
-
- - Pass the `:for` option to pick up the default values from an `Ash.Resource`.
- - If the `Ash.Resource` has no default options set, the function will fall
- back to the application environment.
-
- ## Encoding queries
-
- To encode the returned query as a string, you will need to use
- `Plug.Conn.Query.encode/1`. `URI.encode_query/1` does not support bracket
- notation for arrays and maps.
-
- ## Examples
-
- iex> to_query(%AshPagify{})
- []
-
- iex> f = %AshPagify{offset: 40, limit: 20}
- iex> to_query(f)
- [limit: 20, offset: 40]
-
- iex> f = %AshPagify{offset: 40, limit: 20}
- iex> to_query(f, default_limit: 20)
- [offset: 40]
-
- iex> f = %AshPagify{order_by: [name: :asc]}
- iex> to_query(f, for: AshPagify.Factory.Post)
- []
-
- iex> f = %AshPagify{scopes: %{status: :active}}
- iex> to_query(f, for: AshPagify.Factory.Post)
- [scopes: %{status: :active}]
-
- iex> f = %AshPagify{search: "foo"}
- iex> to_query(f, for: AshPagify.Factory.Post)
- [search: "foo"]
-
- Encoding the query as a string:
-
- iex> f = %AshPagify{order_by: [name: :desc, age: :asc]}
- iex> to_query(f)
- [order_by: ["-name", "age"]]
- iex> f |> to_query |> Plug.Conn.Query.encode()
- "order_by[]=-name&order_by[]=age"
-
- iex> f = %AshPagify{filter_form: %{"field" => "comments_count", "operator" => "gt", "value" => 2}}
- iex> to_query(f)
- [filter_form: %{"field" => "comments_count", "operator" => "gt", "value" => 2}]
- iex> f |> to_query |> Plug.Conn.Query.encode()
- "filter_form[field]=comments_count&filter_form[operator]=gt&filter_form[value]=2"
-
- iex> f = %AshPagify{scopes: %{status: :active}}
- iex> to_query(f)
- [scopes: %{status: :active}]
- iex> f |> to_query |> Plug.Conn.Query.encode()
- "scopes[status]=active"
-
- iex> f = %AshPagify{search: "foo"}
- iex> to_query(f)
- [search: "foo"]
- iex> f |> to_query |> Plug.Conn.Query.encode()
- "search=foo"
- """
- @spec to_query(AshPagify.t(), Keyword.t()) :: Keyword.t()
- def to_query(%AshPagify{} = ash_pagify, opts \\ []) do
- default_limit = Misc.get_option(:default_limit, opts)
-
- default_order = :default_order |> Misc.get_option(opts, nil) |> AshPagify.concat_sort()
- current_order = AshPagify.concat_sort(ash_pagify.order_by)
-
- []
- |> Misc.maybe_put(:offset, ash_pagify.offset, 0)
- |> Misc.maybe_put(:limit, ash_pagify.limit, default_limit)
- |> Misc.maybe_put(:order_by, current_order, default_order)
- |> Misc.maybe_put(:filter_form, ash_pagify.filter_form)
- |> Misc.maybe_put(:search, ash_pagify.search)
- |> Misc.maybe_put_scopes(ash_pagify, opts)
- end
-
- @doc """
- Builds a path that includes query parameters for the given `AshPagify` struct
- using the referenced Components path helper function.
-
- The first argument can be either one of:
-
- - an MFA tuple (module, function name as atom, arguments)
- - a 2-tuple (function, arguments)
- - a URL string, usually produced with a verified route (e.g. `~p"/some/path"`)
- - a function that takes the AshPagify parameters as a keyword list as an argument
-
- Default values for `scopes`, `limit` and `order_by` are omitted from the query parameters.
- To pick up the default parameters from an `Ash.Resource`, you need to pass the
- `:for` option. If you pass a `AshPagify.Meta` struct as the second argument,
- these options are retrieved from the struct automatically.
-
- ## Examples
-
- ### With a verified route
-
- The examples below use plain URL strings without the p-sigil, so that the
- doc tests work, but in your application, you can use verified routes or
- anything else that produces a URL.
-
- iex> ash_pagify = %AshPagify{offset: 20, limit: 10}
- iex> path = build_path("/posts", ash_pagify)
- iex> %URI{path: parsed_path, query: parsed_query} = URI.parse(path)
- iex> {parsed_path, URI.decode_query(parsed_query)}
- {"/posts", %{"offset" => "20", "limit" => "10"}}
-
- The AshPagify query parameters will be merged into existing query parameters.
-
- iex> ash_pagify = %AshPagify{offset: 20, limit: 10}
- iex> path = build_path("/posts?category=A", ash_pagify)
- iex> %URI{path: parsed_path, query: parsed_query} = URI.parse(path)
- iex> {parsed_path, URI.decode_query(parsed_query)}
- {"/posts", %{"offset" => "20", "limit" => "10", "category" => "A"}}
-
- ### With an MFA tuple
-
- iex> ash_pagify = %AshPagify{offset: 20, limit: 10}
- iex> build_path(
- ...> {AshPagify.ComponentsTest, :route_helper, [%Plug.Conn{}, :posts]},
- ...> ash_pagify
- ...> )
- "/posts?limit=10&offset=20"
-
- ### With a function/arguments tuple
-
- iex> post_path = fn _conn, :index, query ->
- ...> "/posts?" <> Plug.Conn.Query.encode(query)
- ...> end
- iex> ash_pagify = %AshPagify{offset: 20, limit: 10}
- iex> build_path({post_path, [%Plug.Conn{}, :index]}, ash_pagify)
- "/posts?limit=10&offset=20"
-
- We're defining fake path helpers for the scope of the doctests. In a real
- Phoenix application, you would pass something like
- `{Routes, :post_path, args}` or `{&Routes.post_path/3, args}` as the
- first argument.
-
- ### Passing a `AshPagify.Meta` struct or a keyword list
-
- You can also pass a `AshPagify.Meta` struct or a keyword list as the third
- argument.
-
- iex> post_path = fn _conn, :index, query ->
- ...> "/posts?" <> Plug.Conn.Query.encode(query)
- ...> end
- iex> ash_pagify = %AshPagify{offset: 20, limit: 10}
- iex> meta = %AshPagify.Meta{ash_pagify: ash_pagify, resource: AshPagify.Factory.Post}
- iex> build_path({post_path, [%Plug.Conn{}, :index]}, meta)
- "/posts?limit=10&offset=20"
- iex> query_params = to_query(ash_pagify)
- iex> build_path({post_path, [%Plug.Conn{}, :index]}, query_params)
- "/posts?limit=10&offset=20"
-
- ### Additional path parameters
-
- If the path helper takes additional path parameters, just add them to the
- second argument.
-
- iex> user_post_path = fn _conn, :index, id, query ->
- ...> "/users/\#{id}/posts?" <> Plug.Conn.Query.encode(query)
- ...> end
- iex> ash_pagify = %AshPagify{offset: 20, limit: 10}
- iex> build_path({user_post_path, [%Plug.Conn{}, :index, 123]}, ash_pagify)
- "/users/123/posts?limit=10&offset=20"
-
- ### Additional query parameters
-
- If the last path helper argument is a query parameter list, the AshPagify
- parameters are merged into it.
-
- iex> post_url = fn _conn, :index, query ->
- ...> "https://posts.ash_pagify/posts?" <> Plug.Conn.Query.encode(query)
- ...> end
- iex> ash_pagify = %AshPagify{order_by: [name: :desc]}
- iex> build_path({post_url, [%Plug.Conn{}, :index, [user_id: 123]]}, ash_pagify)
- "https://posts.ash_pagify/posts?user_id=123&order_by[]=-name"
- iex> build_path(
- ...> {post_url,
- ...> [%Plug.Conn{}, :index, [category: "small", user_id: 123]]},
- ...> ash_pagify
- ...> )
- "https://posts.ash_pagify/posts?category=small&user_id=123&order_by[]=-name"
-
- ### Set page as path parameter
-
- Finally, you can also pass a function that takes the AshPagify parameters as
- a keyword list as an argument. Default values will not be included in the
- parameters passed to the function. You can use this if you need to set some
- of the parameters as path parameters instead of query parameters.
-
- iex> ash_pagify = %AshPagify{offset: 20, limit: 10}
- iex> build_path(
- ...> fn params ->
- ...> {offset, params} = Keyword.pop(params, :offset)
- ...> query = Plug.Conn.Query.encode(params)
- ...> if offset, do: "/posts/page/\#{offset}?\#{query}", else: "/posts?\#{query}"
- ...> end,
- ...> ash_pagify
- ...> )
- "/posts/page/20?limit=10"
-
- Note that in this example, the anonymous function just returns a string. With
- Phoenix 1.7, you will be able to use verified routes.
-
- build_path(
- fn params ->
- {offset, query} = Keyword.pop(params, :offset)
- if offset, do: ~p"/posts/page/\#{offset}?\#{query}", else: ~p"/posts?\#{query}"
- end,
- ash_pagify
- )
-
- Note that the keyword list passed to the path builder function is built using
- `Plug.Conn.Query.encode/2`, which means filter_forms are formatted as maps.
-
- ### Set filter_form value as path parameter
- iex> ash_pagify = %AshPagify{
- ...> offset: 20,
- ...> order_by: [:updated_at],
- ...> filter_form: %{
- ...> "field" => "author",
- ...> "operator" => "eq",
- ...> "value" => "John"
- ...> }
- ...> }
- iex> build_path(
- ...> fn params ->
- ...> {offset, params} = Keyword.pop(params, :offset)
- ...> filter_form = Keyword.get(params, :filter_form, %{})
- ...> author = Map.get(filter_form, "value", nil)
- ...> params = Keyword.put(params, :filter_form, %{})
- ...> query = Plug.Conn.Query.encode(params)
- ...>
- ...> case {offset, author} do
- ...> {nil, nil} -> "/posts?\#{query}"
- ...> {offset, nil} -> "/posts/page/\#{offset}?\#{query}"
- ...> {nil, author} -> "/posts/author/\#{author}?\#{query}"
- ...> {offset, author} -> "/posts/author/\#{author}/page/\#{offset}?\#{query}"
- ...> end
- ...> end,
- ...> ash_pagify
- ...> )
- "/posts/author/John/page/20?order_by[]=updated_at"
-
- ### If only path is set
-
- If only the path is set, it is returned as is.
-
- iex> build_path("/posts", nil)
- "/posts"
- """
- @spec build_path(pagination_path(), Meta.t() | AshPagify.t() | Keyword.t(), Keyword.t()) ::
- String.t()
- def build_path(path, meta_or_ash_pagify_or_params, opts \\ [])
-
- def build_path(
- path,
- %Meta{ash_pagify: ash_pagify, resource: resource, default_scopes: default_scopes},
- opts
- )
- when is_atom(resource) and resource != nil do
- opts =
- opts
- |> Keyword.put(:for, resource)
- |> Keyword.put(:default_scopes, default_scopes)
-
- build_path(path, ash_pagify, opts)
- end
-
- def build_path(path, %AshPagify{} = ash_pagify, opts) do
- build_path(path, to_query(ash_pagify, opts))
- end
-
- def build_path({module, func, args}, ash_pagify_params, _opts)
- when is_atom(module) and is_atom(func) and is_list(args) and is_list(ash_pagify_params) do
- final_args = build_final_args(args, ash_pagify_params)
- apply(module, func, final_args)
- end
-
- def build_path({func, args}, ash_pagify_params, _opts)
- when is_function(func) and is_list(args) and is_list(ash_pagify_params) do
- final_args = build_final_args(args, ash_pagify_params)
- apply(func, final_args)
- end
-
- def build_path(func, ash_pagify_params, _opts)
- when is_function(func, 1) and is_list(ash_pagify_params) do
- func.(ash_pagify_params)
- end
-
- def build_path(uri, ash_pagify_params, _opts)
- when is_binary(uri) and is_list(ash_pagify_params) do
- ash_pagify_params_map = Map.new(ash_pagify_params)
- build_path(uri, ash_pagify_params_map)
- end
-
- def build_path(uri, ash_pagify_params, _opts)
- when is_binary(uri) and is_map(ash_pagify_params) do
- uri = URI.parse(uri)
-
- query =
- (uri.query || "")
- |> Query.decode()
- |> Map.merge(Misc.remove_nil_values(ash_pagify_params))
-
- query = if query != %{}, do: Query.encode(query)
-
- uri
- |> Map.put(:query, query)
- |> URI.to_string()
- end
-
- def build_path(uri, nil, _opts) when is_binary(uri) do
- uri
- end
-
- defp build_final_args(args, ash_pagify_params) do
- case Enum.reverse(args) do
- [last_arg | rest] when is_list(last_arg) ->
- query_arg = Keyword.merge(last_arg, ash_pagify_params)
- Enum.reverse([query_arg | rest])
-
- _ ->
- args ++ [ash_pagify_params]
- end
- end
-
- @doc """
- Wrapper around `build_path/3` that builds a path with the updated scope.
-
- Examples
-
- iex> ash_pagify = %AshPagify{offset: 20, limit: 10}
- iex> meta = %AshPagify.Meta{ash_pagify: ash_pagify, resource: AshPagify.Factory.Post}
- iex> build_scope_path("/posts", meta, %{status: :active})
- "/posts?limit=10&scopes[status]=active"
- """
- @spec build_scope_path(pagination_path(), Meta.t() | nil, map(), Keyword.t()) :: String.t()
- def build_scope_path(path, meta, scope, opts \\ [])
-
- def build_scope_path(
- path,
- %Meta{ash_pagify: ash_pagify, resource: resource, default_scopes: default_scopes},
- scope,
- opts
- )
- when is_atom(resource) and resource != nil do
- opts =
- opts
- |> Keyword.put(:for, resource)
- |> Keyword.put(:default_scopes, default_scopes)
-
- ash_pagify = AshPagify.set_scope(ash_pagify, scope)
-
- build_path(path, ash_pagify, opts)
- end
-
- def build_scope_path(path, nil, scope, opts) do
- build_path(path, [scopes: scope], opts)
- end
-end
diff --git a/lib/wanderer_app_web/components/pagination.ex b/lib/wanderer_app_web/components/pagination.ex
deleted file mode 100644
index 66637bb2..00000000
--- a/lib/wanderer_app_web/components/pagination.ex
+++ /dev/null
@@ -1,115 +0,0 @@
-defmodule WandererAppWeb.Components.Pagination do
- @moduledoc """
- Pagination component for AshPagify.
- """
-
- alias WandererAppWeb.Components
- alias AshPagify.Meta
- alias AshPagify.Misc
-
- @spec default_opts() :: [Components.pagination_option()]
- def default_opts do
- [
- current_link_attrs: [
- class: "pagination-link is-current",
- aria: [current: "page"]
- ],
- disabled_class: "disabled",
- ellipsis_attrs: [class: "pagination-ellipsis"],
- ellipsis_content: Phoenix.HTML.raw("…"),
- next_link_attrs: [
- aria: [label: "Go to next page"],
- class: "pagination-next"
- ],
- next_link_content: "Next",
- page_links: :all,
- pagination_link_aria_label: &"Go to page #{&1}",
- pagination_link_attrs: [class: "pagination-link"],
- previous_link_attrs: [
- aria: [label: "Go to previous page"],
- class: "pagination-previous"
- ],
- previous_link_content: "Previous",
- wrapper_attrs: [
- class: "pagination",
- role: "navigation",
- aria: [label: "pagination"]
- ]
- ]
- end
-
- def merge_opts(opts) do
- default_opts()
- |> Misc.list_merge(Misc.global_option(:pagination) || [])
- |> Misc.list_merge(opts)
- end
-
- def max_pages(:all, total_pages), do: total_pages
- def max_pages(:hide, _), do: 0
- def max_pages({:ellipsis, max_pages}, _), do: max_pages
-
- def show_pagination(nil), do: false
-
- def show_pagination?(%Meta{errors: [], total_pages: total_pages}) do
- total_pages > 1
- end
-
- def show_pagination?(_), do: false
-
- def get_page_link_range(current_page, max_pages, total_pages) do
- # number of additional pages to show before or after current page
- additional = ceil(max_pages / 2)
-
- cond do
- max_pages >= total_pages ->
- 1..total_pages
-
- current_page + additional > total_pages ->
- (total_pages - max_pages + 1)..total_pages
-
- true ->
- first = max(current_page - additional + 1, 1)
- last = min(first + max_pages - 1, total_pages)
- first..last
- end
- end
-
- @spec build_page_link_helper(Meta.t(), Components.pagination_path()) ::
- (integer() -> String.t() | nil)
- def build_page_link_helper(_meta, nil), do: fn _offset -> nil end
-
- def build_page_link_helper(%Meta{} = meta, path) do
- query_params = build_query_params(meta)
-
- fn offset ->
- params = maybe_put_offset(query_params, offset)
- Components.build_path(path, params)
- end
- end
-
- defp build_query_params(%Meta{} = meta) do
- Components.to_query(meta.ash_pagify, for: meta.resource, default_scopes: meta.default_scopes)
- end
-
- defp maybe_put_offset(params, 0), do: Keyword.delete(params, :offset)
- defp maybe_put_offset(params, offset), do: Keyword.put(params, :offset, offset)
-
- def attrs_for_page_link(page, %{current_page: page}, opts) do
- add_page_link_aria_label(opts[:current_link_attrs], page, opts)
- end
-
- def attrs_for_page_link(page, _meta, opts) do
- add_page_link_aria_label(opts[:pagination_link_attrs], page, opts)
- end
-
- defp add_page_link_aria_label(attrs, page, opts) do
- aria_label = opts[:pagination_link_aria_label].(page)
-
- Keyword.update(
- attrs,
- :aria,
- [label: aria_label],
- &Keyword.put(&1, :label, aria_label)
- )
- end
-end
diff --git a/lib/wanderer_app_web/live/maps/components/map_balance_component.ex b/lib/wanderer_app_web/live/maps/components/map_balance_component.ex
new file mode 100644
index 00000000..f3597245
--- /dev/null
+++ b/lib/wanderer_app_web/live/maps/components/map_balance_component.ex
@@ -0,0 +1,208 @@
+defmodule WandererAppWeb.Maps.MapBalanceComponent do
+ use WandererAppWeb, :live_component
+ use LiveViewEvents
+
+ require Logger
+
+ alias BetterNumber, as: Number
+ alias WandererApp.License.LicenseManager
+
+ @impl true
+ def mount(socket) do
+ {:ok,
+ assign(socket,
+ is_topping_up?: false,
+ error: nil
+ )}
+ end
+
+ @impl true
+ def update(%{map_id: map_id, current_user: current_user} = assigns, socket) do
+ socket = handle_info_or_assign(socket, assigns)
+
+ {:ok, map} = WandererApp.MapRepo.get(map_id)
+
+ {:ok, map_balance} = WandererApp.Map.SubscriptionManager.get_balance(map)
+
+ {:ok, user_balance} =
+ current_user.id
+ |> WandererApp.User.load()
+ |> WandererApp.User.get_balance()
+
+ socket =
+ socket
+ |> assign(assigns)
+ |> assign(
+ map_id: map_id,
+ map: map,
+ map_balance: map_balance,
+ user_balance: user_balance,
+ topup_form: %{} |> to_form()
+ )
+
+ {:ok, socket}
+ end
+
+ @impl true
+ def handle_event("show_topup", _, socket),
+ do:
+ {:noreply,
+ socket
+ |> assign(
+ :amounts,
+ [
+ {"150M", 150_000_000},
+ {"300M", 300_000_000},
+ {"600M", 600_000_000},
+ {"1.2B", 1_200_000_000},
+ {"2.4B", 2_400_000_000},
+ {"5B", 5_000_000_000}
+ ]
+ )
+ |> assign(is_topping_up?: true)}
+
+ @impl true
+ def handle_event("hide_topup", _, socket),
+ do: {:noreply, socket |> assign(is_topping_up?: false)}
+
+ @impl true
+ def handle_event(
+ "topup",
+ %{"amount" => amount} = _event,
+ %{assigns: %{current_user: current_user, map: map, map_id: map_id}} = socket
+ ) do
+ amount = amount |> Decimal.new() |> Decimal.to_float()
+
+ user =
+ current_user.id
+ |> WandererApp.User.load()
+
+ {:ok, user_balance} =
+ user
+ |> WandererApp.User.get_balance()
+
+ case amount <= user_balance do
+ true ->
+ {:ok, _t} =
+ WandererApp.Api.MapTransaction.create(%{
+ map_id: map_id,
+ user_id: current_user.id,
+ amount: amount,
+ type: :in
+ })
+
+ {:ok, user} =
+ user
+ |> WandererApp.Api.User.update_balance(%{
+ balance: (user_balance || 0.0) - amount
+ })
+
+ {:ok, user_balance} =
+ user
+ |> WandererApp.User.get_balance()
+
+ {:ok, map_balance} = WandererApp.Map.SubscriptionManager.get_balance(map)
+
+ {:noreply,
+ socket
+ |> assign(is_topping_up?: false, map_balance: map_balance, user_balance: user_balance)}
+
+ _ ->
+ notify_to(
+ socket.assigns.notify_to,
+ socket.assigns.event_name,
+ {:flash, :error, "You don't have enough ISK on your account balance!"}
+ )
+
+ {:noreply, socket}
+ end
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+
+
+ Account balance
+
+ ISK {@user_balance
+ |> Number.to_human(units: ["", "K", "M", "B", "T", "P"])}
+
+
+
+
+
+ <.button
+ :if={not @is_topping_up?}
+ class="mt-2"
+ type="button"
+ phx-click="show_topup"
+ phx-target={@myself}
+ >
+ Top Up
+
+
+ Map balance
+
+ ISK {@map_balance
+ |> Number.to_human(units: ["", "K", "M", "B", "T", "P"])}
+
+
+
+
+
+
+
+ How to top up map balance?
+
+
+ -
+ Top Up your account balance:
+ Click on 'Deposit ISK' button on user profile page.
+
+ -
+ Wait for account balance updated:
+ Check transactions section on
+ user profile page
+
+ -
+ Use 'Top Up' button:
+ Click on the 'Top Up' button & select the amount you wish to transfer to the map balance.
+
+ -
+ Accept the transfer:
+ Finish the transaction by clicking on the 'Top Up' button.
+
+
+
+
+ <.form
+ :let={f}
+ :if={@is_topping_up?}
+ for={@topup_form}
+ class="mt-2"
+ phx-submit="topup"
+ phx-target={@myself}
+ >
+ <.input
+ type="select"
+ field={f[:amount]}
+ class="select h-8 min-h-[10px] !pt-1 !pb-1 text-sm bg-neutral-900"
+ label="Topup amount"
+ placeholder="Select topup amount"
+ options={@amounts}
+ />
+
+ <.button class="mt-2" type="button" phx-click="hide_topup" phx-target={@myself}>
+ Cancel
+
+ <.button class="mt-2" type="submit">
+ Top Up
+
+
+
+
+ """
+ end
+end
diff --git a/lib/wanderer_app_web/live/maps/components/map_subscriptions_component.ex b/lib/wanderer_app_web/live/maps/components/map_subscriptions_component.ex
index 70df4ccd..8a7b76f2 100644
--- a/lib/wanderer_app_web/live/maps/components/map_subscriptions_component.ex
+++ b/lib/wanderer_app_web/live/maps/components/map_subscriptions_component.ex
@@ -25,7 +25,7 @@ defmodule WandererAppWeb.Maps.MapSubscriptionsComponent do
end
@impl true
- def update(%{map_id: map_id, current_user: current_user} = assigns, socket) do
+ def update(%{map_id: map_id} = assigns, socket) do
socket = handle_info_or_assign(socket, assigns)
subscription_form = %{
@@ -217,12 +217,6 @@ defmodule WandererAppWeb.Maps.MapSubscriptionsComponent do
{:flash, :info, "Subscription added!"}
)
- notify_to(
- socket.assigns.notify_to,
- socket.assigns.event_name,
- :update_map_balance
- )
-
{:noreply,
socket
|> assign(
@@ -338,12 +332,6 @@ defmodule WandererAppWeb.Maps.MapSubscriptionsComponent do
{:flash, :info, "Subscription updated!"}
)
- notify_to(
- socket.assigns.notify_to,
- socket.assigns.event_name,
- :update_map_balance
- )
-
{:noreply,
socket
|> assign(
@@ -464,22 +452,22 @@ defmodule WandererAppWeb.Maps.MapSubscriptionsComponent do
<.table
- class="!max-h-[300px] !overflow-y-auto"
+ class="!max-h-[200px] !overflow-y-auto"
empty_label="No active subscriptions, using alpha plan by default."
id="active-subscriptions-tbl"
rows={@map_subscriptions}
>
<:col :let={subscription} label="Subscription Plan">
- <%= subscription.plan %>
+ {subscription.plan}
<:col :let={subscription} label="Status">
- <%= subscription.status %>
+ {subscription.status}
<:col :let={subscription} label="Characters Limit">
- <%= subscription.characters_limit %>
+ {subscription.characters_limit}
<:col :let={subscription} label="Hubs Limit">
- <%= subscription.hubs_limit %>
+ {subscription.hubs_limit}
<:col :let={subscription} label="Active Till">
<.local_time
@@ -487,11 +475,11 @@ defmodule WandererAppWeb.Maps.MapSubscriptionsComponent do
id={"subscription-active-till-#{subscription.id}"}
at={subscription.active_till}
>
- <%= subscription.active_till %>
+ {subscription.active_till}
<:col :let={subscription} label="Auto Renew">
- <%= if subscription.auto_renew?, do: "Yes", else: "No" %>
+ {if subscription.auto_renew?, do: "Yes", else: "No"}
<:action :let={subscription}>
Additional price (mounthly)
- ISK <%= @additional_price
- |> Number.to_human(units: ["", "K", "M", "B", "T", "P"]) %>
+ ISK {@additional_price
+ |> Number.to_human(units: ["", "K", "M", "B", "T", "P"])}
diff --git a/lib/wanderer_app_web/live/maps/map_audit_live.html.heex b/lib/wanderer_app_web/live/maps/map_audit_live.html.heex
index 747c06a7..4ba74689 100644
--- a/lib/wanderer_app_web/live/maps/map_audit_live.html.heex
+++ b/lib/wanderer_app_web/live/maps/map_audit_live.html.heex
@@ -103,7 +103,7 @@
<.live_component
module={UserActivity}
@@ -118,7 +118,7 @@
diff --git a/lib/wanderer_app_web/live/maps/maps_live.ex b/lib/wanderer_app_web/live/maps/maps_live.ex
index 40bea757..cc97e23a 100644
--- a/lib/wanderer_app_web/live/maps/maps_live.ex
+++ b/lib/wanderer_app_web/live/maps/maps_live.ex
@@ -144,8 +144,6 @@ defmodule WandererAppWeb.MapsLive do
map
|> WandererApp.Map.Server.get_export_settings()
- {:ok, map_balance} = WandererApp.Map.SubscriptionManager.get_balance(map)
-
{:ok, options_form_data} = WandererApp.MapRepo.options_to_form_data(map)
socket
@@ -165,8 +163,6 @@ defmodule WandererAppWeb.MapsLive do
is_adding_subscription?: false,
selected_subscription: nil,
options_form: options_form_data |> to_form(),
- map_balance: map_balance,
- topup_form: %{} |> to_form(),
layout_options: [
{"Left To Right", "left_to_right"},
{"Top To Bottom", "top_to_bottom"}
@@ -322,70 +318,6 @@ defmodule WandererAppWeb.MapsLive do
def handle_event("change_settings_tab", %{"tab" => tab}, socket),
do: {:noreply, socket |> assign(active_settings_tab: tab)}
- @impl true
- def handle_event("show_topup", _, socket),
- do:
- {:noreply,
- socket
- |> assign(
- :amounts,
- [
- {"150M", 150_000_000},
- {"300M", 300_000_000},
- {"600M", 600_000_000},
- {"1.2B", 1_200_000_000},
- {"2.4B", 2_400_000_000},
- {"5B", 5_000_000_000}
- ]
- )
- |> assign(is_topping_up?: true)}
-
- @impl true
- def handle_event("hide_topup", _, socket),
- do: {:noreply, socket |> assign(is_topping_up?: false)}
-
- @impl true
- def handle_event(
- "topup",
- %{"amount" => amount} = _event,
- %{assigns: %{current_user: current_user, map: map, map_id: map_id}} = socket
- ) do
- amount = amount |> Decimal.new() |> Decimal.to_float()
-
- user =
- current_user.id
- |> WandererApp.User.load()
-
- {:ok, user_balance} =
- user
- |> WandererApp.User.get_balance()
-
- case amount <= user_balance do
- true ->
- {:ok, _t} =
- WandererApp.Api.MapTransaction.create(%{
- map_id: map_id,
- user_id: current_user.id,
- amount: amount,
- type: :in
- })
-
- {:ok, _user} =
- user
- |> WandererApp.Api.User.update_balance(%{
- balance: (user_balance || 0.0) - amount
- })
-
- {:ok, map_balance} = WandererApp.Map.SubscriptionManager.get_balance(map)
-
- {:noreply, socket |> assign(is_topping_up?: false, map_balance: map_balance)}
-
- _ ->
- {:noreply,
- socket |> put_flash(:error, "You don't have enough ISK on your account balance!")}
- end
- end
-
def handle_event("open_acl", %{"data" => id}, socket) do
{:noreply,
socket
@@ -536,22 +468,12 @@ defmodule WandererAppWeb.MapsLive do
@impl true
def handle_info(
- {"subscriptions_event", {:flash, type, message}},
+ {_event, {:flash, type, message}},
socket
) do
{:noreply, socket |> put_flash(type, message)}
end
- @impl true
- def handle_info(
- {"subscriptions_event", :update_map_balance},
- %{assigns: %{map: map}} = socket
- )
- when not is_nil(map) do
- {:ok, map_balance} = WandererApp.Map.SubscriptionManager.get_balance(map)
- {:noreply, socket |> assign(map_balance: map_balance)}
- end
-
@impl true
def handle_info(
{ref, result},
diff --git a/lib/wanderer_app_web/live/maps/maps_live.html.heex b/lib/wanderer_app_web/live/maps/maps_live.html.heex
index 703a48e7..edec174f 100644
--- a/lib/wanderer_app_web/live/maps/maps_live.html.heex
+++ b/lib/wanderer_app_web/live/maps/maps_live.html.heex
@@ -531,54 +531,15 @@
-
-
-
-
- <.button
- :if={not @is_topping_up?}
- class="mt-2"
- type="button"
- phx-click="show_topup"
- >
- <.icon name="hero-banknotes-solid" class="w-4 h-4" /> Top Up
-
-
- Map balance
-
- ISK <%= @map_balance
- |> Number.to_human(units: ["", "K", "M", "B", "T", "P"]) %>
-
-
-
-
-
- <.form
- :let={f}
- :if={@is_topping_up?}
- for={@topup_form}
- class="mt-2"
- phx-change="validate_topup"
- phx-submit="topup"
- >
- <.input
- type="select"
- field={f[:amount]}
- class="select h-8 min-h-[10px] !pt-1 !pb-1 text-sm bg-neutral-900"
- label="Topup amount"
- placeholder="Select topup amount"
- options={@amounts}
- />
-
- <.button class="mt-2" type="button" phx-click="hide_topup">
- Cancel
-
- <.button class="mt-2" type="submit">
- Top Up
-
-
-
-
+ <.live_component
+ :if={@active_settings_tab == "balance"}
+ module={WandererAppWeb.Maps.MapBalanceComponent}
+ id="map-balance-component"
+ map_id={@map.id}
+ notify_to={self()}
+ event_name="balance_event"
+ current_user={@current_user}
+ />
<.live_component
:if={@active_settings_tab == "subscription"}
diff --git a/lib/wanderer_app_web/live/profile/profile_live.html.heex b/lib/wanderer_app_web/live/profile/profile_live.html.heex
index cb40fac8..e9c97386 100644
--- a/lib/wanderer_app_web/live/profile/profile_live.html.heex
+++ b/lib/wanderer_app_web/live/profile/profile_live.html.heex
@@ -15,7 +15,7 @@
- Wanderer Balance
+ Account Balance
ISK <%= @wanderer_balance
@@ -308,27 +308,24 @@
-
- Open Your Wallet:
- Click on the wallet icon in the Neocom menu to access your financial overview.
+ Open corporations overview:
+ Click on the 'Social' and then on 'Corporation' in the Neocom menu to access corporations search.
-
- Navigate to the 'Transfer ISK' Tab:
- Find the 'Transfer ISK' option in your wallet interface.
+ Search for a Corporation:
+ Type in the search bar the name: <%= @corporation_info["name"] %>.
-
- Choose 'Give ISK':
- Click on the 'Give ISK' button to initiate the transfer.
-
- -
- Enter Recipient's Name:
- Type in the name of the Wanderer name: <%= @corporation_info["name"] %>.
+ Choose 'Give Money':
+ Select the 'Give Money' in the context menu to initiate the transfer.
+
-
Specify the Amount:
Input the amount of ISK you wish to transfer to the corporate account.
-
- Add a Note (Optional):
+ Add a Reason (Optional):
Include a short note or reason for the transfer if desired.
-
| | |