Skip to content

Commit 3747d5f

Browse files
Fix: Translate label of Name field properly
Feat: Add confirmation email; The submitter now also gets an email
1 parent ff432b0 commit 3747d5f

File tree

16 files changed

+298
-100
lines changed

16 files changed

+298
-100
lines changed

frontend/eslint.config.js

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ export default [
2020
sourceType: 'module',
2121
},
2222
},
23+
rules: {
24+
"@typescript-eslint/no-namespace": "off"
25+
}
2326
},
2427
{
2528
ignores: [

frontend/src/plugins/locales/en.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const EN: Locale = {
88
subtitle: "Declare digitally at Sticky",
99
invalidFieldsError: "One or multiple fields are incorreect",
1010
form: {
11-
name: "Naam",
11+
name: "Name",
1212
iban: "IBAN",
1313
email: "Email",
1414
value: "Amount",

frontend/src/scripts/digidecs.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@ import {ApiError} from "@/scripts/core/error";
33
import {fetch1} from "@/scripts/core/fetch1";
44
import {server} from "@/main";
55

6+
export enum DigidecsLocale {
7+
NL,
8+
EN,
9+
}
10+
11+
export namespace DigidecsLocale {
12+
export function serverName(locale: DigidecsLocale): string {
13+
switch(locale) {
14+
case DigidecsLocale.EN: return "En"
15+
case DigidecsLocale.NL: return "Nl"
16+
}
17+
}
18+
}
619

720
export class Digidecs {
821
trackingId: string;
@@ -22,6 +35,7 @@ export class Digidecs {
2235
commission: string,
2336
notes: string | null,
2437
attachments: File[],
38+
locale: Locale,
2539
): Promise<Result<Digidecs, ApiError>> {
2640
const r = await fetch1(`${server}/api/digidecs/start`, {
2741
method: 'POST',
@@ -36,6 +50,7 @@ export class Digidecs {
3650
what: what,
3751
commission: commission,
3852
notes: notes,
53+
locale: DigidecsLocale.serverName(locale),
3954
attachments: attachments.map((att) => {
4055
return {
4156
name: att.name,
@@ -89,6 +104,4 @@ export class Digidecs {
89104
return Result.err(r.unwrapErr());
90105
}
91106
}
92-
93-
94-
}
107+
}

frontend/src/views/HomeView.vue

+11-3
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
119119
import {defineComponent} from "vue";
120120
import {InputValidationRules} from "@/main";
121-
import {Digidecs} from "@/scripts/digidecs";
121+
import {Digidecs, DigidecsLocale} from "@/scripts/digidecs";
122122
import MaterialBanner from "@/views/components/MaterialBanner.vue";
123123
124124
interface Data {
@@ -230,7 +230,8 @@ export default defineComponent({
230230
this.form.what,
231231
this.form.commission,
232232
this.form.notes,
233-
this.form.files
233+
this.form.files,
234+
this.getCurrentLocale(),
234235
);
235236
236237
if(r.isErr()) {
@@ -261,7 +262,14 @@ export default defineComponent({
261262
262263
this.$router.push('/complete');
263264
},
264-
265+
getCurrentLocale(): DigidecsLocale {
266+
const currentLocale = this.$i18n.locale;
267+
switch(currentLocale) {
268+
case "nl": return DigidecsLocale.NL;
269+
case "en-US": return DigidecsLocale.EN;
270+
default: return DigidecsLocale.NL;
271+
}
272+
},
265273
displayError() {
266274
this.error = this.$t('error');
267275
this.loading = false;

server/src/email/mod.rs

+78-22
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ use tracing::{debug, error, trace};
1515
pub mod ipv4;
1616
pub mod template;
1717

18+
pub enum EmailLanguage {
19+
Nl,
20+
En,
21+
}
22+
1823
#[derive(Debug, Error)]
1924
pub enum SendError {
2025
#[error("Failed to parse email address")]
@@ -37,31 +42,39 @@ pub struct Attachment {
3742
pub mime: String,
3843
}
3944

40-
pub async fn send_email(
45+
pub struct TreasurerEmailData<'a> {
46+
pub to: &'a str,
47+
pub body: &'a str,
48+
pub reply_to_name: &'a str,
49+
pub reply_to_email: &'a str,
50+
pub commission: &'a str,
51+
pub attachments: Vec<Attachment>,
52+
}
53+
54+
pub async fn send_treasurer_email(
4155
smtp_config: &SmtpConfig,
4256
local_addr4: Ipv4Addr,
43-
to: &str,
44-
body: String,
45-
reply_to_name: String,
46-
reply_to_email: &str,
47-
commission: &str,
48-
attachments: Vec<Attachment>,
57+
data: TreasurerEmailData<'_>,
4958
) -> Result<(), SendError> {
50-
let mb_to = Mailbox::from_str(to)?;
59+
let mb_to = Mailbox::from_str(data.to)?;
5160

5261
let mb_from = Mailbox::new(
5362
Some(smtp_config.from_name.clone()),
5463
Address::from_str(&smtp_config.from_email)?,
5564
);
5665

57-
let mb_reply_to = Mailbox::new(Some(reply_to_name), Address::from_str(&reply_to_email)?);
66+
let mb_reply_to = Mailbox::new(
67+
Some(data.reply_to_name.to_string()),
68+
Address::from_str(data.reply_to_email)?,
69+
);
5870

5971
let msg = Message::builder()
6072
.reply_to(mb_reply_to)
6173
.from(mb_from)
6274
.to(mb_to)
6375
.subject(format!(
64-
"[DigiDecs] Nieuwe declaratie: {commission} ({})",
76+
"[DigiDecs] Nieuwe declaratie: {} ({})",
77+
data.commission,
6578
rand::thread_rng()
6679
.sample_iter(rand::distributions::Alphanumeric)
6780
.take(6)
@@ -70,9 +83,9 @@ pub async fn send_email(
7083
));
7184

7285
let mut mp = MultiPart::mixed().build();
73-
mp = mp.singlepart(SinglePart::html(body));
86+
mp = mp.singlepart(SinglePart::html(data.body.to_string()));
7487

75-
for att in attachments {
88+
for att in data.attachments {
7689
mp = mp.singlepart(
7790
lettre::message::Attachment::new(att.name)
7891
.body(att.content, ContentType::parse(&att.mime)?),
@@ -81,24 +94,71 @@ pub async fn send_email(
8194

8295
let msg = msg.multipart(mp)?;
8396

97+
let mut conn = smtp_connect(smtp_config, local_addr4).await?;
98+
99+
trace!("Sending email");
100+
conn.send(msg.envelope(), &msg.formatted()).await?;
101+
102+
Ok(())
103+
}
104+
105+
pub async fn send_submitter_email(
106+
smtp_config: &SmtpConfig,
107+
local_addr4: Ipv4Addr,
108+
to_email: &str,
109+
body: String,
110+
name: &str,
111+
locale: &EmailLanguage,
112+
) -> Result<(), SendError> {
113+
let mb_to = Mailbox::new(Some(name.to_string()), Address::from_str(to_email)?);
114+
115+
let mb_from = Mailbox::new(
116+
Some(smtp_config.from_name.clone()),
117+
Address::from_str(&smtp_config.from_email)?,
118+
);
119+
120+
let msg = Message::builder()
121+
.to(mb_to)
122+
.from(mb_from)
123+
.subject(submitter_subject(locale))
124+
.singlepart(SinglePart::html(body))?;
125+
126+
let mut conn = smtp_connect(smtp_config, local_addr4).await?;
127+
trace!("Sending email");
128+
conn.send(msg.envelope(), &msg.formatted()).await?;
129+
130+
Ok(())
131+
}
132+
133+
fn submitter_subject(locale: &EmailLanguage) -> String {
134+
match locale {
135+
EmailLanguage::Nl => "Je DigiDecs is ontvangen!".into(),
136+
EmailLanguage::En => "Your DigiDecs has been received!".into(),
137+
}
138+
}
139+
140+
async fn smtp_connect(
141+
config: &SmtpConfig,
142+
bind_addr: Ipv4Addr,
143+
) -> Result<AsyncSmtpConnection, SendError> {
84144
let client_id =
85-
ClientId::Domain(get_ehlo_domain(&smtp_config.from_email).ok_or(SendError::EmailParse)?);
145+
ClientId::Domain(get_ehlo_domain(&config.from_email).ok_or(SendError::EmailParse)?);
86146

87147
trace!("Opening SMTP connection");
88148
let mut conn = AsyncSmtpConnection::connect_tokio1(
89-
(smtp_config.smtp_relay.as_str(), 587),
149+
(config.smtp_relay.as_str(), 587),
90150
Some(Duration::from_secs(3)),
91151
&client_id,
92152
// We cannot do STARTTLS (which uses port 465, which is blocked by Hetzner), so use port 587
93153
// Port 587 starts out with regular SMTP commands, after the EHLO we upgrade to STARTTLS
94154
None,
95-
Some(IpAddr::V4(local_addr4)),
155+
Some(IpAddr::V4(bind_addr)),
96156
)
97157
.await?;
98158

99159
if conn.can_starttls() {
100160
conn.starttls(
101-
TlsParameters::new_rustls(smtp_config.smtp_relay.as_str().into())?,
161+
TlsParameters::new_rustls(config.smtp_relay.as_str().into())?,
102162
&client_id,
103163
)
104164
.await?;
@@ -107,15 +167,11 @@ pub async fn send_email(
107167
trace!("Checking SMTP connection");
108168
if conn.test_connected().await {
109169
debug!("SMTP connection OK");
170+
Ok(conn)
110171
} else {
111172
error!("Could not connect to server (SMTP)");
112-
return Err(SendError::Connect);
173+
Err(SendError::Connect)
113174
}
114-
115-
trace!("Sending email");
116-
conn.send(msg.envelope(), &msg.formatted()).await?;
117-
118-
Ok(())
119175
}
120176

121177
fn get_ehlo_domain(email: &str) -> Option<String> {

server/src/email/template.rs

+30-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
use crate::email::EmailLanguage;
12
use handlebars::{Handlebars, RenderError};
23
use serde::Serialize;
34

4-
static TEMPLATE: &str = include_str!("templates/email_template.hbs");
5+
static TREASURER_TEMPLATE: &str = include_str!("templates/treasurer.hbs");
6+
static SUBMITTER_NL_TEMPLATE: &str = include_str!("templates/submitter_nl.hbs");
7+
static SUBMITTER_EN_TEMPLATE: &str = include_str!("templates/submitter_en.hbs");
8+
static HEADER_PARTIAL: &str = include_str!("templates/header.partial.hbs");
59

610
#[derive(Debug, Serialize)]
7-
pub struct TemplateData {
11+
pub struct TreasurerData {
812
pub name: String,
913
pub iban: String,
1014
pub email: String,
@@ -14,12 +18,34 @@ pub struct TemplateData {
1418
pub notes: Option<String>,
1519
}
1620

17-
pub fn render_template(data: &TemplateData) -> Result<String, RenderError> {
21+
#[derive(Debug, Serialize)]
22+
pub struct SubmitterData {
23+
pub first_name: String,
24+
}
25+
26+
pub fn render_treasurer(data: &TreasurerData) -> Result<String, RenderError> {
27+
render_template(TREASURER_TEMPLATE, &data)
28+
}
29+
30+
pub fn render_submitter(
31+
data: &SubmitterData,
32+
locale: &EmailLanguage,
33+
) -> Result<String, RenderError> {
34+
let t = match locale {
35+
EmailLanguage::Nl => SUBMITTER_NL_TEMPLATE,
36+
EmailLanguage::En => SUBMITTER_EN_TEMPLATE,
37+
};
38+
39+
render_template(t, &data)
40+
}
41+
42+
fn render_template<T: Serialize>(template: &str, data: &T) -> Result<String, RenderError> {
1843
let mut engine = Handlebars::new();
1944
engine.set_strict_mode(true);
45+
engine.register_partial("header", HEADER_PARTIAL)?;
2046

2147
#[cfg(debug_assertions)]
2248
engine.set_dev_mode(true);
2349

24-
engine.render_template(TEMPLATE, data)
50+
engine.render_template(template, data)
2551
}

server/src/email/templates/email_template.hbs

-46
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<head>
2+
<link rel="preconnect" href="https://fonts.googleapis.com">
3+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
4+
<link href="https://fonts.googleapis.com/css2?family=Oxygen&display=swap" rel="stylesheet">
5+
6+
<style>
7+
.container {
8+
display: flex;
9+
flex-direction: row;
10+
justify-content: center;
11+
}
12+
13+
p, h3 {
14+
font-family: 'Oxygen', sans-serif;
15+
}
16+
17+
.banner {
18+
background-color: #197052;
19+
color: white;
20+
padding: 8px
21+
}
22+
</style>
23+
</head>

0 commit comments

Comments
 (0)