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)