diff --git a/assets/js/hooks.js b/assets/js/hooks.js
index 8ff6494..e297a73 100644
--- a/assets/js/hooks.js
+++ b/assets/js/hooks.js
@@ -595,6 +595,11 @@ const createAidMap = element => {
state.expandServerCluster = point => {
if (!point || !state.map) return
+ if (point.expansion_lookup === true) {
+ state.onClusterExpand?.({cluster_id: point.cluster_id})
+ return
+ }
+
const count = Number(point.count) || 1
const requestedZoom = Number(point.expansion_zoom)
const currentZoom = state.map.getZoom()
@@ -625,6 +630,41 @@ const createAidMap = element => {
})
}
+ state.applyClusterExpansion = expansion => {
+ if (!expansion || !state.map) return
+
+ const longitude = Number(expansion.longitude)
+ const latitude = Number(expansion.latitude)
+ const requestedZoom = Number(expansion.zoom)
+ const count = Number(expansion.point_count) || 1
+
+ if (![longitude, latitude, requestedZoom].every(Number.isFinite)) return
+
+ const currentZoom = state.map.getZoom()
+ const expansionZoom = clusterExpansionTarget(currentZoom, requestedZoom)
+
+ if (currentZoom >= 21.75 && expansionZoom <= 22) {
+ const label = (state.element.dataset.clusterLabel || "%{count} items")
+ .replace("%{count}", String(count))
+
+ new maplibregl.Popup({offset: 18})
+ .setLngLat([longitude, latitude])
+ .setDOMContent(state.popupContent({
+ title: label,
+ location: state.element.dataset.clusterMaxZoomLabel || ""
+ }))
+ .addTo(state.map)
+ return
+ }
+
+ state.clusterExpansionPending = true
+ state.map.easeTo({
+ center: [longitude, latitude],
+ zoom: expansionZoom,
+ duration: 300
+ })
+ }
+
state.syncClusterImages = points => {
if (!state.map || state.element.dataset.discoveryMap !== "true") return
@@ -2011,6 +2051,9 @@ export const Hooks = {
this.pushEvent("map-viewport-change", viewport)
this.aidMap.onClusterExpand = viewport =>
this.pushEvent("expand-map-cluster", viewport)
+ this.handleEvent("map-cluster-expansion", expansion =>
+ this.aidMap.applyClusterExpansion(expansion)
+ )
this.aidMap.mount()
},
updated() {
diff --git a/docs/architecture.md b/docs/architecture.md
index ab291fd..dc960eb 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -130,14 +130,19 @@ traffic; the public OpenStreetMap tile service is best-effort and has a usage
policy, not an application backend.
Request discovery is bounded by the validated map viewport sent by the
-browser. PostGIS filters open requests against that area and groups visible
-coordinates into Web Mercator grid cells sized from the geographic bounds and
-rendered map dimensions. Dense cells are returned as aggregate clusters;
-single cells preserve a request marker or privacy-radius area. Coordinate-free
-requests are excluded from the map and appear in the list only when the user
-supplies a matching public area/city search. LiveView subscribes to bounded
-regional PubSub tiles plus the signed-in user's topic instead of the global
-request stream.
+browser. Persistent hourly rollups store additive Web Mercator cell counts for
+zoom hierarchy levels 0 through 12. A read combines future-hour summaries with
+raw rows from the current partial hour, then subtracts blocked owners exactly.
+At higher zoom levels, and for searches whose predicates are not represented
+by the rollup dimensions, PostGIS reads the bounded raw rows. Dense cells are
+returned as aggregate clusters; a cell containing one visible record is
+hydrated as its request marker or privacy-radius area. Cluster expansion uses
+the exact surviving records in that cell, so bounds and zoom remain correct
+for the current viewer rather than exposing a summary-only approximation.
+Coordinate-free requests are excluded from the map and appear in the list only
+when the user supplies a matching public area/city search. LiveView subscribes
+to bounded regional PubSub tiles plus the signed-in user's topic instead of the
+global request stream.
## Data durability and retention
diff --git a/docs/performance.md b/docs/performance.md
index b7971b7..4720d23 100644
--- a/docs/performance.md
+++ b/docs/performance.md
@@ -822,10 +822,37 @@ application-level samples recorded the following medians:
| World | 56.182 / 3,975.605 ms | 3,910.191 / 7,416.714 ms | 10 / 791,753 |
Server clustering kept each measured map payload between 135 and 1,463 bytes
-instead of returning hundreds of thousands of points. The broad low-zoom map
-queries remain the measured optimization target: clustering bounds the
-network/browser payload, but PostgreSQL still reads and groups the matching
-records.
+instead of returning hundreds of thousands of points. At that revision, the
+broad low-zoom map queries were the measured optimization target: clustering
+bounded the network/browser payload, but PostgreSQL still read and grouped the
+matching records.
+
+### Persistent discovery-rollup candidate
+
+Observed locally on 2026-08-27 against a scratch clone of the persistent
+million-request plus million-activity database. PostgreSQL was constrained to
+one CPU. The source scale database and its named volume were not modified.
+The candidate migration built hourly Web Mercator rollups for levels 0 through
+12 in 16.7 seconds inside a 18.2-second full migration run. The resulting help
+rollup contained 406,244 rows and occupied 116 MiB; the Activity rollup
+contained 338,293 rows and occupied 88 MiB.
+
+Warm authenticated discovery observations were:
+
+| Query | No blocked users | One blocked user |
+| --- | ---: | ---: |
+| Help world / Europe / Kyiv level 10 | ~9 / 60 / 2 ms | ~16 / 76 / 3.6 ms |
+| Activity world / Europe / Kyiv level 10 | ~7 / 50 / 2.6 ms | ~12 / 7 / 3.2 ms |
+| Raw high-zoom help / Activity | ~19 / 24 ms | ~28 / 30 ms |
+
+The represented totals remained exact at 950,523 visible help requests and
+1,000,000 visible activities. A deliberately concentrated case with 100
+blocked synthetic owners, requiring exact subtraction of roughly 100,000
+objects, took approximately 0.5–0.8 seconds. That case remains a measured
+worst-case optimization target; no arbitrary block-list limit or hidden
+fallback was introduced. These workstation observations verify the query
+shape and relative improvement. They are not a production capacity claim or a
+minimum-resource requirement.
A headed Chrome check used the same persistent profile. It verified 24-card
initial pages, duplicate-free 48-card pagination, List/Map/Together layouts,
diff --git a/docs/support-and-content-removal.md b/docs/support-and-content-removal.md
index 35e6a22..7f290bd 100644
--- a/docs/support-and-content-removal.md
+++ b/docs/support-and-content-removal.md
@@ -40,21 +40,33 @@ at most 20 locations in one notice.
## Contact verification and status access
-Public support submissions display a reference but never expose the signed
-status token in the redirect. They are stored as `pending_verification`, are not
-visible in the operator queue, and do not produce an operator alert. The private
-status link is sent to the contact email. Opening it atomically verifies the
-address, changes the request to `open`, makes it visible to authorised staff,
-and enqueues at most one metadata-only operator alert when that optional mode is
-enabled. Opening the same link again does not send another alert. An authenticated
-submission uses the confirmed account email, is verified immediately, and enters
-the queue without this extra step. Ongoing requester messages update the staff
-workspace through PubSub and do not generate one operator email per message.
+Ordinary public support submissions display a reference but never expose the
+signed status token in the redirect. They are stored as
+`pending_verification`, are not visible in the operator queue, and do not
+produce an operator alert. The private status link is sent to the contact
+email. Opening it atomically verifies the address, changes the request to
+`open`, makes it visible to authorised staff, and enqueues at most one
+metadata-only operator alert when that optional mode is enabled. Opening the
+same link again does not send another alert. An authenticated submission uses
+the confirmed account email, is verified immediately, and enters the queue
+without this extra step. Ongoing requester messages update the staff workspace
+through PubSub and do not generate one operator email per message.
-Exact repeats of the same unverified public support submission are represented
-by one pending row. Its temporary fingerprint is a SHA-256 digest of normalized
-form fields; it is cleared on verification and does not replace the original
-record or its audit history. A pending support request or an emailed removal
+Anonymous `account_access` and `account_deletion` submissions intentionally
+use a stricter lifecycle contract. The normalized submitted address must match
+exactly one existing account. The response is generic in every case, so it does
+not disclose whether an account exists. An unknown or ambiguous address creates
+no support row and sends no email. A unique match creates one account-linked
+pending request per lifecycle kind and sends confirmation only to the email
+already stored on that account. The request enters the staff queue only after
+that mailbox confirms it. Repeated submissions reuse the pending request rather
+than creating queue spam. No submitted address is silently substituted into a
+different field or linked to a different account.
+
+Exact repeats of the same unverified ordinary public support submission are
+represented by one pending row. Its temporary fingerprint is a SHA-256 digest
+of normalized form fields; it is cleared on verification and does not replace
+the original record or its audit history. A pending support request or an emailed removal
notice that is still unverified after
`PUBLIC_CONTACT_VERIFICATION_MAX_AGE_SECONDS` is deleted by the maintenance
worker together with its creation audit entry. This verification-lifecycle
diff --git a/lib/who_need_help/accounts.ex b/lib/who_need_help/accounts.ex
index 4abd7fb..0df8074 100644
--- a/lib/who_need_help/accounts.ex
+++ b/lib/who_need_help/accounts.ex
@@ -101,6 +101,29 @@ defmodule WhoNeedHelp.Accounts do
Repo.get_by(User, email: String.trim(email))
end
+ @doc """
+ Looks up an account by its normalized email without requiring confirmation.
+
+ The result is deliberately explicit when legacy data contains more than one
+ case-variant of the same address so callers never choose an account
+ arbitrarily.
+ """
+ def get_user_by_normalized_email(email) when is_binary(email) do
+ normalized = email |> String.trim() |> String.downcase()
+
+ users =
+ User
+ |> where([user], fragment("lower(?)", user.email) == ^normalized)
+ |> limit(2)
+ |> Repo.all()
+
+ case users do
+ [user] -> {:ok, user}
+ [] -> {:error, :not_found}
+ [_first, _second] -> {:error, :ambiguous}
+ end
+ end
+
@doc """
Gets a user by email and password.
diff --git a/lib/who_need_help/activities.ex b/lib/who_need_help/activities.ex
index 0c36158..b5cec90 100644
--- a/lib/who_need_help/activities.ex
+++ b/lib/who_need_help/activities.ex
@@ -10,6 +10,7 @@ defmodule WhoNeedHelp.Activities do
alias WhoNeedHelp.Activities.{Activity, Message, Participant}
alias WhoNeedHelp.Catalog
alias WhoNeedHelp.Catalog.Category
+ alias WhoNeedHelp.Discovery.Rollup
alias WhoNeedHelp.Help.{DiscoveryCluster, DiscoveryViewport}
alias WhoNeedHelp.Pagination
alias WhoNeedHelp.Repo
@@ -62,6 +63,19 @@ defmodule WhoNeedHelp.Activities do
def map_discovery_items(%Scope{user: user}, filters, %DiscoveryViewport{} = viewport) do
level = DiscoveryCluster.level_for_zoom(viewport.zoom)
+
+ if DiscoveryCluster.rollup_level?(level) and blank_area_filter?(filters) do
+ Rollup.activity_items(user, filters, viewport, level)
+ else
+ raw_map_discovery_items(user, filters, viewport, level)
+ end
+ end
+
+ def map_cluster_expansion(%Scope{user: user}, filters, cluster_id) do
+ Rollup.activity_expansion(user, filters, cluster_id)
+ end
+
+ defp raw_map_discovery_items(user, filters, viewport, level) do
cell_count = Integer.pow(2, level) * 1.0
points =
@@ -166,13 +180,13 @@ defmodule WhoNeedHelp.Activities do
|> where(
[activity],
activity.creator_id not in subquery(
- from block in Block, where: block.blocker_id == ^user.id, select: block.blocked_id
+ from(block in Block, where: block.blocker_id == ^user.id, select: block.blocked_id)
)
)
|> where(
[activity],
activity.creator_id not in subquery(
- from block in Block, where: block.blocked_id == ^user.id, select: block.blocker_id
+ from(block in Block, where: block.blocked_id == ^user.id, select: block.blocker_id)
)
)
|> before_my_activity(cursor)
@@ -528,10 +542,11 @@ defmodule WhoNeedHelp.Activities do
defp approved_participant_id?(activity_id, user_id) do
Repo.exists?(
- from participant in Participant,
+ from(participant in Participant,
where:
participant.activity_id == ^activity_id and participant.user_id == ^user_id and
participant.status == :approved
+ )
)
end
@@ -794,13 +809,13 @@ defmodule WhoNeedHelp.Activities do
|> where(
[activity],
activity.creator_id not in subquery(
- from block in Block, where: block.blocker_id == ^user.id, select: block.blocked_id
+ from(block in Block, where: block.blocker_id == ^user.id, select: block.blocked_id)
)
)
|> where(
[activity],
activity.creator_id not in subquery(
- from block in Block, where: block.blocked_id == ^user.id, select: block.blocker_id
+ from(block in Block, where: block.blocked_id == ^user.id, select: block.blocker_id)
)
)
|> maybe_filter_category(filters["category_id"] || filters[:category_id])
@@ -1015,6 +1030,14 @@ defmodule WhoNeedHelp.Activities do
|> String.replace("_", "\\_")
end
+ defp blank_area_filter?(filters) do
+ case filters["area"] || filters[:area] do
+ nil -> true
+ area when is_binary(area) -> String.trim(area) == ""
+ _invalid -> false
+ end
+ end
+
defp locked_activity(id) do
Activity
|> where([activity], activity.id == ^id)
diff --git a/lib/who_need_help/discovery/rollup.ex b/lib/who_need_help/discovery/rollup.ex
new file mode 100644
index 0000000..9c0de4a
--- /dev/null
+++ b/lib/who_need_help/discovery/rollup.ex
@@ -0,0 +1,645 @@
+defmodule WhoNeedHelp.Discovery.Rollup do
+ @moduledoc """
+ Reads the persistent, zoom-indexed discovery summaries.
+
+ The summary tables intentionally contain no user-specific state. A map read
+ combines their future-hour buckets with exact rows from the current partial
+ hour and subtracts rows owned by either side of a block. This keeps expiry
+ and blocking exact without grouping the complete discovery data set for
+ every viewport request.
+ """
+
+ alias WhoNeedHelp.Accounts.User
+ alias WhoNeedHelp.Help.{DiscoveryCluster, DiscoveryViewport}
+ alias WhoNeedHelp.Repo
+
+ @type filters :: %{optional(String.t() | atom()) => term()}
+
+ def help_items(%User{} = user, filters, %DiscoveryViewport{} = viewport, level)
+ when is_integer(level) do
+ with true <- DiscoveryCluster.rollup_level?(level),
+ {:ok, category_id} <- optional_uuid(filter(filters, :category_id)),
+ {:ok, urgency} <- optional_urgency(filter(filters, :urgency)) do
+ {now, next_hour} = time_window()
+
+ query_help_rows(viewport, level, [
+ level,
+ now,
+ next_hour,
+ Ecto.UUID.dump!(user.id),
+ category_id,
+ urgency
+ ])
+ |> Enum.map(&help_item(&1, level))
+ else
+ _unsupported_or_invalid_filter -> []
+ end
+ end
+
+ def activity_items(%User{} = user, filters, %DiscoveryViewport{} = viewport, level)
+ when is_integer(level) do
+ with true <- DiscoveryCluster.rollup_level?(level),
+ {:ok, category_id} <- optional_uuid(filter(filters, :category_id)) do
+ {now, next_hour} = time_window()
+
+ query_activity_rows(viewport, level, [
+ level,
+ now,
+ next_hour,
+ Ecto.UUID.dump!(user.id),
+ category_id
+ ])
+ |> Enum.map(&activity_item(&1, level))
+ else
+ _unsupported_or_invalid_filter -> []
+ end
+ end
+
+ def help_expansion(%User{} = user, filters, cluster_id) when is_binary(cluster_id) do
+ with {:ok, {level, cell_x, cell_y}} <-
+ DiscoveryCluster.parse_cluster_id(:request, cluster_id),
+ true <- DiscoveryCluster.rollup_level?(level),
+ {:ok, category_id} <- optional_uuid(filter(filters, :category_id)),
+ {:ok, urgency} <- optional_urgency(filter(filters, :urgency)),
+ [count, west, south, east, north] when count > 0 <-
+ help_expansion_row(
+ expansion_params(
+ user,
+ level,
+ cell_x,
+ cell_y,
+ category_id,
+ urgency
+ )
+ ) do
+ {:ok, expansion(cluster_id, level, count, west, south, east, north)}
+ else
+ _invalid_or_empty -> :error
+ end
+ end
+
+ def activity_expansion(%User{} = user, filters, cluster_id) when is_binary(cluster_id) do
+ with {:ok, {level, cell_x, cell_y}} <-
+ DiscoveryCluster.parse_cluster_id(:activity, cluster_id),
+ true <- DiscoveryCluster.rollup_level?(level),
+ {:ok, category_id} <- optional_uuid(filter(filters, :category_id)),
+ [count, west, south, east, north] when count > 0 <-
+ activity_expansion_row(
+ activity_expansion_params(user, level, cell_x, cell_y, category_id)
+ ) do
+ {:ok, expansion(cluster_id, level, count, west, south, east, north)}
+ else
+ _invalid_or_empty -> :error
+ end
+ end
+
+ def prune_expired do
+ {_now, current_hour} = time_window()
+
+ help =
+ Repo.query!(
+ "DELETE FROM help_discovery_rollups WHERE visible_until_hour < $1",
+ [current_hour],
+ log: false
+ ).num_rows
+
+ activities =
+ Repo.query!(
+ "DELETE FROM activity_discovery_rollups WHERE visible_until_hour < $1",
+ [current_hour],
+ log: false
+ ).num_rows
+
+ %{help: help, activities: activities}
+ end
+
+ defp help_sql(viewport, level) do
+ cell_filter = cell_filter_sql(viewport, level)
+ cell_x = cell_x_sql("request.public_longitude", "$1")
+ cell_y = cell_y_sql("request.public_latitude", "$1")
+
+ """
+ WITH blocked_users AS MATERIALIZED (
+ #{blocked_users_sql()}
+ ), future_rollup AS MATERIALIZED (
+ SELECT
+ cell_x,
+ cell_y,
+ sum(point_count)::bigint AS point_count,
+ sum(point_count) FILTER (WHERE urgency = 'now')::bigint AS urgent_count
+ FROM help_discovery_rollups
+ WHERE level = $1
+ AND visible_until_hour >= $3
+ AND ($5::uuid IS NULL OR category_id = $5)
+ AND ($6::text IS NULL OR urgency = $6)
+ AND #{cell_filter}
+ GROUP BY cell_x, cell_y
+ ), current_points AS MATERIALIZED (
+ SELECT
+ #{cell_x} AS cell_x,
+ #{cell_y} AS cell_y,
+ request.urgency
+ FROM help_requests AS request
+ WHERE #{eligible_help_sql("request")}
+ AND request.expires_at > $2
+ AND request.expires_at < $3
+ AND ($5::uuid IS NULL OR request.category_id = $5)
+ AND ($6::text IS NULL OR request.urgency = $6)
+ AND NOT #{blocked_user_sql("request.requester_id")}
+ ), current_cells AS MATERIALIZED (
+ SELECT
+ cell_x,
+ cell_y,
+ count(*)::bigint AS point_count,
+ count(*) FILTER (WHERE urgency = 'now')::bigint AS urgent_count
+ FROM current_points
+ WHERE #{cell_filter}
+ GROUP BY cell_x, cell_y
+ ), blocked_future_points AS MATERIALIZED (
+ SELECT
+ #{cell_x} AS cell_x,
+ #{cell_y} AS cell_y,
+ request.urgency
+ FROM blocked_users
+ JOIN help_requests AS request ON request.requester_id = blocked_users.user_id
+ WHERE #{eligible_help_sql("request")}
+ AND request.expires_at >= $3
+ AND ($5::uuid IS NULL OR request.category_id = $5)
+ AND ($6::text IS NULL OR request.urgency = $6)
+ ), blocked_future_cells AS MATERIALIZED (
+ SELECT
+ cell_x,
+ cell_y,
+ -count(*)::bigint AS point_count,
+ -count(*) FILTER (WHERE urgency = 'now')::bigint AS urgent_count
+ FROM blocked_future_points
+ WHERE #{cell_filter}
+ GROUP BY cell_x, cell_y
+ ), totals AS MATERIALIZED (
+ SELECT
+ cell_x,
+ cell_y,
+ sum(point_count)::bigint AS point_count,
+ sum(urgent_count)::bigint AS urgent_count
+ FROM (
+ SELECT * FROM future_rollup
+ UNION ALL
+ SELECT * FROM current_cells
+ UNION ALL
+ SELECT * FROM blocked_future_cells
+ ) AS contributions
+ GROUP BY cell_x, cell_y
+ HAVING sum(point_count) > 0
+ )
+ SELECT
+ totals.cell_x,
+ totals.cell_y,
+ totals.point_count,
+ totals.urgent_count,
+ singleton.id,
+ singleton.title,
+ singleton.location_label,
+ singleton.public_longitude,
+ singleton.public_latitude,
+ singleton.location_visibility,
+ singleton.location_radius_meters
+ FROM totals
+ LEFT JOIN LATERAL (
+ SELECT
+ request.id,
+ request.title,
+ request.location_label,
+ request.public_longitude,
+ request.public_latitude,
+ request.location_visibility,
+ request.location_radius_meters
+ FROM help_requests AS request
+ WHERE totals.point_count = 1
+ AND #{eligible_help_sql("request")}
+ AND request.expires_at > $2
+ AND ($5::uuid IS NULL OR request.category_id = $5)
+ AND ($6::text IS NULL OR request.urgency = $6)
+ AND NOT #{blocked_user_sql("request.requester_id")}
+ AND #{cell_x} = totals.cell_x
+ AND #{cell_y} = totals.cell_y
+ LIMIT 1
+ ) AS singleton ON totals.point_count = 1
+ ORDER BY totals.cell_x, totals.cell_y
+ """
+ end
+
+ defp activity_sql(viewport, level) do
+ cell_filter = cell_filter_sql(viewport, level)
+ cell_x = cell_x_sql("activity.public_longitude", "$1")
+ cell_y = cell_y_sql("activity.public_latitude", "$1")
+
+ """
+ WITH blocked_users AS MATERIALIZED (
+ #{blocked_users_sql()}
+ ), future_rollup AS MATERIALIZED (
+ SELECT cell_x, cell_y, sum(point_count)::bigint AS point_count
+ FROM activity_discovery_rollups
+ WHERE level = $1
+ AND visible_until_hour >= $3
+ AND ($5::uuid IS NULL OR category_id = $5)
+ AND #{cell_filter}
+ GROUP BY cell_x, cell_y
+ ), current_points AS MATERIALIZED (
+ SELECT #{cell_x} AS cell_x, #{cell_y} AS cell_y
+ FROM activities AS activity
+ WHERE #{eligible_activity_sql("activity")}
+ AND activity.starts_at > $2
+ AND activity.join_deadline > $2
+ AND (activity.starts_at < $3 OR activity.join_deadline < $3)
+ AND ($5::uuid IS NULL OR activity.category_id = $5)
+ AND NOT #{blocked_user_sql("activity.creator_id")}
+ ), current_cells AS MATERIALIZED (
+ SELECT cell_x, cell_y, count(*)::bigint AS point_count
+ FROM current_points
+ WHERE #{cell_filter}
+ GROUP BY cell_x, cell_y
+ ), blocked_future_points AS MATERIALIZED (
+ SELECT #{cell_x} AS cell_x, #{cell_y} AS cell_y
+ FROM blocked_users
+ JOIN activities AS activity ON activity.creator_id = blocked_users.user_id
+ WHERE #{eligible_activity_sql("activity")}
+ AND activity.starts_at > $2
+ AND activity.join_deadline > $2
+ AND activity.starts_at >= $3
+ AND activity.join_deadline >= $3
+ AND ($5::uuid IS NULL OR activity.category_id = $5)
+ ), blocked_future_cells AS MATERIALIZED (
+ SELECT cell_x, cell_y, -count(*)::bigint AS point_count
+ FROM blocked_future_points
+ WHERE #{cell_filter}
+ GROUP BY cell_x, cell_y
+ ), totals AS MATERIALIZED (
+ SELECT cell_x, cell_y, sum(point_count)::bigint AS point_count
+ FROM (
+ SELECT * FROM future_rollup
+ UNION ALL
+ SELECT * FROM current_cells
+ UNION ALL
+ SELECT * FROM blocked_future_cells
+ ) AS contributions
+ GROUP BY cell_x, cell_y
+ HAVING sum(point_count) > 0
+ )
+ SELECT
+ totals.cell_x,
+ totals.cell_y,
+ totals.point_count,
+ singleton.id,
+ singleton.title,
+ singleton.location_label,
+ singleton.public_longitude,
+ singleton.public_latitude
+ FROM totals
+ LEFT JOIN LATERAL (
+ SELECT
+ activity.id,
+ activity.title,
+ activity.location_label,
+ activity.public_longitude,
+ activity.public_latitude
+ FROM activities AS activity
+ WHERE totals.point_count = 1
+ AND #{eligible_activity_sql("activity")}
+ AND activity.starts_at > $2
+ AND activity.join_deadline > $2
+ AND ($5::uuid IS NULL OR activity.category_id = $5)
+ AND NOT #{blocked_user_sql("activity.creator_id")}
+ AND #{cell_x} = totals.cell_x
+ AND #{cell_y} = totals.cell_y
+ LIMIT 1
+ ) AS singleton ON totals.point_count = 1
+ ORDER BY totals.cell_x, totals.cell_y
+ """
+ end
+
+ defp help_expansion_sql do
+ cell_x = cell_x_sql("request.public_longitude", "$3")
+ cell_y = cell_y_sql("request.public_latitude", "$3")
+
+ """
+ SELECT
+ count(*)::bigint,
+ min(request.public_longitude),
+ min(request.public_latitude),
+ max(request.public_longitude),
+ max(request.public_latitude)
+ FROM help_requests AS request
+ WHERE #{eligible_help_sql("request")}
+ AND request.expires_at > $1
+ AND NOT #{expansion_blocked_owner_sql("request.requester_id")}
+ AND ($6::uuid IS NULL OR request.category_id = $6)
+ AND ($7::text IS NULL OR request.urgency = $7)
+ AND request.public_longitude >= $8
+ AND request.public_longitude <= $9
+ AND request.public_latitude >= $10
+ AND request.public_latitude <= $11
+ AND #{cell_x} = $4
+ AND #{cell_y} = $5
+ """
+ end
+
+ defp activity_expansion_sql do
+ cell_x = cell_x_sql("activity.public_longitude", "$3")
+ cell_y = cell_y_sql("activity.public_latitude", "$3")
+
+ """
+ SELECT
+ count(*)::bigint,
+ min(activity.public_longitude),
+ min(activity.public_latitude),
+ max(activity.public_longitude),
+ max(activity.public_latitude)
+ FROM activities AS activity
+ WHERE #{eligible_activity_sql("activity")}
+ AND activity.starts_at > $1
+ AND activity.join_deadline > $1
+ AND NOT #{expansion_blocked_owner_sql("activity.creator_id")}
+ AND ($6::uuid IS NULL OR activity.category_id = $6)
+ AND activity.public_longitude >= $7
+ AND activity.public_longitude <= $8
+ AND activity.public_latitude >= $9
+ AND activity.public_latitude <= $10
+ AND #{cell_x} = $4
+ AND #{cell_y} = $5
+ """
+ end
+
+ defp expansion_params(user, level, cell_x, cell_y, category_id, urgency) do
+ bounds = DiscoveryCluster.cell_bounds(level, cell_x, cell_y)
+
+ [
+ DateTime.utc_now(:second) |> DateTime.to_naive(),
+ Ecto.UUID.dump!(user.id),
+ level,
+ cell_x,
+ cell_y,
+ category_id,
+ urgency,
+ bounds.west,
+ bounds.east,
+ bounds.south,
+ bounds.north
+ ]
+ end
+
+ defp activity_expansion_params(user, level, cell_x, cell_y, category_id) do
+ bounds = DiscoveryCluster.cell_bounds(level, cell_x, cell_y)
+
+ [
+ DateTime.utc_now(:second) |> DateTime.to_naive(),
+ Ecto.UUID.dump!(user.id),
+ level,
+ cell_x,
+ cell_y,
+ category_id,
+ bounds.west,
+ bounds.east,
+ bounds.south,
+ bounds.north
+ ]
+ end
+
+ # The statement is assembled only from hard-coded SQL fragments plus
+ # viewport cell ranges already normalised to integers. Every external filter
+ # remains a PostgreSQL bind parameter.
+ # sobelow_skip ["SQL.Query"]
+ defp query_help_rows(viewport, level, params) do
+ Repo.query!(help_sql(viewport, level), params, log: false).rows
+ end
+
+ # sobelow_skip ["SQL.Query"]
+ defp query_activity_rows(viewport, level, params) do
+ Repo.query!(activity_sql(viewport, level), params, log: false).rows
+ end
+
+ # sobelow_skip ["SQL.Query"]
+ defp help_expansion_row(params) do
+ case Repo.query!(help_expansion_sql(), params, log: false).rows do
+ [row] -> row
+ _unexpected -> nil
+ end
+ end
+
+ # sobelow_skip ["SQL.Query"]
+ defp activity_expansion_row(params) do
+ case Repo.query!(activity_expansion_sql(), params, log: false).rows do
+ [row] -> row
+ _unexpected -> nil
+ end
+ end
+
+ defp expansion(cluster_id, level, count, west, south, east, north) do
+ %{
+ cluster_id: cluster_id,
+ point_count: count,
+ bounds: %{west: west, south: south, east: east, north: north},
+ longitude: (west + east) / 2.0,
+ latitude: (south + north) / 2.0,
+ zoom: DiscoveryCluster.expansion_zoom_for_bounds(level, west, south, east, north)
+ }
+ end
+
+ defp help_item(
+ [
+ cell_x,
+ cell_y,
+ 1,
+ urgent_count,
+ id,
+ title,
+ location,
+ longitude,
+ latitude,
+ visibility,
+ radius
+ ],
+ level
+ )
+ when not is_nil(id) do
+ %{
+ type: "request",
+ id: load_uuid(id),
+ parent_cluster_id: DiscoveryCluster.parent_id(:request, level, cell_x, cell_y),
+ hierarchy_level: level,
+ title: title,
+ location: location,
+ latitude: latitude,
+ longitude: longitude,
+ exact: visibility == "exact_public",
+ radius_meters: radius,
+ count: 1,
+ urgent_count: urgent_count
+ }
+ end
+
+ defp help_item([cell_x, cell_y, count, urgent_count | _singleton], level) do
+ cluster_item(:request, level, cell_x, cell_y, count)
+ |> Map.put(:urgent_count, urgent_count)
+ end
+
+ defp activity_item(
+ [cell_x, cell_y, 1, id, title, location, longitude, latitude],
+ level
+ )
+ when not is_nil(id) do
+ %{
+ type: "activity",
+ id: load_uuid(id),
+ parent_cluster_id: DiscoveryCluster.parent_id(:activity, level, cell_x, cell_y),
+ hierarchy_level: level,
+ title: title,
+ location: location,
+ latitude: latitude,
+ longitude: longitude,
+ exact: false,
+ count: 1
+ }
+ end
+
+ defp activity_item([cell_x, cell_y, count | _singleton], level) do
+ cluster_item(:activity, level, cell_x, cell_y, count)
+ end
+
+ defp cluster_item(kind, level, cell_x, cell_y, count) do
+ cluster_id = DiscoveryCluster.cluster_id(kind, level, cell_x, cell_y)
+ {longitude, latitude} = DiscoveryCluster.cell_center(level, cell_x, cell_y)
+
+ %{
+ type: "cluster",
+ id: cluster_id,
+ cluster_id: cluster_id,
+ parent_cluster_id: DiscoveryCluster.parent_id(kind, level, cell_x, cell_y),
+ hierarchy_level: level,
+ point_count: count,
+ count: count,
+ latitude: latitude,
+ longitude: longitude,
+ expansion_lookup: true
+ }
+ end
+
+ defp time_window do
+ now = DateTime.utc_now(:second) |> DateTime.to_naive()
+ current_hour = %{now | minute: 0, second: 0, microsecond: {0, 0}}
+ {now, NaiveDateTime.add(current_hour, 3_600, :second)}
+ end
+
+ defp filter(filters, key), do: filters[to_string(key)] || filters[key]
+
+ defp optional_uuid(value) when value in [nil, ""], do: {:ok, nil}
+
+ defp optional_uuid(value) do
+ case Ecto.UUID.dump(value) do
+ {:ok, dumped} -> {:ok, dumped}
+ :error -> :error
+ end
+ end
+
+ defp optional_urgency(value) when value in [nil, ""], do: {:ok, nil}
+ defp optional_urgency(value) when value in [:now, "now"], do: {:ok, "now"}
+ defp optional_urgency(value) when value in [:today, "today"], do: {:ok, "today"}
+ defp optional_urgency(value) when value in [:scheduled, "scheduled"], do: {:ok, "scheduled"}
+ defp optional_urgency(_value), do: :error
+
+ defp load_uuid(binary) when is_binary(binary) do
+ case Ecto.UUID.load(binary) do
+ {:ok, uuid} -> uuid
+ :error -> binary
+ end
+ end
+
+ defp cell_filter_sql(viewport, level) do
+ viewport
+ |> DiscoveryCluster.viewport_cell_ranges(level)
+ |> Enum.map_join(" OR ", fn {west, south, east, north} ->
+ "(cell_x BETWEEN #{west} AND #{east} AND cell_y BETWEEN #{south} AND #{north})"
+ end)
+ |> then(&"(#{&1})")
+ end
+
+ defp blocked_users_sql do
+ """
+ SELECT block.blocked_id AS user_id
+ FROM blocks AS block
+ WHERE block.blocker_id = $4
+ UNION
+ SELECT block.blocker_id AS user_id
+ FROM blocks AS block
+ WHERE block.blocked_id = $4
+ """
+ end
+
+ defp blocked_user_sql(owner) do
+ """
+ EXISTS (
+ SELECT 1 FROM blocked_users WHERE blocked_users.user_id = #{owner}
+ )
+ """
+ end
+
+ defp expansion_blocked_owner_sql(owner) do
+ """
+ EXISTS (
+ SELECT 1
+ FROM blocks AS block
+ WHERE (block.blocker_id = $2 AND block.blocked_id = #{owner})
+ OR (block.blocked_id = $2 AND block.blocker_id = #{owner})
+ )
+ """
+ end
+
+ defp eligible_help_sql(alias_name) do
+ """
+ #{alias_name}.status = 'open'
+ AND #{alias_name}.hidden_at IS NULL
+ AND #{alias_name}.location_visibility <> 'hidden'
+ AND #{alias_name}.public_longitude IS NOT NULL
+ AND #{alias_name}.public_latitude IS NOT NULL
+ """
+ end
+
+ defp eligible_activity_sql(alias_name) do
+ """
+ #{alias_name}.status = 'open'
+ AND #{alias_name}.hidden_at IS NULL
+ AND #{alias_name}.location_visibility = 'approximate_public'
+ AND #{alias_name}.public_longitude IS NOT NULL
+ AND #{alias_name}.public_latitude IS NOT NULL
+ """
+ end
+
+ defp cell_x_sql(longitude, level) do
+ """
+ least(
+ power(2, #{level})::bigint - 1,
+ greatest(0, floor(((#{longitude} + 180.0) / 360.0) * power(2, #{level})))::bigint
+ )
+ """
+ end
+
+ defp cell_y_sql(latitude, level) do
+ """
+ least(
+ power(2, #{level})::bigint - 1,
+ greatest(
+ 0,
+ floor(
+ (
+ ln(
+ tan(
+ pi() / 4.0 +
+ radians(least(85.05112878, greatest(-85.05112878, #{latitude}))) / 2.0
+ )
+ ) / pi() + 1.0
+ ) / 2.0 * power(2, #{level})
+ )::bigint
+ )
+ )
+ """
+ end
+end
diff --git a/lib/who_need_help/help.ex b/lib/who_need_help/help.ex
index ce5f14a..34ed0b0 100644
--- a/lib/who_need_help/help.ex
+++ b/lib/who_need_help/help.ex
@@ -7,6 +7,7 @@ defmodule WhoNeedHelp.Help do
alias WhoNeedHelp.Accounts.Scope
alias WhoNeedHelp.Catalog
alias WhoNeedHelp.Catalog.Category
+ alias WhoNeedHelp.Discovery.Rollup
alias WhoNeedHelp.Help.{Assignment, DiscoveryCluster, DiscoveryViewport, HelpRequest}
alias WhoNeedHelp.Pagination
alias WhoNeedHelp.ProductAnalytics
@@ -73,6 +74,19 @@ defmodule WhoNeedHelp.Help do
def map_discovery_items(%Scope{user: user}, filters, %DiscoveryViewport{} = viewport) do
level = DiscoveryCluster.level_for_zoom(viewport.zoom)
+
+ if DiscoveryCluster.rollup_level?(level) and blank_area_filter?(filters) do
+ Rollup.help_items(user, filters, viewport, level)
+ else
+ raw_map_discovery_items(user, filters, viewport, level)
+ end
+ end
+
+ def map_cluster_expansion(%Scope{user: user}, filters, cluster_id) do
+ Rollup.help_expansion(user, filters, cluster_id)
+ end
+
+ defp raw_map_discovery_items(user, filters, viewport, level) do
cell_count = Integer.pow(2, level) * 1.0
points =
@@ -211,9 +225,10 @@ defmodule WhoNeedHelp.Help do
[request],
request.requester_id == ^user.id or
request.id in subquery(
- from assignment in Assignment,
+ from(assignment in Assignment,
where: assignment.helper_id == ^user.id,
select: assignment.request_id
+ )
)
)
|> before_my_request(cursor)
@@ -1136,6 +1151,14 @@ defmodule WhoNeedHelp.Help do
|> String.replace("_", "\\_")
end
+ defp blank_area_filter?(filters) do
+ case filters["area"] || filters[:area] do
+ nil -> true
+ area when is_binary(area) -> String.trim(area) == ""
+ _invalid -> false
+ end
+ end
+
defp open_requests_query(user, filters, viewport) do
now = DateTime.utc_now(:second)
@@ -1147,13 +1170,13 @@ defmodule WhoNeedHelp.Help do
|> where(
[request],
request.requester_id not in subquery(
- from block in Block, where: block.blocker_id == ^user.id, select: block.blocked_id
+ from(block in Block, where: block.blocker_id == ^user.id, select: block.blocked_id)
)
)
|> where(
[request],
request.requester_id not in subquery(
- from block in Block, where: block.blocked_id == ^user.id, select: block.blocker_id
+ from(block in Block, where: block.blocked_id == ^user.id, select: block.blocker_id)
)
)
|> filter_open_requests(filters)
diff --git a/lib/who_need_help/help/discovery_cluster.ex b/lib/who_need_help/help/discovery_cluster.ex
index 586f494..a6c7a80 100644
--- a/lib/who_need_help/help/discovery_cluster.ex
+++ b/lib/who_need_help/help/discovery_cluster.ex
@@ -24,6 +24,7 @@ defmodule WhoNeedHelp.Help.DiscoveryCluster do
@minimum_level 0
@maximum_map_zoom 22
@maximum_level @maximum_map_zoom + @cluster_level_offset
+ @maximum_rollup_level 12
@type kind :: :request | :activity
@@ -34,6 +35,8 @@ defmodule WhoNeedHelp.Help.DiscoveryCluster do
|> clamp(@minimum_level, @maximum_level)
end
+ def rollup_level?(level), do: is_integer(level) and level in 0..@maximum_rollup_level
+
def cell_size_meters(level) when is_integer(level) and level in 0..@maximum_level do
@world_width_meters / :math.pow(2.0, level)
end
@@ -53,8 +56,16 @@ defmodule WhoNeedHelp.Help.DiscoveryCluster do
def viewport_envelopes(%DiscoveryViewport{} = viewport, level)
when is_integer(level) and level in 0..@maximum_level do
viewport
+ |> viewport_cell_ranges(level)
+ |> Enum.map(&cell_range_envelope(&1, level))
+ |> Enum.uniq()
+ end
+
+ def viewport_cell_ranges(%DiscoveryViewport{} = viewport, level)
+ when is_integer(level) and level in 0..@maximum_level do
+ viewport
|> DiscoveryViewport.envelopes()
- |> Enum.map(&align_envelope_to_cells(&1, level))
+ |> Enum.map(&envelope_cell_range(&1, level))
|> Enum.uniq()
end
@@ -64,6 +75,37 @@ defmodule WhoNeedHelp.Help.DiscoveryCluster do
"#{kind}:#{level}:#{cell_x}:#{cell_y}"
end
+ def parse_cluster_id(kind, cluster_id)
+ when kind in [:request, :activity] and is_binary(cluster_id) do
+ with [encoded_kind, level, cell_x, cell_y] <- String.split(cluster_id, ":"),
+ true <- encoded_kind == Atom.to_string(kind),
+ {level, ""} <- Integer.parse(level),
+ {cell_x, ""} <- Integer.parse(cell_x),
+ {cell_y, ""} <- Integer.parse(cell_y),
+ true <- level in 0..@maximum_level,
+ maximum_index = Integer.pow(2, level) - 1,
+ true <- cell_x in 0..maximum_index and cell_y in 0..maximum_index do
+ {:ok, {level, cell_x, cell_y}}
+ else
+ _invalid_cluster_id -> :error
+ end
+ end
+
+ def cell_bounds(level, cell_x, cell_y)
+ when is_integer(level) and level in 0..@maximum_level and is_integer(cell_x) and
+ is_integer(cell_y) do
+ maximum_index = Integer.pow(2, level) - 1
+ cell_x = clamp(cell_x, 0, maximum_index)
+ cell_y = clamp(cell_y, 0, maximum_index)
+
+ %{
+ west: cell_x |> cell_start(level) |> mercator_x_to_longitude(),
+ south: cell_y |> cell_start(level) |> mercator_y_to_latitude(),
+ east: cell_x |> cell_end(level) |> mercator_x_to_longitude(),
+ north: cell_y |> cell_end(level) |> mercator_y_to_latitude()
+ }
+ end
+
@doc """
Returns the stable geographic center of one hierarchy cell.
@@ -152,7 +194,7 @@ defmodule WhoNeedHelp.Help.DiscoveryCluster do
|> floor()
end
- defp align_envelope_to_cells({west, south, east, north}, level) do
+ defp envelope_cell_range({west, south, east, north}, level) do
maximum_index = Integer.pow(2, level) - 1
west_index =
@@ -183,6 +225,10 @@ defmodule WhoNeedHelp.Help.DiscoveryCluster do
|> Kernel.+(1)
|> clamp(0, maximum_index)
+ {west_index, south_index, east_index, north_index}
+ end
+
+ defp cell_range_envelope({west_index, south_index, east_index, north_index}, level) do
{
west_index |> cell_start(level) |> mercator_x_to_longitude(),
south_index |> cell_start(level) |> mercator_y_to_latitude(),
diff --git a/lib/who_need_help/support.ex b/lib/who_need_help/support.ex
index cbec73e..a297788 100644
--- a/lib/who_need_help/support.ex
+++ b/lib/who_need_help/support.ex
@@ -40,6 +40,15 @@ defmodule WhoNeedHelp.Support do
def create_request(scope, attrs, client_scope) when is_map(attrs) do
user = scope_user(scope)
attrs = normalize_keys(attrs)
+
+ if is_nil(user) and anonymous_account_lifecycle_kind?(attrs["kind"]) do
+ create_anonymous_account_lifecycle_request(attrs, client_scope)
+ else
+ create_standard_request(user, attrs, client_scope)
+ end
+ end
+
+ defp create_standard_request(user, attrs, client_scope) do
contact_email = if user, do: user.email, else: attrs["contact_email"]
attrs = Map.put(attrs, "contact_email", contact_email)
fingerprint = if user, do: nil, else: public_submission_fingerprint(attrs)
@@ -88,6 +97,119 @@ defmodule WhoNeedHelp.Support do
end
end
+ defp create_anonymous_account_lifecycle_request(attrs, client_scope) do
+ contact_email = attrs["contact_email"]
+
+ with :ok <- validate_anonymous_account_lifecycle_attrs(attrs),
+ {:ok, _limits} <-
+ RateLimiter.check_many(rate_scopes(nil, contact_email, client_scope)) do
+ case Accounts.get_user_by_normalized_email(contact_email || "") do
+ {:ok, user} -> create_pending_account_lifecycle_request(user, attrs)
+ {:error, reason} when reason in [:not_found, :ambiguous] -> {:ok, :accepted}
+ end
+ end
+ end
+
+ defp validate_anonymous_account_lifecycle_attrs(attrs) do
+ changeset =
+ %SupportRequest{reference: "SUP-VALIDATION"}
+ |> SupportRequest.submission_changeset(attrs)
+
+ if changeset.valid?, do: :ok, else: {:error, changeset}
+ end
+
+ defp create_pending_account_lifecycle_request(%User{} = user, attrs) do
+ attrs = Map.put(attrs, "contact_email", user.email)
+ kind = account_lifecycle_kind(attrs["kind"])
+
+ result =
+ Repo.transact(fn ->
+ case pending_account_lifecycle_request(user.id, kind, lock: true) do
+ %SupportRequest{} = request ->
+ with {:ok, _job} <- enqueue_created_email(request) do
+ {:ok, request}
+ end
+
+ nil ->
+ insert_pending_account_lifecycle_request(user, attrs)
+ end
+ end)
+
+ result
+ |> resolve_pending_account_lifecycle_conflict(user.id, kind)
+ |> broadcast_request_update()
+ end
+
+ defp insert_pending_account_lifecycle_request(%User{} = user, attrs) do
+ with {:ok, request} <-
+ %SupportRequest{
+ reference: unique_reference("SUP"),
+ requester_id: user.id,
+ status: :pending_verification
+ }
+ |> SupportRequest.submission_changeset(attrs)
+ |> Repo.insert(),
+ {:ok, _audit} <-
+ Trust.audit(
+ nil,
+ "support_request.created",
+ "support_request",
+ request.id,
+ %{"kind" => to_string(request.kind)}
+ ),
+ {:ok, _event} <-
+ record_status_event(request, nil, request.status, nil, :requester),
+ {:ok, _job} <- enqueue_created_email(request) do
+ {:ok, request}
+ end
+ end
+
+ defp pending_account_lifecycle_request(user_id, kind, options \\ []) do
+ query =
+ from request in SupportRequest,
+ where:
+ request.requester_id == ^user_id and request.kind == ^kind and
+ request.status == :pending_verification and
+ is_nil(request.contact_verified_at)
+
+ query = if Keyword.get(options, :lock, false), do: lock(query, "FOR UPDATE"), else: query
+ Repo.one(query)
+ end
+
+ defp resolve_pending_account_lifecycle_conflict(
+ {:error, %Ecto.Changeset{} = changeset} = error,
+ user_id,
+ kind
+ ) do
+ if pending_account_lifecycle_constraint_error?(changeset) do
+ case pending_account_lifecycle_request(user_id, kind) do
+ %SupportRequest{} = request -> {:ok, request}
+ nil -> error
+ end
+ else
+ error
+ end
+ end
+
+ defp resolve_pending_account_lifecycle_conflict(result, _user_id, _kind), do: result
+
+ defp pending_account_lifecycle_constraint_error?(changeset) do
+ Enum.any?(changeset.errors, fn
+ {_field, {_message, metadata}} ->
+ metadata[:constraint_name] ==
+ "support_requests_one_pending_account_lifecycle_per_user_index"
+
+ _other ->
+ false
+ end)
+ end
+
+ defp anonymous_account_lifecycle_kind?(kind),
+ do: kind in ["account_access", "account_deletion"]
+
+ defp account_lifecycle_kind("account_access"), do: :account_access
+ defp account_lifecycle_kind("account_deletion"), do: :account_deletion
+
def create_inbound_request(attrs, external_source, external_id)
when is_map(attrs) and is_binary(external_source) and is_binary(external_id) do
attrs = normalize_keys(attrs)
diff --git a/lib/who_need_help/support/support_request.ex b/lib/who_need_help/support/support_request.ex
index 8efc81f..a4bbecd 100644
--- a/lib/who_need_help/support/support_request.ex
+++ b/lib/who_need_help/support/support_request.ex
@@ -67,6 +67,9 @@ defmodule WhoNeedHelp.Support.SupportRequest do
|> validate_length(:details, min: 10, max: 5_000)
|> unique_constraint(:reference)
|> unique_constraint(:public_submission_fingerprint)
+ |> unique_constraint([:requester_id, :kind],
+ name: :support_requests_one_pending_account_lifecycle_per_user_index
+ )
end
def inbound_changeset(request, attrs, external_source, external_id) do
diff --git a/lib/who_need_help/workers/expire_requests.ex b/lib/who_need_help/workers/expire_requests.ex
index dd00ee0..eb51d21 100644
--- a/lib/who_need_help/workers/expire_requests.ex
+++ b/lib/who_need_help/workers/expire_requests.ex
@@ -4,6 +4,7 @@ defmodule WhoNeedHelp.Workers.ExpireRequests do
import Ecto.Query
alias WhoNeedHelp.Help
alias WhoNeedHelp.ContentRemoval.Notice
+ alias WhoNeedHelp.Discovery.Rollup
alias WhoNeedHelp.Help.HelpRequest
alias WhoNeedHelp.Repo
alias WhoNeedHelp.Support.SupportRequest
@@ -20,6 +21,7 @@ defmodule WhoNeedHelp.Workers.ExpireRequests do
{:ok, removal_pruned} <- prune_unverified_removal(verification_cutoff(now), 0) do
{pruned_buckets, _} = RateLimiter.prune_expired()
{pruned_user_tokens, _} = WhoNeedHelp.Accounts.delete_expired_user_tokens(now)
+ discovery_rollups_pruned = Rollup.prune_expired()
{:ok,
%{
@@ -29,7 +31,9 @@ defmodule WhoNeedHelp.Workers.ExpireRequests do
unverified_support_pruned: support_pruned,
unverified_removal_pruned: removal_pruned,
rate_limit_buckets_pruned: pruned_buckets,
- user_tokens_pruned: pruned_user_tokens
+ user_tokens_pruned: pruned_user_tokens,
+ help_discovery_rollups_pruned: discovery_rollups_pruned.help,
+ activity_discovery_rollups_pruned: discovery_rollups_pruned.activities
}}
end
end
diff --git a/lib/who_need_help_web/controllers/support_controller.ex b/lib/who_need_help_web/controllers/support_controller.ex
index 447750b..84f46b4 100644
--- a/lib/who_need_help_web/controllers/support_controller.ex
+++ b/lib/who_need_help_web/controllers/support_controller.ex
@@ -28,8 +28,11 @@ defmodule WhoNeedHelpWeb.SupportController do
params,
ClientIp.rate_limit_scope(conn)
) do
- {:ok, request} ->
- redirect(conn, to: ~p"/support/received?reference=#{request.reference}")
+ {:ok, %SupportRequest{} = request} ->
+ redirect_after_create(conn, request, params)
+
+ {:ok, :accepted} ->
+ redirect(conn, to: ~p"/support/received?flow=account")
{:error, %Ecto.Changeset{} = changeset} ->
conn
@@ -70,8 +73,11 @@ defmodule WhoNeedHelpWeb.SupportController do
params,
ClientIp.rate_limit_scope(conn)
) do
- {:ok, request} ->
- redirect(conn, to: ~p"/support/received?reference=#{request.reference}")
+ {:ok, %SupportRequest{} = request} ->
+ redirect_after_create(conn, request, params)
+
+ {:ok, :accepted} ->
+ redirect(conn, to: ~p"/support/received?flow=account")
{:error, %Ecto.Changeset{} = changeset} ->
conn
@@ -192,14 +198,34 @@ defmodule WhoNeedHelpWeb.SupportController do
end
end
- def received(conn, %{"reference" => reference}) when is_binary(reference) do
- render(conn, :received,
- reference: String.slice(reference, 0, 64),
- page_title: gettext("Support request received")
- )
+ def received(conn, params) do
+ reference =
+ case params["reference"] do
+ reference when is_binary(reference) -> String.slice(reference, 0, 64)
+ _other -> nil
+ end
+
+ account_lookup = params["flow"] == "account"
+
+ if account_lookup or is_binary(reference) do
+ render(conn, :received,
+ reference: reference,
+ account_lookup: account_lookup,
+ page_title: gettext("Support request received")
+ )
+ else
+ redirect(conn, to: ~p"/support")
+ end
end
- def received(conn, _params), do: redirect(conn, to: ~p"/support")
+ defp redirect_after_create(conn, request, params) do
+ if is_nil(conn.assigns.current_scope) and
+ params["kind"] in ["account_access", "account_deletion"] do
+ redirect(conn, to: ~p"/support/received?flow=account")
+ else
+ redirect(conn, to: ~p"/support/received?reference=#{request.reference}")
+ end
+ end
defp render_case(conn, request, token) do
Phoenix.LiveView.Controller.live_render(conn, WhoNeedHelpWeb.SupportCaseLive,
diff --git a/lib/who_need_help_web/controllers/support_html/received.html.heex b/lib/who_need_help_web/controllers/support_html/received.html.heex
index 57f15ae..c9d3d0d 100644
--- a/lib/who_need_help_web/controllers/support_html/received.html.heex
+++ b/lib/who_need_help_web/controllers/support_html/received.html.heex
@@ -6,14 +6,18 @@
>
- {if @current_scope,
- do: gettext("REQUEST RECEIVED"),
- else: gettext("EMAIL CONFIRMATION REQUIRED")}
+ {cond do
+ @current_scope -> gettext("REQUEST RECEIVED")
+ @account_lookup -> gettext("REQUEST CHECKED")
+ true -> gettext("EMAIL CONFIRMATION REQUIRED")
+ end}
- {if @current_scope,
- do: gettext("Support request created"),
- else: gettext("Check your email")}
+ {cond do
+ @current_scope -> gettext("Support request created")
+ @account_lookup -> gettext("Check your email")
+ true -> gettext("Check your email")
+ end}
{gettext(
@@ -21,10 +25,15 @@
reference: @reference
)}
-
+
+ {gettext(
+ "If that address belongs to a Who Need Help account, we sent a private confirmation link to the account email. No support request is created until that link is confirmed."
+ )}
+
+
{gettext(
"We saved pending request %{reference}. It is not in the support queue yet. Open the private link sent to the contact email to verify the address and submit the request for review.",
- reference: @reference
+ reference: @reference || ""
)}
diff --git a/lib/who_need_help_web/live/activity_live/index.ex b/lib/who_need_help_web/live/activity_live/index.ex
index 5ca1285..60586b8 100644
--- a/lib/who_need_help_web/live/activity_live/index.ex
+++ b/lib/who_need_help_web/live/activity_live/index.ex
@@ -193,6 +193,20 @@ defmodule WhoNeedHelpWeb.ActivityLive.Index do
end
end
+ def handle_event("expand-map-cluster", %{"cluster_id" => cluster_id}, socket) do
+ case Activities.map_cluster_expansion(
+ socket.assigns.current_scope,
+ socket.assigns.filters,
+ cluster_id
+ ) do
+ {:ok, expansion} ->
+ {:noreply, push_event(socket, "map-cluster-expansion", expansion)}
+
+ :error ->
+ {:noreply, socket}
+ end
+ end
+
def handle_event("expand-map-cluster", params, socket) do
case DiscoveryViewport.cast(params) do
{:ok, viewport} ->
diff --git a/lib/who_need_help_web/live/request_live/index.ex b/lib/who_need_help_web/live/request_live/index.ex
index 5b9391f..e29d33a 100644
--- a/lib/who_need_help_web/live/request_live/index.ex
+++ b/lib/who_need_help_web/live/request_live/index.ex
@@ -163,6 +163,20 @@ defmodule WhoNeedHelpWeb.RequestLive.Index do
end
end
+ def handle_event("expand-map-cluster", %{"cluster_id" => cluster_id}, socket) do
+ case Help.map_cluster_expansion(
+ socket.assigns.current_scope,
+ socket.assigns.filters,
+ cluster_id
+ ) do
+ {:ok, expansion} ->
+ {:noreply, push_event(socket, "map-cluster-expansion", expansion)}
+
+ :error ->
+ {:noreply, socket}
+ end
+ end
+
def handle_event("expand-map-cluster", params, socket) do
case DiscoveryViewport.cast(params) do
{:ok, viewport} ->
diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot
index 286cd75..cf65bf7 100644
--- a/priv/gettext/default.pot
+++ b/priv/gettext/default.pot
@@ -252,7 +252,7 @@ msgstr ""
msgid "Activity restored."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:522
+#: lib/who_need_help_web/live/activity_live/index.ex:536
#: lib/who_need_help_web/live/activity_live/new.ex:204
#, elixir-autogen, elixir-format
msgid "Activity type"
@@ -270,12 +270,12 @@ msgstr ""
msgid "Administrator"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:524
+#: lib/who_need_help_web/live/activity_live/index.ex:538
#, elixir-autogen, elixir-format
msgid "All activities"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:558
+#: lib/who_need_help_web/live/request_live/index.ex:572
#, elixir-autogen, elixir-format
msgid "All categories"
msgstr ""
@@ -290,7 +290,7 @@ msgstr ""
msgid "Another helper already accepted this request."
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:573
+#: lib/who_need_help_web/live/request_live/index.ex:587
#, elixir-autogen, elixir-format
msgid "Any urgency"
msgstr ""
@@ -405,16 +405,16 @@ msgstr ""
msgid "Cancel request"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:453
+#: lib/who_need_help_web/live/activity_live/index.ex:467
#: lib/who_need_help_web/live/activity_live/show.ex:344
-#: lib/who_need_help_web/live/request_live/index.ex:478
-#: lib/who_need_help_web/live/request_live/index.ex:479
+#: lib/who_need_help_web/live/request_live/index.ex:492
+#: lib/who_need_help_web/live/request_live/index.ex:493
#: lib/who_need_help_web/live/request_live/show.ex:1005
#, elixir-autogen, elixir-format
msgid "Cancelled"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:556
+#: lib/who_need_help_web/live/request_live/index.ex:570
#: lib/who_need_help_web/live/request_live/new.ex:302
#, elixir-autogen, elixir-format
msgid "Category"
@@ -494,7 +494,7 @@ msgstr ""
msgid "Coffee and conversation"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:467
+#: lib/who_need_help_web/live/activity_live/index.ex:481
#, elixir-autogen, elixir-format
msgid "Coffee, cinema, walks, and hikes are separate from urgent help and never affect helper ratings."
msgstr ""
@@ -509,19 +509,19 @@ msgstr ""
msgid "Community helpers"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:715
+#: lib/who_need_help_web/live/activity_live/index.ex:729
#: lib/who_need_help_web/live/activity_live/show.ex:403
#: lib/who_need_help_web/live/activity_live/show.ex:639
#: lib/who_need_help_web/live/activity_live/show.ex:698
-#: lib/who_need_help_web/live/request_live/index.ex:756
+#: lib/who_need_help_web/live/request_live/index.ex:770
#, elixir-autogen, elixir-format
msgid "Community member"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:452
+#: lib/who_need_help_web/live/activity_live/index.ex:466
#: lib/who_need_help_web/live/activity_live/show.ex:343
-#: lib/who_need_help_web/live/request_live/index.ex:476
-#: lib/who_need_help_web/live/request_live/index.ex:477
+#: lib/who_need_help_web/live/request_live/index.ex:490
+#: lib/who_need_help_web/live/request_live/index.ex:491
#: lib/who_need_help_web/live/request_live/show.ex:1004
#: lib/who_need_help_web/live/request_live/show.ex:1023
#, elixir-autogen, elixir-format
@@ -569,13 +569,13 @@ msgstr ""
msgid "Confirming..."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:473
+#: lib/who_need_help_web/live/activity_live/index.ex:487
#: lib/who_need_help_web/live/activity_live/new.ex:12
#, elixir-autogen, elixir-format
msgid "Create activity"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:790
+#: lib/who_need_help_web/live/activity_live/index.ex:804
#: lib/who_need_help_web/live/activity_live/new.ex:180
#, elixir-autogen, elixir-format
msgid "Create an activity"
@@ -586,7 +586,7 @@ msgstr ""
msgid "Create category"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:507
+#: lib/who_need_help_web/live/request_live/index.ex:521
#, elixir-autogen, elixir-format
msgid "Create urgent request"
msgstr ""
@@ -731,7 +731,7 @@ msgstr ""
msgid "Explain what is already arranged and what the volunteer needs to do."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:464
+#: lib/who_need_help_web/live/activity_live/index.ex:478
#, elixir-autogen, elixir-format
msgid "Find people to join"
msgstr ""
@@ -878,8 +878,8 @@ msgstr ""
msgid "In a browser this works only while the page remains open. The Android app shows a persistent notification and can continue after it is minimized. Raw coordinates are removed when sharing stops."
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:474
-#: lib/who_need_help_web/live/request_live/index.ex:475
+#: lib/who_need_help_web/live/request_live/index.ex:488
+#: lib/who_need_help_web/live/request_live/index.ex:489
#: lib/who_need_help_web/live/request_live/show.ex:1003
#, elixir-autogen, elixir-format
msgid "In progress"
@@ -997,8 +997,8 @@ msgstr ""
msgid "Match and coordinate"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:472
-#: lib/who_need_help_web/live/request_live/index.ex:473
+#: lib/who_need_help_web/live/request_live/index.ex:486
+#: lib/who_need_help_web/live/request_live/index.ex:487
#: lib/who_need_help_web/live/request_live/show.ex:1001
#, elixir-autogen, elixir-format
msgid "Matched"
@@ -1024,7 +1024,7 @@ msgstr ""
msgid "Medicine requests"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:480
+#: lib/who_need_help_web/live/activity_live/index.ex:494
#, elixir-autogen, elixir-format
msgid "Meet in public first, keep exact details inside the approved group, and leave or report anything that feels unsafe."
msgstr ""
@@ -1071,7 +1071,7 @@ msgstr ""
msgid "Moderator access is required."
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:498
+#: lib/who_need_help_web/live/request_live/index.ex:512
#, elixir-autogen, elixir-format
msgid "NEARBY MUTUAL AID"
msgstr ""
@@ -1147,9 +1147,9 @@ msgid "Not for emergencies"
msgstr ""
#: lib/who_need_help_web/live/notification_live.ex:736
-#: lib/who_need_help_web/live/request_live/index.ex:462
-#: lib/who_need_help_web/live/request_live/index.ex:463
-#: lib/who_need_help_web/live/request_live/index.ex:574
+#: lib/who_need_help_web/live/request_live/index.ex:476
+#: lib/who_need_help_web/live/request_live/index.ex:477
+#: lib/who_need_help_web/live/request_live/index.ex:588
#: lib/who_need_help_web/live/request_live/new.ex:449
#: lib/who_need_help_web/live/request_live/show.ex:992
#: lib/who_need_help_web/live/request_live/show.ex:993
@@ -1157,7 +1157,7 @@ msgstr ""
msgid "Now"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:462
+#: lib/who_need_help_web/live/activity_live/index.ex:476
#, elixir-autogen, elixir-format
msgid "OPTIONAL SOCIAL PLANS"
msgstr ""
@@ -1175,14 +1175,14 @@ msgstr ""
#: lib/who_need_help/notifications/text.ex:140
#: lib/who_need_help_web/controllers/content_removal_html.ex:21
#: lib/who_need_help_web/controllers/support_html.ex:17
-#: lib/who_need_help_web/live/activity_live/index.ex:451
+#: lib/who_need_help_web/live/activity_live/index.ex:465
#: lib/who_need_help_web/live/activity_live/show.ex:342
#: lib/who_need_help_web/live/moderation_live.ex:636
#: lib/who_need_help_web/live/moderation_live.ex:817
#: lib/who_need_help_web/live/moderation_live.ex:941
#: lib/who_need_help_web/live/report_live.ex:104
-#: lib/who_need_help_web/live/request_live/index.ex:470
-#: lib/who_need_help_web/live/request_live/index.ex:471
+#: lib/who_need_help_web/live/request_live/index.ex:484
+#: lib/who_need_help_web/live/request_live/index.ex:485
#: lib/who_need_help_web/live/request_live/show.ex:1000
#: lib/who_need_help_web/live/support_operation_live.ex:310
#: lib/who_need_help_web/live/support_operation_live.ex:414
@@ -1627,9 +1627,9 @@ msgid "Save profile"
msgstr ""
#: lib/who_need_help_web/live/notification_live.ex:738
-#: lib/who_need_help_web/live/request_live/index.ex:466
-#: lib/who_need_help_web/live/request_live/index.ex:467
-#: lib/who_need_help_web/live/request_live/index.ex:576
+#: lib/who_need_help_web/live/request_live/index.ex:480
+#: lib/who_need_help_web/live/request_live/index.ex:481
+#: lib/who_need_help_web/live/request_live/index.ex:590
#: lib/who_need_help_web/live/request_live/new.ex:451
#: lib/who_need_help_web/live/request_live/show.ex:996
#: lib/who_need_help_web/live/request_live/show.ex:997
@@ -1894,7 +1894,7 @@ msgstr ""
msgid "This interaction is blocked."
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:514
+#: lib/who_need_help_web/live/request_live/index.ex:528
#: lib/who_need_help_web/live/request_live/show.ex:1071
#, elixir-autogen, elixir-format
msgid "This is not an emergency service. Contact local emergency services for immediate danger."
@@ -1942,9 +1942,9 @@ msgid "To see sent emails, visit"
msgstr ""
#: lib/who_need_help_web/live/notification_live.ex:737
-#: lib/who_need_help_web/live/request_live/index.ex:464
-#: lib/who_need_help_web/live/request_live/index.ex:465
-#: lib/who_need_help_web/live/request_live/index.ex:575
+#: lib/who_need_help_web/live/request_live/index.ex:478
+#: lib/who_need_help_web/live/request_live/index.ex:479
+#: lib/who_need_help_web/live/request_live/index.ex:589
#: lib/who_need_help_web/live/request_live/new.ex:450
#: lib/who_need_help_web/live/request_live/show.ex:994
#: lib/who_need_help_web/live/request_live/show.ex:995
@@ -2014,7 +2014,7 @@ msgid "Unique people"
msgstr ""
#: lib/who_need_help_web/live/notification_live.ex:731
-#: lib/who_need_help_web/live/request_live/index.ex:571
+#: lib/who_need_help_web/live/request_live/index.ex:585
#: lib/who_need_help_web/live/request_live/new.ex:447
#, elixir-autogen, elixir-format
msgid "Urgency"
@@ -2147,7 +2147,7 @@ msgstr ""
msgid "Who Need Help was developed with the local Codex CLI authenticated through the builder's ChatGPT subscription. No OpenAI API key or usage-based API fallback is used."
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:499
+#: lib/who_need_help_web/live/request_live/index.ex:513
#, elixir-autogen, elixir-format
msgid "Who needs help?"
msgstr ""
@@ -2261,7 +2261,7 @@ msgstr ""
msgid "Your review is revealed only after both participants submit."
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:824
+#: lib/who_need_help_web/live/request_live/index.ex:838
#, elixir-autogen, elixir-format
msgid "Your trust summary"
msgstr ""
@@ -2272,16 +2272,16 @@ msgstr ""
msgid "before confirming."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:714
+#: lib/who_need_help_web/live/activity_live/index.ex:728
#: lib/who_need_help_web/live/category_proposal_live.ex:252
-#: lib/who_need_help_web/live/request_live/index.ex:755
+#: lib/who_need_help_web/live/request_live/index.ex:769
#, elixir-autogen, elixir-format
msgid "by %{name}"
msgstr ""
#: lib/who_need_help_web/live/profile_live.ex:190
#: lib/who_need_help_web/live/public_profile_live.ex:133
-#: lib/who_need_help_web/live/request_live/index.ex:828
+#: lib/who_need_help_web/live/request_live/index.ex:842
#: lib/who_need_help_web/live/request_live/show.ex:1478
#, elixir-autogen, elixir-format
msgid "completed"
@@ -2317,7 +2317,7 @@ msgstr ""
msgid "participants only"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:832
+#: lib/who_need_help_web/live/request_live/index.ex:846
#: lib/who_need_help_web/live/request_live/show.ex:1482
#, elixir-autogen, elixir-format
msgid "people"
@@ -2377,7 +2377,7 @@ msgstr ""
#: lib/who_need_help_web/live/activity_live/show.ex:719
#: lib/who_need_help_web/live/profile_live.ex:285
#: lib/who_need_help_web/live/public_profile_live.ex:98
-#: lib/who_need_help_web/live/request_live/index.ex:836
+#: lib/who_need_help_web/live/request_live/index.ex:850
#: lib/who_need_help_web/live/request_live/show.ex:1142
#: lib/who_need_help_web/live/request_live/show.ex:1580
#, elixir-autogen, elixir-format
@@ -2410,8 +2410,8 @@ msgstr ""
msgid "Approved"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:480
-#: lib/who_need_help_web/live/request_live/index.ex:481
+#: lib/who_need_help_web/live/request_live/index.ex:494
+#: lib/who_need_help_web/live/request_live/index.ex:495
#, elixir-autogen, elixir-format
msgid "Expired"
msgstr ""
@@ -2465,21 +2465,21 @@ msgstr ""
msgid "Load earlier messages"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:726
-#: lib/who_need_help_web/live/activity_live/index.ex:819
+#: lib/who_need_help_web/live/activity_live/index.ex:740
+#: lib/who_need_help_web/live/activity_live/index.ex:833
#: lib/who_need_help_web/live/category_proposal_live.ex:284
#: lib/who_need_help_web/live/leaderboard_live.ex:91
#: lib/who_need_help_web/live/profile_live.ex:233
#: lib/who_need_help_web/live/profile_live.ex:363
#: lib/who_need_help_web/live/public_profile_live.ex:178
#: lib/who_need_help_web/live/report_live.ex:98
-#: lib/who_need_help_web/live/request_live/index.ex:905
+#: lib/who_need_help_web/live/request_live/index.ex:919
#, elixir-autogen, elixir-format
msgid "Load more"
msgstr ""
#: lib/who_need_help_web/live/activity_live/show.ex:544
-#: lib/who_need_help_web/live/request_live/index.ex:801
+#: lib/who_need_help_web/live/request_live/index.ex:815
#: lib/who_need_help_web/live/request_live/show.ex:1324
#, elixir-autogen, elixir-format
msgid "The map is unavailable in this browser. Request details remain usable."
@@ -2847,7 +2847,7 @@ msgstr ""
msgid "Back to removal form"
msgstr ""
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:34
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:43
#: lib/who_need_help_web/controllers/support_html/verify.html.heex:30
#, elixir-autogen, elixir-format
msgid "Back to support"
@@ -2859,7 +2859,8 @@ msgstr ""
msgid "CONTENT REMOVAL"
msgstr ""
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:16
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:18
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:19
#, elixir-autogen, elixir-format
msgid "Check your email"
msgstr ""
@@ -3139,7 +3140,7 @@ msgstr ""
msgid "Reported locations"
msgstr ""
-#: lib/who_need_help_web/controllers/support_controller.ex:241
+#: lib/who_need_help_web/controllers/support_controller.ex:267
#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:6
#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:42
#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:128
@@ -3290,8 +3291,8 @@ msgstr ""
msgid "Too many removal notices. Please try again later."
msgstr ""
-#: lib/who_need_help_web/controllers/support_controller.ex:42
-#: lib/who_need_help_web/controllers/support_controller.ex:84
+#: lib/who_need_help_web/controllers/support_controller.ex:45
+#: lib/who_need_help_web/controllers/support_controller.ex:90
#, elixir-autogen, elixir-format
msgid "Too many support requests. Please try again later."
msgstr ""
@@ -3373,13 +3374,13 @@ msgstr ""
msgid "Your relationship to the affected person or rights holder"
msgstr ""
-#: lib/who_need_help_web/controllers/support_controller.ex:55
-#: lib/who_need_help_web/controllers/support_controller.ex:66
+#: lib/who_need_help_web/controllers/support_controller.ex:58
+#: lib/who_need_help_web/controllers/support_controller.ex:69
#, elixir-autogen, elixir-format
msgid "Delete my Who Need Help account"
msgstr ""
-#: lib/who_need_help_web/controllers/support_controller.ex:56
+#: lib/who_need_help_web/controllers/support_controller.ex:59
#, elixir-autogen, elixir-format
msgid "Please delete my account and associated personal data."
msgstr ""
@@ -4316,7 +4317,7 @@ msgid "View my notices"
msgstr ""
#: lib/who_need_help_web/controllers/support_html/new.html.heex:13
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:32
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:41
#, elixir-autogen, elixir-format
msgid "View my requests"
msgstr ""
@@ -4431,37 +4432,37 @@ msgstr ""
msgid "Exact point selected. Drag the red pin or click the map to move it."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:533
-#: lib/who_need_help_web/live/request_live/index.ex:581
+#: lib/who_need_help_web/live/activity_live/index.ex:547
+#: lib/who_need_help_web/live/request_live/index.ex:595
#, elixir-autogen, elixir-format
msgid "Area or city"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:656
-#: lib/who_need_help_web/live/request_live/index.ex:702
+#: lib/who_need_help_web/live/activity_live/index.ex:670
+#: lib/who_need_help_web/live/request_live/index.ex:716
#, elixir-autogen, elixir-format
msgid "Choose an area on the map"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:501
+#: lib/who_need_help_web/live/request_live/index.ex:515
#, elixir-autogen, elixir-format
msgid "Explore privacy-friendly areas and public exact meeting points chosen by requesters."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:623
-#: lib/who_need_help_web/live/request_live/index.ex:669
+#: lib/who_need_help_web/live/activity_live/index.ex:637
+#: lib/who_need_help_web/live/request_live/index.ex:683
#, elixir-autogen, elixir-format
msgid "List"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:777
+#: lib/who_need_help_web/live/request_live/index.ex:791
#, elixir-autogen, elixir-format
msgid "Load more in this area"
msgstr ""
#: lib/who_need_help_web/components/layouts/root.html.heex:5
-#: lib/who_need_help_web/live/activity_live/index.ex:643
-#: lib/who_need_help_web/live/request_live/index.ex:689
+#: lib/who_need_help_web/live/activity_live/index.ex:657
+#: lib/who_need_help_web/live/request_live/index.ex:703
#, elixir-autogen, elixir-format
msgid "Map"
msgstr ""
@@ -4471,41 +4472,41 @@ msgstr ""
msgid "Map preview"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:672
-#: lib/who_need_help_web/live/request_live/index.ex:718
+#: lib/who_need_help_web/live/activity_live/index.ex:686
+#: lib/who_need_help_web/live/request_live/index.ex:732
#, elixir-autogen, elixir-format
msgid "Move or zoom the map to search somewhere else."
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:715
+#: lib/who_need_help_web/live/request_live/index.ex:729
#, elixir-autogen, elixir-format
msgid "No open requests in this area"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:704
+#: lib/who_need_help_web/live/request_live/index.ex:718
#, elixir-autogen, elixir-format
msgid "Only requests from the selected map area will be loaded."
msgstr ""
#: lib/who_need_help_web/components/layouts/root.html.heex:8
-#: lib/who_need_help_web/live/request_live/index.ex:804
+#: lib/who_need_help_web/live/request_live/index.ex:818
#, elixir-autogen, elixir-format
msgid "Open request"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:534
-#: lib/who_need_help_web/live/request_live/index.ex:582
+#: lib/who_need_help_web/live/activity_live/index.ex:548
+#: lib/who_need_help_web/live/request_live/index.ex:596
#, elixir-autogen, elixir-format
msgid "Optional text search"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:783
+#: lib/who_need_help_web/live/request_live/index.ex:797
#, elixir-autogen, elixir-format
msgid "Requests without map coordinates appear only when their public area description matches the text search."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:614
-#: lib/who_need_help_web/live/request_live/index.ex:660
+#: lib/who_need_help_web/live/activity_live/index.ex:628
+#: lib/who_need_help_web/live/request_live/index.ex:674
#, elixir-autogen, elixir-format
msgid "Results layout"
msgstr ""
@@ -4515,21 +4516,21 @@ msgstr ""
msgid "Static preview"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:633
-#: lib/who_need_help_web/live/request_live/index.ex:679
+#: lib/who_need_help_web/live/activity_live/index.ex:647
+#: lib/who_need_help_web/live/request_live/index.ex:693
#, elixir-autogen, elixir-format
msgid "Together"
msgstr ""
#: lib/who_need_help_web/components/layouts/root.html.heex:6
#: lib/who_need_help_web/controllers/page_html/home.html.heex:166
-#: lib/who_need_help_web/live/activity_live/index.ex:749
+#: lib/who_need_help_web/live/activity_live/index.ex:763
#: lib/who_need_help_web/live/activity_live/new.ex:355
#: lib/who_need_help_web/live/activity_live/new.ex:414
#: lib/who_need_help_web/live/activity_live/show.ex:546
#: lib/who_need_help_web/live/notification_live.ex:590
#: lib/who_need_help_web/live/notification_live.ex:609
-#: lib/who_need_help_web/live/request_live/index.ex:803
+#: lib/who_need_help_web/live/request_live/index.ex:817
#: lib/who_need_help_web/live/request_live/new.ex:485
#: lib/who_need_help_web/live/request_live/new.ex:563
#: lib/who_need_help_web/live/request_live/show.ex:1326
@@ -4886,7 +4887,7 @@ msgid "Nearby alert deleted."
msgstr ""
#: lib/who_need_help_web/live/notification_live.ex:453
-#: lib/who_need_help_web/live/request_live/index.ex:527
+#: lib/who_need_help_web/live/request_live/index.ex:541
#, elixir-autogen, elixir-format
msgid "Nearby requests"
msgstr ""
@@ -5209,7 +5210,7 @@ msgid "WebGL is unavailable in this browser, so the full map cannot start. Selec
msgstr ""
#: lib/who_need_help_web/components/layouts/root.html.heex:7
-#: lib/who_need_help_web/live/activity_live/index.ex:750
+#: lib/who_need_help_web/live/activity_live/index.ex:764
#: lib/who_need_help_web/live/activity_live/show.ex:547
#: lib/who_need_help_web/live/request_live/show.ex:1327
#, elixir-autogen, elixir-format
@@ -5250,7 +5251,7 @@ msgid "Case status"
msgstr ""
#: lib/who_need_help_web/controllers/page_html/child_safety.html.heex:42
-#: lib/who_need_help_web/controllers/support_controller.ex:242
+#: lib/who_need_help_web/controllers/support_controller.ex:268
#: lib/who_need_help_web/live/report_live.ex:50
#, elixir-autogen, elixir-format
msgid "Contact support"
@@ -5272,8 +5273,8 @@ msgstr ""
msgid "Conversation"
msgstr ""
-#: lib/who_need_help_web/controllers/support_controller.ex:152
-#: lib/who_need_help_web/controllers/support_controller.ex:168
+#: lib/who_need_help_web/controllers/support_controller.ex:158
+#: lib/who_need_help_web/controllers/support_controller.ex:174
#: lib/who_need_help_web/live/support_case_live.ex:73
#, elixir-autogen, elixir-format
msgid "Could not send your message. Please check it and try again."
@@ -5336,7 +5337,7 @@ msgstr ""
msgid "No status history is available for this older case."
msgstr ""
-#: lib/who_need_help_web/controllers/support_controller.ex:187
+#: lib/who_need_help_web/controllers/support_controller.ex:193
#: lib/who_need_help_web/live/support_case_live.ex:95
#, elixir-autogen, elixir-format
msgid "Only a resolved or closed request can be reopened."
@@ -5441,12 +5442,12 @@ msgstr ""
msgid "Status history"
msgstr ""
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:15
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:17
#, elixir-autogen, elixir-format
msgid "Support request created"
msgstr ""
-#: lib/who_need_help_web/controllers/support_controller.ex:198
+#: lib/who_need_help_web/controllers/support_controller.ex:214
#, elixir-autogen, elixir-format
msgid "Support request received"
msgstr ""
@@ -5482,7 +5483,7 @@ msgstr ""
msgid "The requester states whether the medicine is paid, reserved, or still needs to be purchased."
msgstr ""
-#: lib/who_need_help_web/controllers/support_controller.ex:182
+#: lib/who_need_help_web/controllers/support_controller.ex:188
#: lib/who_need_help_web/live/support_case_live.ex:88
#, elixir-autogen, elixir-format
msgid "The support request is open again."
@@ -5510,7 +5511,7 @@ msgstr ""
msgid "This support request is no longer available."
msgstr ""
-#: lib/who_need_help_web/controllers/support_controller.ex:158
+#: lib/who_need_help_web/controllers/support_controller.ex:164
#: lib/who_need_help_web/live/support_case_live.ex:60
#, elixir-autogen, elixir-format
msgid "Too many messages. Please try again later."
@@ -5521,7 +5522,7 @@ msgstr ""
msgid "Use the dedicated urgent process. Never upload or reproduce the image or video."
msgstr ""
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:19
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:23
#, elixir-autogen, elixir-format
msgid "We created support request %{reference} and added it to your account. You can open it now and return to it from My support requests."
msgstr ""
@@ -5547,7 +5548,7 @@ msgstr ""
msgid "Your content-removal notices"
msgstr ""
-#: lib/who_need_help_web/controllers/support_controller.ex:145
+#: lib/who_need_help_web/controllers/support_controller.ex:151
#: lib/who_need_help_web/live/support_case_live.ex:53
#, elixir-autogen, elixir-format
msgid "Your message was sent to support."
@@ -5711,7 +5712,7 @@ msgstr ""
msgid "Delivery settings"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:760
+#: lib/who_need_help_web/live/request_live/index.ex:774
#, elixir-autogen, elixir-format
msgid "Expires"
msgstr ""
@@ -5731,22 +5732,22 @@ msgstr ""
msgid "Notification sections"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:742
+#: lib/who_need_help_web/live/activity_live/index.ex:756
#, elixir-autogen, elixir-format
msgid "%{count} activities"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:805
+#: lib/who_need_help_web/live/request_live/index.ex:819
#, elixir-autogen, elixir-format
msgid "%{count} requests"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:785
+#: lib/who_need_help_web/live/activity_live/index.ex:799
#, elixir-autogen, elixir-format
msgid "Activities you create or ask to join will appear here. Leaving removes an activity from this list."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:488
+#: lib/who_need_help_web/live/activity_live/index.ex:502
#, elixir-autogen, elixir-format
msgid "Activity views"
msgstr ""
@@ -5761,12 +5762,12 @@ msgstr ""
msgid "Confirm the safety guidance before asking nearby people for help."
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:853
+#: lib/who_need_help_web/live/request_live/index.ex:867
#, elixir-autogen, elixir-format
msgid "Create request"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:876
+#: lib/who_need_help_web/live/request_live/index.ex:890
#, elixir-autogen, elixir-format
msgid "Create your first request"
msgstr ""
@@ -5781,43 +5782,43 @@ msgstr ""
msgid "Location"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:494
+#: lib/who_need_help_web/live/activity_live/index.ex:508
#, elixir-autogen, elixir-format
msgid "My activities"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:528
-#: lib/who_need_help_web/live/request_live/index.ex:847
+#: lib/who_need_help_web/live/request_live/index.ex:542
+#: lib/who_need_help_web/live/request_live/index.ex:861
#, elixir-autogen, elixir-format
msgid "My requests"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:493
+#: lib/who_need_help_web/live/activity_live/index.ex:507
#, elixir-autogen, elixir-format
msgid "Nearby activities"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:783
+#: lib/who_need_help_web/live/activity_live/index.ex:797
#, elixir-autogen, elixir-format
msgid "No activities yet"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:669
+#: lib/who_need_help_web/live/activity_live/index.ex:683
#, elixir-autogen, elixir-format
msgid "No open activities in this area"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:871
+#: lib/who_need_help_web/live/request_live/index.ex:885
#, elixir-autogen, elixir-format
msgid "No requests yet"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:658
+#: lib/who_need_help_web/live/activity_live/index.ex:672
#, elixir-autogen, elixir-format
msgid "Only activities from the selected map area will be loaded."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:741
+#: lib/who_need_help_web/live/activity_live/index.ex:755
#, elixir-autogen, elixir-format
msgid "Open activity"
msgstr ""
@@ -5827,12 +5828,12 @@ msgstr ""
msgid "Request form steps"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:522
+#: lib/who_need_help_web/live/request_live/index.ex:536
#, elixir-autogen, elixir-format
msgid "Request views"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:873
+#: lib/who_need_help_web/live/request_live/index.ex:887
#, elixir-autogen, elixir-format
msgid "Requests you publish or accept will appear here."
msgstr ""
@@ -5857,7 +5858,7 @@ msgstr ""
msgid "Share only as much location detail as helpers need."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:747
+#: lib/who_need_help_web/live/activity_live/index.ex:761
#, elixir-autogen, elixir-format
msgid "The map is unavailable in this browser. Activity details remain usable."
msgstr ""
@@ -5867,7 +5868,7 @@ msgstr ""
msgid "Timing"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:849
+#: lib/who_need_help_web/live/request_live/index.ex:863
#, elixir-autogen, elixir-format
msgid "Track requests you created and requests you accepted as a helper."
msgstr ""
@@ -6522,91 +6523,91 @@ msgstr ""
msgid "Choose the meeting point on the map or use your current location."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:573
-#: lib/who_need_help_web/live/activity_live/index.ex:668
-#: lib/who_need_help_web/live/activity_live/index.ex:775
-#: lib/who_need_help_web/live/request_live/index.ex:619
-#: lib/who_need_help_web/live/request_live/index.ex:714
-#: lib/who_need_help_web/live/request_live/index.ex:863
+#: lib/who_need_help_web/live/activity_live/index.ex:587
+#: lib/who_need_help_web/live/activity_live/index.ex:682
+#: lib/who_need_help_web/live/activity_live/index.ex:789
+#: lib/who_need_help_web/live/request_live/index.ex:633
+#: lib/who_need_help_web/live/request_live/index.ex:728
+#: lib/who_need_help_web/live/request_live/index.ex:877
#, elixir-autogen, elixir-format
msgid "Loading results…"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:744
+#: lib/who_need_help_web/live/activity_live/index.ex:758
#, elixir-autogen, elixir-format
msgid "Maximum zoom reached. Matching activities are listed beside the map."
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:807
+#: lib/who_need_help_web/live/request_live/index.ex:821
#, elixir-autogen, elixir-format
msgid "Maximum zoom reached. Matching requests are listed beside the map."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:554
+#: lib/who_need_help_web/live/activity_live/index.ex:568
#, elixir-autogen, elixir-format
msgid "%{count} activity in the selected map area"
msgid_plural "%{count} activities in the selected map area"
msgstr[0] ""
msgstr[1] ""
-#: lib/who_need_help_web/live/request_live/index.ex:600
+#: lib/who_need_help_web/live/request_live/index.ex:614
#, elixir-autogen, elixir-format
msgid "%{count} request in the selected map area"
msgid_plural "%{count} requests in the selected map area"
msgstr[0] ""
msgstr[1] ""
-#: lib/who_need_help_web/live/activity_live/index.ex:579
-#: lib/who_need_help_web/live/request_live/index.ex:625
+#: lib/who_need_help_web/live/activity_live/index.ex:593
+#: lib/who_need_help_web/live/request_live/index.ex:639
#, elixir-autogen, elixir-format
msgid "Map moved. Results still show the previous area."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:597
+#: lib/who_need_help_web/live/activity_live/index.ex:611
#, elixir-autogen, elixir-format
msgid "Move or zoom the map, then choose “Show activities in this area”."
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:643
+#: lib/who_need_help_web/live/request_live/index.ex:657
#, elixir-autogen, elixir-format
msgid "Move or zoom the map, then choose “Show requests in this area”."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:591
-#: lib/who_need_help_web/live/request_live/index.ex:637
+#: lib/who_need_help_web/live/activity_live/index.ex:605
+#: lib/who_need_help_web/live/request_live/index.ex:651
#, elixir-autogen, elixir-format
msgid "Results update automatically after you stop moving the map."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:761
+#: lib/who_need_help_web/live/activity_live/index.ex:775
#, elixir-autogen, elixir-format
msgid "Show activities in this area"
msgstr ""
-#: lib/who_need_help_web/live/request_live/index.ex:819
+#: lib/who_need_help_web/live/request_live/index.ex:833
#, elixir-autogen, elixir-format
msgid "Show requests in this area"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:563
-#: lib/who_need_help_web/live/request_live/index.ex:609
+#: lib/who_need_help_web/live/activity_live/index.ex:577
+#: lib/who_need_help_web/live/request_live/index.ex:623
#, elixir-autogen, elixir-format
msgid "Showing %{shown} of %{count} in the list."
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:611
-#: lib/who_need_help_web/live/request_live/index.ex:657
+#: lib/who_need_help_web/live/activity_live/index.ex:625
+#: lib/who_need_help_web/live/request_live/index.ex:671
#, elixir-autogen, elixir-format
msgid "Update results as map moves"
msgstr ""
-#: lib/who_need_help_web/live/activity_live/index.ex:585
-#: lib/who_need_help_web/live/request_live/index.ex:631
+#: lib/who_need_help_web/live/activity_live/index.ex:599
+#: lib/who_need_help_web/live/request_live/index.ex:645
#, elixir-autogen, elixir-format
msgid "Updating results for the visible map area…"
msgstr ""
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:11
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:12
#, elixir-autogen, elixir-format
msgid "EMAIL CONFIRMATION REQUIRED"
msgstr ""
@@ -6626,7 +6627,7 @@ msgstr ""
msgid "Pending email verification"
msgstr ""
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:25
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:34
#, elixir-autogen, elixir-format
msgid "We saved pending request %{reference}. It is not in the support queue yet. Open the private link sent to the contact email to verify the address and submit the request for review."
msgstr ""
@@ -7690,7 +7691,7 @@ msgstr ""
msgid "Your email was confirmed and the notice was sent for review."
msgstr ""
-#: lib/who_need_help_web/controllers/support_controller.ex:122
+#: lib/who_need_help_web/controllers/support_controller.ex:128
#, elixir-autogen, elixir-format
msgid "Your email was confirmed and the request was sent to support."
msgstr ""
@@ -7888,7 +7889,7 @@ msgstr ""
msgid "Confirm content-removal notice"
msgstr ""
-#: lib/who_need_help_web/controllers/support_controller.ex:106
+#: lib/who_need_help_web/controllers/support_controller.ex:112
#: lib/who_need_help_web/controllers/support_html/verify.html.heex:9
#: lib/who_need_help_web/controllers/support_html/verify.html.heex:23
#, elixir-autogen, elixir-format
@@ -7914,3 +7915,13 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "If you did not submit this request, close this page. Nothing will be sent to support."
msgstr ""
+
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:29
+#, elixir-autogen, elixir-format
+msgid "If that address belongs to a Who Need Help account, we sent a private confirmation link to the account email. No support request is created until that link is confirmed."
+msgstr ""
+
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:11
+#, elixir-autogen, elixir-format
+msgid "REQUEST CHECKED"
+msgstr ""
diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po
index 1d4fdaf..b94deb7 100644
--- a/priv/gettext/en/LC_MESSAGES/default.po
+++ b/priv/gettext/en/LC_MESSAGES/default.po
@@ -252,7 +252,7 @@ msgstr "Activity published. You approve every participant."
msgid "Activity restored."
msgstr "Activity restored."
-#: lib/who_need_help_web/live/activity_live/index.ex:504
+#: lib/who_need_help_web/live/activity_live/index.ex:536
#: lib/who_need_help_web/live/activity_live/new.ex:204
#, elixir-autogen, elixir-format
msgid "Activity type"
@@ -270,12 +270,12 @@ msgstr "Add unverified link"
msgid "Administrator"
msgstr "Administrator"
-#: lib/who_need_help_web/live/activity_live/index.ex:506
+#: lib/who_need_help_web/live/activity_live/index.ex:538
#, elixir-autogen, elixir-format
msgid "All activities"
msgstr "All activities"
-#: lib/who_need_help_web/live/request_live/index.ex:539
+#: lib/who_need_help_web/live/request_live/index.ex:572
#, elixir-autogen, elixir-format
msgid "All categories"
msgstr "All categories"
@@ -290,7 +290,7 @@ msgstr "Already registered?"
msgid "Another helper already accepted this request."
msgstr "Another helper already accepted this request."
-#: lib/who_need_help_web/live/request_live/index.ex:554
+#: lib/who_need_help_web/live/request_live/index.ex:587
#, elixir-autogen, elixir-format
msgid "Any urgency"
msgstr "Any urgency"
@@ -405,16 +405,16 @@ msgstr "Cancel activity"
msgid "Cancel request"
msgstr "Cancel request"
-#: lib/who_need_help_web/live/activity_live/index.ex:435
+#: lib/who_need_help_web/live/activity_live/index.ex:467
#: lib/who_need_help_web/live/activity_live/show.ex:344
-#: lib/who_need_help_web/live/request_live/index.ex:459
-#: lib/who_need_help_web/live/request_live/index.ex:460
+#: lib/who_need_help_web/live/request_live/index.ex:492
+#: lib/who_need_help_web/live/request_live/index.ex:493
#: lib/who_need_help_web/live/request_live/show.ex:1005
#, elixir-autogen, elixir-format
msgid "Cancelled"
msgstr "Cancelled"
-#: lib/who_need_help_web/live/request_live/index.ex:537
+#: lib/who_need_help_web/live/request_live/index.ex:570
#: lib/who_need_help_web/live/request_live/new.ex:302
#, elixir-autogen, elixir-format
msgid "Category"
@@ -494,7 +494,7 @@ msgstr "Close"
msgid "Coffee and conversation"
msgstr "Coffee and conversation"
-#: lib/who_need_help_web/live/activity_live/index.ex:449
+#: lib/who_need_help_web/live/activity_live/index.ex:481
#, elixir-autogen, elixir-format
msgid "Coffee, cinema, walks, and hikes are separate from urgent help and never affect helper ratings."
msgstr "Coffee, cinema, walks, and hikes are separate from urgent help and never affect helper ratings."
@@ -509,19 +509,19 @@ msgstr "Comment (optional)"
msgid "Community helpers"
msgstr "Community helpers"
-#: lib/who_need_help_web/live/activity_live/index.ex:697
+#: lib/who_need_help_web/live/activity_live/index.ex:729
#: lib/who_need_help_web/live/activity_live/show.ex:403
#: lib/who_need_help_web/live/activity_live/show.ex:639
#: lib/who_need_help_web/live/activity_live/show.ex:698
-#: lib/who_need_help_web/live/request_live/index.ex:737
+#: lib/who_need_help_web/live/request_live/index.ex:770
#, elixir-autogen, elixir-format
msgid "Community member"
msgstr "Community member"
-#: lib/who_need_help_web/live/activity_live/index.ex:434
+#: lib/who_need_help_web/live/activity_live/index.ex:466
#: lib/who_need_help_web/live/activity_live/show.ex:343
-#: lib/who_need_help_web/live/request_live/index.ex:457
-#: lib/who_need_help_web/live/request_live/index.ex:458
+#: lib/who_need_help_web/live/request_live/index.ex:490
+#: lib/who_need_help_web/live/request_live/index.ex:491
#: lib/who_need_help_web/live/request_live/show.ex:1004
#: lib/who_need_help_web/live/request_live/show.ex:1023
#, elixir-autogen, elixir-format
@@ -569,13 +569,13 @@ msgstr "Confirm your email and ensure your account is active before publishing."
msgid "Confirming..."
msgstr "Confirming..."
-#: lib/who_need_help_web/live/activity_live/index.ex:455
+#: lib/who_need_help_web/live/activity_live/index.ex:487
#: lib/who_need_help_web/live/activity_live/new.ex:12
#, elixir-autogen, elixir-format
msgid "Create activity"
msgstr "Create activity"
-#: lib/who_need_help_web/live/activity_live/index.ex:772
+#: lib/who_need_help_web/live/activity_live/index.ex:804
#: lib/who_need_help_web/live/activity_live/new.ex:180
#, elixir-autogen, elixir-format
msgid "Create an activity"
@@ -586,7 +586,7 @@ msgstr "Create an activity"
msgid "Create category"
msgstr "Create category"
-#: lib/who_need_help_web/live/request_live/index.ex:488
+#: lib/who_need_help_web/live/request_live/index.ex:521
#, elixir-autogen, elixir-format
msgid "Create urgent request"
msgstr "Create urgent request"
@@ -731,7 +731,7 @@ msgstr "Expires:"
msgid "Explain what is already arranged and what the volunteer needs to do."
msgstr "Explain what is already arranged and what the volunteer needs to do."
-#: lib/who_need_help_web/live/activity_live/index.ex:446
+#: lib/who_need_help_web/live/activity_live/index.ex:478
#, elixir-autogen, elixir-format
msgid "Find people to join"
msgstr "Find people to join"
@@ -878,8 +878,8 @@ msgstr "Impersonation"
msgid "In a browser this works only while the page remains open. The Android app shows a persistent notification and can continue after it is minimized. Raw coordinates are removed when sharing stops."
msgstr "In a browser this works only while the page remains open. The Android app shows a persistent notification and can continue after it is minimized. Raw coordinates are removed when sharing stops."
-#: lib/who_need_help_web/live/request_live/index.ex:455
-#: lib/who_need_help_web/live/request_live/index.ex:456
+#: lib/who_need_help_web/live/request_live/index.ex:488
+#: lib/who_need_help_web/live/request_live/index.ex:489
#: lib/who_need_help_web/live/request_live/show.ex:1003
#, elixir-autogen, elixir-format
msgid "In progress"
@@ -997,8 +997,8 @@ msgstr "Mark completed"
msgid "Match and coordinate"
msgstr "Match and coordinate"
-#: lib/who_need_help_web/live/request_live/index.ex:453
-#: lib/who_need_help_web/live/request_live/index.ex:454
+#: lib/who_need_help_web/live/request_live/index.ex:486
+#: lib/who_need_help_web/live/request_live/index.ex:487
#: lib/who_need_help_web/live/request_live/show.ex:1001
#, elixir-autogen, elixir-format
msgid "Matched"
@@ -1024,7 +1024,7 @@ msgstr "Medicine pickup remains the first priority. Roadside categories use the
msgid "Medicine requests"
msgstr "Medicine requests"
-#: lib/who_need_help_web/live/activity_live/index.ex:462
+#: lib/who_need_help_web/live/activity_live/index.ex:494
#, elixir-autogen, elixir-format
msgid "Meet in public first, keep exact details inside the approved group, and leave or report anything that feels unsafe."
msgstr "Meet in public first, keep exact details inside the approved group, and leave or report anything that feels unsafe."
@@ -1071,7 +1071,7 @@ msgstr "Moderator"
msgid "Moderator access is required."
msgstr "Moderator access is required."
-#: lib/who_need_help_web/live/request_live/index.ex:479
+#: lib/who_need_help_web/live/request_live/index.ex:512
#, elixir-autogen, elixir-format
msgid "NEARBY MUTUAL AID"
msgstr "NEARBY MUTUAL AID"
@@ -1147,9 +1147,9 @@ msgid "Not for emergencies"
msgstr "Not for emergencies"
#: lib/who_need_help_web/live/notification_live.ex:736
-#: lib/who_need_help_web/live/request_live/index.ex:443
-#: lib/who_need_help_web/live/request_live/index.ex:444
-#: lib/who_need_help_web/live/request_live/index.ex:555
+#: lib/who_need_help_web/live/request_live/index.ex:476
+#: lib/who_need_help_web/live/request_live/index.ex:477
+#: lib/who_need_help_web/live/request_live/index.ex:588
#: lib/who_need_help_web/live/request_live/new.ex:449
#: lib/who_need_help_web/live/request_live/show.ex:992
#: lib/who_need_help_web/live/request_live/show.ex:993
@@ -1157,7 +1157,7 @@ msgstr "Not for emergencies"
msgid "Now"
msgstr "Now"
-#: lib/who_need_help_web/live/activity_live/index.ex:444
+#: lib/who_need_help_web/live/activity_live/index.ex:476
#, elixir-autogen, elixir-format
msgid "OPTIONAL SOCIAL PLANS"
msgstr "OPTIONAL SOCIAL PLANS"
@@ -1175,14 +1175,14 @@ msgstr "Only the organizer and approved participants can read or send these mess
#: lib/who_need_help/notifications/text.ex:140
#: lib/who_need_help_web/controllers/content_removal_html.ex:21
#: lib/who_need_help_web/controllers/support_html.ex:17
-#: lib/who_need_help_web/live/activity_live/index.ex:433
+#: lib/who_need_help_web/live/activity_live/index.ex:465
#: lib/who_need_help_web/live/activity_live/show.ex:342
#: lib/who_need_help_web/live/moderation_live.ex:636
#: lib/who_need_help_web/live/moderation_live.ex:817
#: lib/who_need_help_web/live/moderation_live.ex:941
#: lib/who_need_help_web/live/report_live.ex:104
-#: lib/who_need_help_web/live/request_live/index.ex:451
-#: lib/who_need_help_web/live/request_live/index.ex:452
+#: lib/who_need_help_web/live/request_live/index.ex:484
+#: lib/who_need_help_web/live/request_live/index.ex:485
#: lib/who_need_help_web/live/request_live/show.ex:1000
#: lib/who_need_help_web/live/support_operation_live.ex:310
#: lib/who_need_help_web/live/support_operation_live.ex:414
@@ -1627,9 +1627,9 @@ msgid "Save profile"
msgstr "Save profile"
#: lib/who_need_help_web/live/notification_live.ex:738
-#: lib/who_need_help_web/live/request_live/index.ex:447
-#: lib/who_need_help_web/live/request_live/index.ex:448
-#: lib/who_need_help_web/live/request_live/index.ex:557
+#: lib/who_need_help_web/live/request_live/index.ex:480
+#: lib/who_need_help_web/live/request_live/index.ex:481
+#: lib/who_need_help_web/live/request_live/index.ex:590
#: lib/who_need_help_web/live/request_live/new.ex:451
#: lib/who_need_help_web/live/request_live/show.ex:996
#: lib/who_need_help_web/live/request_live/show.ex:997
@@ -1894,7 +1894,7 @@ msgstr "This activity state has already changed."
msgid "This interaction is blocked."
msgstr "This interaction is blocked."
-#: lib/who_need_help_web/live/request_live/index.ex:495
+#: lib/who_need_help_web/live/request_live/index.ex:528
#: lib/who_need_help_web/live/request_live/show.ex:1071
#, elixir-autogen, elixir-format
msgid "This is not an emergency service. Contact local emergency services for immediate danger."
@@ -1942,9 +1942,9 @@ msgid "To see sent emails, visit"
msgstr "To see sent emails, visit"
#: lib/who_need_help_web/live/notification_live.ex:737
-#: lib/who_need_help_web/live/request_live/index.ex:445
-#: lib/who_need_help_web/live/request_live/index.ex:446
-#: lib/who_need_help_web/live/request_live/index.ex:556
+#: lib/who_need_help_web/live/request_live/index.ex:478
+#: lib/who_need_help_web/live/request_live/index.ex:479
+#: lib/who_need_help_web/live/request_live/index.ex:589
#: lib/who_need_help_web/live/request_live/new.ex:450
#: lib/who_need_help_web/live/request_live/show.ex:994
#: lib/who_need_help_web/live/request_live/show.ex:995
@@ -2014,7 +2014,7 @@ msgid "Unique people"
msgstr "Unique people"
#: lib/who_need_help_web/live/notification_live.ex:731
-#: lib/who_need_help_web/live/request_live/index.ex:552
+#: lib/who_need_help_web/live/request_live/index.ex:585
#: lib/who_need_help_web/live/request_live/new.ex:447
#, elixir-autogen, elixir-format
msgid "Urgency"
@@ -2147,7 +2147,7 @@ msgstr "Who Need Help home"
msgid "Who Need Help was developed with the local Codex CLI authenticated through the builder's ChatGPT subscription. No OpenAI API key or usage-based API fallback is used."
msgstr "Who Need Help was developed with the local Codex CLI authenticated through the builder's ChatGPT subscription. No OpenAI API key or usage-based API fallback is used."
-#: lib/who_need_help_web/live/request_live/index.ex:480
+#: lib/who_need_help_web/live/request_live/index.ex:513
#, elixir-autogen, elixir-format
msgid "Who needs help?"
msgstr "Who needs help?"
@@ -2261,7 +2261,7 @@ msgstr "Your requests"
msgid "Your review is revealed only after both participants submit."
msgstr "Your review is revealed only after both participants submit."
-#: lib/who_need_help_web/live/request_live/index.ex:805
+#: lib/who_need_help_web/live/request_live/index.ex:838
#, elixir-autogen, elixir-format
msgid "Your trust summary"
msgstr "Your trust summary"
@@ -2272,16 +2272,16 @@ msgstr "Your trust summary"
msgid "before confirming."
msgstr "before confirming."
-#: lib/who_need_help_web/live/activity_live/index.ex:696
+#: lib/who_need_help_web/live/activity_live/index.ex:728
#: lib/who_need_help_web/live/category_proposal_live.ex:252
-#: lib/who_need_help_web/live/request_live/index.ex:736
+#: lib/who_need_help_web/live/request_live/index.ex:769
#, elixir-autogen, elixir-format
msgid "by %{name}"
msgstr "by %{name}"
#: lib/who_need_help_web/live/profile_live.ex:190
#: lib/who_need_help_web/live/public_profile_live.ex:133
-#: lib/who_need_help_web/live/request_live/index.ex:809
+#: lib/who_need_help_web/live/request_live/index.ex:842
#: lib/who_need_help_web/live/request_live/show.ex:1478
#, elixir-autogen, elixir-format
msgid "completed"
@@ -2317,7 +2317,7 @@ msgstr "organizer"
msgid "participants only"
msgstr "participants only"
-#: lib/who_need_help_web/live/request_live/index.ex:813
+#: lib/who_need_help_web/live/request_live/index.ex:846
#: lib/who_need_help_web/live/request_live/show.ex:1482
#, elixir-autogen, elixir-format
msgid "people"
@@ -2377,7 +2377,7 @@ msgstr "urgent"
#: lib/who_need_help_web/live/activity_live/show.ex:719
#: lib/who_need_help_web/live/profile_live.ex:285
#: lib/who_need_help_web/live/public_profile_live.ex:98
-#: lib/who_need_help_web/live/request_live/index.ex:817
+#: lib/who_need_help_web/live/request_live/index.ex:850
#: lib/who_need_help_web/live/request_live/show.ex:1142
#: lib/who_need_help_web/live/request_live/show.ex:1580
#, elixir-autogen, elixir-format
@@ -2410,8 +2410,8 @@ msgstr "← Requests"
msgid "Approved"
msgstr "Approved"
-#: lib/who_need_help_web/live/request_live/index.ex:461
-#: lib/who_need_help_web/live/request_live/index.ex:462
+#: lib/who_need_help_web/live/request_live/index.ex:494
+#: lib/who_need_help_web/live/request_live/index.ex:495
#, elixir-autogen, elixir-format
msgid "Expired"
msgstr "Expired"
@@ -2465,21 +2465,21 @@ msgstr "Unusual action rate"
msgid "Load earlier messages"
msgstr "Load earlier messages"
-#: lib/who_need_help_web/live/activity_live/index.ex:708
-#: lib/who_need_help_web/live/activity_live/index.ex:801
+#: lib/who_need_help_web/live/activity_live/index.ex:740
+#: lib/who_need_help_web/live/activity_live/index.ex:833
#: lib/who_need_help_web/live/category_proposal_live.ex:284
#: lib/who_need_help_web/live/leaderboard_live.ex:91
#: lib/who_need_help_web/live/profile_live.ex:233
#: lib/who_need_help_web/live/profile_live.ex:363
#: lib/who_need_help_web/live/public_profile_live.ex:178
#: lib/who_need_help_web/live/report_live.ex:98
-#: lib/who_need_help_web/live/request_live/index.ex:886
+#: lib/who_need_help_web/live/request_live/index.ex:919
#, elixir-autogen, elixir-format
msgid "Load more"
msgstr "Load more"
#: lib/who_need_help_web/live/activity_live/show.ex:544
-#: lib/who_need_help_web/live/request_live/index.ex:782
+#: lib/who_need_help_web/live/request_live/index.ex:815
#: lib/who_need_help_web/live/request_live/show.ex:1324
#, elixir-autogen, elixir-format
msgid "The map is unavailable in this browser. Request details remain usable."
@@ -2847,7 +2847,7 @@ msgstr "Appeal a moderation decision"
msgid "Back to removal form"
msgstr "Back to removal form"
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:34
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:43
#: lib/who_need_help_web/controllers/support_html/verify.html.heex:30
#, elixir-autogen, elixir-format
msgid "Back to support"
@@ -2859,7 +2859,8 @@ msgstr "Back to support"
msgid "CONTENT REMOVAL"
msgstr "CONTENT REMOVAL"
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:16
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:18
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:19
#, elixir-autogen, elixir-format
msgid "Check your email"
msgstr "Check your email"
@@ -3139,7 +3140,7 @@ msgstr "Report illegal or harmful content"
msgid "Reported locations"
msgstr "Reported locations"
-#: lib/who_need_help_web/controllers/support_controller.ex:241
+#: lib/who_need_help_web/controllers/support_controller.ex:267
#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:6
#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:42
#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:128
@@ -3290,8 +3291,8 @@ msgstr "Threat to life or safety"
msgid "Too many removal notices. Please try again later."
msgstr "Too many removal notices. Please try again later."
-#: lib/who_need_help_web/controllers/support_controller.ex:42
-#: lib/who_need_help_web/controllers/support_controller.ex:84
+#: lib/who_need_help_web/controllers/support_controller.ex:45
+#: lib/who_need_help_web/controllers/support_controller.ex:90
#, elixir-autogen, elixir-format
msgid "Too many support requests. Please try again later."
msgstr "Too many support requests. Please try again later."
@@ -3373,13 +3374,13 @@ msgstr "Your reference is %{reference}"
msgid "Your relationship to the affected person or rights holder"
msgstr "Your relationship to the affected person or rights holder"
-#: lib/who_need_help_web/controllers/support_controller.ex:55
-#: lib/who_need_help_web/controllers/support_controller.ex:66
+#: lib/who_need_help_web/controllers/support_controller.ex:58
+#: lib/who_need_help_web/controllers/support_controller.ex:69
#, elixir-autogen, elixir-format
msgid "Delete my Who Need Help account"
msgstr "Delete my Who Need Help account"
-#: lib/who_need_help_web/controllers/support_controller.ex:56
+#: lib/who_need_help_web/controllers/support_controller.ex:59
#, elixir-autogen, elixir-format
msgid "Please delete my account and associated personal data."
msgstr "Please delete my account and associated personal data."
@@ -4316,7 +4317,7 @@ msgid "View my notices"
msgstr "View my notices"
#: lib/who_need_help_web/controllers/support_html/new.html.heex:13
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:32
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:41
#, elixir-autogen, elixir-format
msgid "View my requests"
msgstr "View my requests"
@@ -4431,37 +4432,37 @@ msgstr "Drag to move the exact point"
msgid "Exact point selected. Drag the red pin or click the map to move it."
msgstr "Exact point selected. Drag the red pin or click the map to move it."
-#: lib/who_need_help_web/live/activity_live/index.ex:515
-#: lib/who_need_help_web/live/request_live/index.ex:562
+#: lib/who_need_help_web/live/activity_live/index.ex:547
+#: lib/who_need_help_web/live/request_live/index.ex:595
#, elixir-autogen, elixir-format
msgid "Area or city"
msgstr "Area or city"
-#: lib/who_need_help_web/live/activity_live/index.ex:638
-#: lib/who_need_help_web/live/request_live/index.ex:683
+#: lib/who_need_help_web/live/activity_live/index.ex:670
+#: lib/who_need_help_web/live/request_live/index.ex:716
#, elixir-autogen, elixir-format
msgid "Choose an area on the map"
msgstr "Choose an area on the map"
-#: lib/who_need_help_web/live/request_live/index.ex:482
+#: lib/who_need_help_web/live/request_live/index.ex:515
#, elixir-autogen, elixir-format
msgid "Explore privacy-friendly areas and public exact meeting points chosen by requesters."
msgstr "Explore privacy-friendly areas and public exact meeting points chosen by requesters."
-#: lib/who_need_help_web/live/activity_live/index.ex:605
-#: lib/who_need_help_web/live/request_live/index.ex:650
+#: lib/who_need_help_web/live/activity_live/index.ex:637
+#: lib/who_need_help_web/live/request_live/index.ex:683
#, elixir-autogen, elixir-format
msgid "List"
msgstr "List"
-#: lib/who_need_help_web/live/request_live/index.ex:758
+#: lib/who_need_help_web/live/request_live/index.ex:791
#, elixir-autogen, elixir-format
msgid "Load more in this area"
msgstr "Load more in this area"
#: lib/who_need_help_web/components/layouts/root.html.heex:5
-#: lib/who_need_help_web/live/activity_live/index.ex:625
-#: lib/who_need_help_web/live/request_live/index.ex:670
+#: lib/who_need_help_web/live/activity_live/index.ex:657
+#: lib/who_need_help_web/live/request_live/index.ex:703
#, elixir-autogen, elixir-format
msgid "Map"
msgstr "Map"
@@ -4471,41 +4472,41 @@ msgstr "Map"
msgid "Map preview"
msgstr "Map preview"
-#: lib/who_need_help_web/live/activity_live/index.ex:654
-#: lib/who_need_help_web/live/request_live/index.ex:699
+#: lib/who_need_help_web/live/activity_live/index.ex:686
+#: lib/who_need_help_web/live/request_live/index.ex:732
#, elixir-autogen, elixir-format
msgid "Move or zoom the map to search somewhere else."
msgstr "Move or zoom the map to search somewhere else."
-#: lib/who_need_help_web/live/request_live/index.ex:696
+#: lib/who_need_help_web/live/request_live/index.ex:729
#, elixir-autogen, elixir-format
msgid "No open requests in this area"
msgstr "No open requests in this area"
-#: lib/who_need_help_web/live/request_live/index.ex:685
+#: lib/who_need_help_web/live/request_live/index.ex:718
#, elixir-autogen, elixir-format
msgid "Only requests from the selected map area will be loaded."
msgstr "Only requests from the selected map area will be loaded."
#: lib/who_need_help_web/components/layouts/root.html.heex:8
-#: lib/who_need_help_web/live/request_live/index.ex:785
+#: lib/who_need_help_web/live/request_live/index.ex:818
#, elixir-autogen, elixir-format
msgid "Open request"
msgstr "Open request"
-#: lib/who_need_help_web/live/activity_live/index.ex:516
-#: lib/who_need_help_web/live/request_live/index.ex:563
+#: lib/who_need_help_web/live/activity_live/index.ex:548
+#: lib/who_need_help_web/live/request_live/index.ex:596
#, elixir-autogen, elixir-format
msgid "Optional text search"
msgstr "Optional text search"
-#: lib/who_need_help_web/live/request_live/index.ex:764
+#: lib/who_need_help_web/live/request_live/index.ex:797
#, elixir-autogen, elixir-format
msgid "Requests without map coordinates appear only when their public area description matches the text search."
msgstr "Requests without map coordinates appear only when their public area description matches the text search."
-#: lib/who_need_help_web/live/activity_live/index.ex:596
-#: lib/who_need_help_web/live/request_live/index.ex:641
+#: lib/who_need_help_web/live/activity_live/index.ex:628
+#: lib/who_need_help_web/live/request_live/index.ex:674
#, elixir-autogen, elixir-format
msgid "Results layout"
msgstr "Results layout"
@@ -4515,21 +4516,21 @@ msgstr "Results layout"
msgid "Static preview"
msgstr "Static preview"
-#: lib/who_need_help_web/live/activity_live/index.ex:615
-#: lib/who_need_help_web/live/request_live/index.ex:660
+#: lib/who_need_help_web/live/activity_live/index.ex:647
+#: lib/who_need_help_web/live/request_live/index.ex:693
#, elixir-autogen, elixir-format
msgid "Together"
msgstr "Together"
#: lib/who_need_help_web/components/layouts/root.html.heex:6
#: lib/who_need_help_web/controllers/page_html/home.html.heex:166
-#: lib/who_need_help_web/live/activity_live/index.ex:731
+#: lib/who_need_help_web/live/activity_live/index.ex:763
#: lib/who_need_help_web/live/activity_live/new.ex:355
#: lib/who_need_help_web/live/activity_live/new.ex:414
#: lib/who_need_help_web/live/activity_live/show.ex:546
#: lib/who_need_help_web/live/notification_live.ex:590
#: lib/who_need_help_web/live/notification_live.ex:609
-#: lib/who_need_help_web/live/request_live/index.ex:784
+#: lib/who_need_help_web/live/request_live/index.ex:817
#: lib/who_need_help_web/live/request_live/new.ex:485
#: lib/who_need_help_web/live/request_live/new.ex:563
#: lib/who_need_help_web/live/request_live/show.ex:1326
@@ -4886,7 +4887,7 @@ msgid "Nearby alert deleted."
msgstr "Nearby alert deleted."
#: lib/who_need_help_web/live/notification_live.ex:453
-#: lib/who_need_help_web/live/request_live/index.ex:508
+#: lib/who_need_help_web/live/request_live/index.ex:541
#, elixir-autogen, elixir-format
msgid "Nearby requests"
msgstr "Nearby requests"
@@ -5209,7 +5210,7 @@ msgid "WebGL is unavailable in this browser, so the full map cannot start. Selec
msgstr "WebGL is unavailable in this browser, so the full map cannot start. Select an example below or retry the map."
#: lib/who_need_help_web/components/layouts/root.html.heex:7
-#: lib/who_need_help_web/live/activity_live/index.ex:732
+#: lib/who_need_help_web/live/activity_live/index.ex:764
#: lib/who_need_help_web/live/activity_live/show.ex:547
#: lib/who_need_help_web/live/request_live/show.ex:1327
#, elixir-autogen, elixir-format
@@ -5250,7 +5251,7 @@ msgid "Case status"
msgstr "Case status"
#: lib/who_need_help_web/controllers/page_html/child_safety.html.heex:42
-#: lib/who_need_help_web/controllers/support_controller.ex:242
+#: lib/who_need_help_web/controllers/support_controller.ex:268
#: lib/who_need_help_web/live/report_live.ex:50
#, elixir-autogen, elixir-format
msgid "Contact support"
@@ -5272,8 +5273,8 @@ msgstr "Content-removal notice received"
msgid "Conversation"
msgstr "Conversation"
-#: lib/who_need_help_web/controllers/support_controller.ex:152
-#: lib/who_need_help_web/controllers/support_controller.ex:168
+#: lib/who_need_help_web/controllers/support_controller.ex:158
+#: lib/who_need_help_web/controllers/support_controller.ex:174
#: lib/who_need_help_web/live/support_case_live.ex:73
#, elixir-autogen, elixir-format
msgid "Could not send your message. Please check it and try again."
@@ -5336,7 +5337,7 @@ msgstr "No reports submitted"
msgid "No status history is available for this older case."
msgstr "No status history is available for this older case."
-#: lib/who_need_help_web/controllers/support_controller.ex:187
+#: lib/who_need_help_web/controllers/support_controller.ex:193
#: lib/who_need_help_web/live/support_case_live.ex:95
#, elixir-autogen, elixir-format
msgid "Only a resolved or closed request can be reopened."
@@ -5441,12 +5442,12 @@ msgstr "Send message"
msgid "Status history"
msgstr "Status history"
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:15
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:17
#, elixir-autogen, elixir-format
msgid "Support request created"
msgstr "Support request created"
-#: lib/who_need_help_web/controllers/support_controller.ex:198
+#: lib/who_need_help_web/controllers/support_controller.ex:214
#, elixir-autogen, elixir-format
msgid "Support request received"
msgstr "Support request received"
@@ -5482,7 +5483,7 @@ msgstr "TRUST AND SAFETY"
msgid "The requester states whether the medicine is paid, reserved, or still needs to be purchased."
msgstr "The requester states whether the medicine is paid, reserved, or still needs to be purchased."
-#: lib/who_need_help_web/controllers/support_controller.ex:182
+#: lib/who_need_help_web/controllers/support_controller.ex:188
#: lib/who_need_help_web/live/support_case_live.ex:88
#, elixir-autogen, elixir-format
msgid "The support request is open again."
@@ -5510,7 +5511,7 @@ msgstr "This private history shows reports you submitted and the latest moderati
msgid "This support request is no longer available."
msgstr "This support request is no longer available."
-#: lib/who_need_help_web/controllers/support_controller.ex:158
+#: lib/who_need_help_web/controllers/support_controller.ex:164
#: lib/who_need_help_web/live/support_case_live.ex:60
#, elixir-autogen, elixir-format
msgid "Too many messages. Please try again later."
@@ -5521,7 +5522,7 @@ msgstr "Too many messages. Please try again later."
msgid "Use the dedicated urgent process. Never upload or reproduce the image or video."
msgstr "Use the dedicated urgent process. Never upload or reproduce the image or video."
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:19
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:23
#, elixir-autogen, elixir-format
msgid "We created support request %{reference} and added it to your account. You can open it now and return to it from My support requests."
msgstr "We created support request %{reference} and added it to your account. You can open it now and return to it from My support requests."
@@ -5547,7 +5548,7 @@ msgstr "You"
msgid "Your content-removal notices"
msgstr "Your content-removal notices"
-#: lib/who_need_help_web/controllers/support_controller.ex:145
+#: lib/who_need_help_web/controllers/support_controller.ex:151
#: lib/who_need_help_web/live/support_case_live.ex:53
#, elixir-autogen, elixir-format
msgid "Your message was sent to support."
@@ -5711,7 +5712,7 @@ msgstr "Your review is now visible"
msgid "Delivery settings"
msgstr "Delivery settings"
-#: lib/who_need_help_web/live/request_live/index.ex:741
+#: lib/who_need_help_web/live/request_live/index.ex:774
#, elixir-autogen, elixir-format
msgid "Expires"
msgstr "Expires"
@@ -5731,22 +5732,22 @@ msgstr "Nearby alerts"
msgid "Notification sections"
msgstr "Notification sections"
-#: lib/who_need_help_web/live/activity_live/index.ex:724
+#: lib/who_need_help_web/live/activity_live/index.ex:756
#, elixir-autogen, elixir-format
msgid "%{count} activities"
msgstr "%{count} activities"
-#: lib/who_need_help_web/live/request_live/index.ex:786
+#: lib/who_need_help_web/live/request_live/index.ex:819
#, elixir-autogen, elixir-format
msgid "%{count} requests"
msgstr "%{count} requests"
-#: lib/who_need_help_web/live/activity_live/index.ex:767
+#: lib/who_need_help_web/live/activity_live/index.ex:799
#, elixir-autogen, elixir-format
msgid "Activities you create or ask to join will appear here. Leaving removes an activity from this list."
msgstr "Activities you create or ask to join will appear here. Leaving removes an activity from this list."
-#: lib/who_need_help_web/live/activity_live/index.ex:470
+#: lib/who_need_help_web/live/activity_live/index.ex:502
#, elixir-autogen, elixir-format
msgid "Activity views"
msgstr "Activity views"
@@ -5761,12 +5762,12 @@ msgstr "Choose a category and describe the task clearly."
msgid "Confirm the safety guidance before asking nearby people for help."
msgstr "Confirm the safety guidance before asking nearby people for help."
-#: lib/who_need_help_web/live/request_live/index.ex:834
+#: lib/who_need_help_web/live/request_live/index.ex:867
#, elixir-autogen, elixir-format
msgid "Create request"
msgstr "Create request"
-#: lib/who_need_help_web/live/request_live/index.ex:857
+#: lib/who_need_help_web/live/request_live/index.ex:890
#, elixir-autogen, elixir-format
msgid "Create your first request"
msgstr "Create your first request"
@@ -5781,43 +5782,43 @@ msgstr "Help details"
msgid "Location"
msgstr "Location"
-#: lib/who_need_help_web/live/activity_live/index.ex:476
+#: lib/who_need_help_web/live/activity_live/index.ex:508
#, elixir-autogen, elixir-format
msgid "My activities"
msgstr "My activities"
-#: lib/who_need_help_web/live/request_live/index.ex:509
-#: lib/who_need_help_web/live/request_live/index.ex:828
+#: lib/who_need_help_web/live/request_live/index.ex:542
+#: lib/who_need_help_web/live/request_live/index.ex:861
#, elixir-autogen, elixir-format
msgid "My requests"
msgstr "My requests"
-#: lib/who_need_help_web/live/activity_live/index.ex:475
+#: lib/who_need_help_web/live/activity_live/index.ex:507
#, elixir-autogen, elixir-format
msgid "Nearby activities"
msgstr "Nearby activities"
-#: lib/who_need_help_web/live/activity_live/index.ex:765
+#: lib/who_need_help_web/live/activity_live/index.ex:797
#, elixir-autogen, elixir-format
msgid "No activities yet"
msgstr "No activities yet"
-#: lib/who_need_help_web/live/activity_live/index.ex:651
+#: lib/who_need_help_web/live/activity_live/index.ex:683
#, elixir-autogen, elixir-format
msgid "No open activities in this area"
msgstr "No open activities in this area"
-#: lib/who_need_help_web/live/request_live/index.ex:852
+#: lib/who_need_help_web/live/request_live/index.ex:885
#, elixir-autogen, elixir-format
msgid "No requests yet"
msgstr "No requests yet"
-#: lib/who_need_help_web/live/activity_live/index.ex:640
+#: lib/who_need_help_web/live/activity_live/index.ex:672
#, elixir-autogen, elixir-format
msgid "Only activities from the selected map area will be loaded."
msgstr "Only activities from the selected map area will be loaded."
-#: lib/who_need_help_web/live/activity_live/index.ex:723
+#: lib/who_need_help_web/live/activity_live/index.ex:755
#, elixir-autogen, elixir-format
msgid "Open activity"
msgstr "Open activity"
@@ -5827,12 +5828,12 @@ msgstr "Open activity"
msgid "Request form steps"
msgstr "Request form steps"
-#: lib/who_need_help_web/live/request_live/index.ex:503
+#: lib/who_need_help_web/live/request_live/index.ex:536
#, elixir-autogen, elixir-format
msgid "Request views"
msgstr "Request views"
-#: lib/who_need_help_web/live/request_live/index.ex:854
+#: lib/who_need_help_web/live/request_live/index.ex:887
#, elixir-autogen, elixir-format
msgid "Requests you publish or accept will appear here."
msgstr "Requests you publish or accept will appear here."
@@ -5857,7 +5858,7 @@ msgstr "Set urgency and when this request should close."
msgid "Share only as much location detail as helpers need."
msgstr "Share only as much location detail as helpers need."
-#: lib/who_need_help_web/live/activity_live/index.ex:729
+#: lib/who_need_help_web/live/activity_live/index.ex:761
#, elixir-autogen, elixir-format
msgid "The map is unavailable in this browser. Activity details remain usable."
msgstr "The map is unavailable in this browser. Activity details remain usable."
@@ -5867,7 +5868,7 @@ msgstr "The map is unavailable in this browser. Activity details remain usable."
msgid "Timing"
msgstr "Timing"
-#: lib/who_need_help_web/live/request_live/index.ex:830
+#: lib/who_need_help_web/live/request_live/index.ex:863
#, elixir-autogen, elixir-format
msgid "Track requests you created and requests you accepted as a helper."
msgstr "Track requests you created and requests you accepted as a helper."
@@ -6522,91 +6523,91 @@ msgstr "What should people see before approval?"
msgid "Choose the meeting point on the map or use your current location."
msgstr "Choose the meeting point on the map or use your current location."
-#: lib/who_need_help_web/live/activity_live/index.ex:555
-#: lib/who_need_help_web/live/activity_live/index.ex:650
-#: lib/who_need_help_web/live/activity_live/index.ex:757
-#: lib/who_need_help_web/live/request_live/index.ex:600
-#: lib/who_need_help_web/live/request_live/index.ex:695
-#: lib/who_need_help_web/live/request_live/index.ex:844
+#: lib/who_need_help_web/live/activity_live/index.ex:587
+#: lib/who_need_help_web/live/activity_live/index.ex:682
+#: lib/who_need_help_web/live/activity_live/index.ex:789
+#: lib/who_need_help_web/live/request_live/index.ex:633
+#: lib/who_need_help_web/live/request_live/index.ex:728
+#: lib/who_need_help_web/live/request_live/index.ex:877
#, elixir-autogen, elixir-format
msgid "Loading results…"
msgstr "Loading results…"
-#: lib/who_need_help_web/live/activity_live/index.ex:726
+#: lib/who_need_help_web/live/activity_live/index.ex:758
#, elixir-autogen, elixir-format
msgid "Maximum zoom reached. Matching activities are listed beside the map."
msgstr "Maximum zoom reached. Matching activities are listed beside the map."
-#: lib/who_need_help_web/live/request_live/index.ex:788
+#: lib/who_need_help_web/live/request_live/index.ex:821
#, elixir-autogen, elixir-format
msgid "Maximum zoom reached. Matching requests are listed beside the map."
msgstr "Maximum zoom reached. Matching requests are listed beside the map."
-#: lib/who_need_help_web/live/activity_live/index.ex:536
+#: lib/who_need_help_web/live/activity_live/index.ex:568
#, elixir-autogen, elixir-format
msgid "%{count} activity in the selected map area"
msgid_plural "%{count} activities in the selected map area"
msgstr[0] "%{count} activity in the selected map area"
msgstr[1] "%{count} activities in the selected map area"
-#: lib/who_need_help_web/live/request_live/index.ex:581
+#: lib/who_need_help_web/live/request_live/index.ex:614
#, elixir-autogen, elixir-format
msgid "%{count} request in the selected map area"
msgid_plural "%{count} requests in the selected map area"
msgstr[0] "%{count} request in the selected map area"
msgstr[1] "%{count} requests in the selected map area"
-#: lib/who_need_help_web/live/activity_live/index.ex:561
-#: lib/who_need_help_web/live/request_live/index.ex:606
+#: lib/who_need_help_web/live/activity_live/index.ex:593
+#: lib/who_need_help_web/live/request_live/index.ex:639
#, elixir-autogen, elixir-format
msgid "Map moved. Results still show the previous area."
msgstr "Map moved. Results still show the previous area."
-#: lib/who_need_help_web/live/activity_live/index.ex:579
+#: lib/who_need_help_web/live/activity_live/index.ex:611
#, elixir-autogen, elixir-format
msgid "Move or zoom the map, then choose “Show activities in this area”."
msgstr "Move or zoom the map, then choose “Show activities in this area”."
-#: lib/who_need_help_web/live/request_live/index.ex:624
+#: lib/who_need_help_web/live/request_live/index.ex:657
#, elixir-autogen, elixir-format
msgid "Move or zoom the map, then choose “Show requests in this area”."
msgstr "Move or zoom the map, then choose “Show requests in this area”."
-#: lib/who_need_help_web/live/activity_live/index.ex:573
-#: lib/who_need_help_web/live/request_live/index.ex:618
+#: lib/who_need_help_web/live/activity_live/index.ex:605
+#: lib/who_need_help_web/live/request_live/index.ex:651
#, elixir-autogen, elixir-format
msgid "Results update automatically after you stop moving the map."
msgstr "Results update automatically after you stop moving the map."
-#: lib/who_need_help_web/live/activity_live/index.ex:743
+#: lib/who_need_help_web/live/activity_live/index.ex:775
#, elixir-autogen, elixir-format
msgid "Show activities in this area"
msgstr "Show activities in this area"
-#: lib/who_need_help_web/live/request_live/index.ex:800
+#: lib/who_need_help_web/live/request_live/index.ex:833
#, elixir-autogen, elixir-format
msgid "Show requests in this area"
msgstr "Show requests in this area"
-#: lib/who_need_help_web/live/activity_live/index.ex:545
-#: lib/who_need_help_web/live/request_live/index.ex:590
+#: lib/who_need_help_web/live/activity_live/index.ex:577
+#: lib/who_need_help_web/live/request_live/index.ex:623
#, elixir-autogen, elixir-format
msgid "Showing %{shown} of %{count} in the list."
msgstr "Showing %{shown} of %{count} in the list."
-#: lib/who_need_help_web/live/activity_live/index.ex:593
-#: lib/who_need_help_web/live/request_live/index.ex:638
+#: lib/who_need_help_web/live/activity_live/index.ex:625
+#: lib/who_need_help_web/live/request_live/index.ex:671
#, elixir-autogen, elixir-format
msgid "Update results as map moves"
msgstr "Update results as map moves"
-#: lib/who_need_help_web/live/activity_live/index.ex:567
-#: lib/who_need_help_web/live/request_live/index.ex:612
+#: lib/who_need_help_web/live/activity_live/index.ex:599
+#: lib/who_need_help_web/live/request_live/index.ex:645
#, elixir-autogen, elixir-format
msgid "Updating results for the visible map area…"
msgstr "Updating results for the visible map area…"
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:11
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:12
#, elixir-autogen, elixir-format
msgid "EMAIL CONFIRMATION REQUIRED"
msgstr "EMAIL CONFIRMATION REQUIRED"
@@ -6626,7 +6627,7 @@ msgstr "Waiting for location permission…"
msgid "Pending email verification"
msgstr "Pending email verification"
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:25
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:34
#, elixir-autogen, elixir-format
msgid "We saved pending request %{reference}. It is not in the support queue yet. Open the private link sent to the contact email to verify the address and submit the request for review."
msgstr "We saved pending request %{reference}. It is not in the support queue yet. Open the private link sent to the contact email to verify the address and submit the request for review."
@@ -7690,7 +7691,7 @@ msgstr "You do not have permission for this action. Role changes also require a
msgid "Your email was confirmed and the notice was sent for review."
msgstr "Your email was confirmed and the notice was sent for review."
-#: lib/who_need_help_web/controllers/support_controller.ex:122
+#: lib/who_need_help_web/controllers/support_controller.ex:128
#, elixir-autogen, elixir-format
msgid "Your email was confirmed and the request was sent to support."
msgstr "Your email was confirmed and the request was sent to support."
@@ -7888,7 +7889,7 @@ msgstr "Copy and paste this secure address into your browser:"
msgid "Confirm content-removal notice"
msgstr "Confirm content-removal notice"
-#: lib/who_need_help_web/controllers/support_controller.ex:106
+#: lib/who_need_help_web/controllers/support_controller.ex:112
#: lib/who_need_help_web/controllers/support_html/verify.html.heex:9
#: lib/who_need_help_web/controllers/support_html/verify.html.heex:23
#, elixir-autogen, elixir-format
@@ -7914,3 +7915,13 @@ msgstr "If you did not submit this notice, close this page. Nothing will be sent
#, elixir-autogen, elixir-format
msgid "If you did not submit this request, close this page. Nothing will be sent to support."
msgstr "If you did not submit this request, close this page. Nothing will be sent to support."
+
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:29
+#, elixir-autogen, elixir-format
+msgid "If that address belongs to a Who Need Help account, we sent a private confirmation link to the account email. No support request is created until that link is confirmed."
+msgstr "If that address belongs to a Who Need Help account, we sent a private confirmation link to the account email. No support request is created until that link is confirmed."
+
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:11
+#, elixir-autogen, elixir-format
+msgid "REQUEST CHECKED"
+msgstr "REQUEST CHECKED"
diff --git a/priv/gettext/ru/LC_MESSAGES/default.po b/priv/gettext/ru/LC_MESSAGES/default.po
index e0ad085..e388850 100644
--- a/priv/gettext/ru/LC_MESSAGES/default.po
+++ b/priv/gettext/ru/LC_MESSAGES/default.po
@@ -262,7 +262,7 @@ msgstr "Активность опубликована. Вы одобряете
msgid "Activity restored."
msgstr "Активность восстановлена."
-#: lib/who_need_help_web/live/activity_live/index.ex:504
+#: lib/who_need_help_web/live/activity_live/index.ex:536
#: lib/who_need_help_web/live/activity_live/new.ex:204
#, elixir-autogen, elixir-format
msgid "Activity type"
@@ -280,12 +280,12 @@ msgstr "Добавить неподтверждённую ссылку"
msgid "Administrator"
msgstr "Администратор"
-#: lib/who_need_help_web/live/activity_live/index.ex:506
+#: lib/who_need_help_web/live/activity_live/index.ex:538
#, elixir-autogen, elixir-format
msgid "All activities"
msgstr "Все активности"
-#: lib/who_need_help_web/live/request_live/index.ex:539
+#: lib/who_need_help_web/live/request_live/index.ex:572
#, elixir-autogen, elixir-format
msgid "All categories"
msgstr "Все категории"
@@ -300,7 +300,7 @@ msgstr "Уже зарегистрированы?"
msgid "Another helper already accepted this request."
msgstr "Другой помощник уже принял эту заявку."
-#: lib/who_need_help_web/live/request_live/index.ex:554
+#: lib/who_need_help_web/live/request_live/index.ex:587
#, elixir-autogen, elixir-format
msgid "Any urgency"
msgstr "Любая срочность"
@@ -420,16 +420,16 @@ msgstr "Отменить активность"
msgid "Cancel request"
msgstr "Отменить заявку"
-#: lib/who_need_help_web/live/activity_live/index.ex:435
+#: lib/who_need_help_web/live/activity_live/index.ex:467
#: lib/who_need_help_web/live/activity_live/show.ex:344
-#: lib/who_need_help_web/live/request_live/index.ex:459
-#: lib/who_need_help_web/live/request_live/index.ex:460
+#: lib/who_need_help_web/live/request_live/index.ex:492
+#: lib/who_need_help_web/live/request_live/index.ex:493
#: lib/who_need_help_web/live/request_live/show.ex:1005
#, elixir-autogen, elixir-format
msgid "Cancelled"
msgstr "Отменено"
-#: lib/who_need_help_web/live/request_live/index.ex:537
+#: lib/who_need_help_web/live/request_live/index.ex:570
#: lib/who_need_help_web/live/request_live/new.ex:302
#, elixir-autogen, elixir-format
msgid "Category"
@@ -510,7 +510,7 @@ msgstr "Закрыть"
msgid "Coffee and conversation"
msgstr "Кофе и разговоры"
-#: lib/who_need_help_web/live/activity_live/index.ex:449
+#: lib/who_need_help_web/live/activity_live/index.ex:481
#, elixir-autogen, elixir-format
msgid "Coffee, cinema, walks, and hikes are separate from urgent help and never affect helper ratings."
msgstr ""
@@ -527,19 +527,19 @@ msgstr "Комментарий (необязательно)"
msgid "Community helpers"
msgstr "Помощники сообщества"
-#: lib/who_need_help_web/live/activity_live/index.ex:697
+#: lib/who_need_help_web/live/activity_live/index.ex:729
#: lib/who_need_help_web/live/activity_live/show.ex:403
#: lib/who_need_help_web/live/activity_live/show.ex:639
#: lib/who_need_help_web/live/activity_live/show.ex:698
-#: lib/who_need_help_web/live/request_live/index.ex:737
+#: lib/who_need_help_web/live/request_live/index.ex:770
#, elixir-autogen, elixir-format
msgid "Community member"
msgstr "Член сообщества"
-#: lib/who_need_help_web/live/activity_live/index.ex:434
+#: lib/who_need_help_web/live/activity_live/index.ex:466
#: lib/who_need_help_web/live/activity_live/show.ex:343
-#: lib/who_need_help_web/live/request_live/index.ex:457
-#: lib/who_need_help_web/live/request_live/index.ex:458
+#: lib/who_need_help_web/live/request_live/index.ex:490
+#: lib/who_need_help_web/live/request_live/index.ex:491
#: lib/who_need_help_web/live/request_live/show.ex:1004
#: lib/who_need_help_web/live/request_live/show.ex:1023
#, elixir-autogen, elixir-format
@@ -589,13 +589,13 @@ msgstr ""
msgid "Confirming..."
msgstr "Подтверждение…"
-#: lib/who_need_help_web/live/activity_live/index.ex:455
+#: lib/who_need_help_web/live/activity_live/index.ex:487
#: lib/who_need_help_web/live/activity_live/new.ex:12
#, elixir-autogen, elixir-format
msgid "Create activity"
msgstr "Создать активность"
-#: lib/who_need_help_web/live/activity_live/index.ex:772
+#: lib/who_need_help_web/live/activity_live/index.ex:804
#: lib/who_need_help_web/live/activity_live/new.ex:180
#, elixir-autogen, elixir-format
msgid "Create an activity"
@@ -606,7 +606,7 @@ msgstr "Создать активность"
msgid "Create category"
msgstr "Создать категорию"
-#: lib/who_need_help_web/live/request_live/index.ex:488
+#: lib/who_need_help_web/live/request_live/index.ex:521
#, elixir-autogen, elixir-format
msgid "Create urgent request"
msgstr "Создать срочную заявку"
@@ -761,7 +761,7 @@ msgstr "Истекает:"
msgid "Explain what is already arranged and what the volunteer needs to do."
msgstr "Объясните, что уже сделано и что нужно сделать волонтеру."
-#: lib/who_need_help_web/live/activity_live/index.ex:446
+#: lib/who_need_help_web/live/activity_live/index.ex:478
#, elixir-autogen, elixir-format
msgid "Find people to join"
msgstr "Найти компанию"
@@ -927,8 +927,8 @@ msgstr ""
"Android показывает постоянное уведомление и может продолжать передачу после "
"сворачивания. При остановке передачи исходные координаты удаляются."
-#: lib/who_need_help_web/live/request_live/index.ex:455
-#: lib/who_need_help_web/live/request_live/index.ex:456
+#: lib/who_need_help_web/live/request_live/index.ex:488
+#: lib/who_need_help_web/live/request_live/index.ex:489
#: lib/who_need_help_web/live/request_live/show.ex:1003
#, elixir-autogen, elixir-format
msgid "In progress"
@@ -1053,8 +1053,8 @@ msgstr "Отметить завершённой"
msgid "Match and coordinate"
msgstr "Найдите помощника и согласуйте детали"
-#: lib/who_need_help_web/live/request_live/index.ex:453
-#: lib/who_need_help_web/live/request_live/index.ex:454
+#: lib/who_need_help_web/live/request_live/index.ex:486
+#: lib/who_need_help_web/live/request_live/index.ex:487
#: lib/who_need_help_web/live/request_live/show.ex:1001
#, elixir-autogen, elixir-format
msgid "Matched"
@@ -1083,7 +1083,7 @@ msgstr ""
msgid "Medicine requests"
msgstr "Заявки на лекарства"
-#: lib/who_need_help_web/live/activity_live/index.ex:462
+#: lib/who_need_help_web/live/activity_live/index.ex:494
#, elixir-autogen, elixir-format
msgid "Meet in public first, keep exact details inside the approved group, and leave or report anything that feels unsafe."
msgstr ""
@@ -1133,7 +1133,7 @@ msgstr "Модератор"
msgid "Moderator access is required."
msgstr "Требуется доступ модератора."
-#: lib/who_need_help_web/live/request_live/index.ex:479
+#: lib/who_need_help_web/live/request_live/index.ex:512
#, elixir-autogen, elixir-format
msgid "NEARBY MUTUAL AID"
msgstr "ВЗАИМОПОМОЩЬ РЯДОМ"
@@ -1211,9 +1211,9 @@ msgid "Not for emergencies"
msgstr "Не для чрезвычайных ситуаций"
#: lib/who_need_help_web/live/notification_live.ex:736
-#: lib/who_need_help_web/live/request_live/index.ex:443
-#: lib/who_need_help_web/live/request_live/index.ex:444
-#: lib/who_need_help_web/live/request_live/index.ex:555
+#: lib/who_need_help_web/live/request_live/index.ex:476
+#: lib/who_need_help_web/live/request_live/index.ex:477
+#: lib/who_need_help_web/live/request_live/index.ex:588
#: lib/who_need_help_web/live/request_live/new.ex:449
#: lib/who_need_help_web/live/request_live/show.ex:992
#: lib/who_need_help_web/live/request_live/show.ex:993
@@ -1221,7 +1221,7 @@ msgstr "Не для чрезвычайных ситуаций"
msgid "Now"
msgstr "Сейчас"
-#: lib/who_need_help_web/live/activity_live/index.ex:444
+#: lib/who_need_help_web/live/activity_live/index.ex:476
#, elixir-autogen, elixir-format
msgid "OPTIONAL SOCIAL PLANS"
msgstr "НЕОБЯЗАТЕЛЬНЫЕ СОВМЕСТНЫЕ ПЛАНЫ"
@@ -1241,14 +1241,14 @@ msgstr ""
#: lib/who_need_help/notifications/text.ex:140
#: lib/who_need_help_web/controllers/content_removal_html.ex:21
#: lib/who_need_help_web/controllers/support_html.ex:17
-#: lib/who_need_help_web/live/activity_live/index.ex:433
+#: lib/who_need_help_web/live/activity_live/index.ex:465
#: lib/who_need_help_web/live/activity_live/show.ex:342
#: lib/who_need_help_web/live/moderation_live.ex:636
#: lib/who_need_help_web/live/moderation_live.ex:817
#: lib/who_need_help_web/live/moderation_live.ex:941
#: lib/who_need_help_web/live/report_live.ex:104
-#: lib/who_need_help_web/live/request_live/index.ex:451
-#: lib/who_need_help_web/live/request_live/index.ex:452
+#: lib/who_need_help_web/live/request_live/index.ex:484
+#: lib/who_need_help_web/live/request_live/index.ex:485
#: lib/who_need_help_web/live/request_live/show.ex:1000
#: lib/who_need_help_web/live/support_operation_live.ex:310
#: lib/who_need_help_web/live/support_operation_live.ex:414
@@ -1712,9 +1712,9 @@ msgid "Save profile"
msgstr "Сохранить профиль"
#: lib/who_need_help_web/live/notification_live.ex:738
-#: lib/who_need_help_web/live/request_live/index.ex:447
-#: lib/who_need_help_web/live/request_live/index.ex:448
-#: lib/who_need_help_web/live/request_live/index.ex:557
+#: lib/who_need_help_web/live/request_live/index.ex:480
+#: lib/who_need_help_web/live/request_live/index.ex:481
+#: lib/who_need_help_web/live/request_live/index.ex:590
#: lib/who_need_help_web/live/request_live/new.ex:451
#: lib/who_need_help_web/live/request_live/show.ex:996
#: lib/who_need_help_web/live/request_live/show.ex:997
@@ -1988,7 +1988,7 @@ msgstr "Состояние этой активности уже изменило
msgid "This interaction is blocked."
msgstr "Это взаимодействие заблокировано."
-#: lib/who_need_help_web/live/request_live/index.ex:495
+#: lib/who_need_help_web/live/request_live/index.ex:528
#: lib/who_need_help_web/live/request_live/show.ex:1071
#, elixir-autogen, elixir-format
msgid "This is not an emergency service. Contact local emergency services for immediate danger."
@@ -2041,9 +2041,9 @@ msgid "To see sent emails, visit"
msgstr "Чтобы посмотреть отправленные письма, откройте"
#: lib/who_need_help_web/live/notification_live.ex:737
-#: lib/who_need_help_web/live/request_live/index.ex:445
-#: lib/who_need_help_web/live/request_live/index.ex:446
-#: lib/who_need_help_web/live/request_live/index.ex:556
+#: lib/who_need_help_web/live/request_live/index.ex:478
+#: lib/who_need_help_web/live/request_live/index.ex:479
+#: lib/who_need_help_web/live/request_live/index.ex:589
#: lib/who_need_help_web/live/request_live/new.ex:450
#: lib/who_need_help_web/live/request_live/show.ex:994
#: lib/who_need_help_web/live/request_live/show.ex:995
@@ -2113,7 +2113,7 @@ msgid "Unique people"
msgstr "Уникальные люди"
#: lib/who_need_help_web/live/notification_live.ex:731
-#: lib/who_need_help_web/live/request_live/index.ex:552
+#: lib/who_need_help_web/live/request_live/index.ex:585
#: lib/who_need_help_web/live/request_live/new.ex:447
#, elixir-autogen, elixir-format
msgid "Urgency"
@@ -2257,7 +2257,7 @@ msgstr ""
"через подписку ChatGPT разработчика. Ключ OpenAI API и резервный вариант API"
" с оплатой по использованию не применяются."
-#: lib/who_need_help_web/live/request_live/index.ex:480
+#: lib/who_need_help_web/live/request_live/index.ex:513
#, elixir-autogen, elixir-format
msgid "Who needs help?"
msgstr "Кому нужна помощь?"
@@ -2377,7 +2377,7 @@ msgid "Your review is revealed only after both participants submit."
msgstr ""
"Ваш отзыв откроется только после того, как его отправят оба участника."
-#: lib/who_need_help_web/live/request_live/index.ex:805
+#: lib/who_need_help_web/live/request_live/index.ex:838
#, elixir-autogen, elixir-format
msgid "Your trust summary"
msgstr "Ваши показатели доверия"
@@ -2388,16 +2388,16 @@ msgstr "Ваши показатели доверия"
msgid "before confirming."
msgstr "перед подтверждением."
-#: lib/who_need_help_web/live/activity_live/index.ex:696
+#: lib/who_need_help_web/live/activity_live/index.ex:728
#: lib/who_need_help_web/live/category_proposal_live.ex:252
-#: lib/who_need_help_web/live/request_live/index.ex:736
+#: lib/who_need_help_web/live/request_live/index.ex:769
#, elixir-autogen, elixir-format
msgid "by %{name}"
msgstr "автор: %{name}"
#: lib/who_need_help_web/live/profile_live.ex:190
#: lib/who_need_help_web/live/public_profile_live.ex:133
-#: lib/who_need_help_web/live/request_live/index.ex:809
+#: lib/who_need_help_web/live/request_live/index.ex:842
#: lib/who_need_help_web/live/request_live/show.ex:1478
#, elixir-autogen, elixir-format
msgid "completed"
@@ -2433,7 +2433,7 @@ msgstr "организатор"
msgid "participants only"
msgstr "только участники"
-#: lib/who_need_help_web/live/request_live/index.ex:813
+#: lib/who_need_help_web/live/request_live/index.ex:846
#: lib/who_need_help_web/live/request_live/show.ex:1482
#, elixir-autogen, elixir-format
msgid "people"
@@ -2493,7 +2493,7 @@ msgstr "срочно"
#: lib/who_need_help_web/live/activity_live/show.ex:719
#: lib/who_need_help_web/live/profile_live.ex:285
#: lib/who_need_help_web/live/public_profile_live.ex:98
-#: lib/who_need_help_web/live/request_live/index.ex:817
+#: lib/who_need_help_web/live/request_live/index.ex:850
#: lib/who_need_help_web/live/request_live/show.ex:1142
#: lib/who_need_help_web/live/request_live/show.ex:1580
#, elixir-autogen, elixir-format
@@ -2526,8 +2526,8 @@ msgstr "← Заявки"
msgid "Approved"
msgstr "Одобрено"
-#: lib/who_need_help_web/live/request_live/index.ex:461
-#: lib/who_need_help_web/live/request_live/index.ex:462
+#: lib/who_need_help_web/live/request_live/index.ex:494
+#: lib/who_need_help_web/live/request_live/index.ex:495
#, elixir-autogen, elixir-format
msgid "Expired"
msgstr "Истекло"
@@ -2581,21 +2581,21 @@ msgstr "Необычная частота действий"
msgid "Load earlier messages"
msgstr "Загрузить предыдущие сообщения"
-#: lib/who_need_help_web/live/activity_live/index.ex:708
-#: lib/who_need_help_web/live/activity_live/index.ex:801
+#: lib/who_need_help_web/live/activity_live/index.ex:740
+#: lib/who_need_help_web/live/activity_live/index.ex:833
#: lib/who_need_help_web/live/category_proposal_live.ex:284
#: lib/who_need_help_web/live/leaderboard_live.ex:91
#: lib/who_need_help_web/live/profile_live.ex:233
#: lib/who_need_help_web/live/profile_live.ex:363
#: lib/who_need_help_web/live/public_profile_live.ex:178
#: lib/who_need_help_web/live/report_live.ex:98
-#: lib/who_need_help_web/live/request_live/index.ex:886
+#: lib/who_need_help_web/live/request_live/index.ex:919
#, elixir-autogen, elixir-format
msgid "Load more"
msgstr "Загрузить ещё"
#: lib/who_need_help_web/live/activity_live/show.ex:544
-#: lib/who_need_help_web/live/request_live/index.ex:782
+#: lib/who_need_help_web/live/request_live/index.ex:815
#: lib/who_need_help_web/live/request_live/show.ex:1324
#, elixir-autogen, elixir-format
msgid "The map is unavailable in this browser. Request details remain usable."
@@ -2963,7 +2963,7 @@ msgstr "Обжаловать решение модерации"
msgid "Back to removal form"
msgstr "Вернуться к форме удаления"
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:34
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:43
#: lib/who_need_help_web/controllers/support_html/verify.html.heex:30
#, elixir-autogen, elixir-format
msgid "Back to support"
@@ -2975,7 +2975,8 @@ msgstr "Вернуться в поддержку"
msgid "CONTENT REMOVAL"
msgstr "УДАЛЕНИЕ КОНТЕНТА"
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:16
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:18
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:19
#, elixir-autogen, elixir-format
msgid "Check your email"
msgstr "Проверьте почту"
@@ -3255,7 +3256,7 @@ msgstr "Сообщить о незаконном или вредоносном
msgid "Reported locations"
msgstr "Указанные адреса контента"
-#: lib/who_need_help_web/controllers/support_controller.ex:241
+#: lib/who_need_help_web/controllers/support_controller.ex:267
#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:6
#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:42
#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:128
@@ -3406,8 +3407,8 @@ msgstr "Угроза жизни или безопасности"
msgid "Too many removal notices. Please try again later."
msgstr "Слишком много уведомлений об удалении. Повторите попытку позже."
-#: lib/who_need_help_web/controllers/support_controller.ex:42
-#: lib/who_need_help_web/controllers/support_controller.ex:84
+#: lib/who_need_help_web/controllers/support_controller.ex:45
+#: lib/who_need_help_web/controllers/support_controller.ex:90
#, elixir-autogen, elixir-format
msgid "Too many support requests. Please try again later."
msgstr "Слишком много обращений в поддержку. Повторите попытку позже."
@@ -3489,13 +3490,13 @@ msgstr "Номер вашего обращения: %{reference}"
msgid "Your relationship to the affected person or rights holder"
msgstr "Ваше отношение к пострадавшему лицу или правообладателю"
-#: lib/who_need_help_web/controllers/support_controller.ex:55
-#: lib/who_need_help_web/controllers/support_controller.ex:66
+#: lib/who_need_help_web/controllers/support_controller.ex:58
+#: lib/who_need_help_web/controllers/support_controller.ex:69
#, elixir-autogen, elixir-format
msgid "Delete my Who Need Help account"
msgstr "Удалить мой аккаунт Who Need Help"
-#: lib/who_need_help_web/controllers/support_controller.ex:56
+#: lib/who_need_help_web/controllers/support_controller.ex:59
#, elixir-autogen, elixir-format
msgid "Please delete my account and associated personal data."
msgstr "Пожалуйста, удалите мой аккаунт и связанные персональные данные."
@@ -4432,7 +4433,7 @@ msgid "View my notices"
msgstr "Посмотреть мои уведомления"
#: lib/who_need_help_web/controllers/support_html/new.html.heex:13
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:32
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:41
#, elixir-autogen, elixir-format
msgid "View my requests"
msgstr "Посмотреть мои обращения"
@@ -4547,37 +4548,37 @@ msgstr "Перетащите, чтобы переместить точную т
msgid "Exact point selected. Drag the red pin or click the map to move it."
msgstr "Точная точка выбрана. Перетащите красный маркер или нажмите на карту, чтобы переместить её."
-#: lib/who_need_help_web/live/activity_live/index.ex:515
-#: lib/who_need_help_web/live/request_live/index.ex:562
+#: lib/who_need_help_web/live/activity_live/index.ex:547
+#: lib/who_need_help_web/live/request_live/index.ex:595
#, elixir-autogen, elixir-format
msgid "Area or city"
msgstr "Район или город"
-#: lib/who_need_help_web/live/activity_live/index.ex:638
-#: lib/who_need_help_web/live/request_live/index.ex:683
+#: lib/who_need_help_web/live/activity_live/index.ex:670
+#: lib/who_need_help_web/live/request_live/index.ex:716
#, elixir-autogen, elixir-format
msgid "Choose an area on the map"
msgstr "Выберите область на карте"
-#: lib/who_need_help_web/live/request_live/index.ex:482
+#: lib/who_need_help_web/live/request_live/index.ex:515
#, elixir-autogen, elixir-format
msgid "Explore privacy-friendly areas and public exact meeting points chosen by requesters."
msgstr "Просматривайте безопасные для приватности области и точные публичные места встречи, выбранные авторами заявок."
-#: lib/who_need_help_web/live/activity_live/index.ex:605
-#: lib/who_need_help_web/live/request_live/index.ex:650
+#: lib/who_need_help_web/live/activity_live/index.ex:637
+#: lib/who_need_help_web/live/request_live/index.ex:683
#, elixir-autogen, elixir-format
msgid "List"
msgstr "Список"
-#: lib/who_need_help_web/live/request_live/index.ex:758
+#: lib/who_need_help_web/live/request_live/index.ex:791
#, elixir-autogen, elixir-format
msgid "Load more in this area"
msgstr "Загрузить ещё в этой области"
#: lib/who_need_help_web/components/layouts/root.html.heex:5
-#: lib/who_need_help_web/live/activity_live/index.ex:625
-#: lib/who_need_help_web/live/request_live/index.ex:670
+#: lib/who_need_help_web/live/activity_live/index.ex:657
+#: lib/who_need_help_web/live/request_live/index.ex:703
#, elixir-autogen, elixir-format
msgid "Map"
msgstr "Карта"
@@ -4587,41 +4588,41 @@ msgstr "Карта"
msgid "Map preview"
msgstr "Предпросмотр карты"
-#: lib/who_need_help_web/live/activity_live/index.ex:654
-#: lib/who_need_help_web/live/request_live/index.ex:699
+#: lib/who_need_help_web/live/activity_live/index.ex:686
+#: lib/who_need_help_web/live/request_live/index.ex:732
#, elixir-autogen, elixir-format
msgid "Move or zoom the map to search somewhere else."
msgstr "Переместите или масштабируйте карту, чтобы искать в другом месте."
-#: lib/who_need_help_web/live/request_live/index.ex:696
+#: lib/who_need_help_web/live/request_live/index.ex:729
#, elixir-autogen, elixir-format
msgid "No open requests in this area"
msgstr "В этой области нет открытых заявок"
-#: lib/who_need_help_web/live/request_live/index.ex:685
+#: lib/who_need_help_web/live/request_live/index.ex:718
#, elixir-autogen, elixir-format
msgid "Only requests from the selected map area will be loaded."
msgstr "Будут загружены только заявки из выбранной области карты."
#: lib/who_need_help_web/components/layouts/root.html.heex:8
-#: lib/who_need_help_web/live/request_live/index.ex:785
+#: lib/who_need_help_web/live/request_live/index.ex:818
#, elixir-autogen, elixir-format
msgid "Open request"
msgstr "Открыть заявку"
-#: lib/who_need_help_web/live/activity_live/index.ex:516
-#: lib/who_need_help_web/live/request_live/index.ex:563
+#: lib/who_need_help_web/live/activity_live/index.ex:548
+#: lib/who_need_help_web/live/request_live/index.ex:596
#, elixir-autogen, elixir-format
msgid "Optional text search"
msgstr "Необязательный текстовый поиск"
-#: lib/who_need_help_web/live/request_live/index.ex:764
+#: lib/who_need_help_web/live/request_live/index.ex:797
#, elixir-autogen, elixir-format
msgid "Requests without map coordinates appear only when their public area description matches the text search."
msgstr "Заявки без координат на карте отображаются только тогда, когда описание публичной области совпадает с текстовым поиском."
-#: lib/who_need_help_web/live/activity_live/index.ex:596
-#: lib/who_need_help_web/live/request_live/index.ex:641
+#: lib/who_need_help_web/live/activity_live/index.ex:628
+#: lib/who_need_help_web/live/request_live/index.ex:674
#, elixir-autogen, elixir-format
msgid "Results layout"
msgstr "Расположение результатов"
@@ -4631,21 +4632,21 @@ msgstr "Расположение результатов"
msgid "Static preview"
msgstr "Статический просмотр"
-#: lib/who_need_help_web/live/activity_live/index.ex:615
-#: lib/who_need_help_web/live/request_live/index.ex:660
+#: lib/who_need_help_web/live/activity_live/index.ex:647
+#: lib/who_need_help_web/live/request_live/index.ex:693
#, elixir-autogen, elixir-format
msgid "Together"
msgstr "Вместе"
#: lib/who_need_help_web/components/layouts/root.html.heex:6
#: lib/who_need_help_web/controllers/page_html/home.html.heex:166
-#: lib/who_need_help_web/live/activity_live/index.ex:731
+#: lib/who_need_help_web/live/activity_live/index.ex:763
#: lib/who_need_help_web/live/activity_live/new.ex:355
#: lib/who_need_help_web/live/activity_live/new.ex:414
#: lib/who_need_help_web/live/activity_live/show.ex:546
#: lib/who_need_help_web/live/notification_live.ex:590
#: lib/who_need_help_web/live/notification_live.ex:609
-#: lib/who_need_help_web/live/request_live/index.ex:784
+#: lib/who_need_help_web/live/request_live/index.ex:817
#: lib/who_need_help_web/live/request_live/new.ex:485
#: lib/who_need_help_web/live/request_live/new.ex:563
#: lib/who_need_help_web/live/request_live/show.ex:1326
@@ -5003,7 +5004,7 @@ msgid "Nearby alert deleted."
msgstr "Уведомление поблизости удалено."
#: lib/who_need_help_web/live/notification_live.ex:453
-#: lib/who_need_help_web/live/request_live/index.ex:508
+#: lib/who_need_help_web/live/request_live/index.ex:541
#, elixir-autogen, elixir-format
msgid "Nearby requests"
msgstr "Заявки поблизости"
@@ -5326,7 +5327,7 @@ msgid "WebGL is unavailable in this browser, so the full map cannot start. Selec
msgstr "WebGL недоступен в этом браузере, поэтому полная карта не может запуститься. Выберите пример ниже или попробуйте запустить карту ещё раз."
#: lib/who_need_help_web/components/layouts/root.html.heex:7
-#: lib/who_need_help_web/live/activity_live/index.ex:732
+#: lib/who_need_help_web/live/activity_live/index.ex:764
#: lib/who_need_help_web/live/activity_live/show.ex:547
#: lib/who_need_help_web/live/request_live/show.ex:1327
#, elixir-autogen, elixir-format
@@ -5367,7 +5368,7 @@ msgid "Case status"
msgstr "Статус обращения"
#: lib/who_need_help_web/controllers/page_html/child_safety.html.heex:42
-#: lib/who_need_help_web/controllers/support_controller.ex:242
+#: lib/who_need_help_web/controllers/support_controller.ex:268
#: lib/who_need_help_web/live/report_live.ex:50
#, elixir-autogen, elixir-format
msgid "Contact support"
@@ -5389,8 +5390,8 @@ msgstr "Уведомление об удалении контента получ
msgid "Conversation"
msgstr "Переписка"
-#: lib/who_need_help_web/controllers/support_controller.ex:152
-#: lib/who_need_help_web/controllers/support_controller.ex:168
+#: lib/who_need_help_web/controllers/support_controller.ex:158
+#: lib/who_need_help_web/controllers/support_controller.ex:174
#: lib/who_need_help_web/live/support_case_live.ex:73
#, elixir-autogen, elixir-format
msgid "Could not send your message. Please check it and try again."
@@ -5453,7 +5454,7 @@ msgstr "Жалоб не отправлено"
msgid "No status history is available for this older case."
msgstr "Для этого старого обращения история статусов недоступна."
-#: lib/who_need_help_web/controllers/support_controller.ex:187
+#: lib/who_need_help_web/controllers/support_controller.ex:193
#: lib/who_need_help_web/live/support_case_live.ex:95
#, elixir-autogen, elixir-format
msgid "Only a resolved or closed request can be reopened."
@@ -5558,12 +5559,12 @@ msgstr "Отправить сообщение"
msgid "Status history"
msgstr "История статусов"
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:15
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:17
#, elixir-autogen, elixir-format
msgid "Support request created"
msgstr "Обращение в поддержку создано"
-#: lib/who_need_help_web/controllers/support_controller.ex:198
+#: lib/who_need_help_web/controllers/support_controller.ex:214
#, elixir-autogen, elixir-format
msgid "Support request received"
msgstr "Обращение в поддержку получено"
@@ -5599,7 +5600,7 @@ msgstr "ДОВЕРИЕ И БЕЗОПАСНОСТЬ"
msgid "The requester states whether the medicine is paid, reserved, or still needs to be purchased."
msgstr "Автор заявки указывает, оплачено ли лекарство, зарезервировано или его ещё нужно купить."
-#: lib/who_need_help_web/controllers/support_controller.ex:182
+#: lib/who_need_help_web/controllers/support_controller.ex:188
#: lib/who_need_help_web/live/support_case_live.ex:88
#, elixir-autogen, elixir-format
msgid "The support request is open again."
@@ -5627,7 +5628,7 @@ msgstr "В этой личной истории показаны ваши жал
msgid "This support request is no longer available."
msgstr "Это обращение в поддержку больше недоступно."
-#: lib/who_need_help_web/controllers/support_controller.ex:158
+#: lib/who_need_help_web/controllers/support_controller.ex:164
#: lib/who_need_help_web/live/support_case_live.ex:60
#, elixir-autogen, elixir-format
msgid "Too many messages. Please try again later."
@@ -5638,7 +5639,7 @@ msgstr "Слишком много сообщений. Попробуйте по
msgid "Use the dedicated urgent process. Never upload or reproduce the image or video."
msgstr "Используйте специальную срочную процедуру. Никогда не загружайте и не воспроизводите изображение или видео."
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:19
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:23
#, elixir-autogen, elixir-format
msgid "We created support request %{reference} and added it to your account. You can open it now and return to it from My support requests."
msgstr "Мы создали обращение %{reference} и добавили его в ваш аккаунт. Его можно открыть сейчас или вернуться к нему из раздела «Мои обращения»."
@@ -5664,7 +5665,7 @@ msgstr "Вы"
msgid "Your content-removal notices"
msgstr "Ваши уведомления об удалении контента"
-#: lib/who_need_help_web/controllers/support_controller.ex:145
+#: lib/who_need_help_web/controllers/support_controller.ex:151
#: lib/who_need_help_web/live/support_case_live.ex:53
#, elixir-autogen, elixir-format
msgid "Your message was sent to support."
@@ -5828,7 +5829,7 @@ msgstr "Теперь ваш отзыв виден"
msgid "Delivery settings"
msgstr "Настройки доставки"
-#: lib/who_need_help_web/live/request_live/index.ex:741
+#: lib/who_need_help_web/live/request_live/index.ex:774
#, elixir-autogen, elixir-format
msgid "Expires"
msgstr "Истекает"
@@ -5848,22 +5849,22 @@ msgstr "Оповещения рядом"
msgid "Notification sections"
msgstr "Разделы уведомлений"
-#: lib/who_need_help_web/live/activity_live/index.ex:724
+#: lib/who_need_help_web/live/activity_live/index.ex:756
#, elixir-autogen, elixir-format
msgid "%{count} activities"
msgstr "Активностей: %{count}"
-#: lib/who_need_help_web/live/request_live/index.ex:786
+#: lib/who_need_help_web/live/request_live/index.ex:819
#, elixir-autogen, elixir-format
msgid "%{count} requests"
msgstr "Заявок: %{count}"
-#: lib/who_need_help_web/live/activity_live/index.ex:767
+#: lib/who_need_help_web/live/activity_live/index.ex:799
#, elixir-autogen, elixir-format
msgid "Activities you create or ask to join will appear here. Leaving removes an activity from this list."
msgstr "Созданные вами активности и те, к которым вы попросили присоединиться, появятся здесь. После выхода активность исчезнет из списка."
-#: lib/who_need_help_web/live/activity_live/index.ex:470
+#: lib/who_need_help_web/live/activity_live/index.ex:502
#, elixir-autogen, elixir-format
msgid "Activity views"
msgstr "Разделы активностей"
@@ -5878,12 +5879,12 @@ msgstr "Выберите категорию и понятно опишите з
msgid "Confirm the safety guidance before asking nearby people for help."
msgstr "Подтвердите правила безопасности перед публикацией заявки."
-#: lib/who_need_help_web/live/request_live/index.ex:834
+#: lib/who_need_help_web/live/request_live/index.ex:867
#, elixir-autogen, elixir-format
msgid "Create request"
msgstr "Создать заявку"
-#: lib/who_need_help_web/live/request_live/index.ex:857
+#: lib/who_need_help_web/live/request_live/index.ex:890
#, elixir-autogen, elixir-format
msgid "Create your first request"
msgstr "Создать первую заявку"
@@ -5898,43 +5899,43 @@ msgstr "Что нужно сделать"
msgid "Location"
msgstr "Место"
-#: lib/who_need_help_web/live/activity_live/index.ex:476
+#: lib/who_need_help_web/live/activity_live/index.ex:508
#, elixir-autogen, elixir-format
msgid "My activities"
msgstr "Мои активности"
-#: lib/who_need_help_web/live/request_live/index.ex:509
-#: lib/who_need_help_web/live/request_live/index.ex:828
+#: lib/who_need_help_web/live/request_live/index.ex:542
+#: lib/who_need_help_web/live/request_live/index.ex:861
#, elixir-autogen, elixir-format
msgid "My requests"
msgstr "Мои заявки"
-#: lib/who_need_help_web/live/activity_live/index.ex:475
+#: lib/who_need_help_web/live/activity_live/index.ex:507
#, elixir-autogen, elixir-format
msgid "Nearby activities"
msgstr "Активности рядом"
-#: lib/who_need_help_web/live/activity_live/index.ex:765
+#: lib/who_need_help_web/live/activity_live/index.ex:797
#, elixir-autogen, elixir-format
msgid "No activities yet"
msgstr "Активностей пока нет"
-#: lib/who_need_help_web/live/activity_live/index.ex:651
+#: lib/who_need_help_web/live/activity_live/index.ex:683
#, elixir-autogen, elixir-format
msgid "No open activities in this area"
msgstr "В этой области нет открытых активностей"
-#: lib/who_need_help_web/live/request_live/index.ex:852
+#: lib/who_need_help_web/live/request_live/index.ex:885
#, elixir-autogen, elixir-format
msgid "No requests yet"
msgstr "Заявок пока нет"
-#: lib/who_need_help_web/live/activity_live/index.ex:640
+#: lib/who_need_help_web/live/activity_live/index.ex:672
#, elixir-autogen, elixir-format
msgid "Only activities from the selected map area will be loaded."
msgstr "Будут загружены только активности из выбранной области карты."
-#: lib/who_need_help_web/live/activity_live/index.ex:723
+#: lib/who_need_help_web/live/activity_live/index.ex:755
#, elixir-autogen, elixir-format
msgid "Open activity"
msgstr "Открыть активность"
@@ -5944,12 +5945,12 @@ msgstr "Открыть активность"
msgid "Request form steps"
msgstr "Шаги создания заявки"
-#: lib/who_need_help_web/live/request_live/index.ex:503
+#: lib/who_need_help_web/live/request_live/index.ex:536
#, elixir-autogen, elixir-format
msgid "Request views"
msgstr "Разделы заявок"
-#: lib/who_need_help_web/live/request_live/index.ex:854
+#: lib/who_need_help_web/live/request_live/index.ex:887
#, elixir-autogen, elixir-format
msgid "Requests you publish or accept will appear here."
msgstr "Здесь появятся заявки, которые вы создали или приняли."
@@ -5974,7 +5975,7 @@ msgstr "Укажите срочность и время закрытия зая
msgid "Share only as much location detail as helpers need."
msgstr "Покажите помощникам только необходимую информацию о месте."
-#: lib/who_need_help_web/live/activity_live/index.ex:729
+#: lib/who_need_help_web/live/activity_live/index.ex:761
#, elixir-autogen, elixir-format
msgid "The map is unavailable in this browser. Activity details remain usable."
msgstr "Карта недоступна в этом браузере. Информация об активностях по-прежнему доступна."
@@ -5984,7 +5985,7 @@ msgstr "Карта недоступна в этом браузере. Инфор
msgid "Timing"
msgstr "Время"
-#: lib/who_need_help_web/live/request_live/index.ex:830
+#: lib/who_need_help_web/live/request_live/index.ex:863
#, elixir-autogen, elixir-format
msgid "Track requests you created and requests you accepted as a helper."
msgstr "Следите за созданными заявками и за теми, которые вы приняли как помощник."
@@ -6639,27 +6640,27 @@ msgstr "Что должны видеть люди до одобрения?"
msgid "Choose the meeting point on the map or use your current location."
msgstr "Выберите место встречи на карте или используйте своё текущее местоположение."
-#: lib/who_need_help_web/live/activity_live/index.ex:555
-#: lib/who_need_help_web/live/activity_live/index.ex:650
-#: lib/who_need_help_web/live/activity_live/index.ex:757
-#: lib/who_need_help_web/live/request_live/index.ex:600
-#: lib/who_need_help_web/live/request_live/index.ex:695
-#: lib/who_need_help_web/live/request_live/index.ex:844
+#: lib/who_need_help_web/live/activity_live/index.ex:587
+#: lib/who_need_help_web/live/activity_live/index.ex:682
+#: lib/who_need_help_web/live/activity_live/index.ex:789
+#: lib/who_need_help_web/live/request_live/index.ex:633
+#: lib/who_need_help_web/live/request_live/index.ex:728
+#: lib/who_need_help_web/live/request_live/index.ex:877
#, elixir-autogen, elixir-format
msgid "Loading results…"
msgstr "Загружаем результаты…"
-#: lib/who_need_help_web/live/activity_live/index.ex:726
+#: lib/who_need_help_web/live/activity_live/index.ex:758
#, elixir-autogen, elixir-format
msgid "Maximum zoom reached. Matching activities are listed beside the map."
msgstr "Достигнут максимальный масштаб. Подходящие мероприятия перечислены рядом с картой."
-#: lib/who_need_help_web/live/request_live/index.ex:788
+#: lib/who_need_help_web/live/request_live/index.ex:821
#, elixir-autogen, elixir-format
msgid "Maximum zoom reached. Matching requests are listed beside the map."
msgstr "Достигнут максимальный масштаб. Подходящие заявки перечислены рядом с картой."
-#: lib/who_need_help_web/live/activity_live/index.ex:536
+#: lib/who_need_help_web/live/activity_live/index.ex:568
#, elixir-autogen, elixir-format
msgid "%{count} activity in the selected map area"
msgid_plural "%{count} activities in the selected map area"
@@ -6667,7 +6668,7 @@ msgstr[0] "%{count} мероприятие в выбранной области
msgstr[1] "%{count} мероприятия в выбранной области карты"
msgstr[2] "%{count} мероприятий в выбранной области карты"
-#: lib/who_need_help_web/live/request_live/index.ex:581
+#: lib/who_need_help_web/live/request_live/index.ex:614
#, elixir-autogen, elixir-format
msgid "%{count} request in the selected map area"
msgid_plural "%{count} requests in the selected map area"
@@ -6675,57 +6676,57 @@ msgstr[0] "%{count} заявка в выбранной области карты
msgstr[1] "%{count} заявки в выбранной области карты"
msgstr[2] "%{count} заявок в выбранной области карты"
-#: lib/who_need_help_web/live/activity_live/index.ex:561
-#: lib/who_need_help_web/live/request_live/index.ex:606
+#: lib/who_need_help_web/live/activity_live/index.ex:593
+#: lib/who_need_help_web/live/request_live/index.ex:639
#, elixir-autogen, elixir-format
msgid "Map moved. Results still show the previous area."
msgstr "Карта перемещена. Пока показаны результаты для предыдущей области."
-#: lib/who_need_help_web/live/activity_live/index.ex:579
+#: lib/who_need_help_web/live/activity_live/index.ex:611
#, elixir-autogen, elixir-format
msgid "Move or zoom the map, then choose “Show activities in this area”."
msgstr "Переместите или масштабируйте карту, затем нажмите «Показать мероприятия в этой области»."
-#: lib/who_need_help_web/live/request_live/index.ex:624
+#: lib/who_need_help_web/live/request_live/index.ex:657
#, elixir-autogen, elixir-format
msgid "Move or zoom the map, then choose “Show requests in this area”."
msgstr "Переместите или масштабируйте карту, затем нажмите «Показать заявки в этой области»."
-#: lib/who_need_help_web/live/activity_live/index.ex:573
-#: lib/who_need_help_web/live/request_live/index.ex:618
+#: lib/who_need_help_web/live/activity_live/index.ex:605
+#: lib/who_need_help_web/live/request_live/index.ex:651
#, elixir-autogen, elixir-format
msgid "Results update automatically after you stop moving the map."
msgstr "Результаты обновятся автоматически, когда вы закончите перемещать карту."
-#: lib/who_need_help_web/live/activity_live/index.ex:743
+#: lib/who_need_help_web/live/activity_live/index.ex:775
#, elixir-autogen, elixir-format
msgid "Show activities in this area"
msgstr "Показать мероприятия в этой области"
-#: lib/who_need_help_web/live/request_live/index.ex:800
+#: lib/who_need_help_web/live/request_live/index.ex:833
#, elixir-autogen, elixir-format
msgid "Show requests in this area"
msgstr "Показать заявки в этой области"
-#: lib/who_need_help_web/live/activity_live/index.ex:545
-#: lib/who_need_help_web/live/request_live/index.ex:590
+#: lib/who_need_help_web/live/activity_live/index.ex:577
+#: lib/who_need_help_web/live/request_live/index.ex:623
#, elixir-autogen, elixir-format
msgid "Showing %{shown} of %{count} in the list."
msgstr "В списке показано %{shown} из %{count}."
-#: lib/who_need_help_web/live/activity_live/index.ex:593
-#: lib/who_need_help_web/live/request_live/index.ex:638
+#: lib/who_need_help_web/live/activity_live/index.ex:625
+#: lib/who_need_help_web/live/request_live/index.ex:671
#, elixir-autogen, elixir-format
msgid "Update results as map moves"
msgstr "Обновлять результаты при перемещении карты"
-#: lib/who_need_help_web/live/activity_live/index.ex:567
-#: lib/who_need_help_web/live/request_live/index.ex:612
+#: lib/who_need_help_web/live/activity_live/index.ex:599
+#: lib/who_need_help_web/live/request_live/index.ex:645
#, elixir-autogen, elixir-format
msgid "Updating results for the visible map area…"
msgstr "Обновляем результаты для видимой области карты…"
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:11
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:12
#, elixir-autogen, elixir-format
msgid "EMAIL CONFIRMATION REQUIRED"
msgstr "ТРЕБУЕТСЯ ПОДТВЕРЖДЕНИЕ EMAIL"
@@ -6745,7 +6746,7 @@ msgstr "Ожидаем разрешения на геолокацию…"
msgid "Pending email verification"
msgstr "Ожидает подтверждения email"
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:25
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:34
#, elixir-autogen, elixir-format
msgid "We saved pending request %{reference}. It is not in the support queue yet. Open the private link sent to the contact email to verify the address and submit the request for review."
msgstr "Мы сохранили ожидающую заявку %{reference}. Она ещё не попала в очередь поддержки. Откройте приватную ссылку из письма, чтобы подтвердить адрес и отправить заявку на рассмотрение."
@@ -7810,7 +7811,7 @@ msgstr "У вас нет разрешения на это действие. Дл
msgid "Your email was confirmed and the notice was sent for review."
msgstr "Ваш email подтверждён, уведомление отправлено на проверку."
-#: lib/who_need_help_web/controllers/support_controller.ex:122
+#: lib/who_need_help_web/controllers/support_controller.ex:128
#, elixir-autogen, elixir-format
msgid "Your email was confirmed and the request was sent to support."
msgstr "Ваш email подтверждён, обращение отправлено в поддержку."
@@ -8008,7 +8009,7 @@ msgstr "Скопируйте этот безопасный адрес и вст
msgid "Confirm content-removal notice"
msgstr "Подтвердить уведомление об удалении контента"
-#: lib/who_need_help_web/controllers/support_controller.ex:106
+#: lib/who_need_help_web/controllers/support_controller.ex:112
#: lib/who_need_help_web/controllers/support_html/verify.html.heex:9
#: lib/who_need_help_web/controllers/support_html/verify.html.heex:23
#, elixir-autogen, elixir-format
@@ -8034,3 +8035,13 @@ msgstr "Если вы не отправляли это уведомление,
#, elixir-autogen, elixir-format
msgid "If you did not submit this request, close this page. Nothing will be sent to support."
msgstr "Если вы не отправляли это обращение, закройте страницу. В поддержку ничего не поступит."
+
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:29
+#, elixir-autogen, elixir-format
+msgid "If that address belongs to a Who Need Help account, we sent a private confirmation link to the account email. No support request is created until that link is confirmed."
+msgstr "Если этот адрес принадлежит аккаунту Who Need Help, мы отправили приватную ссылку подтверждения на электронную почту аккаунта. Обращение в поддержку будет создано только после перехода по этой ссылке."
+
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:11
+#, elixir-autogen, elixir-format
+msgid "REQUEST CHECKED"
+msgstr "ЗАПРОС ПРОВЕРЕН"
diff --git a/priv/gettext/uk/LC_MESSAGES/default.po b/priv/gettext/uk/LC_MESSAGES/default.po
index 711a69e..34ef52b 100644
--- a/priv/gettext/uk/LC_MESSAGES/default.po
+++ b/priv/gettext/uk/LC_MESSAGES/default.po
@@ -262,7 +262,7 @@ msgstr "Активність опубліковано. Ви схвалюєте
msgid "Activity restored."
msgstr "Активність відновлено."
-#: lib/who_need_help_web/live/activity_live/index.ex:504
+#: lib/who_need_help_web/live/activity_live/index.ex:536
#: lib/who_need_help_web/live/activity_live/new.ex:204
#, elixir-autogen, elixir-format
msgid "Activity type"
@@ -280,12 +280,12 @@ msgstr "Додати непідтверджене посилання"
msgid "Administrator"
msgstr "Адміністратор"
-#: lib/who_need_help_web/live/activity_live/index.ex:506
+#: lib/who_need_help_web/live/activity_live/index.ex:538
#, elixir-autogen, elixir-format
msgid "All activities"
msgstr "Усі активності"
-#: lib/who_need_help_web/live/request_live/index.ex:539
+#: lib/who_need_help_web/live/request_live/index.ex:572
#, elixir-autogen, elixir-format
msgid "All categories"
msgstr "Усі категорії"
@@ -300,7 +300,7 @@ msgstr "Уже зареєстровані?"
msgid "Another helper already accepted this request."
msgstr "Інший помічник уже прийняв цю заявку."
-#: lib/who_need_help_web/live/request_live/index.ex:554
+#: lib/who_need_help_web/live/request_live/index.ex:587
#, elixir-autogen, elixir-format
msgid "Any urgency"
msgstr "Будь-яка терміновість"
@@ -420,16 +420,16 @@ msgstr "Скасувати активність"
msgid "Cancel request"
msgstr "Скасувати запит"
-#: lib/who_need_help_web/live/activity_live/index.ex:435
+#: lib/who_need_help_web/live/activity_live/index.ex:467
#: lib/who_need_help_web/live/activity_live/show.ex:344
-#: lib/who_need_help_web/live/request_live/index.ex:459
-#: lib/who_need_help_web/live/request_live/index.ex:460
+#: lib/who_need_help_web/live/request_live/index.ex:492
+#: lib/who_need_help_web/live/request_live/index.ex:493
#: lib/who_need_help_web/live/request_live/show.ex:1005
#, elixir-autogen, elixir-format
msgid "Cancelled"
msgstr "Скасовано"
-#: lib/who_need_help_web/live/request_live/index.ex:537
+#: lib/who_need_help_web/live/request_live/index.ex:570
#: lib/who_need_help_web/live/request_live/new.ex:302
#, elixir-autogen, elixir-format
msgid "Category"
@@ -510,7 +510,7 @@ msgstr "Закрити"
msgid "Coffee and conversation"
msgstr "Кава й розмова"
-#: lib/who_need_help_web/live/activity_live/index.ex:449
+#: lib/who_need_help_web/live/activity_live/index.ex:481
#, elixir-autogen, elixir-format
msgid "Coffee, cinema, walks, and hikes are separate from urgent help and never affect helper ratings."
msgstr ""
@@ -527,19 +527,19 @@ msgstr "Коментар (необов’язково)"
msgid "Community helpers"
msgstr "Помічники спільноти"
-#: lib/who_need_help_web/live/activity_live/index.ex:697
+#: lib/who_need_help_web/live/activity_live/index.ex:729
#: lib/who_need_help_web/live/activity_live/show.ex:403
#: lib/who_need_help_web/live/activity_live/show.ex:639
#: lib/who_need_help_web/live/activity_live/show.ex:698
-#: lib/who_need_help_web/live/request_live/index.ex:737
+#: lib/who_need_help_web/live/request_live/index.ex:770
#, elixir-autogen, elixir-format
msgid "Community member"
msgstr "Член спільноти"
-#: lib/who_need_help_web/live/activity_live/index.ex:434
+#: lib/who_need_help_web/live/activity_live/index.ex:466
#: lib/who_need_help_web/live/activity_live/show.ex:343
-#: lib/who_need_help_web/live/request_live/index.ex:457
-#: lib/who_need_help_web/live/request_live/index.ex:458
+#: lib/who_need_help_web/live/request_live/index.ex:490
+#: lib/who_need_help_web/live/request_live/index.ex:491
#: lib/who_need_help_web/live/request_live/show.ex:1004
#: lib/who_need_help_web/live/request_live/show.ex:1023
#, elixir-autogen, elixir-format
@@ -590,13 +590,13 @@ msgstr ""
msgid "Confirming..."
msgstr "Підтвердження…"
-#: lib/who_need_help_web/live/activity_live/index.ex:455
+#: lib/who_need_help_web/live/activity_live/index.ex:487
#: lib/who_need_help_web/live/activity_live/new.ex:12
#, elixir-autogen, elixir-format
msgid "Create activity"
msgstr "Створити активність"
-#: lib/who_need_help_web/live/activity_live/index.ex:772
+#: lib/who_need_help_web/live/activity_live/index.ex:804
#: lib/who_need_help_web/live/activity_live/new.ex:180
#, elixir-autogen, elixir-format
msgid "Create an activity"
@@ -607,7 +607,7 @@ msgstr "Створити активність"
msgid "Create category"
msgstr "Створити категорію"
-#: lib/who_need_help_web/live/request_live/index.ex:488
+#: lib/who_need_help_web/live/request_live/index.ex:521
#, elixir-autogen, elixir-format
msgid "Create urgent request"
msgstr "Створити термінову заявку"
@@ -760,7 +760,7 @@ msgstr "Спливає:"
msgid "Explain what is already arranged and what the volunteer needs to do."
msgstr "Поясніть, що вже організовано й що має зробити волонтер."
-#: lib/who_need_help_web/live/activity_live/index.ex:446
+#: lib/who_need_help_web/live/activity_live/index.ex:478
#, elixir-autogen, elixir-format
msgid "Find people to join"
msgstr "Знайти компанію"
@@ -925,8 +925,8 @@ msgstr ""
"Android показує постійне сповіщення й може продовжувати передавання після "
"згортання. Після зупинення передавання вихідні координати видаляються."
-#: lib/who_need_help_web/live/request_live/index.ex:455
-#: lib/who_need_help_web/live/request_live/index.ex:456
+#: lib/who_need_help_web/live/request_live/index.ex:488
+#: lib/who_need_help_web/live/request_live/index.ex:489
#: lib/who_need_help_web/live/request_live/show.ex:1003
#, elixir-autogen, elixir-format
msgid "In progress"
@@ -1049,8 +1049,8 @@ msgstr "Позначити завершеною"
msgid "Match and coordinate"
msgstr "Знайдіть помічника й узгодьте деталі"
-#: lib/who_need_help_web/live/request_live/index.ex:453
-#: lib/who_need_help_web/live/request_live/index.ex:454
+#: lib/who_need_help_web/live/request_live/index.ex:486
+#: lib/who_need_help_web/live/request_live/index.ex:487
#: lib/who_need_help_web/live/request_live/show.ex:1001
#, elixir-autogen, elixir-format
msgid "Matched"
@@ -1079,7 +1079,7 @@ msgstr ""
msgid "Medicine requests"
msgstr "Запити на ліки"
-#: lib/who_need_help_web/live/activity_live/index.ex:462
+#: lib/who_need_help_web/live/activity_live/index.ex:494
#, elixir-autogen, elixir-format
msgid "Meet in public first, keep exact details inside the approved group, and leave or report anything that feels unsafe."
msgstr ""
@@ -1129,7 +1129,7 @@ msgstr "Модератор"
msgid "Moderator access is required."
msgstr "Потрібен доступ модератора."
-#: lib/who_need_help_web/live/request_live/index.ex:479
+#: lib/who_need_help_web/live/request_live/index.ex:512
#, elixir-autogen, elixir-format
msgid "NEARBY MUTUAL AID"
msgstr "ВЗАЄМОДОПОМОГА ПОРУЧ"
@@ -1207,9 +1207,9 @@ msgid "Not for emergencies"
msgstr "Не для екстрених ситуацій"
#: lib/who_need_help_web/live/notification_live.ex:736
-#: lib/who_need_help_web/live/request_live/index.ex:443
-#: lib/who_need_help_web/live/request_live/index.ex:444
-#: lib/who_need_help_web/live/request_live/index.ex:555
+#: lib/who_need_help_web/live/request_live/index.ex:476
+#: lib/who_need_help_web/live/request_live/index.ex:477
+#: lib/who_need_help_web/live/request_live/index.ex:588
#: lib/who_need_help_web/live/request_live/new.ex:449
#: lib/who_need_help_web/live/request_live/show.ex:992
#: lib/who_need_help_web/live/request_live/show.ex:993
@@ -1217,7 +1217,7 @@ msgstr "Не для екстрених ситуацій"
msgid "Now"
msgstr "Зараз"
-#: lib/who_need_help_web/live/activity_live/index.ex:444
+#: lib/who_need_help_web/live/activity_live/index.ex:476
#, elixir-autogen, elixir-format
msgid "OPTIONAL SOCIAL PLANS"
msgstr "НЕОБОВ’ЯЗКОВІ СПІЛЬНІ ПЛАНИ"
@@ -1237,14 +1237,14 @@ msgstr ""
#: lib/who_need_help/notifications/text.ex:140
#: lib/who_need_help_web/controllers/content_removal_html.ex:21
#: lib/who_need_help_web/controllers/support_html.ex:17
-#: lib/who_need_help_web/live/activity_live/index.ex:433
+#: lib/who_need_help_web/live/activity_live/index.ex:465
#: lib/who_need_help_web/live/activity_live/show.ex:342
#: lib/who_need_help_web/live/moderation_live.ex:636
#: lib/who_need_help_web/live/moderation_live.ex:817
#: lib/who_need_help_web/live/moderation_live.ex:941
#: lib/who_need_help_web/live/report_live.ex:104
-#: lib/who_need_help_web/live/request_live/index.ex:451
-#: lib/who_need_help_web/live/request_live/index.ex:452
+#: lib/who_need_help_web/live/request_live/index.ex:484
+#: lib/who_need_help_web/live/request_live/index.ex:485
#: lib/who_need_help_web/live/request_live/show.ex:1000
#: lib/who_need_help_web/live/support_operation_live.ex:310
#: lib/who_need_help_web/live/support_operation_live.ex:414
@@ -1709,9 +1709,9 @@ msgid "Save profile"
msgstr "Зберегти профіль"
#: lib/who_need_help_web/live/notification_live.ex:738
-#: lib/who_need_help_web/live/request_live/index.ex:447
-#: lib/who_need_help_web/live/request_live/index.ex:448
-#: lib/who_need_help_web/live/request_live/index.ex:557
+#: lib/who_need_help_web/live/request_live/index.ex:480
+#: lib/who_need_help_web/live/request_live/index.ex:481
+#: lib/who_need_help_web/live/request_live/index.ex:590
#: lib/who_need_help_web/live/request_live/new.ex:451
#: lib/who_need_help_web/live/request_live/show.ex:996
#: lib/who_need_help_web/live/request_live/show.ex:997
@@ -1985,7 +1985,7 @@ msgstr "Цей стан активності вже змінено."
msgid "This interaction is blocked."
msgstr "Ця взаємодія заблокована."
-#: lib/who_need_help_web/live/request_live/index.ex:495
+#: lib/who_need_help_web/live/request_live/index.ex:528
#: lib/who_need_help_web/live/request_live/show.ex:1071
#, elixir-autogen, elixir-format
msgid "This is not an emergency service. Contact local emergency services for immediate danger."
@@ -2038,9 +2038,9 @@ msgid "To see sent emails, visit"
msgstr "Щоб переглянути надіслані листи, відкрийте"
#: lib/who_need_help_web/live/notification_live.ex:737
-#: lib/who_need_help_web/live/request_live/index.ex:445
-#: lib/who_need_help_web/live/request_live/index.ex:446
-#: lib/who_need_help_web/live/request_live/index.ex:556
+#: lib/who_need_help_web/live/request_live/index.ex:478
+#: lib/who_need_help_web/live/request_live/index.ex:479
+#: lib/who_need_help_web/live/request_live/index.ex:589
#: lib/who_need_help_web/live/request_live/new.ex:450
#: lib/who_need_help_web/live/request_live/show.ex:994
#: lib/who_need_help_web/live/request_live/show.ex:995
@@ -2110,7 +2110,7 @@ msgid "Unique people"
msgstr "Унікальні люди"
#: lib/who_need_help_web/live/notification_live.ex:731
-#: lib/who_need_help_web/live/request_live/index.ex:552
+#: lib/who_need_help_web/live/request_live/index.ex:585
#: lib/who_need_help_web/live/request_live/new.ex:447
#, elixir-autogen, elixir-format
msgid "Urgency"
@@ -2253,7 +2253,7 @@ msgstr ""
"через підписку ChatGPT розробника. Ключ OpenAI API та резервний варіант API "
"з оплатою за використання не застосовуються."
-#: lib/who_need_help_web/live/request_live/index.ex:480
+#: lib/who_need_help_web/live/request_live/index.ex:513
#, elixir-autogen, elixir-format
msgid "Who needs help?"
msgstr "Хто потребує допомоги?"
@@ -2371,7 +2371,7 @@ msgid "Your review is revealed only after both participants submit."
msgstr ""
"Ваш відгук відкриється лише після того, як його надішлють обидва учасники."
-#: lib/who_need_help_web/live/request_live/index.ex:805
+#: lib/who_need_help_web/live/request_live/index.ex:838
#, elixir-autogen, elixir-format
msgid "Your trust summary"
msgstr "Ваші показники довіри"
@@ -2382,16 +2382,16 @@ msgstr "Ваші показники довіри"
msgid "before confirming."
msgstr "перед підтвердженням."
-#: lib/who_need_help_web/live/activity_live/index.ex:696
+#: lib/who_need_help_web/live/activity_live/index.ex:728
#: lib/who_need_help_web/live/category_proposal_live.ex:252
-#: lib/who_need_help_web/live/request_live/index.ex:736
+#: lib/who_need_help_web/live/request_live/index.ex:769
#, elixir-autogen, elixir-format
msgid "by %{name}"
msgstr "автор: %{name}"
#: lib/who_need_help_web/live/profile_live.ex:190
#: lib/who_need_help_web/live/public_profile_live.ex:133
-#: lib/who_need_help_web/live/request_live/index.ex:809
+#: lib/who_need_help_web/live/request_live/index.ex:842
#: lib/who_need_help_web/live/request_live/show.ex:1478
#, elixir-autogen, elixir-format
msgid "completed"
@@ -2427,7 +2427,7 @@ msgstr "організатор"
msgid "participants only"
msgstr "лише учасники"
-#: lib/who_need_help_web/live/request_live/index.ex:813
+#: lib/who_need_help_web/live/request_live/index.ex:846
#: lib/who_need_help_web/live/request_live/show.ex:1482
#, elixir-autogen, elixir-format
msgid "people"
@@ -2487,7 +2487,7 @@ msgstr "терміново"
#: lib/who_need_help_web/live/activity_live/show.ex:719
#: lib/who_need_help_web/live/profile_live.ex:285
#: lib/who_need_help_web/live/public_profile_live.ex:98
-#: lib/who_need_help_web/live/request_live/index.ex:817
+#: lib/who_need_help_web/live/request_live/index.ex:850
#: lib/who_need_help_web/live/request_live/show.ex:1142
#: lib/who_need_help_web/live/request_live/show.ex:1580
#, elixir-autogen, elixir-format
@@ -2520,8 +2520,8 @@ msgstr "← Заявки"
msgid "Approved"
msgstr "Схвалено"
-#: lib/who_need_help_web/live/request_live/index.ex:461
-#: lib/who_need_help_web/live/request_live/index.ex:462
+#: lib/who_need_help_web/live/request_live/index.ex:494
+#: lib/who_need_help_web/live/request_live/index.ex:495
#, elixir-autogen, elixir-format
msgid "Expired"
msgstr "Спливло"
@@ -2575,21 +2575,21 @@ msgstr "Незвична частота дій"
msgid "Load earlier messages"
msgstr "Завантажити попередні повідомлення"
-#: lib/who_need_help_web/live/activity_live/index.ex:708
-#: lib/who_need_help_web/live/activity_live/index.ex:801
+#: lib/who_need_help_web/live/activity_live/index.ex:740
+#: lib/who_need_help_web/live/activity_live/index.ex:833
#: lib/who_need_help_web/live/category_proposal_live.ex:284
#: lib/who_need_help_web/live/leaderboard_live.ex:91
#: lib/who_need_help_web/live/profile_live.ex:233
#: lib/who_need_help_web/live/profile_live.ex:363
#: lib/who_need_help_web/live/public_profile_live.ex:178
#: lib/who_need_help_web/live/report_live.ex:98
-#: lib/who_need_help_web/live/request_live/index.ex:886
+#: lib/who_need_help_web/live/request_live/index.ex:919
#, elixir-autogen, elixir-format
msgid "Load more"
msgstr "Завантажити ще"
#: lib/who_need_help_web/live/activity_live/show.ex:544
-#: lib/who_need_help_web/live/request_live/index.ex:782
+#: lib/who_need_help_web/live/request_live/index.ex:815
#: lib/who_need_help_web/live/request_live/show.ex:1324
#, elixir-autogen, elixir-format
msgid "The map is unavailable in this browser. Request details remain usable."
@@ -2957,7 +2957,7 @@ msgstr "Оскаржити рішення модерації"
msgid "Back to removal form"
msgstr "Повернутися до форми видалення"
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:34
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:43
#: lib/who_need_help_web/controllers/support_html/verify.html.heex:30
#, elixir-autogen, elixir-format
msgid "Back to support"
@@ -2969,7 +2969,8 @@ msgstr "Повернутися до підтримки"
msgid "CONTENT REMOVAL"
msgstr "ВИДАЛЕННЯ КОНТЕНТУ"
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:16
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:18
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:19
#, elixir-autogen, elixir-format
msgid "Check your email"
msgstr "Перевірте пошту"
@@ -3249,7 +3250,7 @@ msgstr "Повідомити про незаконний або шкідливи
msgid "Reported locations"
msgstr "Указані адреси контенту"
-#: lib/who_need_help_web/controllers/support_controller.ex:241
+#: lib/who_need_help_web/controllers/support_controller.ex:267
#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:6
#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:42
#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:128
@@ -3400,8 +3401,8 @@ msgstr "Загроза життю або безпеці"
msgid "Too many removal notices. Please try again later."
msgstr "Забагато повідомлень про видалення. Спробуйте пізніше."
-#: lib/who_need_help_web/controllers/support_controller.ex:42
-#: lib/who_need_help_web/controllers/support_controller.ex:84
+#: lib/who_need_help_web/controllers/support_controller.ex:45
+#: lib/who_need_help_web/controllers/support_controller.ex:90
#, elixir-autogen, elixir-format
msgid "Too many support requests. Please try again later."
msgstr "Забагато звернень до підтримки. Спробуйте пізніше."
@@ -3483,13 +3484,13 @@ msgstr "Номер вашого звернення: %{reference}"
msgid "Your relationship to the affected person or rights holder"
msgstr "Ваше відношення до постраждалої особи або правовласника"
-#: lib/who_need_help_web/controllers/support_controller.ex:55
-#: lib/who_need_help_web/controllers/support_controller.ex:66
+#: lib/who_need_help_web/controllers/support_controller.ex:58
+#: lib/who_need_help_web/controllers/support_controller.ex:69
#, elixir-autogen, elixir-format
msgid "Delete my Who Need Help account"
msgstr "Видалити мій обліковий запис Who Need Help"
-#: lib/who_need_help_web/controllers/support_controller.ex:56
+#: lib/who_need_help_web/controllers/support_controller.ex:59
#, elixir-autogen, elixir-format
msgid "Please delete my account and associated personal data."
msgstr "Будь ласка, видаліть мій обліковий запис і пов’язані персональні дані."
@@ -4426,7 +4427,7 @@ msgid "View my notices"
msgstr "Переглянути мої повідомлення"
#: lib/who_need_help_web/controllers/support_html/new.html.heex:13
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:32
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:41
#, elixir-autogen, elixir-format
msgid "View my requests"
msgstr "Переглянути мої звернення"
@@ -4541,37 +4542,37 @@ msgstr "Перетягніть, щоб перемістити точну точ
msgid "Exact point selected. Drag the red pin or click the map to move it."
msgstr "Точну точку вибрано. Перетягніть червоний маркер або натисніть на карту, щоб перемістити її."
-#: lib/who_need_help_web/live/activity_live/index.ex:515
-#: lib/who_need_help_web/live/request_live/index.ex:562
+#: lib/who_need_help_web/live/activity_live/index.ex:547
+#: lib/who_need_help_web/live/request_live/index.ex:595
#, elixir-autogen, elixir-format
msgid "Area or city"
msgstr "Район або місто"
-#: lib/who_need_help_web/live/activity_live/index.ex:638
-#: lib/who_need_help_web/live/request_live/index.ex:683
+#: lib/who_need_help_web/live/activity_live/index.ex:670
+#: lib/who_need_help_web/live/request_live/index.ex:716
#, elixir-autogen, elixir-format
msgid "Choose an area on the map"
msgstr "Виберіть область на карті"
-#: lib/who_need_help_web/live/request_live/index.ex:482
+#: lib/who_need_help_web/live/request_live/index.ex:515
#, elixir-autogen, elixir-format
msgid "Explore privacy-friendly areas and public exact meeting points chosen by requesters."
msgstr "Переглядайте приватні області та точні публічні місця зустрічі, вибрані авторами заявок."
-#: lib/who_need_help_web/live/activity_live/index.ex:605
-#: lib/who_need_help_web/live/request_live/index.ex:650
+#: lib/who_need_help_web/live/activity_live/index.ex:637
+#: lib/who_need_help_web/live/request_live/index.ex:683
#, elixir-autogen, elixir-format
msgid "List"
msgstr "Список"
-#: lib/who_need_help_web/live/request_live/index.ex:758
+#: lib/who_need_help_web/live/request_live/index.ex:791
#, elixir-autogen, elixir-format
msgid "Load more in this area"
msgstr "Завантажити ще в цій області"
#: lib/who_need_help_web/components/layouts/root.html.heex:5
-#: lib/who_need_help_web/live/activity_live/index.ex:625
-#: lib/who_need_help_web/live/request_live/index.ex:670
+#: lib/who_need_help_web/live/activity_live/index.ex:657
+#: lib/who_need_help_web/live/request_live/index.ex:703
#, elixir-autogen, elixir-format
msgid "Map"
msgstr "Карта"
@@ -4581,41 +4582,41 @@ msgstr "Карта"
msgid "Map preview"
msgstr "Попередній перегляд карти"
-#: lib/who_need_help_web/live/activity_live/index.ex:654
-#: lib/who_need_help_web/live/request_live/index.ex:699
+#: lib/who_need_help_web/live/activity_live/index.ex:686
+#: lib/who_need_help_web/live/request_live/index.ex:732
#, elixir-autogen, elixir-format
msgid "Move or zoom the map to search somewhere else."
msgstr "Перемістіть або масштабуйте карту, щоб шукати в іншому місці."
-#: lib/who_need_help_web/live/request_live/index.ex:696
+#: lib/who_need_help_web/live/request_live/index.ex:729
#, elixir-autogen, elixir-format
msgid "No open requests in this area"
msgstr "У цій області немає відкритих заявок"
-#: lib/who_need_help_web/live/request_live/index.ex:685
+#: lib/who_need_help_web/live/request_live/index.ex:718
#, elixir-autogen, elixir-format
msgid "Only requests from the selected map area will be loaded."
msgstr "Буде завантажено лише заявки з вибраної області карти."
#: lib/who_need_help_web/components/layouts/root.html.heex:8
-#: lib/who_need_help_web/live/request_live/index.ex:785
+#: lib/who_need_help_web/live/request_live/index.ex:818
#, elixir-autogen, elixir-format
msgid "Open request"
msgstr "Відкрити заявку"
-#: lib/who_need_help_web/live/activity_live/index.ex:516
-#: lib/who_need_help_web/live/request_live/index.ex:563
+#: lib/who_need_help_web/live/activity_live/index.ex:548
+#: lib/who_need_help_web/live/request_live/index.ex:596
#, elixir-autogen, elixir-format
msgid "Optional text search"
msgstr "Необов’язковий текстовий пошук"
-#: lib/who_need_help_web/live/request_live/index.ex:764
+#: lib/who_need_help_web/live/request_live/index.ex:797
#, elixir-autogen, elixir-format
msgid "Requests without map coordinates appear only when their public area description matches the text search."
msgstr "Заявки без координат на карті з’являються лише тоді, коли опис публічної області збігається з текстовим пошуком."
-#: lib/who_need_help_web/live/activity_live/index.ex:596
-#: lib/who_need_help_web/live/request_live/index.ex:641
+#: lib/who_need_help_web/live/activity_live/index.ex:628
+#: lib/who_need_help_web/live/request_live/index.ex:674
#, elixir-autogen, elixir-format
msgid "Results layout"
msgstr "Розташування результатів"
@@ -4625,21 +4626,21 @@ msgstr "Розташування результатів"
msgid "Static preview"
msgstr "Статичний перегляд"
-#: lib/who_need_help_web/live/activity_live/index.ex:615
-#: lib/who_need_help_web/live/request_live/index.ex:660
+#: lib/who_need_help_web/live/activity_live/index.ex:647
+#: lib/who_need_help_web/live/request_live/index.ex:693
#, elixir-autogen, elixir-format
msgid "Together"
msgstr "Разом"
#: lib/who_need_help_web/components/layouts/root.html.heex:6
#: lib/who_need_help_web/controllers/page_html/home.html.heex:166
-#: lib/who_need_help_web/live/activity_live/index.ex:731
+#: lib/who_need_help_web/live/activity_live/index.ex:763
#: lib/who_need_help_web/live/activity_live/new.ex:355
#: lib/who_need_help_web/live/activity_live/new.ex:414
#: lib/who_need_help_web/live/activity_live/show.ex:546
#: lib/who_need_help_web/live/notification_live.ex:590
#: lib/who_need_help_web/live/notification_live.ex:609
-#: lib/who_need_help_web/live/request_live/index.ex:784
+#: lib/who_need_help_web/live/request_live/index.ex:817
#: lib/who_need_help_web/live/request_live/new.ex:485
#: lib/who_need_help_web/live/request_live/new.ex:563
#: lib/who_need_help_web/live/request_live/show.ex:1326
@@ -4997,7 +4998,7 @@ msgid "Nearby alert deleted."
msgstr "Сповіщення поблизу видалено."
#: lib/who_need_help_web/live/notification_live.ex:453
-#: lib/who_need_help_web/live/request_live/index.ex:508
+#: lib/who_need_help_web/live/request_live/index.ex:541
#, elixir-autogen, elixir-format
msgid "Nearby requests"
msgstr "Заявки поблизу"
@@ -5320,7 +5321,7 @@ msgid "WebGL is unavailable in this browser, so the full map cannot start. Selec
msgstr "WebGL недоступний у цьому браузері, тому повна карта не може запуститися. Виберіть приклад нижче або спробуйте запустити карту ще раз."
#: lib/who_need_help_web/components/layouts/root.html.heex:7
-#: lib/who_need_help_web/live/activity_live/index.ex:732
+#: lib/who_need_help_web/live/activity_live/index.ex:764
#: lib/who_need_help_web/live/activity_live/show.ex:547
#: lib/who_need_help_web/live/request_live/show.ex:1327
#, elixir-autogen, elixir-format
@@ -5361,7 +5362,7 @@ msgid "Case status"
msgstr "Статус звернення"
#: lib/who_need_help_web/controllers/page_html/child_safety.html.heex:42
-#: lib/who_need_help_web/controllers/support_controller.ex:242
+#: lib/who_need_help_web/controllers/support_controller.ex:268
#: lib/who_need_help_web/live/report_live.ex:50
#, elixir-autogen, elixir-format
msgid "Contact support"
@@ -5383,8 +5384,8 @@ msgstr "Повідомлення про видалення контенту от
msgid "Conversation"
msgstr "Розмова"
-#: lib/who_need_help_web/controllers/support_controller.ex:152
-#: lib/who_need_help_web/controllers/support_controller.ex:168
+#: lib/who_need_help_web/controllers/support_controller.ex:158
+#: lib/who_need_help_web/controllers/support_controller.ex:174
#: lib/who_need_help_web/live/support_case_live.ex:73
#, elixir-autogen, elixir-format
msgid "Could not send your message. Please check it and try again."
@@ -5447,7 +5448,7 @@ msgstr "Скарг не подано"
msgid "No status history is available for this older case."
msgstr "Для цього старого звернення історія статусів недоступна."
-#: lib/who_need_help_web/controllers/support_controller.ex:187
+#: lib/who_need_help_web/controllers/support_controller.ex:193
#: lib/who_need_help_web/live/support_case_live.ex:95
#, elixir-autogen, elixir-format
msgid "Only a resolved or closed request can be reopened."
@@ -5552,12 +5553,12 @@ msgstr "Надіслати повідомлення"
msgid "Status history"
msgstr "Історія статусів"
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:15
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:17
#, elixir-autogen, elixir-format
msgid "Support request created"
msgstr "Звернення до підтримки створено"
-#: lib/who_need_help_web/controllers/support_controller.ex:198
+#: lib/who_need_help_web/controllers/support_controller.ex:214
#, elixir-autogen, elixir-format
msgid "Support request received"
msgstr "Звернення до підтримки отримано"
@@ -5593,7 +5594,7 @@ msgstr "ДОВІРА ТА БЕЗПЕКА"
msgid "The requester states whether the medicine is paid, reserved, or still needs to be purchased."
msgstr "Автор заявки вказує, чи ліки вже оплачені, зарезервовані або їх ще потрібно придбати."
-#: lib/who_need_help_web/controllers/support_controller.ex:182
+#: lib/who_need_help_web/controllers/support_controller.ex:188
#: lib/who_need_help_web/live/support_case_live.ex:88
#, elixir-autogen, elixir-format
msgid "The support request is open again."
@@ -5621,7 +5622,7 @@ msgstr "У цій приватній історії показано ваші с
msgid "This support request is no longer available."
msgstr "Це звернення до підтримки більше не доступне."
-#: lib/who_need_help_web/controllers/support_controller.ex:158
+#: lib/who_need_help_web/controllers/support_controller.ex:164
#: lib/who_need_help_web/live/support_case_live.ex:60
#, elixir-autogen, elixir-format
msgid "Too many messages. Please try again later."
@@ -5632,7 +5633,7 @@ msgstr "Забагато повідомлень. Спробуйте пізніш
msgid "Use the dedicated urgent process. Never upload or reproduce the image or video."
msgstr "Скористайтеся спеціальною терміновою процедурою. Ніколи не завантажуйте й не відтворюйте зображення або відео."
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:19
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:23
#, elixir-autogen, elixir-format
msgid "We created support request %{reference} and added it to your account. You can open it now and return to it from My support requests."
msgstr "Ми створили звернення %{reference} і додали його до вашого облікового запису. Його можна відкрити зараз або повернутися до нього з розділу «Мої звернення»."
@@ -5658,7 +5659,7 @@ msgstr "Ви"
msgid "Your content-removal notices"
msgstr "Ваші повідомлення про видалення контенту"
-#: lib/who_need_help_web/controllers/support_controller.ex:145
+#: lib/who_need_help_web/controllers/support_controller.ex:151
#: lib/who_need_help_web/live/support_case_live.ex:53
#, elixir-autogen, elixir-format
msgid "Your message was sent to support."
@@ -5822,7 +5823,7 @@ msgstr "Тепер ваш відгук видно"
msgid "Delivery settings"
msgstr "Налаштування доставки"
-#: lib/who_need_help_web/live/request_live/index.ex:741
+#: lib/who_need_help_web/live/request_live/index.ex:774
#, elixir-autogen, elixir-format
msgid "Expires"
msgstr "Спливає"
@@ -5842,22 +5843,22 @@ msgstr "Сповіщення поруч"
msgid "Notification sections"
msgstr "Розділи сповіщень"
-#: lib/who_need_help_web/live/activity_live/index.ex:724
+#: lib/who_need_help_web/live/activity_live/index.ex:756
#, elixir-autogen, elixir-format
msgid "%{count} activities"
msgstr "Активностей: %{count}"
-#: lib/who_need_help_web/live/request_live/index.ex:786
+#: lib/who_need_help_web/live/request_live/index.ex:819
#, elixir-autogen, elixir-format
msgid "%{count} requests"
msgstr "Запитів: %{count}"
-#: lib/who_need_help_web/live/activity_live/index.ex:767
+#: lib/who_need_help_web/live/activity_live/index.ex:799
#, elixir-autogen, elixir-format
msgid "Activities you create or ask to join will appear here. Leaving removes an activity from this list."
msgstr "Створені вами активності й ті, до яких ви попросили приєднатися, з’являться тут. Після виходу активність зникне зі списку."
-#: lib/who_need_help_web/live/activity_live/index.ex:470
+#: lib/who_need_help_web/live/activity_live/index.ex:502
#, elixir-autogen, elixir-format
msgid "Activity views"
msgstr "Розділи активностей"
@@ -5872,12 +5873,12 @@ msgstr "Оберіть категорію та зрозуміло опишіть
msgid "Confirm the safety guidance before asking nearby people for help."
msgstr "Підтвердьте правила безпеки перед публікацією запиту."
-#: lib/who_need_help_web/live/request_live/index.ex:834
+#: lib/who_need_help_web/live/request_live/index.ex:867
#, elixir-autogen, elixir-format
msgid "Create request"
msgstr "Створити запит"
-#: lib/who_need_help_web/live/request_live/index.ex:857
+#: lib/who_need_help_web/live/request_live/index.ex:890
#, elixir-autogen, elixir-format
msgid "Create your first request"
msgstr "Створити перший запит"
@@ -5892,43 +5893,43 @@ msgstr "Що потрібно зробити"
msgid "Location"
msgstr "Місце"
-#: lib/who_need_help_web/live/activity_live/index.ex:476
+#: lib/who_need_help_web/live/activity_live/index.ex:508
#, elixir-autogen, elixir-format
msgid "My activities"
msgstr "Мої активності"
-#: lib/who_need_help_web/live/request_live/index.ex:509
-#: lib/who_need_help_web/live/request_live/index.ex:828
+#: lib/who_need_help_web/live/request_live/index.ex:542
+#: lib/who_need_help_web/live/request_live/index.ex:861
#, elixir-autogen, elixir-format
msgid "My requests"
msgstr "Мої запити"
-#: lib/who_need_help_web/live/activity_live/index.ex:475
+#: lib/who_need_help_web/live/activity_live/index.ex:507
#, elixir-autogen, elixir-format
msgid "Nearby activities"
msgstr "Активності поруч"
-#: lib/who_need_help_web/live/activity_live/index.ex:765
+#: lib/who_need_help_web/live/activity_live/index.ex:797
#, elixir-autogen, elixir-format
msgid "No activities yet"
msgstr "Активностей поки немає"
-#: lib/who_need_help_web/live/activity_live/index.ex:651
+#: lib/who_need_help_web/live/activity_live/index.ex:683
#, elixir-autogen, elixir-format
msgid "No open activities in this area"
msgstr "У цій області немає відкритих активностей"
-#: lib/who_need_help_web/live/request_live/index.ex:852
+#: lib/who_need_help_web/live/request_live/index.ex:885
#, elixir-autogen, elixir-format
msgid "No requests yet"
msgstr "Запитів поки немає"
-#: lib/who_need_help_web/live/activity_live/index.ex:640
+#: lib/who_need_help_web/live/activity_live/index.ex:672
#, elixir-autogen, elixir-format
msgid "Only activities from the selected map area will be loaded."
msgstr "Буде завантажено лише активності з вибраної області карти."
-#: lib/who_need_help_web/live/activity_live/index.ex:723
+#: lib/who_need_help_web/live/activity_live/index.ex:755
#, elixir-autogen, elixir-format
msgid "Open activity"
msgstr "Відкрити активність"
@@ -5938,12 +5939,12 @@ msgstr "Відкрити активність"
msgid "Request form steps"
msgstr "Кроки створення запиту"
-#: lib/who_need_help_web/live/request_live/index.ex:503
+#: lib/who_need_help_web/live/request_live/index.ex:536
#, elixir-autogen, elixir-format
msgid "Request views"
msgstr "Розділи запитів"
-#: lib/who_need_help_web/live/request_live/index.ex:854
+#: lib/who_need_help_web/live/request_live/index.ex:887
#, elixir-autogen, elixir-format
msgid "Requests you publish or accept will appear here."
msgstr "Тут з’являться запити, які ви створили або прийняли."
@@ -5968,7 +5969,7 @@ msgstr "Укажіть терміновість і час закриття за
msgid "Share only as much location detail as helpers need."
msgstr "Покажіть помічникам лише необхідну інформацію про місце."
-#: lib/who_need_help_web/live/activity_live/index.ex:729
+#: lib/who_need_help_web/live/activity_live/index.ex:761
#, elixir-autogen, elixir-format
msgid "The map is unavailable in this browser. Activity details remain usable."
msgstr "Карта недоступна в цьому браузері. Інформація про активності залишається доступною."
@@ -5978,7 +5979,7 @@ msgstr "Карта недоступна в цьому браузері. Інфо
msgid "Timing"
msgstr "Час"
-#: lib/who_need_help_web/live/request_live/index.ex:830
+#: lib/who_need_help_web/live/request_live/index.ex:863
#, elixir-autogen, elixir-format
msgid "Track requests you created and requests you accepted as a helper."
msgstr "Стежте за створеними запитами й тими, які ви прийняли як помічник."
@@ -6633,27 +6634,27 @@ msgstr "Що мають бачити люди до схвалення?"
msgid "Choose the meeting point on the map or use your current location."
msgstr "Оберіть місце зустрічі на карті або скористайтеся своїм поточним місцезнаходженням."
-#: lib/who_need_help_web/live/activity_live/index.ex:555
-#: lib/who_need_help_web/live/activity_live/index.ex:650
-#: lib/who_need_help_web/live/activity_live/index.ex:757
-#: lib/who_need_help_web/live/request_live/index.ex:600
-#: lib/who_need_help_web/live/request_live/index.ex:695
-#: lib/who_need_help_web/live/request_live/index.ex:844
+#: lib/who_need_help_web/live/activity_live/index.ex:587
+#: lib/who_need_help_web/live/activity_live/index.ex:682
+#: lib/who_need_help_web/live/activity_live/index.ex:789
+#: lib/who_need_help_web/live/request_live/index.ex:633
+#: lib/who_need_help_web/live/request_live/index.ex:728
+#: lib/who_need_help_web/live/request_live/index.ex:877
#, elixir-autogen, elixir-format
msgid "Loading results…"
msgstr "Завантажуємо результати…"
-#: lib/who_need_help_web/live/activity_live/index.ex:726
+#: lib/who_need_help_web/live/activity_live/index.ex:758
#, elixir-autogen, elixir-format
msgid "Maximum zoom reached. Matching activities are listed beside the map."
msgstr "Досягнуто максимального масштабу. Відповідні заходи перелічено поруч із картою."
-#: lib/who_need_help_web/live/request_live/index.ex:788
+#: lib/who_need_help_web/live/request_live/index.ex:821
#, elixir-autogen, elixir-format
msgid "Maximum zoom reached. Matching requests are listed beside the map."
msgstr "Досягнуто максимального масштабу. Відповідні заявки перелічено поруч із картою."
-#: lib/who_need_help_web/live/activity_live/index.ex:536
+#: lib/who_need_help_web/live/activity_live/index.ex:568
#, elixir-autogen, elixir-format
msgid "%{count} activity in the selected map area"
msgid_plural "%{count} activities in the selected map area"
@@ -6661,7 +6662,7 @@ msgstr[0] "%{count} подія у вибраній області карти"
msgstr[1] "%{count} події у вибраній області карти"
msgstr[2] "%{count} подій у вибраній області карти"
-#: lib/who_need_help_web/live/request_live/index.ex:581
+#: lib/who_need_help_web/live/request_live/index.ex:614
#, elixir-autogen, elixir-format
msgid "%{count} request in the selected map area"
msgid_plural "%{count} requests in the selected map area"
@@ -6669,57 +6670,57 @@ msgstr[0] "%{count} заявка у вибраній області карти"
msgstr[1] "%{count} заявки у вибраній області карти"
msgstr[2] "%{count} заявок у вибраній області карти"
-#: lib/who_need_help_web/live/activity_live/index.ex:561
-#: lib/who_need_help_web/live/request_live/index.ex:606
+#: lib/who_need_help_web/live/activity_live/index.ex:593
+#: lib/who_need_help_web/live/request_live/index.ex:639
#, elixir-autogen, elixir-format
msgid "Map moved. Results still show the previous area."
msgstr "Карту переміщено. Поки показано результати для попередньої області."
-#: lib/who_need_help_web/live/activity_live/index.ex:579
+#: lib/who_need_help_web/live/activity_live/index.ex:611
#, elixir-autogen, elixir-format
msgid "Move or zoom the map, then choose “Show activities in this area”."
msgstr "Перемістіть або масштабуйте карту, потім натисніть «Показати події в цій області»."
-#: lib/who_need_help_web/live/request_live/index.ex:624
+#: lib/who_need_help_web/live/request_live/index.ex:657
#, elixir-autogen, elixir-format
msgid "Move or zoom the map, then choose “Show requests in this area”."
msgstr "Перемістіть або масштабуйте карту, потім натисніть «Показати заявки в цій області»."
-#: lib/who_need_help_web/live/activity_live/index.ex:573
-#: lib/who_need_help_web/live/request_live/index.ex:618
+#: lib/who_need_help_web/live/activity_live/index.ex:605
+#: lib/who_need_help_web/live/request_live/index.ex:651
#, elixir-autogen, elixir-format
msgid "Results update automatically after you stop moving the map."
msgstr "Результати оновляться автоматично, коли ви завершите переміщення карти."
-#: lib/who_need_help_web/live/activity_live/index.ex:743
+#: lib/who_need_help_web/live/activity_live/index.ex:775
#, elixir-autogen, elixir-format
msgid "Show activities in this area"
msgstr "Показати події в цій області"
-#: lib/who_need_help_web/live/request_live/index.ex:800
+#: lib/who_need_help_web/live/request_live/index.ex:833
#, elixir-autogen, elixir-format
msgid "Show requests in this area"
msgstr "Показати заявки в цій області"
-#: lib/who_need_help_web/live/activity_live/index.ex:545
-#: lib/who_need_help_web/live/request_live/index.ex:590
+#: lib/who_need_help_web/live/activity_live/index.ex:577
+#: lib/who_need_help_web/live/request_live/index.ex:623
#, elixir-autogen, elixir-format
msgid "Showing %{shown} of %{count} in the list."
msgstr "У списку показано %{shown} із %{count}."
-#: lib/who_need_help_web/live/activity_live/index.ex:593
-#: lib/who_need_help_web/live/request_live/index.ex:638
+#: lib/who_need_help_web/live/activity_live/index.ex:625
+#: lib/who_need_help_web/live/request_live/index.ex:671
#, elixir-autogen, elixir-format
msgid "Update results as map moves"
msgstr "Оновлювати результати під час переміщення карти"
-#: lib/who_need_help_web/live/activity_live/index.ex:567
-#: lib/who_need_help_web/live/request_live/index.ex:612
+#: lib/who_need_help_web/live/activity_live/index.ex:599
+#: lib/who_need_help_web/live/request_live/index.ex:645
#, elixir-autogen, elixir-format
msgid "Updating results for the visible map area…"
msgstr "Оновлюємо результати для видимої області карти…"
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:11
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:12
#, elixir-autogen, elixir-format
msgid "EMAIL CONFIRMATION REQUIRED"
msgstr "ПОТРІБНЕ ПІДТВЕРДЖЕННЯ EMAIL"
@@ -6739,7 +6740,7 @@ msgstr "Очікуємо дозволу на геолокацію…"
msgid "Pending email verification"
msgstr "Очікує підтвердження email"
-#: lib/who_need_help_web/controllers/support_html/received.html.heex:25
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:34
#, elixir-autogen, elixir-format
msgid "We saved pending request %{reference}. It is not in the support queue yet. Open the private link sent to the contact email to verify the address and submit the request for review."
msgstr "Ми зберегли запит %{reference}, що очікує підтвердження. Він ще не потрапив до черги підтримки. Відкрийте приватне посилання з листа, щоб підтвердити адресу та надіслати запит на розгляд."
@@ -7804,7 +7805,7 @@ msgstr "У вас немає дозволу на цю дію. Для зміни
msgid "Your email was confirmed and the notice was sent for review."
msgstr "Ваш email підтверджено, повідомлення надіслано на перевірку."
-#: lib/who_need_help_web/controllers/support_controller.ex:122
+#: lib/who_need_help_web/controllers/support_controller.ex:128
#, elixir-autogen, elixir-format
msgid "Your email was confirmed and the request was sent to support."
msgstr "Ваш email підтверджено, звернення надіслано до підтримки."
@@ -8002,7 +8003,7 @@ msgstr "Скопіюйте цю безпечну адресу та вставт
msgid "Confirm content-removal notice"
msgstr "Підтвердити повідомлення про видалення контенту"
-#: lib/who_need_help_web/controllers/support_controller.ex:106
+#: lib/who_need_help_web/controllers/support_controller.ex:112
#: lib/who_need_help_web/controllers/support_html/verify.html.heex:9
#: lib/who_need_help_web/controllers/support_html/verify.html.heex:23
#, elixir-autogen, elixir-format
@@ -8028,3 +8029,13 @@ msgstr "Якщо ви не надсилали це повідомлення, з
#, elixir-autogen, elixir-format
msgid "If you did not submit this request, close this page. Nothing will be sent to support."
msgstr "Якщо ви не надсилали це звернення, закрийте сторінку. До підтримки нічого не надійде."
+
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:29
+#, elixir-autogen, elixir-format
+msgid "If that address belongs to a Who Need Help account, we sent a private confirmation link to the account email. No support request is created until that link is confirmed."
+msgstr "Якщо ця адреса належить обліковому запису Who Need Help, ми надіслали приватне посилання для підтвердження на електронну пошту облікового запису. Звернення до підтримки буде створено лише після переходу за цим посиланням."
+
+#: lib/who_need_help_web/controllers/support_html/received.html.heex:11
+#, elixir-autogen, elixir-format
+msgid "REQUEST CHECKED"
+msgstr "ЗАПИТ ПЕРЕВІРЕНО"
diff --git a/priv/repo/migration_application_compatibility.tsv b/priv/repo/migration_application_compatibility.tsv
index aaa87e3..c4d0ed0 100644
--- a/priv/repo/migration_application_compatibility.tsv
+++ b/priv/repo/migration_application_compatibility.tsv
@@ -11,3 +11,5 @@
20260801200302 application_safe additive generated public discovery coordinate columns
20260812120611 application_safe dropping the delivery-channel check allows inbox-only subscriptions that old code can still read safely
20260813194249 application_safe additive nullable inbound-provider identity columns and a partial unique index are ignored by the previous application
+20260827173037 application_safe additive discovery rollup tables and maintenance triggers are ignored by the previous application while continuing to follow its writes
+20260827180241 forward_only pending lifecycle uniqueness can reject duplicate account access or deletion requests that the previous application may still attempt to insert
diff --git a/priv/repo/migrations/20260827173037_add_discovery_rollups.exs b/priv/repo/migrations/20260827173037_add_discovery_rollups.exs
new file mode 100644
index 0000000..80c7942
--- /dev/null
+++ b/priv/repo/migrations/20260827173037_add_discovery_rollups.exs
@@ -0,0 +1,469 @@
+defmodule WhoNeedHelp.Repo.Migrations.AddDiscoveryRollups do
+ use Ecto.Migration
+
+ @maximum_rollup_level 12
+
+ def up do
+ create table(:help_discovery_rollups, primary_key: false) do
+ add(:level, :smallint, null: false)
+ add(:cell_x, :integer, null: false)
+ add(:cell_y, :integer, null: false)
+ add(:category_id, :binary_id, null: false)
+ add(:urgency, :string, null: false)
+ add(:visible_until_hour, :utc_datetime, null: false)
+ add(:point_count, :bigint, null: false)
+ end
+
+ create(
+ constraint(:help_discovery_rollups, :help_discovery_rollups_level_range,
+ check: "level BETWEEN 0 AND #{@maximum_rollup_level}"
+ )
+ )
+
+ create(
+ constraint(:help_discovery_rollups, :help_discovery_rollups_non_negative_count,
+ check: "point_count >= 0"
+ )
+ )
+
+ create table(:activity_discovery_rollups, primary_key: false) do
+ add(:level, :smallint, null: false)
+ add(:cell_x, :integer, null: false)
+ add(:cell_y, :integer, null: false)
+ add(:category_id, :binary_id, null: false)
+ add(:visible_until_hour, :utc_datetime, null: false)
+ add(:point_count, :bigint, null: false)
+ end
+
+ create(
+ constraint(:activity_discovery_rollups, :activity_discovery_rollups_level_range,
+ check: "level BETWEEN 0 AND #{@maximum_rollup_level}"
+ )
+ )
+
+ create(
+ constraint(
+ :activity_discovery_rollups,
+ :activity_discovery_rollups_non_negative_count,
+ check: "point_count >= 0"
+ )
+ )
+
+ # The initial snapshot and trigger installation must describe one database
+ # state. Reads remain available, while writes to either discovery source wait
+ # until this migration transaction commits.
+ execute("LOCK TABLE help_requests, activities IN SHARE ROW EXCLUSIVE MODE")
+
+ execute(rebuild_help_rollups_sql())
+ execute(rebuild_activity_rollups_sql())
+ execute(validate_initial_rollups_sql())
+
+ create(
+ unique_index(
+ :help_discovery_rollups,
+ [:level, :cell_x, :cell_y, :category_id, :urgency, :visible_until_hour],
+ name: :help_discovery_rollups_identity
+ )
+ )
+
+ create(
+ index(
+ :help_discovery_rollups,
+ [:level, :cell_x, :cell_y, :visible_until_hour],
+ include: [:category_id, :urgency, :point_count],
+ name: :help_discovery_rollups_viewport
+ )
+ )
+
+ create(
+ index(
+ :help_discovery_rollups,
+ [:level, :category_id, :urgency, :cell_x, :cell_y, :visible_until_hour],
+ include: [:point_count],
+ name: :help_discovery_rollups_filtered_viewport
+ )
+ )
+
+ create(
+ unique_index(
+ :activity_discovery_rollups,
+ [:level, :cell_x, :cell_y, :category_id, :visible_until_hour],
+ name: :activity_discovery_rollups_identity
+ )
+ )
+
+ create(
+ index(
+ :activity_discovery_rollups,
+ [:level, :cell_x, :cell_y, :visible_until_hour],
+ include: [:category_id, :point_count],
+ name: :activity_discovery_rollups_viewport
+ )
+ )
+
+ create(
+ index(
+ :activity_discovery_rollups,
+ [:level, :category_id, :cell_x, :cell_y, :visible_until_hour],
+ include: [:point_count],
+ name: :activity_discovery_rollups_filtered_viewport
+ )
+ )
+
+ execute(help_trigger_function("insert", help_change_source("new_rows", 1)))
+ execute(help_trigger_function("delete", help_change_source("old_rows", -1)))
+
+ execute(
+ help_trigger_function(
+ "update",
+ help_change_source("new_rows", 1) <>
+ " UNION ALL " <>
+ help_change_source("old_rows", -1)
+ )
+ )
+
+ execute(activity_trigger_function("insert", activity_change_source("new_rows", 1)))
+ execute(activity_trigger_function("delete", activity_change_source("old_rows", -1)))
+
+ execute(
+ activity_trigger_function(
+ "update",
+ activity_change_source("new_rows", 1) <>
+ " UNION ALL " <>
+ activity_change_source("old_rows", -1)
+ )
+ )
+
+ execute("""
+ CREATE TRIGGER help_discovery_rollups_after_insert
+ AFTER INSERT ON help_requests
+ REFERENCING NEW TABLE AS new_rows
+ FOR EACH STATEMENT EXECUTE FUNCTION wnh_help_discovery_rollups_insert()
+ """)
+
+ execute("""
+ CREATE TRIGGER help_discovery_rollups_after_delete
+ AFTER DELETE ON help_requests
+ REFERENCING OLD TABLE AS old_rows
+ FOR EACH STATEMENT EXECUTE FUNCTION wnh_help_discovery_rollups_delete()
+ """)
+
+ execute("""
+ CREATE TRIGGER help_discovery_rollups_after_update
+ AFTER UPDATE ON help_requests
+ REFERENCING OLD TABLE AS old_rows NEW TABLE AS new_rows
+ FOR EACH STATEMENT EXECUTE FUNCTION wnh_help_discovery_rollups_update()
+ """)
+
+ execute("""
+ CREATE TRIGGER activity_discovery_rollups_after_insert
+ AFTER INSERT ON activities
+ REFERENCING NEW TABLE AS new_rows
+ FOR EACH STATEMENT EXECUTE FUNCTION wnh_activity_discovery_rollups_insert()
+ """)
+
+ execute("""
+ CREATE TRIGGER activity_discovery_rollups_after_delete
+ AFTER DELETE ON activities
+ REFERENCING OLD TABLE AS old_rows
+ FOR EACH STATEMENT EXECUTE FUNCTION wnh_activity_discovery_rollups_delete()
+ """)
+
+ execute("""
+ CREATE TRIGGER activity_discovery_rollups_after_update
+ AFTER UPDATE ON activities
+ REFERENCING OLD TABLE AS old_rows NEW TABLE AS new_rows
+ FOR EACH STATEMENT EXECUTE FUNCTION wnh_activity_discovery_rollups_update()
+ """)
+ end
+
+ def down do
+ execute("DROP TRIGGER IF EXISTS activity_discovery_rollups_after_update ON activities")
+ execute("DROP TRIGGER IF EXISTS activity_discovery_rollups_after_delete ON activities")
+ execute("DROP TRIGGER IF EXISTS activity_discovery_rollups_after_insert ON activities")
+ execute("DROP TRIGGER IF EXISTS help_discovery_rollups_after_update ON help_requests")
+ execute("DROP TRIGGER IF EXISTS help_discovery_rollups_after_delete ON help_requests")
+ execute("DROP TRIGGER IF EXISTS help_discovery_rollups_after_insert ON help_requests")
+
+ for operation <- ~w(insert delete update) do
+ execute("DROP FUNCTION IF EXISTS wnh_activity_discovery_rollups_#{operation}()")
+ execute("DROP FUNCTION IF EXISTS wnh_help_discovery_rollups_#{operation}()")
+ end
+
+ drop(table(:activity_discovery_rollups))
+ drop(table(:help_discovery_rollups))
+ end
+
+ defp rebuild_help_rollups_sql do
+ """
+ INSERT INTO help_discovery_rollups
+ (level, cell_x, cell_y, category_id, urgency, visible_until_hour, point_count)
+ SELECT
+ level,
+ #{cell_x_sql("request.public_longitude", "level")},
+ #{cell_y_sql("request.public_latitude", "level")},
+ request.category_id,
+ request.urgency,
+ date_trunc('hour', request.expires_at),
+ count(*)::bigint
+ FROM help_requests AS request
+ CROSS JOIN generate_series(0, #{@maximum_rollup_level}) AS level
+ WHERE #{eligible_help_sql("request")}
+ GROUP BY level, cell_x, cell_y, request.category_id, request.urgency,
+ date_trunc('hour', request.expires_at)
+ """
+ end
+
+ defp rebuild_activity_rollups_sql do
+ """
+ INSERT INTO activity_discovery_rollups
+ (level, cell_x, cell_y, category_id, visible_until_hour, point_count)
+ SELECT
+ level,
+ #{cell_x_sql("activity.public_longitude", "level")},
+ #{cell_y_sql("activity.public_latitude", "level")},
+ activity.category_id,
+ date_trunc('hour', least(activity.starts_at, activity.join_deadline)),
+ count(*)::bigint
+ FROM activities AS activity
+ CROSS JOIN generate_series(0, #{@maximum_rollup_level}) AS level
+ WHERE #{eligible_activity_sql("activity")}
+ GROUP BY level, cell_x, cell_y, activity.category_id,
+ date_trunc('hour', least(activity.starts_at, activity.join_deadline))
+ """
+ end
+
+ defp help_change_source(relation, delta) do
+ """
+ SELECT
+ #{delta}::bigint AS delta,
+ request.public_longitude,
+ request.public_latitude,
+ request.category_id,
+ request.urgency,
+ date_trunc('hour', request.expires_at) AS visible_until_hour
+ FROM #{relation} AS request
+ WHERE #{eligible_help_sql("request")}
+ """
+ end
+
+ defp activity_change_source(relation, delta) do
+ """
+ SELECT
+ #{delta}::bigint AS delta,
+ activity.public_longitude,
+ activity.public_latitude,
+ activity.category_id,
+ date_trunc('hour', least(activity.starts_at, activity.join_deadline)) AS visible_until_hour
+ FROM #{relation} AS activity
+ WHERE #{eligible_activity_sql("activity")}
+ """
+ end
+
+ defp help_trigger_function(operation, changes_sql) do
+ """
+ CREATE FUNCTION wnh_help_discovery_rollups_#{operation}()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $function$
+ BEGIN
+ WITH changes AS (
+ #{changes_sql}
+ ), expanded AS (
+ SELECT
+ changes.delta,
+ level,
+ #{cell_x_sql("changes.public_longitude", "level")},
+ #{cell_y_sql("changes.public_latitude", "level")},
+ changes.category_id,
+ changes.urgency,
+ changes.visible_until_hour
+ FROM changes
+ CROSS JOIN generate_series(0, #{@maximum_rollup_level}) AS level
+ ), deltas AS (
+ SELECT
+ level,
+ cell_x,
+ cell_y,
+ category_id,
+ urgency,
+ visible_until_hour,
+ sum(delta)::bigint AS point_count
+ FROM expanded
+ GROUP BY level, cell_x, cell_y, category_id, urgency, visible_until_hour
+ HAVING sum(delta) <> 0
+ )
+ MERGE INTO help_discovery_rollups AS target
+ USING deltas AS source
+ ON target.level = source.level
+ AND target.cell_x = source.cell_x
+ AND target.cell_y = source.cell_y
+ AND target.category_id = source.category_id
+ AND target.urgency = source.urgency
+ AND target.visible_until_hour = source.visible_until_hour
+ WHEN MATCHED AND target.point_count + source.point_count > 0 THEN
+ UPDATE SET point_count = target.point_count + source.point_count
+ WHEN MATCHED THEN DELETE
+ WHEN NOT MATCHED AND source.point_count > 0 THEN
+ INSERT
+ (level, cell_x, cell_y, category_id, urgency, visible_until_hour, point_count)
+ VALUES
+ (source.level, source.cell_x, source.cell_y, source.category_id,
+ source.urgency, source.visible_until_hour, source.point_count);
+
+ RETURN NULL;
+ END
+ $function$
+ """
+ end
+
+ defp activity_trigger_function(operation, changes_sql) do
+ """
+ CREATE FUNCTION wnh_activity_discovery_rollups_#{operation}()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $function$
+ BEGIN
+ WITH changes AS (
+ #{changes_sql}
+ ), expanded AS (
+ SELECT
+ changes.delta,
+ level,
+ #{cell_x_sql("changes.public_longitude", "level")},
+ #{cell_y_sql("changes.public_latitude", "level")},
+ changes.category_id,
+ changes.visible_until_hour
+ FROM changes
+ CROSS JOIN generate_series(0, #{@maximum_rollup_level}) AS level
+ ), deltas AS (
+ SELECT
+ level,
+ cell_x,
+ cell_y,
+ category_id,
+ visible_until_hour,
+ sum(delta)::bigint AS point_count
+ FROM expanded
+ GROUP BY level, cell_x, cell_y, category_id, visible_until_hour
+ HAVING sum(delta) <> 0
+ )
+ MERGE INTO activity_discovery_rollups AS target
+ USING deltas AS source
+ ON target.level = source.level
+ AND target.cell_x = source.cell_x
+ AND target.cell_y = source.cell_y
+ AND target.category_id = source.category_id
+ AND target.visible_until_hour = source.visible_until_hour
+ WHEN MATCHED AND target.point_count + source.point_count > 0 THEN
+ UPDATE SET point_count = target.point_count + source.point_count
+ WHEN MATCHED THEN DELETE
+ WHEN NOT MATCHED AND source.point_count > 0 THEN
+ INSERT (level, cell_x, cell_y, category_id, visible_until_hour, point_count)
+ VALUES
+ (source.level, source.cell_x, source.cell_y, source.category_id,
+ source.visible_until_hour, source.point_count);
+
+ RETURN NULL;
+ END
+ $function$
+ """
+ end
+
+ defp eligible_help_sql(alias_name) do
+ """
+ #{alias_name}.status = 'open'
+ AND #{alias_name}.hidden_at IS NULL
+ AND #{alias_name}.location_visibility <> 'hidden'
+ AND #{alias_name}.public_longitude IS NOT NULL
+ AND #{alias_name}.public_latitude IS NOT NULL
+ AND #{alias_name}.category_id IS NOT NULL
+ AND #{alias_name}.urgency IS NOT NULL
+ AND #{alias_name}.expires_at IS NOT NULL
+ """
+ end
+
+ defp eligible_activity_sql(alias_name) do
+ """
+ #{alias_name}.status = 'open'
+ AND #{alias_name}.hidden_at IS NULL
+ AND #{alias_name}.location_visibility = 'approximate_public'
+ AND #{alias_name}.public_longitude IS NOT NULL
+ AND #{alias_name}.public_latitude IS NOT NULL
+ AND #{alias_name}.category_id IS NOT NULL
+ AND #{alias_name}.starts_at IS NOT NULL
+ AND #{alias_name}.join_deadline IS NOT NULL
+ """
+ end
+
+ defp validate_initial_rollups_sql do
+ """
+ DO $validation$
+ DECLARE
+ expected_help bigint;
+ actual_help bigint;
+ expected_activities bigint;
+ actual_activities bigint;
+ BEGIN
+ SELECT count(*) INTO expected_help
+ FROM help_requests AS request
+ WHERE #{eligible_help_sql("request")};
+
+ SELECT coalesce(sum(point_count), 0) INTO actual_help
+ FROM help_discovery_rollups
+ WHERE level = 0;
+
+ IF expected_help <> actual_help THEN
+ RAISE EXCEPTION
+ 'help discovery rollup validation failed: expected %, got %',
+ expected_help, actual_help;
+ END IF;
+
+ SELECT count(*) INTO expected_activities
+ FROM activities AS activity
+ WHERE #{eligible_activity_sql("activity")};
+
+ SELECT coalesce(sum(point_count), 0) INTO actual_activities
+ FROM activity_discovery_rollups
+ WHERE level = 0;
+
+ IF expected_activities <> actual_activities THEN
+ RAISE EXCEPTION
+ 'activity discovery rollup validation failed: expected %, got %',
+ expected_activities, actual_activities;
+ END IF;
+ END
+ $validation$
+ """
+ end
+
+ defp cell_x_sql(longitude, level) do
+ """
+ least(
+ power(2, #{level})::bigint - 1,
+ greatest(0, floor(((#{longitude} + 180.0) / 360.0) * power(2, #{level})))::bigint
+ ) AS cell_x
+ """
+ end
+
+ defp cell_y_sql(latitude, level) do
+ """
+ least(
+ power(2, #{level})::bigint - 1,
+ greatest(
+ 0,
+ floor(
+ (
+ ln(
+ tan(
+ pi() / 4.0 +
+ radians(least(85.05112878, greatest(-85.05112878, #{latitude}))) / 2.0
+ )
+ ) / pi() + 1.0
+ ) / 2.0 * power(2, #{level})
+ )::bigint
+ )
+ ) AS cell_y
+ """
+ end
+end
diff --git a/priv/repo/migrations/20260827180241_add_pending_account_lifecycle_uniqueness.exs b/priv/repo/migrations/20260827180241_add_pending_account_lifecycle_uniqueness.exs
new file mode 100644
index 0000000..02400a1
--- /dev/null
+++ b/priv/repo/migrations/20260827180241_add_pending_account_lifecycle_uniqueness.exs
@@ -0,0 +1,11 @@
+defmodule WhoNeedHelp.Repo.Migrations.AddPendingAccountLifecycleUniqueness do
+ use Ecto.Migration
+
+ def change do
+ create unique_index(:support_requests, [:requester_id, :kind],
+ name: :support_requests_one_pending_account_lifecycle_per_user_index,
+ where:
+ "requester_id IS NOT NULL AND contact_verified_at IS NULL AND status = 'pending_verification' AND kind IN ('account_access', 'account_deletion')"
+ )
+ end
+end
diff --git a/test/who_need_help/activities_test.exs b/test/who_need_help/activities_test.exs
index 6fd4b4e..61612e0 100644
--- a/test/who_need_help/activities_test.exs
+++ b/test/who_need_help/activities_test.exs
@@ -463,7 +463,18 @@ defmodule WhoNeedHelp.ActivitiesTest do
assert cluster.hierarchy_level == 11
assert String.starts_with?(cluster.cluster_id, "activity:11:")
assert String.starts_with?(cluster.parent_cluster_id, "activity:10:")
- assert cluster.expansion_zoom > 9
+ assert cluster.expansion_lookup
+
+ assert {:ok, expansion} =
+ Activities.map_cluster_expansion(
+ context.outsider_scope,
+ %{},
+ cluster.cluster_id
+ )
+
+ assert expansion.cluster_id == cluster.cluster_id
+ assert expansion.point_count == 3
+ assert expansion.zoom > 9
["activity", "11", cell_x, cell_y] = String.split(cluster.cluster_id, ":")
@@ -472,10 +483,10 @@ defmodule WhoNeedHelp.ActivitiesTest do
assert_in_delta cluster.latitude, expected_latitude, 0.000_001
assert_in_delta cluster.longitude, expected_longitude, 0.000_001
- assert_in_delta cluster.bounds.west, 30.52, 0.000_001
- assert_in_delta cluster.bounds.east, 30.52, 0.000_001
- assert_in_delta cluster.bounds.south, 50.45, 0.000_001
- assert_in_delta cluster.bounds.north, 50.45, 0.000_001
+ assert_in_delta expansion.bounds.west, 30.52, 0.000_001
+ assert_in_delta expansion.bounds.east, 30.52, 0.000_001
+ assert_in_delta expansion.bounds.south, 50.45, 0.000_001
+ assert_in_delta expansion.bounds.north, 50.45, 0.000_001
assert Enum.any?(items, fn
%{
@@ -565,6 +576,37 @@ defmodule WhoNeedHelp.ActivitiesTest do
assert Enum.sort(Enum.map(fixture_items, & &1.latitude)) == [50.451, 50.454]
end
+ test "map rollups follow activity coordinate changes and cancellation", context do
+ {:ok, activity} = Activities.create_activity(context.organizer_scope, context.attrs)
+
+ original_viewport = viewport!(30.50, 50.40, 30.60, 50.50, 9)
+ moved_viewport = viewport!(31.10, 51.10, 31.30, 51.30, 9)
+
+ original_items =
+ Activities.map_discovery_items(context.outsider_scope, %{}, original_viewport)
+
+ assert map_point_count(original_items) == 1
+ assert Enum.any?(original_items, &(&1.type == "activity" and &1.id == activity.id))
+
+ activity =
+ activity
+ |> Ecto.Changeset.change(location: %Geo.Point{coordinates: {31.2, 51.2}, srid: 4326})
+ |> Repo.update!()
+
+ assert Activities.map_discovery_items(context.outsider_scope, %{}, original_viewport) == []
+
+ moved_items =
+ Activities.map_discovery_items(context.outsider_scope, %{}, moved_viewport)
+
+ assert map_point_count(moved_items) == 1
+ assert Enum.any?(moved_items, &(&1.type == "activity" and &1.id == activity.id))
+
+ assert {:ok, _cancelled} =
+ Activities.cancel_activity(context.organizer_scope, activity.id)
+
+ assert Activities.map_discovery_items(context.outsider_scope, %{}, moved_viewport) == []
+ end
+
test "hidden activity is discoverable only through its public area description", context do
{:ok, hidden} =
Activities.create_activity(
@@ -764,4 +806,6 @@ defmodule WhoNeedHelp.ActivitiesTest do
assert is_nil(restored.hidden_at)
assert {:ok, _activity} = Activities.get_activity(context.outsider_scope, activity.id)
end
+
+ defp map_point_count(items), do: Enum.sum(Enum.map(items, & &1.count))
end
diff --git a/test/who_need_help/mutual_aid_flow_test.exs b/test/who_need_help/mutual_aid_flow_test.exs
index 9beed47..df2c1eb 100644
--- a/test/who_need_help/mutual_aid_flow_test.exs
+++ b/test/who_need_help/mutual_aid_flow_test.exs
@@ -580,7 +580,18 @@ defmodule WhoNeedHelp.MutualAidFlowTest do
assert cluster.hierarchy_level == 11
assert String.starts_with?(cluster.cluster_id, "request:11:")
assert String.starts_with?(cluster.parent_cluster_id, "request:10:")
- assert cluster.expansion_zoom > viewport.zoom
+ assert cluster.expansion_lookup
+
+ assert {:ok, expansion} =
+ Help.map_cluster_expansion(
+ context.helper_scope,
+ %{},
+ cluster.cluster_id
+ )
+
+ assert expansion.cluster_id == cluster.cluster_id
+ assert expansion.point_count == 3
+ assert expansion.zoom > viewport.zoom
["request", "11", cell_x, cell_y] = String.split(cluster.cluster_id, ":")
@@ -589,10 +600,10 @@ defmodule WhoNeedHelp.MutualAidFlowTest do
assert_in_delta cluster.latitude, expected_latitude, 0.000_001
assert_in_delta cluster.longitude, expected_longitude, 0.000_001
- assert_in_delta cluster.bounds.west, 30.5234, 0.000_001
- assert_in_delta cluster.bounds.east, 30.5235, 0.000_001
- assert_in_delta cluster.bounds.south, 50.4501, 0.000_001
- assert_in_delta cluster.bounds.north, 50.4501, 0.000_001
+ assert_in_delta expansion.bounds.west, 30.5234, 0.000_001
+ assert_in_delta expansion.bounds.east, 30.5235, 0.000_001
+ assert_in_delta expansion.bounds.south, 50.4501, 0.000_001
+ assert_in_delta expansion.bounds.north, 50.4501, 0.000_001
assert Enum.any?(items, fn
%{
@@ -676,6 +687,34 @@ defmodule WhoNeedHelp.MutualAidFlowTest do
refute_in_delta item.longitude, 30.5267, 0.000_001
end
+ test "map rollups follow request coordinate changes and cancellation", context do
+ {:ok, request} = Help.create_request(context.requester_scope, context.request_attrs)
+
+ original_viewport = viewport!(30.50, 50.40, 30.60, 50.50, 9)
+ moved_viewport = viewport!(31.10, 51.10, 31.30, 51.30, 9)
+
+ original_items =
+ Help.map_discovery_items(context.helper_scope, %{}, original_viewport)
+
+ assert map_point_count(original_items) == 1
+ assert Enum.any?(original_items, &(&1.type == "request" and &1.id == request.id))
+
+ request =
+ request
+ |> Ecto.Changeset.change(location: %Geo.Point{coordinates: {31.2, 51.2}, srid: 4326})
+ |> Repo.update!()
+
+ assert Help.map_discovery_items(context.helper_scope, %{}, original_viewport) == []
+
+ moved_items = Help.map_discovery_items(context.helper_scope, %{}, moved_viewport)
+ assert map_point_count(moved_items) == 1
+ assert Enum.any?(moved_items, &(&1.type == "request" and &1.id == request.id))
+
+ assert {:ok, _cancelled} = Help.cancel_request(context.requester_scope, request.id)
+
+ assert Help.map_discovery_items(context.helper_scope, %{}, moved_viewport) == []
+ end
+
test "scale fixture requests keep distinct synthetic public map coordinates", context do
requests =
for {latitude, index} <- [{"50.451", 1}, {"50.454", 2}] do
@@ -1292,4 +1331,6 @@ defmodule WhoNeedHelp.MutualAidFlowTest do
0 -> Enum.reverse(queries)
end
end
+
+ defp map_point_count(items), do: Enum.sum(Enum.map(items, & &1.count))
end
diff --git a/test/who_need_help/support_and_content_removal_test.exs b/test/who_need_help/support_and_content_removal_test.exs
index 2e0dbc5..5585ed6 100644
--- a/test/who_need_help/support_and_content_removal_test.exs
+++ b/test/who_need_help/support_and_content_removal_test.exs
@@ -242,9 +242,9 @@ defmodule WhoNeedHelp.SupportAndContentRemovalTest do
assert {:ok, pending} =
Support.create_request(nil, %{
- "kind" => "account_access",
+ "kind" => "technical_issue",
"contact_email" => "pending@example.com",
- "subject" => "Pending account access request",
+ "subject" => "Pending technical support request",
"details" => "This public request must remain outside the operator queue."
})
@@ -265,6 +265,67 @@ defmodule WhoNeedHelp.SupportAndContentRemovalTest do
assert Enum.map(Support.paginate_for_staff(moderator_scope).entries, & &1.id) == [verified.id]
end
+ test "anonymous account lifecycle intake creates no case or email for an unknown address" do
+ assert {:ok, :accepted} =
+ Support.create_request(nil, %{
+ "kind" => "account_access",
+ "contact_email" => "missing-account@example.com",
+ "subject" => "Recover account access",
+ "details" => "I need help recovering access to the account for this address."
+ })
+
+ assert Repo.aggregate(SupportRequest, :count) == 0
+ refute_enqueued(worker: SupportConfirmationWorker)
+ end
+
+ test "anonymous account lifecycle intake links one pending case to an unconfirmed account" do
+ user = unconfirmed_user_fixture(%{email: "Pending.Owner@Example.com"})
+
+ attrs = %{
+ "kind" => "account_deletion",
+ "contact_email" => " pending.owner@example.com ",
+ "subject" => "Delete my Who Need Help account",
+ "details" => "Please delete the account and its associated personal data."
+ }
+
+ assert {:ok, first} = Support.create_request(nil, attrs)
+ assert first.requester_id == user.id
+ assert first.contact_email == String.downcase(user.email)
+ assert first.status == :pending_verification
+ assert is_nil(first.contact_verified_at)
+
+ assert {:ok, second} =
+ Support.create_request(nil, %{
+ attrs
+ | "details" => "Please delete this account after I confirm ownership."
+ })
+
+ assert second.id == first.id
+
+ assert Repo.aggregate(
+ from(request in SupportRequest,
+ where:
+ request.requester_id == ^user.id and
+ request.kind == :account_deletion and
+ request.status == :pending_verification
+ ),
+ :count
+ ) == 1
+
+ assert_enqueued(
+ worker: SupportConfirmationWorker,
+ queue: :mail,
+ args: %{"request_id" => first.id}
+ )
+
+ assert {:ok, verified} =
+ Support.verify_by_confirmation_token(first.id, Support.confirmation_token(first))
+
+ assert verified.requester_id == user.id
+ assert verified.status == :open
+ assert verified.contact_verified_at
+ end
+
test "authenticated support uses the account email and staff moderation is audited" do
user = user_fixture()
assert_email_sent()
diff --git a/test/who_need_help_web/controllers/support_controller_test.exs b/test/who_need_help_web/controllers/support_controller_test.exs
index eaa0eba..8cb3f87 100644
--- a/test/who_need_help_web/controllers/support_controller_test.exs
+++ b/test/who_need_help_web/controllers/support_controller_test.exs
@@ -90,6 +90,44 @@ defmodule WhoNeedHelpWeb.SupportControllerTest do
assert page =~ "It is not in the support queue yet"
end
+ test "account lifecycle receipt is identical for matching and unknown addresses", %{
+ conn: conn
+ } do
+ user = unconfirmed_user_fixture(%{email: "lifecycle-owner@example.com"})
+
+ params = fn email ->
+ %{
+ "support_request" => %{
+ "kind" => "account_access",
+ "contact_email" => email,
+ "subject" => "Recover account access",
+ "details" => "I need help recovering access to the account for this address."
+ }
+ }
+ end
+
+ matching = post(conn, ~p"/support", params.("LIFECYCLE-OWNER@example.com"))
+ missing = post(recycle(conn), ~p"/support", params.("missing-owner@example.com"))
+
+ assert redirected_to(matching) == ~p"/support/received?flow=account"
+ assert redirected_to(missing) == ~p"/support/received?flow=account"
+
+ matching_page = matching |> recycle() |> get(redirected_to(matching)) |> html_response(200)
+ missing_page = missing |> recycle() |> get(redirected_to(missing)) |> html_response(200)
+
+ for page <- [matching_page, missing_page] do
+ assert page =~ "If that address belongs to a Who Need Help account"
+ assert page =~ "No support request is created until that link is confirmed"
+ refute page =~ "SUP-"
+ end
+
+ assert %SupportRequest{requester_id: requester_id} =
+ Repo.get_by(SupportRequest, kind: :account_access)
+
+ assert requester_id == user.id
+ assert Repo.aggregate(SupportRequest, :count) == 1
+ end
+
test "support intake enforces independent email and client IP limits", %{conn: conn} do
previous = Application.get_env(:who_need_help, :rate_limit_policies)