feat: add join confirmation and mail templating
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
3672ef0d03
commit
6385fbc831
24 changed files with 585 additions and 53 deletions
12
lib/mv_web/emails/email_layout_view.ex
Normal file
12
lib/mv_web/emails/email_layout_view.ex
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
defmodule MvWeb.EmailLayoutView do
|
||||
@moduledoc """
|
||||
Layout view for transactional emails (join confirmation, user confirmation, password reset).
|
||||
|
||||
Renders a single layout template that wraps all email body content.
|
||||
See docs/email-layout-mockup.md for the layout structure.
|
||||
"""
|
||||
use Phoenix.View,
|
||||
root: "lib/mv_web",
|
||||
path: "templates/emails/layouts",
|
||||
namespace: MvWeb
|
||||
end
|
||||
13
lib/mv_web/emails/emails_view.ex
Normal file
13
lib/mv_web/emails/emails_view.ex
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
defmodule MvWeb.EmailsView do
|
||||
@moduledoc """
|
||||
View for transactional email body templates.
|
||||
|
||||
Templates are rendered inside EmailLayoutView layout when sent via Phoenix.Swoosh.
|
||||
"""
|
||||
use Phoenix.View,
|
||||
root: "lib/mv_web",
|
||||
path: "templates/emails",
|
||||
namespace: MvWeb
|
||||
|
||||
use Gettext, backend: MvWeb.Gettext, otp_app: :mv
|
||||
end
|
||||
33
lib/mv_web/emails/join_confirmation_email.ex
Normal file
33
lib/mv_web/emails/join_confirmation_email.ex
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
defmodule MvWeb.Emails.JoinConfirmationEmail do
|
||||
@moduledoc """
|
||||
Sends the join request confirmation email (double opt-in) using the unified email layout.
|
||||
"""
|
||||
use Phoenix.Swoosh,
|
||||
view: MvWeb.EmailsView,
|
||||
layout: {MvWeb.EmailLayoutView, :layout}
|
||||
|
||||
use MvWeb, :verified_routes
|
||||
import Swoosh.Email
|
||||
use Gettext, backend: MvWeb.Gettext, otp_app: :mv
|
||||
|
||||
alias Mv.Mailer
|
||||
|
||||
@doc """
|
||||
Sends the join confirmation email to the given address with the confirmation link.
|
||||
|
||||
Called from the domain after a JoinRequest is created (submit flow).
|
||||
"""
|
||||
def send(email_address, token) when is_binary(email_address) and is_binary(token) do
|
||||
confirm_url = url(~p"/confirm_join/#{token}")
|
||||
subject = gettext("Confirm your membership request")
|
||||
|
||||
new()
|
||||
|> from(Mailer.mail_from())
|
||||
|> to(email_address)
|
||||
|> subject(subject)
|
||||
|> put_view(MvWeb.EmailsView)
|
||||
|> put_layout({MvWeb.EmailLayoutView, "layout.html"})
|
||||
|> render_body("join_confirmation.html", %{confirm_url: confirm_url, subject: subject})
|
||||
|> Mailer.deliver!()
|
||||
end
|
||||
end
|
||||
18
lib/mv_web/templates/emails/join_confirmation.html.heex
Normal file
18
lib/mv_web/templates/emails/join_confirmation.html.heex
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<div style="color: #111827;">
|
||||
<p style="margin: 0 0 16px; font-size: 16px; line-height: 1.5;">
|
||||
{gettext(
|
||||
"We have received your membership request. To complete it, please click the link below."
|
||||
)}
|
||||
</p>
|
||||
<p style="margin: 0 0 24px;">
|
||||
<a
|
||||
href={@confirm_url}
|
||||
style="display: inline-block; padding: 12px 24px; background-color: #2563eb; color: #ffffff; text-decoration: none; border-radius: 6px; font-weight: 500;"
|
||||
>
|
||||
{gettext("Confirm my request")}
|
||||
</a>
|
||||
</p>
|
||||
<p style="margin: 0; font-size: 14px; color: #6b7280;">
|
||||
{gettext("If you did not submit this request, you can ignore this email.")}
|
||||
</p>
|
||||
</div>
|
||||
33
lib/mv_web/templates/emails/layouts/layout.html.heex
Normal file
33
lib/mv_web/templates/emails/layouts/layout.html.heex
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{assigns[:subject] || "Mila"}</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: system-ui, -apple-system, sans-serif; background-color: #f3f4f6;">
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
style="max-width: 600px; margin: 0 auto;"
|
||||
>
|
||||
<tr>
|
||||
<td style="padding: 24px 16px 16px;">
|
||||
<div style="font-size: 18px; font-weight: 600; color: #111827;">Mila</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 16px; background-color: #ffffff; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.08);">
|
||||
{@inner_content}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 16px 24px; font-size: 12px; color: #6b7280;">
|
||||
© {DateTime.utc_now().year} Mila · Mitgliederverwaltung
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
18
lib/mv_web/templates/emails/password_reset.html.heex
Normal file
18
lib/mv_web/templates/emails/password_reset.html.heex
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<div style="color: #111827;">
|
||||
<p style="margin: 0 0 16px; font-size: 16px; line-height: 1.5;">
|
||||
{gettext("You requested a password reset. Click the link below to set a new password.")}
|
||||
</p>
|
||||
<p style="margin: 0 0 24px;">
|
||||
<a
|
||||
href={@reset_url}
|
||||
style="display: inline-block; padding: 12px 24px; background-color: #2563eb; color: #ffffff; text-decoration: none; border-radius: 6px; font-weight: 500;"
|
||||
>
|
||||
{gettext("Reset password")}
|
||||
</a>
|
||||
</p>
|
||||
<p style="margin: 0; font-size: 14px; color: #6b7280;">
|
||||
{gettext(
|
||||
"If you did not request this, you can ignore this email. Your password will remain unchanged."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
16
lib/mv_web/templates/emails/user_confirmation.html.heex
Normal file
16
lib/mv_web/templates/emails/user_confirmation.html.heex
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<div style="color: #111827;">
|
||||
<p style="margin: 0 0 16px; font-size: 16px; line-height: 1.5;">
|
||||
{gettext("Please confirm your email address by clicking the link below.")}
|
||||
</p>
|
||||
<p style="margin: 0 0 24px;">
|
||||
<a
|
||||
href={@confirm_url}
|
||||
style="display: inline-block; padding: 12px 24px; background-color: #2563eb; color: #ffffff; text-decoration: none; border-radius: 6px; font-weight: 500;"
|
||||
>
|
||||
{gettext("Confirm my email")}
|
||||
</a>
|
||||
</p>
|
||||
<p style="margin: 0; font-size: 14px; color: #6b7280;">
|
||||
{gettext("If you did not create an account, you can ignore this email.")}
|
||||
</p>
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue