Packages
phoenix_kit
1.7.131
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/phoenix_kit/notifications/render.ex
defmodule PhoenixKit.Notifications.Render do
@moduledoc """
Human-readable rendering for notifications.
Maps `{activity.action, activity.metadata}` → `%{icon, text, link, actor_uuid}`
so the bell dropdown and inbox page don't need to know the action taxonomy.
Unknown actions fall back to the raw action string with a generic icon, so a
new action that hasn't been mapped yet still displays safely.
"""
alias PhoenixKit.Notifications.Notification
alias PhoenixKit.Utils.Routes
@type render_result :: %{
icon: String.t(),
text: String.t(),
link: String.t() | nil,
actor_uuid: String.t() | nil
}
@doc """
Returns the display payload for a notification.
`notification.activity` must be preloaded.
"""
@spec render(Notification.t()) :: render_result()
def render(%Notification{activity: %_{} = activity}) do
meta = activity.metadata || %{}
{default_icon, default_text} = icon_and_text(activity.action, meta)
# Metadata overrides let callers ship custom display without touching the
# Render action lookup. Any of the three keys can be present independently.
%{
icon: meta_string(meta, "notification_icon") || default_icon,
text: meta_string(meta, "notification_text") || default_text,
link: meta_string(meta, "notification_link") || link_for(activity),
actor_uuid: activity.actor_uuid
}
end
def render(%Notification{} = notification) do
# Standalone notification (V126): `activity` is nil, so this clause —
# not the `%_{}` activity clause above — matches. (An activity-linked
# row that wasn't preloaded carries `%Ecto.Association.NotLoaded{}`,
# which is a struct and would match the clause above; every read path
# preloads `:activity`, so that case doesn't reach Render.) Read the
# display content from the notification's own metadata (same override
# keys as activity metadata); fall back to a safe generic notice.
meta = notification.metadata || %{}
%{
icon: meta_string(meta, "notification_icon") || "hero-bell",
text: meta_string(meta, "notification_text") || "You have a new notification.",
link: meta_string(meta, "notification_link"),
actor_uuid: nil
}
end
# ── Action → (icon, text) ────────────────────────────────────────────
defp icon_and_text("user.roles_updated", meta) do
added = Map.get(meta, "roles_added") || Map.get(meta, "added")
{"hero-identification",
"Your roles were updated#{suffix_if(added, " (added: #{inspect(added)})")}."}
end
defp icon_and_text("user.status_changed", meta) do
status = Map.get(meta, "status_to") || Map.get(meta, "status")
{"hero-user-circle", "Your account status was updated#{suffix_if(status, " to #{status}")}."}
end
defp icon_and_text("user.password_changed", _meta) do
{"hero-lock-closed", "Your password was changed by an administrator."}
end
defp icon_and_text("user.password_reset", _meta) do
{"hero-key", "Your password was reset."}
end
defp icon_and_text("user.email_changed", meta) do
new_email = Map.get(meta, "new_email")
{"hero-envelope", "Your email was changed#{suffix_if(new_email, " to #{new_email}")}."}
end
defp icon_and_text("user.email_confirmed", _meta) do
{"hero-check-badge", "Your email was confirmed."}
end
defp icon_and_text("user.avatar_changed", _meta) do
{"hero-user-circle", "Your avatar was updated."}
end
defp icon_and_text("user.profile_updated", _meta) do
{"hero-pencil-square", "Your profile was updated."}
end
defp icon_and_text("user.note_created", _meta) do
{"hero-clipboard-document", "An admin added a note on your account."}
end
defp icon_and_text("user.note_deleted", _meta) do
{"hero-clipboard-document", "An admin removed a note from your account."}
end
defp icon_and_text("post.liked", _meta) do
{"hero-heart", "Someone liked your post."}
end
defp icon_and_text("post.commented", _meta) do
{"hero-chat-bubble-left-ellipsis", "Someone commented on your post."}
end
defp icon_and_text("comment.liked", _meta) do
{"hero-heart", "Someone liked your comment."}
end
defp icon_and_text("user.followed", _meta) do
{"hero-user-plus", "Someone started following you."}
end
defp icon_and_text(action, _meta) when is_binary(action) do
{"hero-bell", humanize(action)}
end
defp icon_and_text(_action, _meta) do
{"hero-bell", "New notification."}
end
# ── Action → link ────────────────────────────────────────────────────
defp link_for(%_{action: "user." <> _verb}), do: Routes.path("/dashboard/settings")
# No default deep-link target: the caller decides what to do when `link` is nil.
defp link_for(_activity), do: nil
# ── Helpers ──────────────────────────────────────────────────────────
defp suffix_if(nil, _), do: ""
defp suffix_if("", _), do: ""
defp suffix_if(_value, suffix), do: suffix
# Returns the metadata string for `key` iff it's a non-empty binary;
# otherwise nil so the caller can fall through to the default.
defp meta_string(meta, key) when is_map(meta) and is_binary(key) do
case Map.get(meta, key) do
value when is_binary(value) and value != "" -> value
_ -> nil
end
end
defp meta_string(_meta, _key), do: nil
defp humanize(action) do
action
|> String.replace(".", " ")
|> String.replace("_", " ")
|> String.capitalize()
end
end