prop/lib/microwaveprop_web/live/submit_live.ex
Graham McIntire ea96c93e2d
Rename QSO to Contact in UI, add higher bands, improve submit page
- All menus and UI text now say Contacts instead of QSOs
- Add 68, 122, 134, 241 GHz bands to submit form and validation
- Add info box on submit page explaining why contacts matter
- Larger submit button with icon
- Make HRRR partition migration idempotent for partial re-runs
2026-04-01 12:28:24 -05:00

158 lines
4.8 KiB
Elixir

defmodule MicrowavepropWeb.SubmitLive do
@moduledoc false
use MicrowavepropWeb, :live_view
alias Microwaveprop.Radio
alias Microwaveprop.Radio.Contact
alias Microwaveprop.Workers.ContactWeatherEnqueueWorker
@band_options [
{"1296 MHz", "1296"},
{"2304 MHz", "2304"},
{"3456 MHz", "3456"},
{"5760 MHz", "5760"},
{"10 GHz", "10000"},
{"24 GHz", "24000"},
{"47 GHz", "47000"},
{"68 GHz", "68000"},
{"76 GHz", "75000"},
{"122 GHz", "122000"},
{"134 GHz", "134000"},
{"241 GHz", "241000"}
]
@mode_options ~w(CW SSB FM FT8 FT4)
@impl true
def mount(_params, _session, socket) do
changeset = Radio.change_contact(%Contact{})
{:ok,
assign(socket,
page_title: "Submit Contact",
form: to_form(changeset),
band_options: @band_options,
mode_options: @mode_options,
submitted_at: nil
)}
end
@impl true
def handle_event("validate", %{"contact" => contact_params}, socket) do
changeset =
%Contact{}
|> Radio.change_contact(contact_params)
|> Map.put(:action, :validate)
{:noreply, assign(socket, form: to_form(changeset))}
end
# Minimum milliseconds between submissions per LiveView session.
# For broader IP-based rate limiting, use a reverse proxy (nginx limit_req, Cloudflare).
@submission_cooldown_ms 30_000
def handle_event("save", %{"contact" => contact_params}, socket) do
if recently_submitted?(socket) do
{:noreply, put_flash(socket, :error, "Please wait before submitting another contact.")}
else
case Radio.create_contact(contact_params) do
{:ok, contact} ->
ContactWeatherEnqueueWorker.enqueue_for_contact(contact)
{:noreply,
socket
|> assign(submitted_at: System.monotonic_time(:millisecond))
|> put_flash(:info, "Contact submitted successfully!")
|> push_navigate(to: ~p"/contacts/#{contact.id}")}
{:error, changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
end
defp recently_submitted?(socket) do
case socket.assigns.submitted_at do
nil -> false
ts -> System.monotonic_time(:millisecond) - ts < @submission_cooldown_ms
end
end
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<.header>
Submit Contact
<:subtitle>Help us build a better propagation model</:subtitle>
</.header>
<div class="bg-info/10 border border-info/30 rounded-box p-4 mb-6 text-sm leading-relaxed">
<p class="font-semibold text-info mb-1">Every contact matters</p>
<p>
Our propagation model improves with real-world data. Each verified microwave contact
you submit is matched against atmospheric conditions at the time of your contact,
helping us calibrate predictions for all bands from 10 GHz to 241 GHz. The more contacts
we have, the better our forecasts get for everyone.
</p>
</div>
<.form for={@form} id="contact-form" phx-change="validate" phx-submit="save" class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<.input field={@form[:station1]} type="text" label="Station 1" placeholder="W5XD" />
<.input
field={@form[:grid1]}
type="text"
label="Grid 1"
placeholder="EM12kp"
phx-debounce="blur"
/>
<.input field={@form[:station2]} type="text" label="Station 2" placeholder="K5TR" />
<.input
field={@form[:grid2]}
type="text"
label="Grid 2"
placeholder="EM00cd"
phx-debounce="blur"
/>
</div>
<p class="text-sm text-base-content/60 -mt-2">
Be as specific as possible with grid squares (6 characters preferred, e.g. EM12kp).
</p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<.input
field={@form[:band]}
type="select"
label="Band"
prompt="Select band"
options={@band_options}
/>
<.input
field={@form[:mode]}
type="select"
label="Mode"
prompt="Select mode"
options={@mode_options}
/>
<.input field={@form[:qso_timestamp]} type="datetime-local" label="Timestamp (UTC)" />
</div>
<.input
field={@form[:submitter_email]}
type="email"
label="Your Email"
placeholder="you@example.com"
/>
<div class="mt-6">
<.button phx-disable-with="Submitting..." class="btn btn-primary btn-lg w-full sm:w-auto">
<.icon name="hero-arrow-up-tray" class="w-5 h-5" /> Submit Contact
</.button>
</div>
</.form>
</Layouts.app>
"""
end
end