Hi all!
I am working on including a contact form in my Phoenix web application that uses Liveview. The contact form is rendered with a bootstrap modal. When the form is submitted, it sends an email using Bamboo (https://github.com/thoughtbot/bamboo).
Here is the contact-form.html.leex
that contains the form:
<%= form_for @changeset, "#", [phx_submit: :save], fn f -> %>
<div class="form-group">
<%= label f, :first_name, class: "control-label" %>
<%= text_input f, :first_name,
class: "form-control",
value: @form_data["first_name"],
minlength: "2",
required: "required",
step: "1"
%>
<%= error_tag f, :first_name %>
</div>
<div class="form-group">
<%= label f, :last_name, class: "control-label" %>
<%= text_input f, :last_name,
class: "form-control",
value: @form_data["last_name"],
type: "text",
minlength: "2",
required: "required",
step: "2"
%>
<%= error_tag f, :last_name %>
</div>
<div class="form-group">
<%= label f, :email_address, class: "control-label" %>
<%= email_input f, :email_address,
class: "form-control",
value: @form_data["email_address"],
minlength: "2",
required: "required",
step: "3"
%>
<%= error_tag f, :email_address %>
</div>
<div class="form-group">
<%= label f, :subject, class: "control-label" %>
<%= text_input f, :subject,
class: "form-control",
value: @form_data["subject"],
minlength: "2",
required: "required",
step: "4"
%>
<%= error_tag f, :subject %>
</div>
<%= label f, :message, class: "control-label" %>
<%= textarea f, :message,
class: "form-control",
value: @form_data["message"],
minlength: "2",
required: "required",
step: "5"
%>
<%= error_tag f, :message %>
<br>
<div>
<%= submit "Send", phx_disable_with: "Air pigeons prepping for delivery...", class: "btn btn-primary" %>
</div>
<% end %>
Here is client_live.ex
that contains the ClientLive module:
defmodule LarcWebsiteWeb.ClientLive do
use Phoenix.LiveView
alias LarcWebsiteWeb.Router.Helpers, as: Routes
# alias LarcWebsiteWeb.ClientView
alias LarcWebsiteWeb.ContactView
alias LarcWebsite.Accounts
alias LarcWebsite.Accounts.Client
def mount(_params, _session, socket) do
IO.inspect(connected?(socket), label: "CONNTECTION STATUS")
assigns = [
conn: socket,
changeset: Accounts.change_client(%Client{}),
form_data: %{ # This is the form data to be captured and utilized to create a new client
"first_name" => "",
"last_name" => "",
"email_address" => "",
"subject" => "",
"message" => "",
},
]
{:ok, assign(socket, assigns)}
end
def render(assigns) do
ContactView.render("contact-form.html", assigns)
end
def handle_event("save", %{"client" => params}, socket) do
params
|> Accounts.create_client()
|> case do
{:ok, _user} ->
{:noreply,
socket
|> Client.send_contact_email(params)
|> put_flash(:info, "Your message is sent! Expect a reply soon.")
|> redirect(to: Routes.page_path(LarcWebsiteWeb.Endpoint, :index))}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
end
Here is the create_client
function in the Accounts
module:
defmodule LarcWebsite.Accounts do
@moduledoc """
The Accounts context.
"""
import Ecto.Changeset
import Ecto.Query, warn: false
alias LarcWebsite.Repo
alias LarcWebsite.Accounts.Client
---SNIP---
def create_client(attrs \\ %{}) do
%Client{}
|> Client.changeset(attrs)
|> Repo.insert()
end
---SNIP---
Here is the Accounts.Client
module:
defmodule LarcWebsite.Accounts.Client do
use Ecto.Schema
import Ecto.Changeset
alias LarcWebsite.{Mailer, Email}
schema "clients" do
field :email_address, :string
field :first_name, :string
field :last_name, :string
field :message, :string
field :subject, :string
timestamps()
end
@doc false
def changeset(client, attrs) do
email_regex = ~r"[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*"
client
|> cast(attrs, [:first_name, :last_name, :email_address, :subject, :message])
|> validate_required([:first_name, :last_name, :email_address, :subject, :message])
|> validate_format(:email_address, email_regex)
|> validate_length(:first_name, min: 2)
|> validate_length(:last_name, min: 2)
|> validate_length(:subject, min: 2)
|> validate_length(:message, min: 2)
end
def send_contact_email(socket, params) do
# Specific to the handle event for "save"
# Send the email and returns the socket that will be
# passed to put_flash and the redirect
Email.contact_email(params)
|> Mailer.deliver_later()
socket
end
end
Here is the HTML code in app.html.eex
that references the ClientLive
module:
<!-- Contact Model -->
<div class="modal fade" id="Mymodal" tabindex="-1">
<div class="modal-dialog modal-md">
<div class="modal-content bg-light">
<div class="modal-header text-left">
<h1 class="modal-title w-100">
Contact Us
</h1>
<div class="text-left">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">×</span>
</button>
</div>
</div>
<div class="modal-body">
<%= live_render(@conn, LarcWebsiteWeb.ClientLive) %>
</div>
</div>
</div>
</div>
</div>
Here is the output of the terminal when I submit the form:
[debug] QUERY OK db=3.2ms queue=0.1ms idle=1578.9ms
INSERT INTO `clients` (`email_address`,`first_name`,`last_name`,`message`,`subject`,`inserted_at`,`updated_at`) VALUES (?,?,?,?,?,?,?) ["email@email.edu", "Catherine", "Lukner", "New Message. ", "It worked!", ~N[2020-07-07 19:53:27], ~N[2020-07-07 19:53:27]]
[debug] Sending email with Bamboo.SendGridAdapter:
%Bamboo.Email{assigns: %{}, attachments: [], bcc: [], cc: [], from: {nil, "email@gmail.com"}, headers: %{"Reply-To" => "email@email.edu"}, html_body: "\n <p>Catherine Lukner wrote,</p>\n <br>\n <p>New Message. </p>\n <br>\n <p>Reply-To: email@email.edu</p>\n ", private: %{}, subject: "Contact Submission: It worked!", text_body: nil, to: [nil: "email@gmail.com"]}
[debug] QUERY OK db=2.9ms queue=0.1ms idle=1580.1ms
INSERT INTO `clients` (`email_address`,`first_name`,`last_name`,`message`,`subject`,`inserted_at`,`updated_at`) VALUES (?,?,?,?,?,?,?) ["email@email.edu", "Catherine", "Lukner", "New Message. ", "It worked!", ~N[2020-07-07 19:53:27], ~N[2020-07-07 19:53:27]]
[debug] Sending email with Bamboo.SendGridAdapter:
%Bamboo.Email{assigns: %{}, attachments: [], bcc: [], cc: [], from: {nil, "email@gmail.com"}, headers: %{"Reply-To" => "email@email.edu"}, html_body: "\n <p>Catherine Lukner wrote,</p>\n <br>\n <p>New Message. </p>\n <br>\n <p>Reply-To: email@email.edu</p>\n ", private: %{}, subject: "Contact Submission: It worked!", text_body: nil, to: [nil: "email@gmail.com"]}
As you can see, the form is submitted twice and Bamboo is sending two emails. However, this behavior does not occur whenever I submit the form in its own template seperate from app.html.eex
or index.html.eex
.
Versions
Elixir: 1.9.2
Phoenix: 1.5.3
Phoenix Liveview: 0.13.3
Bamboo: 1.5
Expected Behavior
The form submits once and Bamboo sends one email.
Actual Behavior
The form is submitted twice and Bamboo sends two emails.
Any help is much appreciated!